Linux驅(qū)動IO篇——mmap操作

前言

平時我們寫linux驅(qū)動和用戶空間交互時,都是通過copy_from_user把用戶空間傳過來的數(shù)據(jù)進行拷貝,為什么要這么做呢?

因為用戶空間是不能直接內(nèi)核空間數(shù)據(jù)的,他們映射的是不同的地址空間,只能先將數(shù)據(jù)拷貝過來,然后再操作。

如果用戶空間需要傳幾MB的數(shù)據(jù)給內(nèi)核,那么原來的拷貝方式顯然效率特別低,也不太現(xiàn)實,那怎么辦呢?

想想,之所以要拷貝是因為用戶空間不能直接訪問內(nèi)核空間,那如果可以直接訪問內(nèi)核空間的buffer,是不是就解決了。

簡單來說,就是讓一塊物理內(nèi)存擁有兩份映射,即擁有兩個虛擬地址,一個在內(nèi)核空間,一個在用戶空間。關(guān)系如下:

Linux驅(qū)動IO篇——mmap操作

通過mmap映射就可以實現(xiàn)。

應(yīng)用層

應(yīng)用層代碼很簡單,主要就是通過mmap系統(tǒng)調(diào)用進行映射,然后就可以對返回的地址進行操作。

char?*?buf; /*?1.?打開文件?*/ ?fd?=?open("/dev/hello",?O_RDWR); ?if?(fd?==?-1) ?{ ??????printf("can?not?open?file?/dev/hellon"); ??????return?-1; ?}  /*?2.?mmap ???????*?MAP_SHARED??:?多個APP都調(diào)用mmap映射同一塊內(nèi)存時,?對內(nèi)存的修改大家都可以看到。 ???????*???????????????就是說多個APP、驅(qū)動程序?qū)嶋H上訪問的都是同一塊內(nèi)存 ???????*?MAP_PRIVATE?:?創(chuàng)建一個copy?on?write的私有映射。 ???????*???????????????當APP對該內(nèi)存進行修改時,其他程序是看不到這些修改的。 ???????*???????????????就是當APP寫內(nèi)存時,?內(nèi)核會先創(chuàng)建一個拷貝給這個APP, ???????*???????????????這個拷貝是這個APP私有的,?其他APP、驅(qū)動無法訪問。 ???????*/ buf?=??mmap(NULL,?1024*8,?PROT_READ?|?PROT_WRITE,?MAP_SHARED,?fd,?0);

mmap的第一個參數(shù)是想要映射的起始地址,通常設(shè)置為NULL,表示由內(nèi)核來決定該起始地址

第二參數(shù)是要映射的內(nèi)存空間的大小

第三個參數(shù)PROT_READ | PROT_WRITE表示映射后的空間是可讀可寫的。

第四個參數(shù)可填MAP_SHARED或MAP_PRIVATE:

  • MAP_SHARED:多個APP都調(diào)用mmap映射同一塊內(nèi)存時, 對內(nèi)存的修改大家都可以看到。就是說多個APP、驅(qū)動程序?qū)嶋H上訪問的都是同一塊內(nèi)存
  • MAP_PRIVATE:創(chuàng)建一個copy on write的私有映射。當APP對該內(nèi)存進行修改時,其他程序是看不到這些修改的。就是當APP寫內(nèi)存時, 內(nèi)核會先創(chuàng)建一個拷貝給這個APP,這個拷貝是這個APP私有的, 其他APP、驅(qū)動無法訪問。

驅(qū)動層

驅(qū)動層主要是實現(xiàn)mmap接口,而mmap接口的實現(xiàn),主要是調(diào)用了remap_pfn_range函數(shù),函數(shù)原型如下:

int?remap_pfn_range( ??struct?vm_area_struct?*vma,? ??unsigned?long?addr,? ??unsigned?long?pfn,? ??unsigned?long?size,? ??pgprot_t?prot);

vma:描述一片映射區(qū)域的結(jié)構(gòu)體指針

addr:要映射的虛擬地址起始地址

pfn:物理內(nèi)存所對應(yīng)的頁框號,就是將物理地址除以頁大小得到的值

size:映射的大小

prot:該內(nèi)存區(qū)域的訪問權(quán)限

驅(qū)動主要步驟:

1、使用kmalloc或者kzalloc函數(shù)分配一塊內(nèi)存kernel_buf,因為這樣分配的內(nèi)存物理地址是連續(xù)的,mmap后應(yīng)用層會對這一個基地址去訪問這塊內(nèi)存。

2、實現(xiàn)mmap函數(shù)

static?int?hello_drv_mmap(struct?file?*file,?struct?vm_area_struct?*vma) { ?/*?獲得物理地址?*/ ?unsigned?long?phy?=?virt_to_phys(kernel_buf);//kernel_buf是內(nèi)核空間分配的一塊虛擬地址空間 ???? ????/*?設(shè)置屬性:cache,?buffer*/ ?vma->vm_page_prot?=?pgprot_writecombine(vma->vm_page_prot); ???? ????/*?map?*/ ????if(remap_pfn_range(vma,?vma->vm_start,?phy>>PAGE_SHFIT, ??????????????????????vma->vm_end?-?vma->start,?vma->vm_page_prot)){ ?printk("mmap?remap_pfn_range?failedn"); ????return?-ENOBUFS; ?} ?return?0; }  static?struct?file_operations?my_fops?=?{ ?.mmap?=?hello_drv_mmap, };

1、通過virt_to_phys將虛擬地址轉(zhuǎn)為物理地址,這里的kernel_buf是內(nèi)核空間的一塊虛擬地址空間

2、設(shè)置屬性:不使用cache,使用buffer

3、映射:通過remap_pfn_range函數(shù)映射,phy>>PAGE_SHIFT其實就是按page映射,除了這個參數(shù),其他的起始地址、大小和權(quán)限都可以由用戶在系統(tǒng)調(diào)用函數(shù)中指定

當應(yīng)用層調(diào)用mmap后,就會調(diào)用到驅(qū)動層的mmap函數(shù),最終應(yīng)用層的虛擬地址和驅(qū)動中的物理地址就建立了映射關(guān)系,應(yīng)用層也就可以直接訪問驅(qū)動的buffer了。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊13 分享