序言
平常我们写Linux驱动和用户空间交互时,都是通过copy_from_user把用户空间传过来的数据进行拷贝,为何要如此做呢?
由于用户空间是不能直接内核空间数据的,她们映射的是不同的地址空间,只能先将数据拷贝过来,之后再操作。
假如用户空间须要传几MB的数据给内核,这么原先的拷贝方法或许效率非常低,也不太现实,那如何办呢?
想想,之所以要拷贝是由于用户空间不能直接访问内核空间,那假如可以直接访问内核空间的bufferlinux怎么查看系统版本,是不是就解决了。
简单来说,就是让一块化学显存拥有两份映射linux cp,即拥有两个虚拟地址,一个在内核空间,一个在用户空间。关系如下:
通过mmap映射就可以实现。
应用层
应用层代码很简单,主要就是通过mmap系统调用进行映射,之后就可以对返回的地址进行操作。
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都调用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的第一个参数是想要映射的起始地址,一般设置为NULLlinux mmap驱动,表示由内核来决定该起始地址。
第二参数是要映射的显存空间的大小。
第三个参数PROT_READ|PROT_WRITE表示映射后的空间是可读可写的。
第四个参数可填MAP_SHARED或MAP_PRIVATE:
驱动层
驱动层主要是实现mmap插口linux 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 failedn");
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了。