linux内核中的get_user和put_user
CPU平台:arm
在内核空间和用户空间交换数据时,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):
[plain]view plaincopyprint?
- externint__get_user_1(void*);
- externint__get_user_2(void*);
- externint__get_user_4(void*);
- externint__get_user_8(void*);
- externint__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")
- #defineget_user(x,p)\
- ({\
- constregistertypeof(*(p))__user*__pasm("r0")=(p);\//__p的数据类型和*(p)的指针数据类型是一样的,__p=p,且存放在r0寄存器中
- registertypeof(*(p))__r2asm("r2");\//__r2的数据类型和*(p)的数据类型是一样的,且存放在r2寄存器中
- registerint__easm("r0");\//定义__e,存放在寄存器r0,作为返回值
- switch(sizeof(*(__p))){\//对__p所指向的对象长度进行检查,并根据长度调用响应的函数
- case1:\
- __get_user_x(__r2,__p,__e,1,"lr");\
- break;\
- case2:\
- __get_user_x(__r2,__p,__e,2,"r3","lr");\
- break;\
- case4:\
- __get_user_x(__r2,__p,__e,4,"lr");\
- break;\
- case8:\
- __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)。
[plain]view plaincopyprint?
- .global__get_user_1
- __get_user_1:
- 1:ldrbtr2,[r0]
- movr0,#0
- movpc,lr
- .global__get_user_2
- __get_user_2:
- 2:ldrbtr2,[r0],#1
- 3:ldrbtr3,[r0]
- #ifndef__ARMEB__
- orrr2,r2,r3,lsl#8
- #else
- orrr2,r3,r2,lsl#8
- #endif
- movr0,#0
- movpc,lr
- .global__get_user_4
- __get_user_4:
- 4:ldrtr2,[r0]
- movr0,#0
- movpc,lr
- .global__get_user_8
- __get_user_8:
- 5:ldrtr2,[r0],#4
- 6:ldrtr3,[r0]
- movr0,#0
- movpc,lr
- __get_user_bad_8:
- movr3,#0
- __get_user_bad:
- movr2,#0
- movr0,#-EFAULT
- movpc,lr
- .section__ex_table,"a"
- .long1b,__get_user_bad
- .long2b,__get_user_bad
- .long3b,__get_user_bad
- .long4b,__get_user_bad
- .long5b,__get_user_bad_8
- .long6b,__get_user_bad_8
- .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 中定义了如下数据:
[plain]view plaincopyprint?
- .section__ex_table,"a"
- .long1b,__get_user_bad//其中1b对应标号1处的指令,__get_user_bad是1处指令的修复指令。
- .long2b,__get_user_bad
- .long3b,__get_user_bad
- .long4b,__get_user_bad
- .long5b,__get_user_bad_8
- .long6b,__get_user_bad_8
put_user用于将内核空间的一个简单类型变量x拷贝到p所指向的用户空间。该函数可以自动判断变量的类型,如果执行成功则
linux内核get_userput_use 相关文章:
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)
- Windows CE 进程、线程和内存管理(二)(11-09)