1. 程式人生 > >copy_to_user和copy_from_user兩個函式的分析

copy_to_user和copy_from_user兩個函式的分析

在核心的學習中會遇到很多挺有意思的函式,而且能沿著一個函式扯出來很多個相關的函式。copy_to_usercopy_from_user就是在進行驅動相關程式設計的時候,要經常遇到的兩個函式。由於核心空間與使用者空間的記憶體不能直接互訪,因此藉助函式copy_to_user()完成使用者空間到核心空間的複製,函式copy_from_user()完成核心空間到使用者空間的複製。下面我們來仔細的理一下這兩個函式的來龍去脈。

首先,我們來看一下這兩個函式的在原始碼檔案中是如何定義的:

~/arch/i386/lib/usercopy.c

unsigned long

copy_to_user(void __user *to, const void *from, unsigned long n)

{

might_sleep();

BUG_ON((long) n < 0);

if (access_ok(VERIFY_WRITE, to, n))

n = __copy_to_user(to, from, n);

return n;

}

EXPORT_SYMBOL(copy_to_user);

從註釋中就可以看出,這個函式的主要作用就是從核心空間拷貝一塊兒資料到使用者空間,由於這個函式有可能睡眠,所以只能用於使用者空間。它有如下三個引數,

To 目標地址,這個地址是使用者空間的地址;

From 源地址,這個地址是核心空間的地址;

N 將要拷貝的資料的位元組數。

如果資料拷貝成功,則返回零;否則,返回沒有拷貝成功的資料位元組數。

以上是對函式的一些說明,接下來讓我們看看這個函式的內部面目:

引數to的時候有個__user限定,這個在~/include/linux/compiler.h中有如下定義:

# define __user__attribute__((noderef, address_space(1)))

表示這是一個使用者空間的地址,即其指向的為使用者空間的記憶體

大家可能對這個__attribute__感到比較迷惑,不過沒關係,google一下嘛

__attribute__gnu c編譯器的一個功能,它用來讓開發者使用此功能給所宣告的函式或者變數附加一個屬性,以方便編譯器進行錯誤檢查,其實就是一個核心檢查器。

具體可以參考如下:

接下來我們看一下

might_sleep();它有兩個實現版本,debug版本和非debug版本:

debug版本中,在有可能引起sleep的函式中會給出相應的提示,如果是在原子的上下文中執行,則會打印出棧跟蹤的資訊,這是通過__might_sleep(__FILE__, __LINE__);函式來實現的,並且接著呼叫might_resched()函式進行重新排程。

在非debug版本中直接呼叫might_resched()函式進行重新排程。

其實現方式為,在~/ include/linux/kernel.h中:

#ifdef CONFIG_DEBUG_SPINLOCK_SLEEP

void __might_sleep(char *file, int line);

# define might_sleep() /

do { __might_sleep(__FILE__, __LINE__); might_resched(); } while (0)

#else

# define might_sleep() do { might_resched(); } while (0)

#endif

接下來是一個檢查引數合法性的巨集:

BUG_ON((long) n < 0);

其實現為如下(~/include/asm-generic/bug.h):

它通過檢查條件,根據結果來決定是否列印相應的提示資訊;

#ifdef CONFIG_BUG

#ifndef HAVE_ARCH_BUG

#define BUG() do { /

printk("BUG: failure at %s:%d/%s()!/n", __FILE__, __LINE__, __FUNCTION__); /

panic("BUG!"); /

} while (0)

#endif

#ifndef HAVE_ARCH_BUG_ON

#define BUG_ON(condition) do { if (unlikely((condition)!=0)) BUG(); } while(0)

#endif

接下來是一個巨集

access_ok(VERIFY_WRITE, to, n)

它是用來檢查引數中一個指向使用者空間資料塊的指標是否有效,如果有效返回非零,否則返回零。其實現如下(在/include/asm-i386/uaccess.h中):

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

其中__range_ok(addr,size)的實現是通過內嵌彙編來實現的,內容如下(在/include/asm-i386/uaccess.h中)

#define __range_ok(addr,size) ({ /

unsigned long flag,sum; /

__chk_user_ptr(addr); /

asm("addl %3,%1 ; sbbl %0,%0; cmpl %1,%4; sbbl $0,%0" /

:"=&r" (flag), "=r" (sum) /

:"1" (addr),"g" ((int)(size)),"g" (current_thread_info()->addr_limit.seg)); /

flag; })

其實現的功能為:

(u33)addr + (u33)size >= (u33)current->addr_limit.seg

判斷上式是否成立,若不成立則表示地址有效,返回零;否則返回非零

接下來的這個函式才是最重要的函式,它實現了拷貝的工作:

__copy_to_user(to, from, n)

其實現方式如下(在/include/asm-i386/uaccess.h中)

static __always_inline unsigned long __must_check

__copy_to_user(void __user *to, const void *from, unsigned long n)

{

might_sleep();

return __copy_to_user_inatomic(to, from, n);

}

有一個__always_inline巨集,其內容就是inline,一個__must_check,其內容是在gcc3gcc4版本里為__attribute__((warn_unused_result))

其中might_sleep同上面__user時候的註釋。

最終呼叫的是__copy_to_user_inatomic(to, from, n)來完成拷貝工作的,此函式的實現如下(在/include/asm-i386/uaccess.h中)

static __always_inline unsigned long __must_check

__copy_to_user_inatomic(void __user *to, const void *from, unsigned long n)

{

if (__builtin_constant_p(n)) {

unsigned long ret;

switch (n) {

case 1:

__put_user_size(*(u8 *)from, (u8 __user *)to, 1, ret, 1);

return ret;

case 2:

__put_user_size(*(u16 *)from, (u16 __user *)to, 2, ret, 2);

return ret;

case 4:

__put_user_size(*(u32 *)from, (u32 __user *)to, 4, ret, 4);

return ret;

}

}

return __copy_to_user_ll(to, from, n);

}

其中__builtin_constant_p(n)gcc的內建函式,__builtin_constant_p用於判斷一個值是否為編譯時常熟,如果引數n的值為常數,函式返回1否則返回0。很多計算或操作在引數為常數時有更優化的實現,在 GNU C 中用上面的方法可以根據引數是否為常數,只編譯常數版本或非常數版本,這樣既不失通用性,又能在引數是常數時編譯出最優化的程式碼。

如果n為常數12或者4,就會選擇某個swith來執行拷貝動作,拷貝是通過如下函式來實現的(/include/asm-i386/uaccess.h)

#ifdef CONFIG_X86_WP_WORKS_OK

#define __put_user_size(x,ptr,size,retval,errret)/

do {/

retval = 0;/

__chk_user_ptr(ptr);/

switch (size) {/

case 1: __put_user_asm(x,ptr,retval,"b","b","iq",errret);break;/

case 2: __put_user_asm(x,ptr,retval,"w","w","ir",errret);break; /

case 4: __put_user_asm(x,ptr,retval,"l","","ir",errret); break;/

case 8: __put_user_u64((__typeof__(*ptr))(x),ptr,retval); break;/

default: __put_user_bad();/

}/

} while (0)

#else

#define __put_user_size(x,ptr,size,retval,errret)/

do {/

__typeof__(*(ptr)) __pus_tmp = x;/

retval = 0;/

/

if(unlikely(__copy_to_user_ll(ptr, &__pus_tmp, size) != 0))/

retval = errret;/

} while (0)

#endif

其中__put_user_asm為一個巨集,拷貝工作是通過如下的內聯彙編來實現的(/include/asm-i386/uaccess.h)

#define __put_user_asm(x, addr, err, itype, rtype, ltype, errret)/

__asm__ __volatile__(/

"1:mov"itype" %"rtype"1,%2/n"/

"2:/n"/

".section .fixup,/"ax/"/n"/

"3:movl %3,%0/n"/

"jmp 2b/n"/

".previous/n"/

".section __ex_table,/"a/"/n"/

".align 4/n"/

".long 1b,3b/n"/

".previous"/

: "=r"(err)/

: ltype (x), "m"(__m(addr)), "i"(errret), "0"(err))

以上這兩個函式是為了在拷貝小位元組資料比如char/int等資料的時候考慮到效率來實現小資料拷貝。

而若n不是如上所說的常數,則進行資料塊區域拷貝,其實現如下(~/arch/i386/lib/usercopy.c)

unsigned long __copy_to_user_ll(void __user *to, const void *from, unsigned long n)

{

BUG_ON((long) n < 0);

#ifndef CONFIG_X86_WP_WORKS_OK

if (unlikely(boot_cpu_data.wp_works_ok == 0) &&

((unsigned long )to) < TASK_SIZE) {

/*

* CPU does not honor the WP bit when writing

* from supervisory mode, and due to preemption or SMP,

* the page tables can change at any time.

* Do it manually.Manfred <[email protected]>

*/

while (n) {

unsigned long offset = ((unsigned long)to)%PAGE_SIZE;

unsigned long len = PAGE_SIZE - offset;

int retval;

struct page *pg;

void *maddr;

if (len > n)

len = n;

survive:

down_read(&current->mm->mmap_sem);

retval = get_user_pages(current, current->mm,

(unsigned long )to, 1, 1, 0, &pg, NULL);

if (retval == -ENOMEM && current->pid == 1) {

up_read(&current->mm->mmap_sem);

blk_congestion_wait(WRITE, HZ/50);

goto survive;

}

if (retval != 1) {

up_read(&current->mm->mmap_sem);

break;

}

maddr = kmap_atomic(pg, KM_USER0);

memcpy(maddr + offset, from, len);

kunmap_atomic(maddr, KM_USER0);

set_page_dirty_lock(pg);

put_page(pg);

up_read(&current->mm->mmap_sem);

from += len;

to += len;

n -= len;

}

return n;

}

#endif

if (movsl_is_ok(to, from, n))

__copy_user(to, from, n);

else

n = __copy_user_intel(to, from, n);

return n;

}

EXPORT_SYMBOL(__copy_to_user_ll);

下面是copy_from_user函式的實現:

unsigned long

copy_from_user(void *to, const void __user *from, unsigned long n)

{

might_sleep();

BUG_ON((long) n < 0);

if (access_ok(VERIFY_READ, from, n))

n = __copy_from_user(to, from, n);

else

memset(to, 0, n);

return n;

}

EXPORT_SYMBOL(copy_from_user);

其實現方式與copy_to_user函式的實現方式類似:就不再累述了。

如上就是copy_to_usercopy_from_user兩個函式的工作方式,這些進行簡單的分析與跟蹤。細節的部分還有待於進一步研究。

copy_to_user與mmap的工作原理

copy_to_user在每次拷貝時需要檢測指標的合法性,也就是使用者空間的指標所指向的地址的確是一段該程序本身的地址,而不是指向了不屬於它的地方,而且每次都會拷貝一次資料,頻繁訪問記憶體,由於虛擬地址連續,實體地址不一定會連續,從而造成CPU的CACHE頻繁失效,從而使速度降低   
  mmap僅在第一次使用時為程序建立頁表,也就是將一段實體地址對映到一段虛擬地址上,以後操作時不再檢測其地址的合法性(合法性交由CPU頁保護異常來做),另一方面是核心下直接操作mmap地址,可以不用頻繁拷貝,也就是說在核心下直接可用指標向該地址操作,而不再在核心中專門開一個緩衝區,然後將緩衝區中的資料拷貝一次進來,mmap一般是將一段連續的實體地址對映成一段虛擬地址,當然,也可以將每段連續,但各段不連續的實體地址對映成一段連續的虛擬地址,無論如何,其實體地址在每段之中是連續的,這樣一來,就不會造成CPU的CACHE頻繁失效,從而大大節約時間。

相關推薦

copy_to_usercopy_from_user函式分析

在核心的學習中會遇到很多挺有意思的函式,而且能沿著一個函式扯出來很多個相關的函式。copy_to_user和copy_from_user就是在進行驅動相關程式設計的時候,要經常遇到的兩個函式。由於核心空間與使用者空間的記憶體不能直接互訪,因此藉助函式copy_to_user(

【matlab】關於uiwaituiresume函式的理解及用途

uiwait和uiresume兩個函式,在編寫GUI相應程式碼時,是非常有用的兩個函式,他們的主要用途,通俗的來說,就是當程式執行到uiwait時,程式會處於等待中,知道遇到uiresume函式,才會執行uiwait之後的程式。 這一點在多個GUI介面進行資料傳遞時,起到了

loadrunner:文字檢查點web_reg_findweb_find函式的區別

web_reg_find是先註冊(register)後查詢的;使用時將它放在請求語句的前面。 而web_find是查詢前面的請求結果;使用時將它放在請求語句的後面。 另二者的引數也完成不一樣的,web_reg_find引數中SaveCount記錄查詢匹配的次數, web_f

實現一個類,把冒泡插入封裝到函式中去(宣告函式,一個是冒泡,一個是插入),且進行呼叫除錯

實現一個類,把冒泡和插入封裝到兩個函式中去(宣告兩個函式,一個是冒泡,一個是插入),且進行呼叫和除錯 import java.util.Arrays; /* * 實現一個類,把冒泡和插入封裝到兩個函式中去(宣告兩個函式,一個是冒泡,一個是插入),且進行呼叫和除錯 */ public class E

Linux 調研popen/system, 理解這函式fork的區別.

自己的總結:           1.popen是並行(最後子程序是由pclose回收),system是序列(會等待子程序做完事,然後收拾)。           2.system() 在等待命令終止時將忽略SIGINT 和SIGQUIT 訊號,同時阻塞SIGCHLD

從前端後端角度分析jsonp跨域訪問(完整例項)

一、什麼是跨域訪問 舉個栗子:在A網站中,我們希望使用Ajax來獲得B網站中的特定內容。如果A網站與B網站不在同一個域中,那麼就出現了跨域訪問問題。你可以理解為兩個域名之間不能跨過域名來發送請求或者請求資料,否則就是不安全的。跨域訪問違反了同源策略,同源策略的詳

無限極分類不是用遞迴 函式解決 新增迴圈【附上程式碼】

實現無限極分類 首先先看一下資料結構 //資料結構 // $items = array( // 1 => array('id' => 1, 'pid' => 0, 'name' => 'anhui'), // 2 => array('id

C語言:寫函式,分別求最大公約數最小公倍數

題目:寫兩個函式,分別求兩個整數的最大公約數和最小公倍數,用主函式呼叫這個兩個函式,並輸出結果。兩個整數由鍵盤輸入 分析:求最大公約數,需要用到輾轉相除法: 輾轉相除法:設兩數為a、b(a>b

分別採用遞迴非遞迴方式編寫函式,求一棵二叉樹中葉子節點個數

#include #include #define MAXSIZE 50 typedef char datatype; typedef struct node { datatype data; struct node *lchild,*rchild; } bintnode,*bintre

linux驅動platform_set_drvdata platform_get_drvdata這函式

驅動中常用到platform_set_drvdata 和 platform_get_drvdata這兩個函式,用於儲存區域性變數: include/linux/platform_device.h中: static inline void *platform_get_d

用來獲取子串的函式substrsubstring用法比較

substr函式和substring函式都是用來從某個“母字串”中提取“子字串”的函式。但用法有些差別,下面分別介紹。 substr函式 功能:從“母字串”的“指定位置”開始提取“指定長度”的“子字串”。 使用方法:字串資料.substr(start [,length]) s

Hadoop 2.6.5 FileSystemConfiguration對象的探究

family 上傳數據 大數 塊大小 緩存 完成 color span 小夥伴 Hadoop 2.6.5 FileSystem和Configuration兩個對象的探究 版權聲明:本文為yunshuxueyuan原創文章,如需轉載,請標明出處。【http://www.

微信服務號、訂閱號企業號的差別(運營開發角度)

通訊錄 href 開發人員 mark number hide 品牌 log 互聯網 一、運營的角度 1.1、概括 訂閱號: 微信最初的形態是一個純粹的社交工具,也就是人與人之間的聯系工具,當中又分熟人之間的聯系和陌生人之間的聯系。於是就誕生了朋友圈

PP1指向了OO1變量(對象)的地址, 而不是OO1的內容(對象的實際地址)——充分證明@是取變量(對象)的地址,而不是變量裏面的內容,夠清楚!

com 告訴 cnblogs src logs es2017 strong bsp html 如圖,為什麽這樣取出來的p,p1的值不一樣呢? 165232328群友庾偉洪告訴我: P和P1指向了O和O1兩個變量(對象)的地址, 而不是O和O1的內容(對象

今天我們來討論一下displayvisibilityCSS屬性。

分享圖片 font images 渲染 大神 -s rdp abi css 在討論著兩個屬性之前我們先來看看HTML標簽的全局屬性。就是可以直接在HTML標簽上直接寫的屬性。 以下是菜鳥教程的截圖: 1.看以下第一個快捷鍵的屬性accesskey;設置的就不多說了。主要就

無聊時寫的俄羅斯方塊(分為SDLQt版本)

app deb fcm cnn 無聊 線程 dac tutorial spi 6213-ChineseZodiac(map) 多線程問題 【CF472G】【XSY2112】DesignTutorial壓位 大家都開始C++0x了,我也來湊熱鬧,今天的主題是《調侃rvalue

Java集合框架上機練習題:編寫一個Book類,該類至少有nameprice屬性。該類要實現Comparable接口,在接口的compareTo()方法.....

ext .cn 數據庫 識別 方法 屬性 set package compareto 編寫一個Book類,該類至少有name和price兩個屬性。該類要實現Comparable接口,在接口的compareTo()方法中規定兩個Book類實例的大小關系為二者的price屬性的

Linux socket編程示例(最簡單的TCPUDP例子)

步驟 proto 詳解 dto 應該 pro sock bind ram 一、socket編程    網絡功能是Uinux/Linux的一個重要特點,有著悠久的歷史,因此有一個非常固定的編程套路。   基於TCP的網絡編程:     基於連接, 在交互過程中, 服務器

php中array_walk() array_map()函數區別

.html als 就是 gpo map false AR HP www. 兩個函數的共性和區別: 1.傳入這兩個函數的 $value,就是數組中的單一個元素。 2.array_walk() 僅返回true或者false,array_map() 返回處理後的數組; 3.要得