linux内核中的get_user和put_user

linux内核中的get_user和put_user

 

在 内核空间和用户空间交换数据时,get_user和put_user是两个两用的函数。相对于copy_to_user和 copy_from_user(将在另一篇博客中分析),这两个函数主要用于完成一些简单类型变量(char、int、long等)的拷贝任务,对于一些 复合类型的变量,比如数据结构或者数组类型,get_user和put_user函数还是无法胜任,这两个函数内部将对指针指向的对象长度进行检查,在 arm平台上只支持长度为1,2,4,8的变量。下面我具体分析,首先看get_user的定义(linux/include/asm-arm /uaccess.h):

 

  1. extern int __get_user_1(void *);  
  2. extern int __get_user_2(void *);  
  3. extern int __get_user_4(void *);  
  4. extern int __get_user_8(void *);  
  5. extern int __get_user_bad(void);  
  6.   
  7. #define __get_user_x(__r2,__p,__e,__s,__i...)               \  
  8.        __asm__ __volatile__ (                   \  
  9.         __asmeq("%0", "r0") __asmeq("%1", "r2")         \ // 进行判断(#define __asmeq(x, y)  ".ifnc " x "," y " ; .err ; .endif\n\t")  
  10.         "bl __get_user_" #__s               \ //根据参数调用不同的函数,此时r0=指向用户空间的指针,r2=内核空间的变量  
  11.         : "=&r" (__e), "=r" (__r2)              \  
  12.         : "0" (__p)                     \  
  13.         : __i, "cc")  
  14.   
  15. #define get_user(x,p)                           \  
  16.     ({                              \  
  17.         const register typeof(*(p)) __user *__p asm("r0") = (p);\ //__p的数据类型和*(p)的指针数据类型是一样的,__p = p,且存放在r0寄存器中  
  18.         register typeof(*(p)) __r2 asm("r2");           \ //__r2的数据类型和*(p)的数据类型是一样的,且存放在r2寄存器中  
  19.         register int __e asm("r0");             \ //定义__e,存放在寄存器r0,作为返回值  
  20.         switch (sizeof(*(__p))) {               \ //对__p所指向的对象长度进行检查,并根据长度调用响应的函数  
  21.         case 1:                         \  
  22.             __get_user_x(__r2, __p, __e, 1, "lr");      \  
  23.                 break;                      \  
  24.         case 2:                         \  
  25.             __get_user_x(__r2, __p, __e, 2, "r3", "lr");    \  
  26.             break;                      \  
  27.         case 4:                         \  
  28.                 __get_user_x(__r2, __p, __e, 4, "lr");      \  
  29.             break;                      \  
  30.         case 8:                         \  
  31.             __get_user_x(__r2, __p, __e, 8, "lr");      \  
  32.                 break;                      \  
  33.         default: __e = __get_user_bad(); break;         \ //默认处理  
  34.         }                           \  
  35.         x = __r2;                       \  
  36.         __e;                            \  
  37.     })  

上 面的源码涉及到gcc的内联汇编,不太了解的朋友可以参考前面的博客(http://blog.csdn.net/ce123/article /details/8209702)。继续,跟踪__get_user_1等函数的执行,它们的定义如下(linux/arch/arm/lib /getuser.S)。

 

  1. .global __get_user_1  
  2. __get_user_1:  
  3. 1:  ldrbt   r2, [r0]  
  4.     mov r0, #0  
  5.     mov pc, lr  
  6.   
  7.     .global __get_user_2  
  8. __get_user_2:  
  9. 2:  ldrbt   r2, [r0], #1  
  10. 3:  ldrbt   r3, [r0]  
  11. #ifndef __ARMEB__  
  12.     orr r2, r2, r3, lsl #8  
  13. #else  
  14.     orr r2, r3, r2, lsl #8  
  15. #endif  
  16.     mov r0, #0  
  17.     mov pc, lr  
  18.   
  19.     .global __get_user_4  
  20. __get_user_4:  
  21. 4:  ldrt    r2, [r0]  
  22.     mov r0, #0  
  23.     mov pc, lr  
  24.   
  25.     .global __get_user_8  
  26. __get_user_8:  
  27. 5:  ldrt    r2, [r0], #4  
  28. 6:  ldrt    r3, [r0]  
  29.     mov r0, #0  
  30.     mov pc, lr  
  31.   
  32. __get_user_bad_8:  
  33.     mov r3, #0  
  34. __get_user_bad:  
  35.     mov r2, #0  
  36.     mov r0, #-EFAULT  
  37.     mov pc, lr  
  38.   
  39. .section __ex_table, "a"  
  40.     .long   1b, __get_user_bad  
  41.     .long   2b, __get_user_bad  
  42.     .long   3b, __get_user_bad  
  43.     .long   4b, __get_user_bad  
  44.     .long   5b, __get_user_bad_8  
  45.     .long   6b, __get_user_bad_8  
  46. .previous  


这 段代码都是单条汇编指令实现的内存操作,就不进行详细注解了。如果定义__ARMEB__宏,则是支持EABI的大端格式代码 (http://blog.csdn.net/ce123/article/details/8457491),关于大端模式和小端模式的详细介绍,可以 参考http://blog.csdn.net/ce123/article/details/6971544。这段代码在.section __ex_table, "a"之前都是常规的内存拷贝操纵,特殊的地方在于后面定义“__ex_table”section 。

标 号1,2,...,6处是内存访问指令,如果mov的源地址位于一个尚未被提交物理页面的空间中,将产生缺页异常,内核会调用do_page_fault 函数处理这个异常,因为异常发生在内核空间,do_page_fault将调用search_exception_tables在“ __ex_table”中查找异常指令的修复指令,在上面这段带面的最后,“__ex_table”section 中定义了如下数据:

 

  1. .section __ex_table, "a"  
  2.     .long   1b, __get_user_bad //其中1b对应标号1处的指令,__get_user_bad是1处指令的修复指令。  
  3.     .long   2b, __get_user_bad  
  4.     .long   3b, __get_user_bad  
  5.     .long   4b, __get_user_bad  
  6.     .long   5b, __get_user_bad_8  
  7.     .long   6b, __get_user_bad_8  

当标号1处发生缺页异常时,系统将调用do_page_fault提交物理页面,然后跳到__get_user_bad继续执行。get_user函数如果成果执行则返回1,否则返回-EFAULT。

put_user用于将内核空间的一个简单类型变量x拷贝到p所指向的用户空间。该函数可以自动判断变量的类型,如果执行成功则返回0,否则返回-EFAULT。下面给出它们的定义(linux/include/asm-arm/uaccess.h)。

 

  1. extern int __put_user_1(void *, unsigned int);  
  2. extern int __put_user_2(void *, unsigned int);  
  3. extern int __put_user_4(void *, unsigned int);  
  4. extern int __put_user_8(void *, unsigned long long);  
  5. extern int __put_user_bad(void);  
  6.   
  7. #define __put_user_x(__r2,__p,__e,__s)                  \  
  8.        __asm__ __volatile__ (                   \  
  9.         __asmeq("%0", "r0") __asmeq("%2", "r2")         \  
  10.         "bl __put_user_" #__s               \  
  11.         : "=&r" (__e)                       \  
  12.         : "0" (__p), "r" (__r2)                 \  
  13.         : "ip", "lr", "cc")  
  14.   
  15. #define put_user(x,p)                           \  
  16.     ({                              \  
  17.         const register typeof(*(p)) __r2 asm("r2") = (x);   \  
  18.         const register typeof(*(p)) __user *__p asm("r0") = (p);\  
  19.         register int __e asm("r0");             \  
  20.         switch (sizeof(*(__p))) {               \  
  21.         case 1:                         \  
  22.             __put_user_x(__r2, __p, __e, 1);        \  
  23.             break;                      \  
  24.         case 2:                         \  
  25.             __put_user_x(__r2, __p, __e, 2);        \  
  26.             break;                      \  
  27.         case 4:                         \  
  28.             __put_user_x(__r2, __p, __e, 4);        \  
  29.             break;                      \  
  30.         case 8:                         \  
  31.             __put_user_x(__r2, __p, __e, 8);        \  
  32.             break;                      \  
  33.         default: __e = __put_user_bad(); break;         \  
  34.         }                           \  
  35.         __e;                            \  
  36.     })  

__put_user_1等函数的的定义如下(linux/arch/arm/lib/putuser.S)。

 

  1. .global __put_user_1  
  2. __put_user_1:  
  3. 1:  strbt   r2, [r0]  
  4.     mov r0, #0  
  5.     mov pc, lr  
  6.   
  7.     .global __put_user_2  
  8. __put_user_2:  
  9.     mov ip, r2, lsr #8  
  10. #ifndef __ARMEB__  
  11. 2:  strbt   r2, [r0], #1  
  12. 3:  strbt   ip, [r0]  
  13. #else  
  14. 2:  strbt   ip, [r0], #1  
  15. 3:  strbt   r2, [r0]  
  16. #endif  
  17.     mov r0, #0  
  18.     mov pc, lr  
  19.   
  20.     .global __put_user_4  
  21. __put_user_4:  
  22. 4:  strt    r2, [r0]  
  23.     mov r0, #0  
  24.     mov pc, lr  
  25.   
  26.     .global __put_user_8  
  27. __put_user_8:  
  28. 5:  strt    r2, [r0], #4  
  29. 6:  strt    r3, [r0]  
  30.     mov r0, #0  
  31.     mov pc, lr  
  32.   
  33. __put_user_bad:  
  34.     mov r0, #-EFAULT  
  35.     mov pc, lr  
  36.   
  37. .section __ex_table, "a"  
  38.     .long   1b, __put_user_bad  
  39.     .long   2b, __put_user_bad  
  40.     .long   3b, __put_user_bad  
  41.     .long   4b, __put_user_bad  
  42.     .long   5b, __put_user_bad  
  43.     .long   6b, __put_user_bad  
  44. .previous  

put_user函数就不具体分析了。get_user和put_user仅能完成一些简单类型变量的拷贝任务,后面我们将分析copy_to_user和copy_from_user。

posted @ 2016-04-13 11:13  OracleLoyal  阅读(2447)  评论(0编辑  收藏  举报