Linux内核中的get_user和put_user

喝咖啡的IT羊 2013-01-04

Linux内核版本:2.6.14

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):

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;       \
 })

.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

这段代码都是单条汇编指令实现的内存操作,就不进行详细注解了。如果定义__ARMEB__宏,则是支持EABI的大端格式代码(),关于大端模式和小端模式的详细介绍,可以参考

arm中的armeb(armbe)和armel(armle)

ARMEB = ARM EABI Big-endian ,也有称为ARMEB    #大端字节序
ARMEL = ARM EABI Little-endian,也有称为ARMLE    #小端字节序
EABI = Embedded Application Binary Interface

标号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

相关推荐