歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux內核 >> Linux內核中的copy_to_user和copy_from_user

Linux內核中的copy_to_user和copy_from_user

日期:2017/3/1 10:03:43   编辑:Linux內核

Kernel version:2.6.14

CPU architecture:ARM920T

1.copy_from_user

在學習Linux內核驅動的時候,經常會碰到copy_from_user和copy_to_user這兩個函數,設備驅動程序中的ioctl函數就經常會用到。這兩個函數負責在用戶空間和內核空間傳遞數據。首先看看它們的定義(linux/include/asm-arm/uaccess.h),先看copy_from_user:

static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (access_ok(VERIFY_READ, from, n))
n = __arch_copy_from_user(to, from, n);
else /* security hole - plug it */
memzero(to, n);
return n;
}

先看函數的三個參數:*to是內核空間的指針,*from是用戶空間指針,n表示從用戶空間想內核空間拷貝數據的字節數。如果成功執行拷貝操作,則返回0,否則返回還沒有完成拷貝的字節數。

這個函數從結構上來分析,其實都可以分為兩個部分:

首先檢查用戶空間的地址指針是否有效;

調用__arch_copy_from_user函數。

1.1.access_ok

access_ok用來對用戶空間的地址指針from作某種有效性檢驗,這個宏和體系結構相關,在arm平台上為(linux/include/asm-arm/uaccess.h):

#define __range_ok(addr,size) ({ \
unsigned long flag, sum; \
__chk_user_ptr(addr); \
__asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \
: "=&r" (flag), "=&r" (sum) \
: "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \
: "cc"); \
flag; })

#define access_ok(type,addr,size) (__range_ok(addr,size) == 0)

可以看到access_ok中第一個參數type並沒有用到,__range_ok的作用在於判斷addr+size之後是否還在進程的用戶空間范圍之內。下面我們具體看一下。這段代碼涉及到GCC內聯匯編,不懂的朋友可以先看看這篇博客(http://blog.csdn.net/ce123/article/details/8209702)。

(1)unsigned long flag, sum;\\定義兩個變量

flag:保存結果的變量:非零代表地址無效,零代表地址可以訪問。初始存放非零值(current_thread_info()->addr_limit),也就是當前進程的地址上限值。

sum:保存要訪問的地址范圍末端,用於和當前進程地址空間限制數據做比較。

(2)__chk_user_ptr(addr);\\定義是一個空函數

這個函數涉及到__CHECKER__宏的判斷,__CHECKER__宏在通過Sparse(Semantic Parser for C)工具對內核代碼進行檢查時會定義的。在使用make C=1或C=2時便會調用該工具,這個工具可以檢查在代碼中聲明了sparse所能檢查到的相關屬性的內核函數和變量。

如果定義了__CHECKER__,__chk_user_ptr和__chk_io_ptr在這裡只聲明函數,沒有函數體,目的就是在編譯過程中Sparse能夠捕捉到編譯錯誤,檢查參數的類型。

如果沒有定義__CHECKER__,這就是一個空函數。

請看具體的定義(linux/compiler.h):

(3)接下來是匯編:

adds %1, %2, %3

sum = addr + size 這個操作影響狀態位(目的是影響是進位標志C),以下的兩個指令都帶有條件CC,也就是當C=0的時候才執行。

如果上面的加法指令進位了(C=1),則以下的指令都不執行,flag就為初始值current_thread_info()->addr_limit(非0),並返回。

如果沒有進位(C=0),就執行下面的指令:

sbcccs %1, %1, %0

sum = sum - flag - 1,也就是(addr + size) - (current_thread_info()->addr_limit) - 1,操作影響符號位。

如果(addr + size) >= (current_thread_info()->addr_limit) - 1,則C=1

如果(addr + size) < (current_thread_info()->addr_limit) - 1,則C=0

當C=0的時候執行以下指令,否則跳過(flag非零)。

movcc %0, #0

flag = 0,給flag賦值0。

綜上所述:__range_ok宏其實等價於:

如果(addr + size) >= (current_thread_info()->addr_limit) - 1,返回非零值

如果(addr + size) < (current_thread_info()->addr_limit),返回零

而access_ok就是檢驗將要操作的用戶空間的地址范圍是否在當前進程的用戶地址空間限制中。這個宏的功能很簡單,完全可以用C實現,不是必須使用匯編。但於這兩個函數使用頻繁,就使用匯編來實現部分功能來增加效率。

從這裡再次可以認識到,copy_from_user的使用是結合進程上下文的,因為他們要訪問“user”的內存空間,這個“user”必須是某個特定的進程。通過上面的源碼就知道,其中使用了current_thread_info()來檢查空間是否可以訪問。如果在驅動中使用這兩個函數,必須是在實現系統調用的函數中使用,不可在實現中斷處理的函數中使用。如果在中斷上下文中使用了,那代碼就很可能操作了根本不相關的進程地址空間。其次由於操作的頁面可能被換出,這兩個函數可能會休眠,所以同樣不可在中斷上下文中使用。

1.2.__arch_copy_from_user

在深入講解之前,我們先想一個問題:為什麼要使用copy_from_user函數???理論上,內核空間可以直接使用用戶空間傳過來的指針,即使要做數據拷貝的動作,也可以直接使用memcpy,事實上,在沒有MMU的體系架構上,copy_form_user最終的實現就是利用了memcpy。但對於大多數有MMU的平台,情況就有了一些變化:用戶空間傳過來的指針是在虛擬地址空間上的,它指向的虛擬地址空間很可能還沒有真正映射到實際的物理頁面上。但這又能怎樣呢?缺頁導致的異常會透明的被內核予以修復(為缺頁的地址空間提交新的物理頁面),訪問到缺頁的指令會繼續運行仿佛什麼都沒有發生一樣。但這只是用戶空間缺頁異常的行為,在內核空間這樣卻因一場必須被顯示的修復,這是由內核提供的缺頁異常處理函數的設計模式決定的,其背後的思想後:在內核態中,如果程序試圖訪問一個尚未提交物理頁面的用戶空間地址,內核必須對此保持警惕而不能像用戶空間那樣毫無察覺。

如果內核訪問一個尚未被提交物理頁面的空間,將產生缺頁異常,內核會調用do_page_fault,因為異常發生在內核空間,do_page_fault將調用search_exception_tables在“ __ex_table”中查找異常指令的修復指令,在__arch_copy_from_user函數中經常使用USER宏,這個宏中了定義了“__ex_table”section。

linux/include/asm-arm/assembler.h

#define USER(x...) \
9999: x; \
.section __ex_table,"a"; \
.align 3; \
.long 9999b,9001f; \
.previous

該定義中有如下數據;

.long 9999b,9001f;

其中9999b對應標號9999處的指令,9001f是9001處的指令,是9999b處指令的修復指令。這樣,當標號9999處發生缺頁異常時,系統將調用do_page_fault提交物理頁面,然後跳到9001繼續執行。

如果在驅動程序中不使用copy_from_user而用memcpy來代替,對於上述的情形會產生什麼結果呢?當標號9999出發生缺頁異常時,系統在“__ex_table”section總將找不到修復地址,因為memcpy沒有像copy_from_user那樣定義一個“__ex_table”section,此時do_page_fault將通過no_context函數產生Oops。極有可能會看到類似如下信息:

Unable to handle kernel NULL pointer dereference at virtual address 00000fe0

所有為了確保設備驅動程序的安全,應該使用copy_from_user函數而不是memcpy。

下面我們深入分析__arch_copy_from_user函數的實現,該函數是用匯編實現的,定義在linux/arch/arm/lib/uaccess.S文件中。

/* Prototype: unsigned long __arch_copy_from_user(void *to,const void *from,unsigned long n);
* Purpose : copy a block from user memory to kernel memory
* Params : to - kernel memory
* : from - user memory
* : n - number of bytes to copy
* Returns : Number of bytes NOT copied.
*/
.cfu_dest_not_aligned:
rsb ip, ip, #4
cmp ip, #2
USER( ldrbt r3, [r1], #1) @ May fault
strb r3, [r0], #1
USER( ldrgebt r3, [r1], #1) @ May fault
strgeb r3, [r0], #1
USER( ldrgtbt r3, [r1], #1) @ May fault
strgtb r3, [r0], #1
sub r2, r2, ip
b .cfu_dest_aligned

ENTRY(__arch_copy_from_user)
stmfd sp!, {r0, r2, r4 - r7, lr}
cmp r2, #4
blt .cfu_not_enough
PLD( pld [r1, #0] )
PLD( pld [r0, #0] )
ands ip, r0, #3
bne .cfu_dest_not_aligned
.cfu_dest_aligned:
ands ip, r1, #3
bne .cfu_src_not_aligned
/*
* Seeing as there has to be at least 8 bytes to copy, we can
* copy one word, and force a user-mode page fault...
*/

.cfu_0fupi: subs r2, r2, #4
addmi ip, r2, #4
bmi .cfu_0nowords
USER( ldrt r3, [r1], #4)
str r3, [r0], #4
mov ip, r1, lsl #32 - PAGE_SHIFT @ On each page, use a ld/st??t instruction
rsb ip, ip, #0
movs ip, ip, lsr #32 - PAGE_SHIFT
beq .cfu_0fupi
/*
* ip = max no. of bytes to copy before needing another "strt" insn
*/
cmp r2, ip
movlt ip, r2
sub r2, r2, ip
subs ip, ip, #32
blt .cfu_0rem8lp
PLD( pld [r1, #28] )
PLD( pld [r0, #28] )
PLD( subs ip, ip, #64 )
PLD( blt .cfu_0cpynopld )
PLD( pld [r1, #60] )
PLD( pld [r0, #60] )

.cfu_0cpy8lp:
PLD( pld [r1, #92] )
PLD( pld [r0, #92] )
.cfu_0cpynopld: ldmia r1!, {r3 - r6} @ Shouldnt fault
stmia r0!, {r3 - r6}
ldmia r1!, {r3 - r6} @ Shouldnt fault
subs ip, ip, #32
stmia r0!, {r3 - r6}
bpl .cfu_0cpy8lp
PLD( cmn ip, #64 )
PLD( bge .cfu_0cpynopld )
PLD( add ip, ip, #64 )

.cfu_0rem8lp: cmn ip, #16
ldmgeia r1!, {r3 - r6} @ Shouldnt fault
stmgeia r0!, {r3 - r6}
tst ip, #8
ldmneia r1!, {r3 - r4} @ Shouldnt fault
stmneia r0!, {r3 - r4}
tst ip, #4
ldrnet r3, [r1], #4 @ Shouldnt fault
strne r3, [r0], #4
ands ip, ip, #3
beq .cfu_0fupi
.cfu_0nowords: teq ip, #0
beq .cfu_finished
.cfu_nowords: cmp ip, #2
USER( ldrbt r3, [r1], #1) @ May fault
strb r3, [r0], #1
USER( ldrgebt r3, [r1], #1) @ May fault
strgeb r3, [r0], #1
USER( ldrgtbt r3, [r1], #1) @ May fault
strgtb r3, [r0], #1
b .cfu_finished

.cfu_not_enough:
movs ip, r2
bne .cfu_nowords
.cfu_finished: mov r0, #0
add sp, sp, #8
LOADREGS(fd,sp!,{r4 - r7, pc})

.cfu_src_not_aligned:
bic r1, r1, #3
USER( ldrt r7, [r1], #4) @ May fault
cmp ip, #2
bgt .cfu_3fupi
beq .cfu_2fupi
.cfu_1fupi: subs r2, r2, #4
addmi ip, r2, #4
bmi .cfu_1nowords
mov r3, r7, pull #8
USER( ldrt r7, [r1], #4) @ May fault
orr r3, r3, r7, push #24
str r3, [r0], #4
mov ip, r1, lsl #32 - PAGE_SHIFT
rsb ip, ip, #0
movs ip, ip, lsr #32 - PAGE_SHIFT
beq .cfu_1fupi
cmp r2, ip
movlt ip, r2
sub r2, r2, ip
subs ip, ip, #16
blt .cfu_1rem8lp
PLD( pld [r1, #12] )
PLD( pld [r0, #12] )
PLD( subs ip, ip, #32 )
PLD( blt .cfu_1cpynopld )
PLD( pld [r1, #28] )
PLD( pld [r0, #28] )

.cfu_1cpy8lp:
PLD( pld [r1, #44] )
PLD( pld [r0, #44] )
.cfu_1cpynopld: mov r3, r7, pull #8
ldmia r1!, {r4 - r7} @ Shouldnt fault
subs ip, ip, #16
orr r3, r3, r4, push #24
mov r4, r4, pull #8
orr r4, r4, r5, push #24
mov r5, r5, pull #8
orr r5, r5, r6, push #24
mov r6, r6, pull #8
orr r6, r6, r7, push #24
stmia r0!, {r3 - r6}
bpl .cfu_1cpy8lp
PLD( cmn ip, #32 )
PLD( bge .cfu_1cpynopld )
PLD( add ip, ip, #32 )

.cfu_1rem8lp: tst ip, #8
movne r3, r7, pull #8
ldmneia r1!, {r4, r7} @ Shouldnt fault
orrne r3, r3, r4, push #24
movne r4, r4, pull #8
orrne r4, r4, r7, push #24
stmneia r0!, {r3 - r4}
tst ip, #4
movne r3, r7, pull #8
USER( ldrnet r7, [r1], #4) @ May fault
orrne r3, r3, r7, push #24
strne r3, [r0], #4
ands ip, ip, #3
beq .cfu_1fupi
.cfu_1nowords: mov r3, r7, get_byte_1
teq ip, #0
beq .cfu_finished
cmp ip, #2
strb r3, [r0], #1
movge r3, r7, get_byte_2
strgeb r3, [r0], #1
movgt r3, r7, get_byte_3
strgtb r3, [r0], #1
b .cfu_finished

.cfu_2fupi: subs r2, r2, #4
addmi ip, r2, #4
bmi .cfu_2nowords
mov r3, r7, pull #16
USER( ldrt r7, [r1], #4) @ May fault
orr r3, r3, r7, push #16
str r3, [r0], #4
mov ip, r1, lsl #32 - PAGE_SHIFT
rsb ip, ip, #0
movs ip, ip, lsr #32 - PAGE_SHIFT
beq .cfu_2fupi
cmp r2, ip
movlt ip, r2
sub r2, r2, ip
subs ip, ip, #16
blt .cfu_2rem8lp
PLD( pld [r1, #12] )
PLD( pld [r0, #12] )
PLD( subs ip, ip, #32 )
PLD( blt .cfu_2cpynopld )
PLD( pld [r1, #28] )
PLD( pld [r0, #28] )

.cfu_2cpy8lp:
PLD( pld [r1, #44] )
PLD( pld [r0, #44] )
.cfu_2cpynopld: mov r3, r7, pull #16
ldmia r1!, {r4 - r7} @ Shouldnt fault
subs ip, ip, #16
orr r3, r3, r4, push #16
mov r4, r4, pull #16
orr r4, r4, r5, push #16
mov r5, r5, pull #16
orr r5, r5, r6, push #16
mov r6, r6, pull #16
orr r6, r6, r7, push #16
stmia r0!, {r3 - r6}
bpl .cfu_2cpy8lp
PLD( cmn ip, #32 )
PLD( bge .cfu_2cpynopld )
PLD( add ip, ip, #32 )

.cfu_2rem8lp: tst ip, #8
movne r3, r7, pull #16
ldmneia r1!, {r4, r7} @ Shouldnt fault
orrne r3, r3, r4, push #16
movne r4, r4, pull #16
orrne r4, r4, r7, push #16
stmneia r0!, {r3 - r4}
tst ip, #4
movne r3, r7, pull #16
USER( ldrnet r7, [r1], #4) @ May fault
orrne r3, r3, r7, push #16
strne r3, [r0], #4
ands ip, ip, #3
beq .cfu_2fupi
.cfu_2nowords: mov r3, r7, get_byte_2
teq ip, #0
beq .cfu_finished
cmp ip, #2
strb r3, [r0], #1
movge r3, r7, get_byte_3
strgeb r3, [r0], #1
USER( ldrgtbt r3, [r1], #0) @ May fault
strgtb r3, [r0], #1
b .cfu_finished

.cfu_3fupi: subs r2, r2, #4
addmi ip, r2, #4
bmi .cfu_3nowords
mov r3, r7, pull #24
USER( ldrt r7, [r1], #4) @ May fault
orr r3, r3, r7, push #8
str r3, [r0], #4
mov ip, r1, lsl #32 - PAGE_SHIFT
rsb ip, ip, #0
movs ip, ip, lsr #32 - PAGE_SHIFT
beq .cfu_3fupi
cmp r2, ip
movlt ip, r2
sub r2, r2, ip
subs ip, ip, #16
blt .cfu_3rem8lp
PLD( pld [r1, #12] )
PLD( pld [r0, #12] )
PLD( subs ip, ip, #32 )
PLD( blt .cfu_3cpynopld )
PLD( pld [r1, #28] )
PLD( pld [r0, #28] )

.cfu_3cpy8lp:
PLD( pld [r1, #44] )
PLD( pld [r0, #44] )
.cfu_3cpynopld: mov r3, r7, pull #24
ldmia r1!, {r4 - r7} @ Shouldnt fault
orr r3, r3, r4, push #8
mov r4, r4, pull #24
orr r4, r4, r5, push #8
mov r5, r5, pull #24
orr r5, r5, r6, push #8
mov r6, r6, pull #24
orr r6, r6, r7, push #8
stmia r0!, {r3 - r6}
subs ip, ip, #16
bpl .cfu_3cpy8lp
PLD( cmn ip, #32 )
PLD( bge .cfu_3cpynopld )
PLD( add ip, ip, #32 )

.cfu_3rem8lp: tst ip, #8
movne r3, r7, pull #24
ldmneia r1!, {r4, r7} @ Shouldnt fault
orrne r3, r3, r4, push #8
movne r4, r4, pull #24
orrne r4, r4, r7, push #8
stmneia r0!, {r3 - r4}
tst ip, #4
movne r3, r7, pull #24
USER( ldrnet r7, [r1], #4) @ May fault
orrne r3, r3, r7, push #8
strne r3, [r0], #4
ands ip, ip, #3
beq .cfu_3fupi
.cfu_3nowords: mov r3, r7, get_byte_3
teq ip, #0
beq .cfu_finished
cmp ip, #2
strb r3, [r0], #1
USER( ldrgebt r3, [r1], #1) @ May fault
strgeb r3, [r0], #1
USER( ldrgtbt r3, [r1], #1) @ May fault
strgtb r3, [r0], #1
b .cfu_finished

.section .fixup,"ax"
.align 0
/*
* We took an exception. r0 contains a pointer to
* the byte not copied.
*/
9001: ldr r2, [sp], #4 @ void *to
sub r2, r0, r2 @ bytes copied
ldr r1, [sp], #4 @ unsigned long count
subs r4, r1, r2 @ bytes left to copy
movne r1, r4
blne __memzero
mov r0, r4
LOADREGS(fd,sp!, {r4 - r7, pc})
.previous

我們將在另一篇文章中詳細分析該函數。 http://www.linuxidc.com/Linux/2013-01/77501p2.htm

Copyright © Linux教程網 All Rights Reserved