歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux內核

Linux內核中的get_user和put_user

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

上面的源碼涉及到gcc的內聯匯編,不太了解的朋友可以參考前面的文章(http://www.linuxidc.com/Linux/2012-11/74645.htm)。繼續,跟蹤__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

這段代碼都是單條匯編指令實現的內存操作,就不進行詳細注解了。如果定義__ARMEB__宏,則是支持EABI的大端格式代碼(),關於大端模式和小端模式的詳細介紹,可以參考

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

ARMEB = ARM EABI Big-endian ,也有稱為ARMEB    #大端字節序
ARMEL = ARM EABI Little-endian,也有稱為ARMLE    #小端字節序
EABI = Embedded Application Binary Interface

http://www.linuxidc.com/Linux/2013-01/77305.htm 。這段代碼在.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"
 .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

Copyright © Linux教程網 All Rights Reserved