[轉自:http://blog.chinaunix.net/uid-20543672-id-3195249.html] 在學習Linux內核驅動的時候,一開始就會碰到copy_from_user和copy_to_user這兩個常用的函數。這兩個函數在內核使用的非常頻繁,負責將數據從用戶空間拷貝到內核空間以及將數據從內核空間拷貝到用戶空間。在4年半前初學Linux內核驅動程序的時候,我只是知道這個怎么用,并沒有很深入的分析這兩個函數。這次研究內核模塊掛載的時候,又碰到了它們。決定還是認真跟蹤一下函數。 首先這兩個函數的原型在arch/arm/include/asm/uaccess.h文件中:
static inline unsigned long __must_check copy_from_user(void?*to,?const?void __user?*from,?unsigned long n){????if?(access_ok(VERIFY_READ, from, n))????????n?=?__copy_from_user(to, from, n);????else?/*?security hole?-?plug it?*/????????memset(to,?0,?n);????return n;} static inline unsigned long __must_check copy_to_user(void __user?*to,?const?void?*from,?unsigned long n){????if?(access_ok(VERIFY_WRITE, to, n))????????n?=?__copy_to_user(to, from, n);????return n;} 這兩個函數從結構上來分析,其實都可以分為兩個部分: 1、首先檢查用戶空間的地址指針是否有效(難點) 2、調用__copy_from_user和__copy_to_user函數 在這個分析中,我們先易后難。首先看看具體數據拷貝功能的__copy_from_user和__copy_to_user函數 對于ARM構架,沒有單獨實現這兩個函數,所以他們的代碼位于include/asm-generic/uaccess.h
/*?*?帶有MMU的構架應該覆蓋這兩個函數?*/#ifndef __copy_from_userstatic inline __must_check long __copy_from_user(void?*to,????????const?void __user?*?from,?unsigned long n){????if?(__builtin_constant_p(n))?{????????switch(n)?{????????case?1:????????????*(u8?*)to?=?*(u8 __force?*)from;????????????return 0;????????case?2:????????????*(u16?*)to?=?*(u16 __force?*)from;????????????return 0;????????case?4:????????????*(u32?*)to?=?*(u32 __force?*)from;????????????return 0;#ifdef CONFIG_64BIT????????case?8:????????????*(u64?*)to?=?*(u64 __force?*)from;????????????return 0;#endif????????default:????????????break;????????}????} ????memcpy(to,?(const?void __force?*)from,?n);????return 0;}#endif #ifndef __copy_to_userstatic inline __must_check long __copy_to_user(void __user?*to,????????const?void?*from,?unsigned long n){????if?(__builtin_constant_p(n))?{????????switch(n)?{????????case?1:????????????*(u8 __force?*)to?=?*(u8?*)from;????????????return 0;????????case?2:????????????*(u16 __force?*)to?=?*(u16?*)from;????????????return 0;????????case?4:????????????*(u32 __force?*)to?=?*(u32?*)from;????????????return 0;#ifdef CONFIG_64BIT????????case?8:????????????*(u64 __force?*)to?=?*(u64?*)from;????????????return 0;#endif????????default:????????????break;????????}????} ????memcpy((void __force?*)to,?from,?n);????return 0;}#endif 點擊(此處)折疊或打開
GCC的內建函數 __builtin_constant_p 用于判斷一個值是否為編譯時常數,如果參數值是常數,函數返回 1,否則返回 0。 從這兩個函數中可以看出其實結構是一樣的,首先看看n是不是常數,如果是并為1、2、4、8(64bit)則直接就用一個賦值語句拷貝數據。如果不是常數或n過大,則使用memcpy函數。而這個memcpy函數位于lib/string.c:
#ifndef __HAVE_ARCH_MEMCPY/**?*?memcpy?-?Copy one area of memory?to?another?*?@dest:?Where?to?copy?to?*?@src:?Where?to?copy from?*?@count:?The size of the area.?*?*?You should?not?use this?function?to?access IO?space,?use memcpy_toio()?*?or?memcpy_fromio()?instead.?*/void?*memcpy(void?*dest,?const?void?*src,?size_t count){????char?*tmp?=?dest;????const?char?*s?=?src; ????while?(count--)????????*tmp++?=?*s++;????return dest;}EXPORT_SYMBOL(memcpy);#endif 這個函數其實就是一個簡單的利用循環來數據拷貝,非常簡單。 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 好了如何拷貝數據我們已經了解了,現在我們來看看前面的用戶空間指針檢測函數access_ok,這其實是一個宏定義,位于arch/arm/include/asm/uaccess.h文件中:
/* We use 33-bit arithmetic here... */#define __range_ok(addr,size) ({ \unsigned long flag, roksum; \__chk_user_ptr(addr); \__asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \: "=&r" (flag), "=&r" (roksum) \: "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \: "cc"); \flag; }) ...... #define access_ok(type,addr,size) (__range_ok(addr,size) == 0)...... 這個就比較麻煩了,涉及到了C語言中內聯匯編,如果還不熟悉的朋友可以看看《ARM GCC 內嵌匯編手冊》,我也不是很熟。
現在我們來仔細分析__range_ok這個宏:
(1)unsigned long flag, roksum;\\定義兩個變量
?
flag:保存結果的變量:非零代表地址無效,零代表地址可以訪問。初始存放非零值(current_thread_info()->addr_limit),也就是當前進程的地址上限值。 roksum:保存要訪問的地址范圍末端,用于和當前進程地址空間限制數據做比較 ?
?
(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__,這就是一個空函數。
(3)接下來的匯編,我適當地翻譯如下:
? ? ?adds %1, %2, %3
roksum =?addr +?size 這個操作影響狀態位(目的是影響是進位標志C)
以下的兩個指令都帶有條件CC,也就是當C=0的時候才執行。
如果上面的加法指令進位了(C=1),則以下的指令都不執行,flag就為初始值current_thread_info()->addr_limit(非零值),并返回。
如果沒有進位(C=0),就執行下面的指令
? ? sbcccs %1, %1, %0?
roksum =?roksum -?flag,也就是(addr +?size)- (current_thread_info()->addr_limit),操作影響符號位。
如果(addr +?size)>=(current_thread_info()->addr_limit),則C=1
如果(addr +?size)<(current_thread_info()->addr_limit),則C=0
當C=0的時候執行以下指令,否則跳過(flag非零)。 ? ??movcc %0, #0 ? ? flag = 0,給flag賦值0 ?(4)flag;?
? ? 返回flag值
?
綜上所訴:__range_ok宏其實等價于:
如果(addr +?size)>=(current_thread_info()->addr_limit),返回非零值
如果(addr +?size)<(current_thread_info()->addr_limit),返回零
而access_ok就是檢驗將要操作的用戶空間的地址范圍是否在當前進程的用戶地址空間限制中。這個宏的功能很簡單,完全可以用C實現,不是必須使用匯編。個人理解:由于這兩個函數使用頻繁,就使用匯編來實現部分功能來增加效率。
?
? ??從這里再次可以認識到,copy_from_user與copy_to_user的使用是結合進程上下文的,因為他們要訪問“user”的內存空間,這個“user”必須是某個特定的進程。通過上面的源碼就知道,其中使用了current_thread_info()來檢查空間是否可以訪問。如果在驅動中使用這兩個函數,必須是在實現系統調用的函數中使用,不可在實現中斷處理的函數中使用。如果在中斷上下文中使用了,那代碼就很可能操作了根本不相關的進程地址空間。
? ? 其次由于操作的頁面可能被換出,這兩個函數可能會休眠,所以同樣不可在中斷上下文中使用。
轉載于:https://www.cnblogs.com/jiayy/p/4831844.html
總結
以上是生活随笔為你收集整理的基于ARM 构架(带MMU)的copy_from_user与copy_to_user详细分析的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。