适用情形

应用程序和驱动程序之间传递数据时,通常是用read、write函数进行的。这中间涉及copy_to_user、copy_from_user等。
这种方式在数据量比较小时没什么问题,但是数据量比较大时效率就太低了。
比如LCD的显示每次数据都有102460032bpp格式,一帧数据就有2.3MB左右。仅用read、write传输速度比较慢。
改进方法就是直接读写驱动程序中的buffer,这个方法可以通过mmap实现。
它把内核buffer映射到用户态。让App直接读写。

内存映射于数据结构

虚拟地址

CPU发出的地址是虚拟地址,金国MMU映射到物理地址上,对于不同进程的同一个虚拟地址,MMU会把它们映射到不同的物理地址。
如果有两个app1和app2,MMU会把CPU发出的虚拟地址addr映射为物理地址paddr1(app1的)、paddr2(app2的),虽然addr地址是一样的,但是paddr1和paddr2是不同的物理地址

结构分析

  1. 每个App在内核中都有一个task_struct结构体体,表示一个进程。
  2. 每个App都需要占用内存,在task_struct中有mmap属性,是一个mm_struct结构体来管理进程占用。mm_struct用mmap描述虚拟地址,用pgd描述对应的物理地址。(page global directory页目录表)
  3. 每个App都有一系列的VMA,虚拟内存(virtual memory)。比如App有代码段、数据段、BSS段、栈、共享库等。这些单元保存在内存里,而且它们地址空间不同,权限也不同(代码段只读可运行、数据段可读可写),内核都用vm_area_struct描述它。
  4. App的虚拟地址可能相同,物理地址是不一样的,这些关系保存在pgd中。

虚拟地址和物理地址的映射

ARM架构映射

  • 一级页表:MMU根据CPU的虚拟地址,可以找到第1个页表,从第一个页表里得到虚拟地址对应的物理地址。一级页表映射的最小单位是1M。
  • 二级页表:MMU根据CPU的虚拟地址,找到第1个页表,从第1个页表找到第2个页表,从第2个页表确定虚拟地址对应的物理地址。二级页表地址映射最小单位4k、1k。linux里使用4k。
    这个在ARM的架构手册里见过。

一级页表映射过程

一级页表最小单位为1M,那么在32位系统中,地址对应的物理空间232有4G,4G/1M=4096个位置。所以需要212也就是12位的寻址空间。
根据上图的内容显示,31到20位就是1级页表项里对应的物理地址。其中一级页表里的Section Base Address第一个就表示的就是物理基地地址。通过找到不同的页表项,去对应不同的物理地址。
使用一级页表时,现在内存里设置好各个页表项,然后把页表基地址告诉MMU,MMU就启动了。

  1. CPU发出虚拟地址vaddr,假设时0x12345678
  2. MMU根据vaddr的[31:20]找到一级页表项。对应的找到页表里是第0x123个项,0x45678就是段内偏移。
  3. 如果0x123对应的物理基地址是0x81000000
  4. 物理基地址加上偏移量就是实际地址:0x81045678
    所以CPU要访问虚拟地址0x12345678实际上访问的是0x81045678.

二级页表映射过程

首先设置好一级页表、二级页表,并且把一级页表的首地址告诉 MMU。
以下图为例介绍地址映射过程:

  1. CPU 发出虚拟地址 vaddr,假设为 0x12345678
  2. MMU 根据 vaddr[31:20]找到一级页表项:虚拟地址 0x12345678 是虚拟地址空间里第 0x123 个 1M,所以找到页表里第 0x123 项。根据此项内容知道它是一个二级页表项。
  3. 从这个表项里取出地址,假设是 address,这表示的是二级页表项的物理地址;
  4. vaddr[19:12]表示的是二级页表项中的索引 index 即 0x45,在二级页表项中找到第 0x45 项;
  5. 二级页表项格式如下:
    里面含有这 4K 或 1K 物理空间的基地址 page base addr,假设是 0x81889000:
    它跟 vaddr[11:0]组合得到物理地址:0x81889000 + 0x678 = 0x81889678。
    所以 CPU 要访问虚拟地址 0x12345678 时,实际上访问的是 0x81889678 的物理地址。

个人总结

首先有几个概念CPU地址(虚拟地址)、物理地址、MMU、页表、页表项。
CPU地址就是虚拟地址,应用里用的地址。
物理地址,就是硬件里的地址。我觉得就是内存的一块区域。
MMU就是把虚拟地址和物理地址互相转换的工具。
页表是内存中的一个结构,这个结构可以被虚拟地址通过index找到对应的项。然后页表子项里面有对应的物理地址基地址。
之前虚拟地址里面除了用来找页表子项的index,还剩下一些内容,这个内容就可以用作物理地址基地址的偏移。

怎么给App新建一块内存映射

App可以直接调用mmap函数,来分配一块虚拟内存空间。

  1. 当调用mmap函数时,内核会构造一个vm_area_struct结构体,里面还有虚拟地址的地址范围、权限。
  2. 确定物理地址,如果想要映射内核buff,需要得到它的物理地址,这需要自己编写。
  3. 给vm_area_struct和物理地址建立映射关系。

APP 里调用 mmap 时,导致的内核相关函数调用过程如下:
调用mmap时,内核会创建一个vm_area_struct,通过mmap设置的属性决定使用cache或者buffer。然后建立与物理地址之间的映射。

使用mmap时需要选择使用cache和buff

它们都是用来桥接CPU和内存速度不匹配的问题。

  1. cache时一块高速内存,CPU读写内存地址时,常用的和常用的附近的地址都会被存在cache中。加快CPU调度速度。

  2. 写缓冲器相当于一个FIFO,可以把写操作集合起来一次写入内存

  3. 写通:CPU同时写cache和内存,cache也可以把数据给写缓冲器,让写缓冲器一次写完。

  4. 写回:cpu数据写入cache,不立即写入内存,因为不一致此时cache被标记为“脏”。当cache不够时,才需要把脏数据写入内存。写回功能可以大幅提高效率。因为内存数据可能不一致,使用时要小心处理。

所以对于使用cache、buffer就有4中组合,相关文件arch\arm\include\asm\pgtable-2level.h

#define L_PTE_MT_UNCACHED    (_AT(pteval_t, 0x00) << 2)    /* 0000 */
#define L_PTE_MT_BUFFERABLE (_AT(pteval_t, 0x01) << 2)    /* 0001 */
#define L_PTE_MT_WRITETHROUGH   (_AT(pteval_t, 0x02) << 2)    /* 0010 */
#define L_PTE_MT_WRITEBACK  (_AT(pteval_t, 0x03) << 2)    /* 0011 */
#define L_PTE_MT_MINICACHE  (_AT(pteval_t, 0x06) << 2)    /* 0110 (sa1100, xscale) */
#define L_PTE_MT_WRITEALLOC (_AT(pteval_t, 0x07) << 2)    /* 0111 */
#define L_PTE_MT_DEV_SHARED (_AT(pteval_t, 0x04) << 2)    /* 0100 */
#define L_PTE_MT_DEV_NONSHARED  (_AT(pteval_t, 0x0c) << 2)    /* 1100 */
#define L_PTE_MT_DEV_WC     (_AT(pteval_t, 0x09) << 2)    /* 1001 */
#define L_PTE_MT_DEV_CACHED (_AT(pteval_t, 0x0b) << 2)    /* 1011 */
#define L_PTE_MT_VECTORS    (_AT(pteval_t, 0x0f) << 2)    /* 1111 */
#define L_PTE_MT_MASK       (_AT(pteval_t, 0x0f) << 2)

上面前4中对应下表中各项,一一对应(下表来自于s3c2410芯片手册)

是否启用cache 是否启用buffer 说明
0 0 Non-cached, non-buffered (NCNB) 读、写都直达外设硬件
0 1 Non-cached buffered (NCB) 读、写都直达外设硬件;写操作通过 buffer 实现,CPU 不等待写操作完成,CPU 会马上执行下一条指令
1 0 Cached, write-through mode (WT),写通 读:cache hit 时从 cahce 读数据;cache miss 时已入一行数据到 cache;写:通过 buffer 实现,CPU 不等待写操作完成,CPU 会马上执行下一条指令
1 1 Cached, write-back mode (WB),写回 读:cache hit 时从 cahce 读数据;cache miss 时已入一行数据到 cache;写:通过 buffer 实现,cache hit 时新数据不会到达硬件,而是在 cahce 中被标为“脏”;cache miss 时,通过 buffer写入硬件,CPU 不等待写操作完成,CPU 会马上执行下一条指令

第 1 种是不使用 cache 也不使用 buffer,读写时都直达硬件,这适合寄存器的读写。
第 2 种是不使用 cache 但是使用 buffer,写数据时会用 buffer 进行优化,可能会有“写合并”,这适合显存的操作。因为对显存很少有读操作,基本都是写操作,而写操作即使被“合并”也没有关系。
第 3 种是使用 cache 不使用 buffer,就是“write through”,适用于只读设备:在读数据时用 cache加速,基本不需要写。
第 4 种是既使用 cache 又使用 buffer,适合一般的内存读写。

驱动程序要做的事

  1. 确定物理地址
  2. 确定属性:是否使用 cache、buffer
  3. 建立映射关系

Linux源码示例参考:

static int controlfb_mmap(struct fb_info *info,struct vm_area_struct *vma)
{unsigned long mmio_pgoff;unsigned long start;u32 len;start = info->fix.smem_start;                             //1.确定物理地址len = info->fix.smem_len;mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;if (vma->vm_pgoff >= mmio_pgoff) {if (info->var.accel_flags)return -EINVAL;vma->vm_pgoff -= mmio_pgoff;start = info->fix.mmio_start;len = info->fix.mmio_len;vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);     //2. 确定属性:cache?buffer?} else {/* framebuffer */vma->vm_page_prot = pgprot_cached_wthru(vma->vm_page_prot);}return vm_iomap_memory(vma, start, len);            //3.映射
}

源码

mmap_test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>/** ./hello_drv_test -w abc* ./hello_drv_test -r*/int main(int argc, char **argv)
{int fd;char *buf;char str[1024];int len;fd = open("/dev/hello", O_RDWR);if(fd == -1) {printf("can not open file /dev/hello\n");return -1;}buf = mmap(NULL, 1024*8, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);if(buf == MAP_FAILED) {printf("mmap failed\n");return -1;}printf("mmap address = 0x%x\n", buf);printf("buf origin data = %s\n", buf);strcpy(buf, "hello");read(fd, str, 1024);if(strcmp(buf, str) == 0) {printf("compare ok!\n");} else {printf("compare err!\n");printf("str = %s!\n", str);printf("buf = %s!\n", buf);}while(1) {sleep(10);}munmap(buf, 1024*8);close(fd);return 0;
}

mmap_drv.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/uaccess.h>
#include <asm/memory.h>
#include <asm/pgtable.h>
#include <linux/mm.h>
#include <linux/slab.h>static int major = 0;
static char *kernel_buff;
static struct class *hello_class;
static int bufsize = 1024*8;#define MIN(a, b) (a < b ? a : b)static int hello_drv_open(struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static ssize_t hello_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{int ret;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);ret = copy_to_user(buf, kernel_buff, MIN(bufsize, size));return MIN(bufsize, size);
}static ssize_t hello_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int ret;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);ret = copy_from_user(kernel_buff, buf, MIN(1024, size));return ret;
}static int hello_drv_release(struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static int hello_drv_mmap(struct file *file, struct vm_area_struct *vma)
{unsigned long phy = virt_to_phys(kernel_buff);vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);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;
}static struct file_operations hello_drv = {.owner   = THIS_MODULE,.open    = hello_drv_open,.read    = hello_drv_read,.write   = hello_drv_write,.release = hello_drv_release,.mmap    = hello_drv_mmap,
};static int __init hello_init(void)
{int err;kernel_buff = kmalloc(bufsize, GFP_KERNEL);strcpy(kernel_buff, "world");printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);major = register_chrdev(0, "hello", &hello_drv);hello_class = class_create(THIS_MODULE, "hello_class");err = PTR_ERR(hello_class);if(IS_ERR(hello_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "hello");return -1;}device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");    /* /dev/hello */return 0;
}static void __exit hello_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);device_destroy(hello_class, MKDEV(major, 0));class_destroy(hello_class);unregister_chrdev(major, "hello");
}module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

rk3288 mmap原理学习相关推荐

  1. Android架构——ViewModel原理学习总结

    本文是楼主学习ViewModel 源码的一些总结,感觉ViewModel的源码是Android 三大架构中 最容易理解的一个了.本文ViewModel基于版本androidx.lifecycle:li ...

  2. Linux mmap原理

    Linux mmap原理 前言 Linux段页式内存管理 mmap mmap内存映射原理 文字概述 mmap函数参数介绍 源码解析 1. 文件映射 2. 缺页异常 mmap 和常规文件操作的区别 mm ...

  3. Mybatis底层原理学习(二):从源码角度分析一次查询操作过程

    在阅读这篇文章之前,建议先阅读一下我之前写的两篇文章,对理解这篇文章很有帮助,特别是Mybatis新手: 写给mybatis小白的入门指南 mybatis底层原理学习(一):SqlSessionFac ...

  4. linux+mmap父子通信_linux库函数mmap()原理?转载

    linux库函数mmap()原理 转载 1.mmap基本概念 2.mmap内存映射原理 3.mmap和常规文件操作的区别 4.mmap优点总结 5.mmap相关函数 6.mmap使用细节 7.mmap ...

  5. Docker镜像原理学习理解

    Docker镜像原理学习理解 一.Docker镜像的组成 1.Docker镜像图层 2.union file system 3.镜像层-bootfs 4.镜像层-rootfs 5.镜像层-依赖环境 6 ...

  6. Redis主从复制原理学习

    Redis主从复制原理学习总结 - 运维笔记 和Mysql主从复制的原因一样,Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况.为了分担读压力,Redis支持主从复制,Redis的 ...

  7. java锁原理_Java锁原理学习

    Java锁原理学习 为了学习Java锁的原理,参照ReentrantLock实现了自己的可重入锁,代码如下: 先上AQS的相关方法: // AQS = AbstractQueuedSynchroniz ...

  8. 内存映射文件mmap原理分析

    本文来说下内存映射文件 mmap 原理 文章目录 mmap原理分析 mmap原理分析 假设我们要把一个磁盘文件映射到内存里来,然后把映射到内存中的数据通过socket发送出去. 零拷贝有两种实现方式, ...

  9. MOOC人工智能原理学习笔记1

    人工智能原理学习笔记1 The Foundations of AI: Philosophy Mathematics Economics Neuroscience Psychology Computer ...

最新文章

  1. 表达式必须是可修改的左值怎么解决_如何解决代码腐败的味道
  2. 服务器打印文档 图片显示是叉,Lodop背景图无图片时显示放大叉号问题
  3. Linux上的gitlab日常操作
  4. P5 Matlab/Simulink 在时域分析中的应用-《Matlab/Simulink与控制系统仿真》程序指令总结
  5. 写给找工作的朋友——最典的面试葵花宝典
  6. linux禁止ping
  7. Transformer新内核Synthesizer:低复杂度的attention代替点乘式的注意力机制
  8. android页面跳转停止,android – Viewpager上的VideoView,切换页面时停止视频
  9. Java常见排序算法之快速排序详解
  10. 全面剖析《自己动手写操作系统》第五章---makefile文件
  11. EastFax传真服务器与单机传真软件什么区别
  12. 利用teigha制作dwg无单位块工具开发
  13. 加拿大计算机科学专业高中选课,加拿大高中选课攻略
  14. 10.5 Vue电商后台管理完善--订单详情页面显示商品信息,添加备注
  15. php date 格式时分秒,PHP 把秒数转为时分秒格式
  16. 项目源代码迁移到另一个gitlab的方法(保留原来的提交记录)
  17. new joiner
  18. 前端面试题总结(js、html、小程序、React、ES6、Vue、算法、全栈热门视频资源)...
  19. salesforce 和 salesforce platform 的License的区别
  20. 想拿高工资?Java面试资料集合,附赠课程+题库

热门文章

  1. Vue动态设置路由title
  2. dex2oat对应用启动性能的影响
  3. scanf来代替gets
  4. 怎么让计算机休眠的时候不断网,Windows10系统如何让电脑睡眠状态也不断网?
  5. C++ 实现图书类Book
  6. 计算机PE不显示硬盘,笔记本电脑进入PE系统后认不到硬盘的解决办法
  7. 互联网公司的主要角色以及其职责
  8. 小程序自定义导航栏指南
  9. Linux命令行下载OneDrive分享链接中的文件
  10. DAE系统的设计-豆瓣首席架构师洪强宁