linux get user,linux内核中的get_user和put_user

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.youkuaiyun.com/ce123/article/details/8209702)。继续,跟踪__get_user_1等函数的执行,它们的定义如下(linux/arch/arm/lib/getuser.S)。

.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.youkuaiyun.com/ce123/article/details/8457491),关于大端模式和小端模式的详细介绍,可以参考http://blog.youkuaiyun.com/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 中定义了如下数据:

.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

当标号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:strbtr2, [r0]

movr0, #0

movpc, lr

.global__put_user_2

__put_user_2:

movip, r2, lsr #8

#ifndef __ARMEB__

2:strbtr2, [r0], #1

3:strbtip, [r0]

#else

2:strbtip, [r0], #1

3:strbtr2, [r0]

#endif

movr0, #0

movpc, lr

.global__put_user_4

__put_user_4:

4:strtr2, [r0]

movr0, #0

movpc, lr

.global__put_user_8

__put_user_8:

5:strtr2, [r0], #4

6:strtr3, [r0]

movr0, #0

movpc, lr

__put_user_bad:

movr0, #-EFAULT

movpc, lr

.section __ex_table, "a"

.long1b, __put_user_bad

.long2b, __put_user_bad

.long3b, __put_user_bad

.long4b, __put_user_bad

.long5b, __put_user_bad

.long6b, __put_user_bad

.previous

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值