微波EDA网,见证研发工程师的成长!
首页 > 硬件设计 > 嵌入式设计 > linux内核中的copy_to_user和copy_from_user(一)

linux内核中的copy_to_user和copy_from_user(一)

时间:11-22 来源:互联网 点击:
Kernel version:2.6.14

CPU architecture:ARM920T

Author:ce123(http://blog.csdn.net/ce123)

1.copy_from_user

在学习Linux内核驱动的时候,经常会碰到copy_from_user和copy_to_user这两个函数,设备驱动程序中的ioctl函数就经常会用到。这两个函数负责在用户空间和内核空间传递数据。首先看看它们的定义(linux/include/asm-arm/uaccess.h),先看copy_from_user:

[plain]view plaincopyprint?

  1. staticinlineunsignedlongcopy_from_user(void*to,constvoid__user*from,unsignedlongn)
  2. {
  3. if(access_ok(VERIFY_READ,from,n))
  4. n=__arch_copy_from_user(to,from,n);
  5. else/*securityhole-plugit*/
  6. memzero(to,n);
  7. returnn;
  8. }

先看函数的三个参数:*to是内核空间的指针,*from是用户空间指针,n表示从用户空间想内核空间拷贝数据的字节数。如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数。

这个函数从结构上来分析,其实都可以分为两个部分:
  1. 首先检查用户空间的地址指针是否有效;
  2. 调用__arch_copy_from_user函数。

1.1.access_ok

access_ok用来对用户空间的地址指针from作某种有效性检验,这个宏和体系结构相关,在arm平台上为(linux/include/asm-arm/uaccess.h):

[plain]view plaincopyprint?

  1. #define__range_ok(addr,size)({\
  2. unsignedlongflag,sum;\
  3. __chk_user_ptr(addr);\
  4. __asm__("adds%1,%2,%3;sbcccs%1,%1,%0;movcc%0,#0"\
  5. :"=&r"(flag),"=&r"(sum)\
  6. :"r"(addr),"Ir"(size),"0"(current_thread_info()->addr_limit)\
  7. :"cc");\
  8. flag;})
  9. #defineaccess_ok(type,addr,size)(__range_ok(addr,size)==0)

可以看到access_ok中第一个参数type并没有用到,__range_ok的作用在于判断addr+size之后是否还在进程的用户空间范围之内。下面我们具体看一下。这段代码涉及到GCC内联汇编,不懂的朋友可以先看看这篇博客(http://blog.csdn.net/ce123/article/details/8209702)。
(1)unsigned long flag, sum;\\定义两个变量
  • flag:保存结果的变量:非零代表地址无效,零代表地址可以访问。初始存放非零值(current_thread_info()->addr_limit),也就是当前进程的地址上限值。
  • sum:保存要访问的地址范围末端,用于和当前进程地址空间限制数据做比较。

(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__,这就是一个空函数。

请看具体的定义(linux/compiler.h):

[plain]view plaincopyprint?

  1. #ifdef__CHECKER__
  2. ...
  3. externvoid__chk_user_ptr(void__user*);
  4. externvoid__chk_io_ptr(void__iomem*);
  5. #else
  6. ...
  7. #define__chk_user_ptr(x)(void)0
  8. #define__chk_io_ptr(x)(void)0
  9. ...
  10. #endif

(3)接下来是汇编:
adds %1, %2, %3
sum = addr + size 这个操作影响状态位(目的是影响是进位标志C),以下的两个指令都带有条件CC,也就是当C=0的时候才执行。

如果上面的加法指令进位了(C=1),则以下的指令都不执行,flag就为初始值current_thread_info()->addr_limit(非0),并返回。
如果没有进位(C=0),就执行下面的指令:
sbcccs %1, %1, %0
sum = sum - flag - 1,也就是(addr + size) - (current_thread_info()->addr_limit) - 1,操作影响符号位。
如果(addr + size) >= (current_thread_info()->addr_limit) - 1,则C=1
如果(addr + size) < (current_thread_info()->addr_limit) - 1,则C=0
当C=0的时候执行以下指令,否则跳过(flag非零)。
movcc %0, #0
flag = 0,给flag赋值0。

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

  • 如果(addr + size) >= (current_thread_info()->addr_limit) - 1,返回非零值
  • 如果(addr + size) < (current_thread_info()->addr_limit),返回零
而access_ok就是检验将要操作的用户空间的地址范围是否在当前进程的用户地址空间限制中。这个宏的功能很简单,完全可以用C实现,不是必须使用汇编。但于这两个函数使用频繁,就使用汇编来实现部分功能来增加效率。
从这里再次可以认识到,copy_from_user的使用是结合进程上下文的,因为他们要访问“user”的内存空间,这个“user”必须是某个特定的进程。通过上面的源码就知道,其中使用了current_thread_info()来检查空间是否可以访问。如果在驱动中使用这两个函数,必须是在实现系统调用的函数中使用,不可在实现中断处理的函数中使用。如果在中断上下文中使用了,那代码就很可能操作了根本不相关的进程地址空间。其次由于操作的页面可能被换出,这两个函数可能会休眠,所以同样不可在中断上下文中使用。

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top