谷动谷力

 找回密码
 立即注册
查看: 1135|回复: 0
打印 上一主题 下一主题
收起左侧

【Linux驱动开发】Linux驱动IO篇——mmap操作

[复制链接]
跳转到指定楼层
楼主
发表于 2023-6-20 23:40:54 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
【Linux驱动开发】Linux驱动IO篇——mmap操作


前言

平时我们写Linux驱动和用户空间交互时,都是通过copy_from_user把用户空间传过来的数据进行拷贝,为什么要这么做呢?


因为用户空间是不能直接内核空间数据的,他们映射的是不同的地址空间,只能先将数据拷贝过来,然后再操作。


如果用户空间需要传几MB的数据给内核,那么原来的拷贝方式显然效率特别低,也不太现实,那怎么办呢?


想想,之所以要拷贝是因为用户空间不能直接访问内核空间,那如果可以直接访问内核空间的buffer,是不是就解决了。

简单来说,就是让一块物理内存拥有两份映射,即拥有两个虚拟地址,一个在内核空间,一个在用户空间。关系如下:

通过mmap映射就可以实现。


应用层

应用层代码很简单,主要就是通过mmap系统调用进行映射,然后就可以对返回的地址进行操作。

  1. char * buf;
  2. /* 1. 打开文件 */
  3. fd = open("/dev/hello", O_RDWR);
  4. if (fd == -1)
  5. {
  6.       printf("can not open file /dev/hello\n");
  7.       return -1;
  8. }

  9. /* 2. mmap
  10.        * MAP_SHARED  : 多个APP都调用mmap映射同一块内存时, 对内存的修改大家都可以看到。
  11.        *               就是说多个APP、驱动程序实际上访问的都是同一块内存
  12.        * MAP_PRIVATE : 创建一个copy on write的私有映射。
  13.        *               当APP对该内存进行修改时,其他程序是看不到这些修改的。
  14.        *               就是当APP写内存时, 内核会先创建一个拷贝给这个APP,
  15.        *               这个拷贝是这个APP私有的, 其他APP、驱动无法访问。
  16.        */
  17. 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函数,函数原型如下:

  1. int remap_pfn_range(
  2.   struct vm_area_struct *vma,
  3.   unsigned long addr,
  4.   unsigned long pfn,
  5.   unsigned long size,
  6.   pgprot_t prot);
复制代码

vma:描述一片映射区域的结构体指针

addr:要映射的虚拟地址起始地址

pfn:物理内存所对应的页框号,就是将物理地址除以页大小得到的值

size:映射的大小

prot:该内存区域的访问权限


驱动主要步骤:

1、使用kmalloc或者kzalloc函数分配一块内存kernel_buf,因为这样分配的内存物理地址是连续的,mmap后应用层会对这一个基地址去访问这块内存。

2、实现mmap函数

  1. static int hello_drv_mmap(struct file *file, struct vm_area_struct *vma)
  2. {
  3. /* 获得物理地址 */
  4. unsigned long phy = virt_to_phys(kernel_buf);//kernel_buf是内核空间分配的一块虚拟地址空间
  5.    
  6.     /* 设置属性:cache, buffer*/
  7. vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
  8.    
  9.     /* map */
  10.     if(remap_pfn_range(vma, vma->vm_start, phy>>PAGE_SHFIT,
  11.                       vma->vm_end - vma->start, vma->vm_page_prot)){
  12. printk("mmap remap_pfn_range failed\n");
  13.     return -ENOBUFS;
  14. }
  15. return 0;
  16. }

  17. static struct file_operations my_fops = {
  18. .mmap = hello_drv_mmap,
  19. };
复制代码

1、通过virt_to_phys将虚拟地址转为物理地址,这里的kernel_buf是内核空间的一块虚拟地址空间

2、设置属性:不使用cache,使用buffer

3、映射:通过remap_pfn_range函数映射,phy>>PAGE_SHIFT其实就是按page映射,除了这个参数,其他的起始地址、大小和权限都可以由用户在系统调用函数中指定。


当应用层调用mmap后,就会调用到驱动层的mmap函数,最终应用层的虚拟地址和驱动中的物理地址就建立了映射关系,应用层也就可以直接访问驱动的buffer了。

+10
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|深圳市光明谷科技有限公司|光明谷商城|Sunshine Silicon Corpporation ( 粤ICP备14060730号|Sitemap

GMT+8, 2024-11-17 17:49 , Processed in 0.782083 second(s), 44 queries .

Powered by Discuz! X3.2 Licensed

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表