#我們寫Linux驅動程式和使用者空間互動時,都是透過copy_from_user
把使用者空間傳過來的資料進行拷貝,為什麼要這麼做呢?
因為使用者空間是無法直接核心空間資料的,他們映射的是不同的位址空間,只能先將資料拷貝過來,然後再操作。
如果用戶空間需要傳幾MB的資料給內核,那麼原來的拷貝方式顯然效率特別低,也不太現實,那怎麼辦呢?
想想,之所以要拷貝是因為使用者空間不能直接存取核心空間,那如果可以直接存取核心空間的buffer,是不是就解決了。
簡單來說,就是讓一塊實體記憶體擁有兩份映射,即擁有兩個虛擬位址,一個在核心空間,一個在使用者空間。 關係如下:
透過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
:该内存区域的访问权限
驱动主要步骤:
1、使用kmalloc
或者kzalloc
函数分配一块内存kernel_buf
,因为这样分配的内存物理地址是连续的,mmap
后应用层会对这一个基地址去访问这块内存。
2、实现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、通过virt_to_phys
将虚拟地址转为物理地址,这里的kernel_buf
是内核空间的一块虚拟地址空间
2、设置属性:不使用cache,使用buffer
3、映射:通过remap_pfn_range
函数映射,phy>>PAGE_SHIFT
其实就是按page
映射,除了这个参数,其他的起始地址、大小和权限都可以由用户在系统调用函数中指定。
当应用层调用mmap
后,就会调用到驱动层的mmap
函数,最终应用层的虚拟地址和驱动中的物理地址就建立了映射关系,应用层也就可以直接访问驱动的buffer了。
以上是Linux驅動IO篇-mmap操作的詳細內容。更多資訊請關注PHP中文網其他相關文章!