平時我們寫Linux驅動和用戶空間交互時,都是通過 因為用戶空間是不能直接內核空間數據的,他們映射的是不同的地址空間,只能先將數據拷貝過來,然后再操作。 如果用戶空間需要傳幾MB的數據給內核,那么原來的拷貝方式顯然效率特別低,也不太現實,那怎么辦呢? 想想,之所以要拷貝是因為用戶空間不能直接訪問內核空間,那如果可以直接訪問內核空間的buffer,是不是就解決了。 簡單來說,就是讓一塊物理內存擁有兩份映射,即擁有兩個虛擬地址,一個在內核空間,一個在用戶空間。關系如下: 通過 應用層代碼很簡單,主要就是通過 登錄后復制 第二參數是要映射的內存空間的大小。 第三個參數 第四個參數可填 驅動層主要是實現 登錄后復制 驅動主要步驟: 1、使用 2、實現mmap函數 登錄后復制 1、通過 2、設置屬性:不使用cache,使用buffer 3、映射:通過 當應用層調用前言
copy_from_user
把用戶空間傳過來的數據進行拷貝,為什么要這么做呢?mmap
映射就可以實現。應用層
mmap
系統調用進行映射,然后就可以對返回的地址進行操作。char * buf;
/* 1. 打開文件 */
fd = open("/dev/hello", O_RDWR);
if (fd == -1)
{
printf("can not open file /dev/hello\n");
return -1;
}
/* 2. mmap
* MAP_SHARED : 多個APP都調用mmap映射同一塊內存時, 對內存的修改大家都可以看到。
* 就是說多個APP、驅動程序實際上訪問的都是同一塊內存
* MAP_PRIVATE : 創建一個copy on write的私有映射。
* 當APP對該內存進行修改時,其他程序是看不到這些修改的。
* 就是當APP寫內存時, 內核會先創建一個拷貝給這個APP,
* 這個拷貝是這個APP私有的, 其他APP、驅動無法訪問。
*/
buf = mmap(NULL, 1024*8, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
mmap
的第一個參數是想要映射的起始地址,通常設置為NULL
,表示由內核來決定該起始地址。PROT_READ | PROT_WRITE
表示映射后的空間是可讀可寫的。MAP_SHARED
或MAP_PRIVATE
:
MAP_SHARED
:多個APP
都調用mmap
映射同一塊內存時, 對內存的修改大家都可以看到。就是說多個APP
、驅動程序實際上訪問的都是同一塊內存。MAP_PRIVATE
:創建一個copy on write
的私有映射。當APP
對該內存進行修改時,其他程序是看不到這些修改的。就是當APP
寫內存時, 內核會先創建一個拷貝給這個APP
,這個拷貝是這個APP
私有的, 其他APP、驅動無法訪問。驅動層
mmap
接口,而mmap
接口的實現,主要是調用了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
:物理內存所對應的頁框號,就是將物理地址除以頁大小得到的值size
:映射的大小prot
:該內存區域的訪問權限kmalloc
或者kzalloc
函數分配一塊內存kernel_buf
,因為這樣分配的內存物理地址是連續的,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,
};
virt_to_phys
將虛擬地址轉為物理地址,這里的kernel_buf
是內核空間的一塊虛擬地址空間remap_pfn_range
函數映射,phy>>PAGE_SHIFT
其實就是按page
映射,除了這個參數,其他的起始地址、大小和權限都可以由用戶在系統調用函數中指定。mmap
后,就會調用到驅動層的mmap
函數,最終應用層的虛擬地址和驅動中的物理地址就建立了映射關系,應用層也就可以直接訪問驅動的buffer了。
以上就是Linux驅動IO篇——mmap操作的詳細內容,更多請關注www.92cms.cn其它相關文章!