基于ARM 构架(带MMU)的copy_from_user与copy_to_user详细分析

[转自:http://blog.chinaunix.net/uid-20543672-id-3195249.html]

在学习Linux内核驱动的时候,一开始就会碰到copy_from_user和copy_to_user这两个常用的函数。这两个函数在内核使用的非常频繁,负责将数据从用户空间拷贝到内核空间以及将数据从内核空间拷贝到用户空间。在4年半前初学Linux内核驱动程序的时候,我只是知道这个怎么用,并没有很深入的分析这两个函数。这次研究内核模块挂载的时候,又碰到了它们。决定还是认真跟踪一下函数。

首先这两个函数的原型在arch/arm/include/asm/uaccess.h文件中:

  1. static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
  2. {
  3. if (access_ok(VERIFY_READ, from, n))
  4. n = __copy_from_user(to, from, n);
  5. else /* security hole - plug it */
  6. memset(to, 0, n);
  7. return n;
  8. }
  9. static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
  10. {
  11. if (access_ok(VERIFY_WRITE, to, n))
  12. n = __copy_to_user(to, from, n);
  13. return n;
  14. }

这两个函数从结构上来分析,其实都可以分为两个部分:

1、首先检查用户空间的地址指针是否有效(难点)

2、调用__copy_from_user和__copy_to_user函数

在这个分析中,我们先易后难。首先看看具体数据拷贝功能的__copy_from_user和__copy_to_user函数

对于ARM构架,没有单独实现这两个函数,所以他们的代码位于include/asm-generic/uaccess.h

  1. /*
  2. * 带有MMU的构架应该覆盖这两个函数
  3. */
  4. #ifndef __copy_from_user
  5. static inline __must_check long __copy_from_user(void *to,
  6. const void __user * from, unsigned long n)
  7. {
  8. if (__builtin_constant_p(n)) {
  9. switch(n) {
  10. case 1:
  11. *(u8 *)to = *(u8 __force *)from;
  12. return 0;
  13. case 2:
  14. *(u16 *)to = *(u16 __force *)from;
  15. return 0;
  16. case 4:
  17. *(u32 *)to = *(u32 __force *)from;
  18. return 0;
  19. #ifdef CONFIG_64BIT
  20. case 8:
  21. *(u64 *)to = *(u64 __force *)from;
  22. return 0;
  23. #endif
  24. default:
  25. break;
  26. }
  27. }
  28. memcpy(to, (const void __force *)from, n);
  29. return 0;
  30. }
  31. #endif
  32. #ifndef __copy_to_user
  33. static inline __must_check long __copy_to_user(void __user *to,
  34. const void *from, unsigned long n)
  35. {
  36. if (__builtin_constant_p(n)) {
  37. switch(n) {
  38. case 1:
  39. *(u8 __force *)to = *(u8 *)from;
  40. return 0;
  41. case 2:
  42. *(u16 __force *)to = *(u16 *)from;
  43. return 0;
  44. case 4:
  45. *(u32 __force *)to = *(u32 *)from;
  46. return 0;
  47. #ifdef CONFIG_64BIT
  48. case 8:
  49. *(u64 __force *)to = *(u64 *)from;
  50. return 0;
  51. #endif
  52. default:
  53. break;
  54. }
  55. }
  56. memcpy((void __force *)to, from, n);
  57. return 0;
  58. }
  59. #endif

点击(此处)折叠或打开

  1. GCC的内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数值是常数,函数返回 1,否则返回 0。

从这两个函数中可以看出其实结构是一样的,首先看看n是不是常数,如果是并为1、2、4、8(64bit)则直接就用一个赋值语句拷贝数据。如果不是常数或n过大,则使用memcpy函数。而这个memcpy函数位于lib/string.c:

  1. #ifndef __HAVE_ARCH_MEMCPY
  2. /**
  3. * memcpy - Copy one area of memory to another
  4. * @dest: Where to copy to
  5. * @src: Where to copy from
  6. * @count: The size of the area.
  7. *
  8. * You should not use this function to access IO space, use memcpy_toio()
  9. * or memcpy_fromio() instead.
  10. */
  11. void *memcpy(void *dest, const void *src, size_t count)
  12. {
  13. char *tmp = dest;
  14. const char *s = src;
  15. while (count--)
  16. *tmp++ = *s++;
  17. return dest;
  18. }
  19. EXPORT_SYMBOL(memcpy);
  20. #endif

这个函数其实就是一个简单的利用循环来数据拷贝,非常简单。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

好了如何拷贝数据我们已经了解了,现在我们来看看前面的用户空间指针检测函数access_ok,这其实是一个宏定义,位于arch/arm/include/asm/uaccess.h文件中:

  1. /* We use 33-bit arithmetic here... */
  2. #define __range_ok(addr,size) ({ \
  3. unsigned long flag, roksum; \
  4. __chk_user_ptr(addr); \
  5. __asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \
  6. : "=&r" (flag), "=&r" (roksum) \
  7. : "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \
  8. : "cc"); \
  9. flag; })
  10. ......
  11. #define access_ok(type,addr,size) (__range_ok(addr,size) == 0)
  12. ......

这个就比较麻烦了,涉及到了C语言中内联汇编,如果还不熟悉的朋友可以看看《ARM GCC 内嵌汇编手册》,我也不是很熟。

现在我们来仔细分析__range_ok这个宏:

(1)unsigned long flag, roksum;\\定义两个变量

  1. flag:保存结果的变量:非零代表地址无效,零代表地址可以访问。初始存放非零值(current_thread_info()->addr_limit),也就是当前进程的地址上限值。
  2. roksum:保存要访问的地址范围末端,用于和当前进程地址空间限制数据做比较

(2)__chk_user_ptr(addr);\\定义是一个空函数

但是这个函数涉及到__CHECKER__宏的判断,__CHECKER__宏在通过Sparse(Semantic Parser for C)工具对内核代码进行检查时会定义的。在使用make C=1或C=2时便会调用该工具,这个工具可以检查在代码中声明了sparse所能检查到的相关属性的内核函数和变量。

如果定义了__CHECKER__,在网上的资料中这样解释的:__chk_user_ptr和__chk_io_ptr在这里只声明函数,没有函数体,目的就是在编译过程中Sparse能够捕捉到编译错误,检查参数的类型。

如果没有定义__CHECKER__,这就是一个空函数。

(3)接下来的汇编,我适当地翻译如下:

adds %1, %2, %3

roksum = addr + size 这个操作影响状态位(目的是影响是进位标志C)

以下的两个指令都带有条件CC,也就是当C=0的时候才执行。

如果上面的加法指令进位了(C=1),则以下的指令都不执行,flag就为初始值current_thread_info()->addr_limit(非零值),并返回。

如果没有进位(C=0),就执行下面的指令

    sbcccs %1, %1, %0 

roksum = roksum - flag,也就是(addr + size)- (current_thread_info()->addr_limit),操作影响符号位。

如果(addr + size)>=(current_thread_info()->addr_limit),则C=1

如果(addr + size)<(current_thread_info()->addr_limit),则C=0

当C=0的时候执行以下指令,否则跳过(flag非零)。

 movcc %0, #0

flag = 0,给flag赋值0

(4)flag; 

返回flag值

综上所诉:__range_ok宏其实等价于:

如果(addr + size)>=(current_thread_info()->addr_limit),返回非零值

如果(addr + size)<(current_thread_info()->addr_limit),返回零

而access_ok就是检验将要操作的用户空间的地址范围是否在当前进程的用户地址空间限制中。这个宏的功能很简单,完全可以用C实现,不是必须使用汇编。个人理解:由于这两个函数使用频繁,就使用汇编来实现部分功能来增加效率。

 

    从这里再次可以认识到,copy_from_user与copy_to_user的使用是结合进程上下文的,因为他们要访问“user”的内存空间,这个“user”必须是某个特定的进程。通过上面的源码就知道,其中使用了current_thread_info()来检查空间是否可以访问。如果在驱动中使用这两个函数,必须是在实现系统调用的函数中使用,不可在实现中断处理的函数中使用。如果在中断上下文中使用了,那代码就很可能操作了根本不相关的进程地址空间。

    其次由于操作的页面可能被换出,这两个函数可能会休眠,所以同样不可在中断上下文中使用。

时间: 2024-10-15 14:25:47

基于ARM 构架(带MMU)的copy_from_user与copy_to_user详细分析的相关文章

在Azure New Portal上创建基于ARM的带SLB的VM

目前Azure的New Portal在国内已经上线了.本文将介绍最常见的一种场景:通过Azure的New Portal创建带有Server Load Balance的多台虚拟机. 1 创建Resource Group.Storage Account 首先点击New,再点击Sata + Storage,然后选择Storage Account: 出现提示后输入相应的参数,其中的Resource Group选择Create New: 2 创建虚拟网络 在Portal上选择New->Networking

基于ARM处理器的反汇编器软件简单设计及实现

写在前面 2012年写的,仅供参考 反汇编的目的 缺乏某些必要的说明资料的情况下, 想获得某些软件系统的源代码.设计思想及理念, 以便复制, 改造.移植和发展: 从源码上对软件的可靠性和安全性进行验证,对那些直接与CPU 相关的目标代码进行安全性分析: 涉及的主要内容 分析ARM处理器指令的特点,以及编译以后可执行的二进制文件代码的特征: 将二进制机器代码经过指令和数据分开模块的加工处理: 分解标识出指令代码和数据代码: 然后将指令代码反汇编并加工成易于阅读的汇编指令形式的文件: 下面给出个示例

基于ARM的SoC设计入门[转]

原文:基于ARM的SoC设计入门 我们跳过所有对ARM介绍性的描述,直接进入工程师们最关心的问题.要设计一个基于ARM的SoC,我们首先要了解一个基于ARM的SoC的结构.图1是一个典型的SoC的结构: 图1从图1我们可以了解这个的SoC的基本构成: ARM core:ARM966E AMBA 总线:AHB+APB 外设IP(Peripheral IPs):VIC(Vector Interrupt Controller), DMA, UART, RTC, SSP, WDT…… Memory bl

基于ARM的数据中心路在何方?

我们已经了解到百度采用ARM处理器研发数据存储节点.另外,还有一些Startup公司在研发基于ARM的服务器.今年,AMD又宣布开始研发基于ARM的处理器,不再局限于X86处理器架构.这一切都在说明什么?ARM正往数据中心前进. ARM处理器采用RISC的架构,其具有很好的性能/功耗比.对于数据中心而言,低功耗是一个永恒的话题.虽然,在服务器领域,以Intel为首的X86架构一度将MIPS.SPARC击溃,占据了服务器市场的重要份额.所以,如今放眼望去无论是存储设备还是应用服务器,基本都是采用了

Linux 内核高-低端内存设置代码跟踪(ARM构架)

对于ARM中内核如何在启动的时候设置高低端内存的分界线(也是逻辑地址与虚拟地址分界线(虚拟地址)减去那个固定的偏移),这里我稍微引导下(内核分析使用Linux-3.0): 首先定位设置内核虚拟地址起始位置(也就是内核逻辑地址末端+1的地址)的文件:init.c (arch\arm\mm),在这个文件中的void __init bootmem_init(void)函数如下 void __init bootmem_init(void) { unsigned long min, max_low, ma

课程设计个人报告——基于ARM实验箱的捕鱼游戏的设计与实现

课程设计个人报告--基于ARM实验箱的捕鱼游戏的设计与实现 个人贡献 实验环境的搭建 代码调试 在电脑上成功运行 在ARM实验箱上成功实现 给程序增加功能(没成功) 研究程序代码撰写小组报告 一.实验环境 Eclipse软件开发环境: ARM实验箱(HonyaS5PC100): windows操作系统. 二.实践内容 Windows环境下ARM集成开发环境的搭建与使用: 安装软件到模拟器: 连接ARM实验箱与PC机: 将工程代码在ARM实验箱上实现: 给程序增加新的功能. 三.实践步骤 3.1

DreamHouse项目(基于ARM与ZIGBEE技术的智能家居)之项目展示

这是本人做的一次有关智能家居的项目,仅是用来参加比赛的!或者是我用来练手的吧!大概花费了我近2个月的时间来完成他,平台是建立在博创S3C2410的嵌入式的箱子上的,我觉得我唯一没有足够自信说是自己写的代码的话就是驱动部分了,毕竟当时的状态也是刚刚把单片机的知识全部学会罢了,因为这是基础中的基础,对于C语言,在校的学生普遍是没有完全掌握的,对于开发真的知识储备确实不够!好吧废话我不多说了,先讲讲我自己做的项目吧!学生的练手项目,大家应该不会来喷我吧! 上图先! 通用模块 我本人真的是没有做出来,但

基于ARM的车牌识别技术研究与实现

在云盘里包含了我本科毕业设计的全部资料和代码.主要涉及下面摘要中的几个部分.虽然系统无法实用,但是适合机器视觉和嵌入式方向的入门.希望能对有志从事相关方向的朋友有所帮助.本人现在在深圳从事机器视觉算法工程师职业.现在做人脸识别相关系统.希望能和网络上的有志之士一起在相关方向上学习和进步. 本文首先介绍了课题背景和研究现状,然后介绍了方案选择和设计过程.设计过程包括车牌识别程序设计,引导程序设计,内核驱动设计和文件系统设计.车牌识别程序设计中的车牌定位采用边缘检测和支持向量机相结合的定位算法,字符

基于ARM的指纹识别门禁系统设计方案

现代社会高速发展,很多场合需要身份确认,传统的身份识别技术已经不能满足社会要求.人的身体特征具有不可复制性,因此人们开始研究生物识别技术,而指纹具有唯一性.终生不变性.难于伪造等特点,安全性高,因而得到了广泛应用.在一些机要部门,如银行.宾馆.机房等一般都安装有门禁系统,门禁系统是为保障人们生活.工作及财产安全, 对重要通道的出入口进行管理与控制的系统,基于指纹识别技术的门禁系统是一项高科技安全设施,提高了系统的安全性.ARM作为一种嵌入式系统处理器,具有高性能.低功耗.低成本等特点,因而在工业