linux内核中的get_user和put_user
内核版本:2.6.14
CPU平台:arm
嵌入式开发交流群:289195589,欢迎加入!
在内核空间和用户空间交换数据时,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):
extern int __get_user_1(void *); extern int __get_user_2(void *); extern int __get_user_4(void *); extern int __get_user_8(void *); extern int __get_user_bad(void); #define __get_user_x(__r2,__p,__e,__s,__i...) \ __asm__ __volatile__ ( \ __asmeq("%0", "r0") __asmeq("%1", "r2") \ //进行判断(#define __asmeq(x, y) ".ifnc " x "," y " ; .err ; .endif\n\t") "bl __get_user_" #__s \ //根据参数调用不同的函数,此时r0=指向用户空间的指针,r2=内核空间的变量 : "=&r" (__e), "=r" (__r2) \ : "0" (__p) \ : __i, "cc") #define get_user(x,p) \ ({ \ const register typeof(*(p)) __user *__p asm("r0") = (p);\ //__p的数据类型和*(p)的指针数据类型是一样的,__p = p,且存放在r0寄存器中 register typeof(*(p)) __r2 asm("r2"); \ //__r2的数据类型和*(p)的数据类型是一样的,且存放在r2寄存器中 register int __e asm("r0"); \ //定义__e,存放在寄存器r0,作为返回值 switch (sizeof(*(__p))) { \ //对__p所指向的对象长度进行检查,并根据长度调用响应的函数 case 1: \ __get_user_x(__r2, __p, __e, 1, "lr"); \ break; \ case 2: \ __get_user_x(__r2, __p, __e, 2, "r3", "lr"); \ break; \ case 4: \ __get_user_x(__r2, __p, __e, 4, "lr"); \ break; \ case 8: \ __get_user_x(__r2, __p, __e, 8, "lr"); \ break; \ default: __e = __get_user_bad(); break; \ //默认处理 } \ x = __r2; \ __e; \ })
上面的源码涉及到gcc的内联汇编,不太了解的朋友可以参考前面的博客(http://blog.csdn.net/ce123/article/details/8209702)。继续,跟踪__get_user_1等函数的执行,它们的定义如下(linux/arch/arm/lib/getuser.S)。
.global __get_user_1 __get_user_1: 1: ldrbt r2, [r0] mov r0, #0 mov pc, lr .global __get_user_2 __get_user_2: 2: ldrbt r2, [r0], #1 3: ldrbt r3, [r0] #ifndef __ARMEB__ orr r2, r2, r3, lsl #8 #else orr r2, r3, r2, lsl #8 #endif mov r0, #0 mov pc, lr .global __get_user_4 __get_user_4: 4: ldrt r2, [r0] mov r0, #0 mov pc, lr .global __get_user_8 __get_user_8: 5: ldrt r2, [r0], #4 6: ldrt r3, [r0] mov r0, #0 mov pc, lr __get_user_bad_8: mov r3, #0 __get_user_bad: mov r2, #0 mov r0, #-EFAULT mov pc, lr .section __ex_table, "a" .long 1b, __get_user_bad .long 2b, __get_user_bad .long 3b, __get_user_bad .long 4b, __get_user_bad .long 5b, __get_user_bad_8 .long 6b, __get_user_bad_8 .previous
标号1,2,...,6处是内存访问指令,如果mov的源地址位于一个尚未被提交物理页面的空间中,将产生缺页异常,内核会调用do_page_fault函数处理这个异常,因为异常发生在内核空间,do_page_fault将调用search_exception_tables在“ __ex_table”中查找异常指令的修复指令,在上面这段带面的最后,“__ex_table”section 中定义了如下数据:
.section __ex_table, "a" .long 1b, __get_user_bad //其中1b对应标号1处的指令,__get_user_bad是1处指令的修复指令。 .long 2b, __get_user_bad .long 3b, __get_user_bad .long 4b, __get_user_bad .long 5b, __get_user_bad_8 .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)。
extern int __put_user_1(void *, unsigned int); extern int __put_user_2(void *, unsigned int); extern int __put_user_4(void *, unsigned int); extern int __put_user_8(void *, unsigned long long); extern int __put_user_bad(void); #define __put_user_x(__r2,__p,__e,__s) \ __asm__ __volatile__ ( \ __asmeq("%0", "r0") __asmeq("%2", "r2") \ "bl __put_user_" #__s \ : "=&r" (__e) \ : "0" (__p), "r" (__r2) \ : "ip", "lr", "cc") #define put_user(x,p) \ ({ \ const register typeof(*(p)) __r2 asm("r2") = (x); \ const register typeof(*(p)) __user *__p asm("r0") = (p);\ register int __e asm("r0"); \ switch (sizeof(*(__p))) { \ case 1: \ __put_user_x(__r2, __p, __e, 1); \ break; \ case 2: \ __put_user_x(__r2, __p, __e, 2); \ break; \ case 4: \ __put_user_x(__r2, __p, __e, 4); \ break; \ case 8: \ __put_user_x(__r2, __p, __e, 8); \ break; \ default: __e = __put_user_bad(); break; \ } \ __e; \ })__put_user_1等函数的的定义如下( linux/arch/arm/lib/putuser.S )。
.global __put_user_1 __put_user_1: 1: strbt r2, [r0] mov r0, #0 mov pc, lr .global __put_user_2 __put_user_2: mov ip, r2, lsr #8 #ifndef __ARMEB__ 2: strbt r2, [r0], #1 3: strbt ip, [r0] #else 2: strbt ip, [r0], #1 3: strbt r2, [r0] #endif mov r0, #0 mov pc, lr .global __put_user_4 __put_user_4: 4: strt r2, [r0] mov r0, #0 mov pc, lr .global __put_user_8 __put_user_8: 5: strt r2, [r0], #4 6: strt r3, [r0] mov r0, #0 mov pc, lr __put_user_bad: mov r0, #-EFAULT mov pc, lr .section __ex_table, "a" .long 1b, __put_user_bad .long 2b, __put_user_bad .long 3b, __put_user_bad .long 4b, __put_user_bad .long 5b, __put_user_bad .long 6b, __put_user_bad .previousput_user函数就不具体分析了。get_user和put_user仅能完成一些简单类型变量的拷贝任务,后面我们将分析copy_to_user和copy_from_user。