linux驱动之mmap地址映射

应用场景

首先在linux中应用程序无法是直接访问驱动程序的数据的, 需要通过 copy_to_user 和 copy_from_user才能实现数据传输, 那么数据量大了以后如LCD的数据, 那么就会有很长的耗时, 为了解决这一问题, 引入mmap, 将底层物理地址映射出来, 让应用程序得以直接读写这一块内存. mmap的使用说白了, 很简单:
1, 有块虚拟地址
2, 找到要用的那块物理地址
3, 建立映射

内核应用程序开辟的虚拟地址

内核将每个进程都作为一个task_struct结构体存在一个双向循环链表中, 结构体在 include/linux/sched.h中定义. 其中有个struct mm_struct *mm成员, 记录着这个进程的虚拟地址的信息.duan0
在mm_struct中, struct vm_area_struct mmap 链表保存了每一块应用程序虚拟地址(堆空间, 栈空间, bss区, data常量空间, text代码0段)的起始位置和结束位置. 当然虚拟地址不可能是凭空产生, 自然是要有一块相应的物理地址来对应, 这一块物理地址就保存在pgd_t * pgd* 这个成员变量中叫做页目录表, pgd成员记录了对应的物理地址, 也记录了如何映射

进程启动时虚拟地址内核已经帮我们做好了, 当一段程序运行时,便开辟了一块4G虚拟地址(在32位系统中), 在linux中可以在 /proc/进程号/maps 来查看这个进程用到的虚拟地址

页表

将虚拟地址的某一段转换成物理地址的话, 就需要在页表pgd中添加一个页表项.
页表项的内容是个32位的数据, 如下图
在这里插入图片描述
ARM架构内存映射:
RM架构支持一级页表映射,也就是说MMU根据CPU发来的虚拟地址可以找到第1个页表,从第1个页表里就可以知道这个虚拟地址对应的物理地址。一级页表里地址映射的最小单位是1M。
ARM架构还支持二级页表映射,也就是说MMU根据CPU发来的虚拟地址先找到第1个页表,从第1个页表里就可以知道第2级页表在哪里;再取出第2级页表,从第2个页表里才能确定这个虚拟地址对应的物理地址。二级页表地址映射的最小单位有4K、1K,Linux使用4K

一级页表映射过程
一线页表中每一个表项用来设置1M的空间,对于32位的系统,虚拟地址空间有4G,4G/1M=4096。所以一级页表要映射整个4G空间的话,需要4096个页表项。
第0个页表项用来表示虚拟地址第0个1M(虚拟地址为0~0xFFFFF)对应哪一块物理内存,并且有一些权限设置;
第1个页表项用来表示虚拟地址第1个1M(虚拟地址为0x100000~0x1FFFFF)对应哪一块物理内存,并且有一些权限设置;

使用一级页表时
① CPU发出虚拟地址vaddr,假设为0x12345678
② MMU根据vaddr[31:20]找到一级页表项:
在[1:0]发现是个一级页表
虚拟地址0x12345678是虚拟地址空间里第0x123个1M,所以找到页表里第0x123项,根据此项内容知道它是一个段页表项。
段内偏移是0x45678。
③ 从这个表项里取出物理基地址:Section Base Address,假设是0x81000000
④ 物理基地址加上段内偏移得到:0x81045678
所以CPU要访问虚拟地址0x12345678时,实际上访问的是0x81045678的物理地址
二级页表映射过程
① CPU发出虚拟地址vaddr,假设为0x12345678
② MMU根据vaddr[31:20]找到一级页表项:
虚拟地址0x12345678是虚拟地址空间里第0x123个1M,所以找到页表里第0x123项。根据此项的[1:0]内容知道它是一个二级页表项。
③ 从这个表项里取出地址,假设是address,这表示的是二级页表项的物理地址;
④ vaddr[19:12]表示的是二级页表项中的索引index即0x45,在二级页表项中找到第0x45项;
⑤二级页表项格式如下:
在这里插入图片描述
里面含有这4K或1K物理空间的基地址page base addr,假设是0x81889000:
它跟vaddr[11:0]组合得到物理地址:0x81889000 + 0x678 = 0x81889678。
所以CPU要访问虚拟地址0x12345678时,实际上访问的是0x81889678的物理地址

给APP新建一块内存映射

① 得到一个vm_area_struct,它表示APP的一块虚拟内存空间;
很幸运,APP调用mmap系统函数时,内核就帮我们构造了一个vm_area_stuct结构体。里面含有虚拟地址的地址范围、权限。
② 确定物理地址:
你想映射某个内核buffer,你需要得到它的物理地址,这得由你提供。
③ 给vm_area_struct和物理地址建立映射关系:
也很幸运,内核提供有相关函数。
APP里调用mmap时,导致的内核相关函数调用过程如下:在这里插入图片描述
cache和buffer映射属性如何选择:
是否使用cache、是否使用buffer,就有4种组合(Linux内核文件arch\arm\include\asm\pgtable-2level.h):在这里插入图片描述
第1种是不使用cache也不使用buffer,读写时都直达硬件,这适合寄存器的读写。
第2种是不使用cache但是使用buffer,写数据时会用buffer进行优化,可能会有“写合并”,这适合显存的操作。因为对显存很少有读操作,基本都是写操作,而写操作即使被“合并”也没有关系。
第3种是使用cache不使用buffer,就是“write through”,适用于只读设备:在读数据时用cache加速,基本不需要写。
第4种是既使用cache又使用buffer,适合一般的内存读写。

驱动程序要做的事

驱动程序要做的事情有3点:
① 确定物理地址
② 确定属性:是否使用cache、buffer
③ 建立映射关系
编码:
App

fd = open("/dev/hello", O_RDWR);
      /* 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);
      if (buf == MAP_FAILED)
      {
              printf("can not mmap file /dev/hello\n");
              return -1;
      }
      mmap函数MAP_SHARED、MAP_PRIVATE参数。使用MAP_PRIVATE映射时,
      在没有发生写操作时,APP、驱动访问的都是同一块内存;当APP发起写操作时,
      就会触发“copy on write”,即内核会先创建该内存块的拷贝,
      APP的写操作在这个新内存块上进行,这个新内存块是APP私有的,
      别的APP、驱动看不到。
	仅用MAP_SHARED参数时,
	多个APP、驱动读、写时,操作的都是同一个内存块,“共享”。
	printf("mmap address = 0x%x\n", buf);
    printf("buf origin data = %s\n", buf); /* old */

	/* 3. write */
    strcpy(buf, "new");
 	read(fd, str, 1024);
    if (strcmp(buf, str) == 0)
    {
            /* 对于MAP_SHARED映射,APP写的数据驱动可见
             * APP和驱动访问的是同一个内存块
            */
            printf("compare ok!\n");
     }

驱动程序:
分配内存的函数:

kmalloc 分配到的内存物理地址是连续的
kzalloc 分配到的内存物理地址是连续的,内容清0
vmalloc 分配到的内存物理地址不保证是连续的
vzalloc 分配到的内存物理地址不保证是连续的,内容清0
提供mmap函数

static int _drv_mmap(struct file *file, struct vm_area_struct *vma)
{
//获得物理地址
	unsigned long phy = virt_to_phys(bernel_buf);
得到物理地址, kernel_buf是内核使用的虚拟地址用kmalloc分配
//设置属性:cache, buffer
	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
设置属性, 不使用 cache 使用buffer
映射
	if(remap_pfn_range(vma, vma->vm_start, phy>>PAGE_SHIFT,
		vma->vm_end - vma->vm_start, vma->vm_page_prot)){
		printk("mmap remap_pfn_range failed\n");
		return -ENOBUFS;
	}
	return 0;
}

remap_pfn_range中,pfn的意思是“Page Frame Number”
在Linux中,整个物理地址空间可以分为第0页、第1页、第2页,诸如此类,这就是pfn。
假设每页大小是4K,那么给定物理地址phy,它的pfn = phy / 4096 = phy >> 12。内核的page一般是4K,但是也可以配置内核修改page的大小。所以为了通用,pfn = phy >> PAGE_SHIFT。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LINUX设备驱动第三版_ 前言 第一章 设备驱动程序简介 设备驱动程序的作用 内核功能划分 设备和模块的分类 安全问题 版本编号 许可证条款 加入内核开发社团 本书概要 第二章 构造和运行模块 设置测试系统 Hello World模块 核心模块与应用程序的对比 编译和装载 内核符号表 预备知识 初始化和关闭 模块参数 在用户空间编写驱动程序 快速参考 第三章 字符设备驱动程序 scull的设计 主设备号和次设备号 一些重要的数据结构 字符设备的注册 open和release scull的内存使用 read和write 试试新设备 快速参考 第四章 调试技术 内核中的调试支持 通过打印调试 通过查询调试 通过监视调试 调试系统故障 调试器和相关工具 第五章 并发和竞态 scull的缺陷 并发及其管理 信号量和互斥体 completion 自旋锁 锁陷阱 除了锁之外的办法 快速参考 第六章 高级字符驱动程序操作 ioctl 阻塞型I/O poll和select 异步通知 定位设备 设备文件的访问控制 快速参考 第七章 时间、延迟及延缓操作 度量时间差 获取当前时间 延迟执行 内核定时器 tasklet 工作队列 快速参考 第八章 分配内存 kmalloc函数的内幕 后备高速缓存 get_free_page和相关函数 vmalloc及其辅助函数 per-CPU变量 获取大的缓冲区 快速参考 第九章 与硬件通信 I/O端口和I/O内存 使用I/O端口 I/O端口示例 使用I/O内存 快速参考 第十章 中断处理 准备并口 安装中断处理例程 实现中断处理例程 顶半部和底半部 中断共享 中断驱动的I/O 快速参考 第十一章 内核的数据类型 使用标准C语言类型 为数据项分配确定的空间大小 接口特定的类型 其他有关移植性的问题 链表 快速参考 第十二章 PCI驱动程序 PCI接口 ISA回顾 PC/104和PC/104+ 其他的PC总线 SBus NuBus 外部总线 快速参考 第十三章 USB驱动程序 USB设备基础 USB和Sysfs USB urb 编写USB驱动程序 不使用urb的USB传输 快速参考 第十四章 Linux设备模型 kobject、kset和子系统 低层sysfs操作 热插拔事件的产生 总线、设备和驱动程序 类 各环节的整合 热插拔 处理固件 快速索引 第十五章 内存映射和DMA Linux的内存管理 mmap设备操作 执行直接I/O访问 直接内存访问 快速参考 第十六章 块设备驱动程序 注册 块设备操作 请求处理 其他一些细节 快速参考 第十七章 网络驱动程序 snull设计 连接到内核 net_device结构细节 打开和关闭 数据包传输 数据包的接收 中断处理例程 不使用接收中断 链路状态的改变 套接字缓冲区 MAC 地址解析 定制 ioctl 命令 统计信息 组播 其他知识点详解 快速参考 第十八章 TTY驱动程序 小型TTY驱动程序 tty_driver函数指针 TTY线路设置 ioctls proc和sysfs对TTY设备的处理 tty_driver结构详解 tty_operations结构详解 tty_struct结构详解 快速参考 参考书目 9112405-1_o.jpg (85.53 KB, 下载次数: 50)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值