前言
平時(shí)我們寫(xiě)Linux驅動(dòng)和用戶(hù)空間交互時(shí),都是通過(guò)copy_from_user把用戶(hù)空間傳過(guò)來(lái)的數據進(jìn)行拷貝,為什么要這么做呢?
因為用戶(hù)空間是不能直接內核空間數據的,他們映射的是不同的地址空間,只能先將數據拷貝過(guò)來(lái),然后再操作。
如果用戶(hù)空間需要傳幾MB的數據給內核,那么原來(lái)的拷貝方式顯然效率特別低,也不太現實(shí),那怎么辦呢?
想想,之所以要拷貝是因為用戶(hù)空間不能直接訪(fǎng)問(wèn)內核空間,那如果可以直接訪(fǎng)問(wèn)內核空間的buffer,是不是就解決了。
簡(jiǎn)單來(lái)說(shuō),就是讓一塊物理內存擁有兩份映射,即擁有兩個(gè)虛擬地址,一個(gè)在內核空間,一個(gè)在用戶(hù)空間。關(guān)系如下:

通過(guò)mmap映射就可以實(shí)現。
應用層
應用層代碼很簡(jiǎn)單,主要就是通過(guò)mmap系統調用進(jìn)行映射,然后就可以對返回的地址進(jìn)行操作。
char * buf;
/* 1. 打開(kāi)文件 */
fd = open('/dev/hello', O_RDWR);
if (fd == -1)
{
printf('can not open file /dev/hello\n');
return -1;
}
/* 2. mmap
* MAP_SHARED : 多個(gè)APP都調用mmap映射同一塊內存時(shí), 對內存的修改大家都可以看到。
* 就是說(shuō)多個(gè)APP、驅動(dòng)程序實(shí)際上訪(fǎng)問(wèn)的都是同一塊內存
* MAP_PRIVATE : 創(chuàng )建一個(gè)copy on write的私有映射。
* 當APP對該內存進(jìn)行修改時(shí),其他程序是看不到這些修改的。
* 就是當APP寫(xiě)內存時(shí), 內核會(huì )先創(chuàng )建一個(gè)拷貝給這個(gè)APP,
* 這個(gè)拷貝是這個(gè)APP私有的, 其他APP、驅動(dòng)無(wú)法訪(fǎng)問(wèn)。
*/
buf = mmap(NULL, 1024*8, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
mmap的第一個(gè)參數是想要映射的起始地址,通常設置為NULL,表示由內核來(lái)決定該起始地址。
第二參數是要映射的內存空間的大小。
第三個(gè)參數PROT_READ | PROT_WRITE表示映射后的空間是可讀可寫(xiě)的。
第四個(gè)參數可填MAP_SHARED或MAP_PRIVATE:
MAP_SHARED:多個(gè)APP都調用mmap映射同一塊內存時(shí), 對內存的修改大家都可以看到。就是說(shuō)多個(gè)APP、驅動(dòng)程序實(shí)際上訪(fǎng)問(wèn)的都是同一塊內存。
MAP_PRIVATE:創(chuàng )建一個(gè)copy on write的私有映射。當APP對該內存進(jìn)行修改時(shí),其他程序是看不到這些修改的。就是當APP寫(xiě)內存時(shí), 內核會(huì )先創(chuàng )建一個(gè)拷貝給這個(gè)APP,這個(gè)拷貝是這個(gè)APP私有的, 其他APP、驅動(dòng)無(wú)法訪(fǎng)問(wèn)。
驅動(dòng)層
驅動(dòng)層主要是實(shí)現mmap接口,而mmap接口的實(shí)現,主要是調用了remap_pfn_range函數,函數原型如下:int remap_pfn_range(
struct vm_area_struct *vma,
unsigned long addr,
unsigned long pfn,
unsigned long size,
pgprot_t prot);
vma:描述一片映射區域的結構體指針
addr:要映射的虛擬地址起始地址
pfn:物理內存所對應的頁(yè)框號,就是將物理地址除以頁(yè)大小得到的值
size:映射的大小
prot:該內存區域的訪(fǎng)問(wèn)權限
驅動(dòng)主要步驟:
1、使用kmalloc或者kzalloc函數分配一塊內存kernel_buf,因為這樣分配的內存物理地址是連續的,mmap后應用層會(huì )對這一個(gè)基地址去訪(fǎng)問(wèn)這塊內存。
2、實(shí)現mmap函數
static int hello_drv_mmap(struct file *file, struct vm_area_struct *vma)
{
/* 獲得物理地址 */
unsigned long phy = virt_to_phys(kernel_buf);//kernel_buf是內核空間分配的一塊虛擬地址空間
/* 設置屬性: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 failed\n');
return -ENOBUFS;
}
return 0;
}
static struct file_operations my_fops = {
.mmap = hello_drv_mmap,
};
1、通過(guò)virt_to_phys將虛擬地址轉為物理地址,這里的kernel_buf是內核空間的一塊虛擬地址空間
2、設置屬性:不使用cache,使用buffer
3、映射:通過(guò)remap_pfn_range函數映射,phy>>PAGE_SHIFT其實(shí)就是按page映射,除了這個(gè)參數,其他的起始地址、大小和權限都可以由用戶(hù)在系統調用函數中指定。
當應用層調用mmap后,就會(huì )調用到驅動(dòng)層的mmap函數,最終應用層的虛擬地址和驅動(dòng)中的物理地址就建立了映射關(guān)系,應用層也就可以直接訪(fǎng)問(wèn)驅動(dòng)的buffer了。
來(lái)源:嵌入式Linux充電站
溫馨提示:
聯(lián)系客服