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