rk3288 mmap原理学习
适用情形
应用程序和驱动程序之间传递数据时,通常是用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是不同的物理地址
结构分析
- 每个App在内核中都有一个task_struct结构体体,表示一个进程。
- 每个App都需要占用内存,在task_struct中有mmap属性,是一个mm_struct结构体来管理进程占用。mm_struct用mmap描述虚拟地址,用pgd描述对应的物理地址。(page global directory页目录表)
- 每个App都有一系列的VMA,虚拟内存(virtual memory)。比如App有代码段、数据段、BSS段、栈、共享库等。这些单元保存在内存里,而且它们地址空间不同,权限也不同(代码段只读可运行、数据段可读可写),内核都用vm_area_struct描述它。
- 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就启动了。
- CPU发出虚拟地址vaddr,假设时0x12345678
- MMU根据vaddr的[31:20]找到一级页表项。对应的找到页表里是第0x123个项,0x45678就是段内偏移。
- 如果0x123对应的物理基地址是0x81000000
- 物理基地址加上偏移量就是实际地址:0x81045678
所以CPU要访问虚拟地址0x12345678实际上访问的是0x81045678.
二级页表映射过程
首先设置好一级页表、二级页表,并且把一级页表的首地址告诉 MMU。
以下图为例介绍地址映射过程:
- CPU 发出虚拟地址 vaddr,假设为 0x12345678
- MMU 根据 vaddr[31:20]找到一级页表项:虚拟地址 0x12345678 是虚拟地址空间里第 0x123 个 1M,所以找到页表里第 0x123 项。根据此项内容知道它是一个二级页表项。
- 从这个表项里取出地址,假设是 address,这表示的是二级页表项的物理地址;
- vaddr[19:12]表示的是二级页表项中的索引 index 即 0x45,在二级页表项中找到第 0x45 项;
- 二级页表项格式如下:
里面含有这 4K 或 1K 物理空间的基地址 page base addr,假设是 0x81889000:
它跟 vaddr[11:0]组合得到物理地址:0x81889000 + 0x678 = 0x81889678。
所以 CPU 要访问虚拟地址 0x12345678 时,实际上访问的是 0x81889678 的物理地址。
个人总结
首先有几个概念CPU地址(虚拟地址)、物理地址、MMU、页表、页表项。
CPU地址就是虚拟地址,应用里用的地址。
物理地址,就是硬件里的地址。我觉得就是内存的一块区域。
MMU就是把虚拟地址和物理地址互相转换的工具。
页表是内存中的一个结构,这个结构可以被虚拟地址通过index找到对应的项。然后页表子项里面有对应的物理地址基地址。
之前虚拟地址里面除了用来找页表子项的index,还剩下一些内容,这个内容就可以用作物理地址基地址的偏移。
怎么给App新建一块内存映射
App可以直接调用mmap函数,来分配一块虚拟内存空间。
- 当调用mmap函数时,内核会构造一个vm_area_struct结构体,里面还有虚拟地址的地址范围、权限。
- 确定物理地址,如果想要映射内核buff,需要得到它的物理地址,这需要自己编写。
- 给vm_area_struct和物理地址建立映射关系。
APP 里调用 mmap 时,导致的内核相关函数调用过程如下:
调用mmap时,内核会创建一个vm_area_struct,通过mmap设置的属性决定使用cache或者buffer。然后建立与物理地址之间的映射。
使用mmap时需要选择使用cache和buff
它们都是用来桥接CPU和内存速度不匹配的问题。
cache时一块高速内存,CPU读写内存地址时,常用的和常用的附近的地址都会被存在cache中。加快CPU调度速度。
写缓冲器相当于一个FIFO,可以把写操作集合起来一次写入内存
写通:CPU同时写cache和内存,cache也可以把数据给写缓冲器,让写缓冲器一次写完。
写回: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,适合一般的内存读写。
驱动程序要做的事
- 确定物理地址
- 确定属性:是否使用 cache、buffer
- 建立映射关系
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原理学习相关推荐
- Android架构——ViewModel原理学习总结
本文是楼主学习ViewModel 源码的一些总结,感觉ViewModel的源码是Android 三大架构中 最容易理解的一个了.本文ViewModel基于版本androidx.lifecycle:li ...
- Linux mmap原理
Linux mmap原理 前言 Linux段页式内存管理 mmap mmap内存映射原理 文字概述 mmap函数参数介绍 源码解析 1. 文件映射 2. 缺页异常 mmap 和常规文件操作的区别 mm ...
- Mybatis底层原理学习(二):从源码角度分析一次查询操作过程
在阅读这篇文章之前,建议先阅读一下我之前写的两篇文章,对理解这篇文章很有帮助,特别是Mybatis新手: 写给mybatis小白的入门指南 mybatis底层原理学习(一):SqlSessionFac ...
- linux+mmap父子通信_linux库函数mmap()原理?转载
linux库函数mmap()原理 转载 1.mmap基本概念 2.mmap内存映射原理 3.mmap和常规文件操作的区别 4.mmap优点总结 5.mmap相关函数 6.mmap使用细节 7.mmap ...
- Docker镜像原理学习理解
Docker镜像原理学习理解 一.Docker镜像的组成 1.Docker镜像图层 2.union file system 3.镜像层-bootfs 4.镜像层-rootfs 5.镜像层-依赖环境 6 ...
- Redis主从复制原理学习
Redis主从复制原理学习总结 - 运维笔记 和Mysql主从复制的原因一样,Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况.为了分担读压力,Redis支持主从复制,Redis的 ...
- java锁原理_Java锁原理学习
Java锁原理学习 为了学习Java锁的原理,参照ReentrantLock实现了自己的可重入锁,代码如下: 先上AQS的相关方法: // AQS = AbstractQueuedSynchroniz ...
- 内存映射文件mmap原理分析
本文来说下内存映射文件 mmap 原理 文章目录 mmap原理分析 mmap原理分析 假设我们要把一个磁盘文件映射到内存里来,然后把映射到内存中的数据通过socket发送出去. 零拷贝有两种实现方式, ...
- MOOC人工智能原理学习笔记1
人工智能原理学习笔记1 The Foundations of AI: Philosophy Mathematics Economics Neuroscience Psychology Computer ...
最新文章
- 表达式必须是可修改的左值怎么解决_如何解决代码腐败的味道
- 服务器打印文档 图片显示是叉,Lodop背景图无图片时显示放大叉号问题
- Linux上的gitlab日常操作
- P5 Matlab/Simulink 在时域分析中的应用-《Matlab/Simulink与控制系统仿真》程序指令总结
- 写给找工作的朋友——最典的面试葵花宝典
- linux禁止ping
- Transformer新内核Synthesizer:低复杂度的attention代替点乘式的注意力机制
- android页面跳转停止,android – Viewpager上的VideoView,切换页面时停止视频
- Java常见排序算法之快速排序详解
- 全面剖析《自己动手写操作系统》第五章---makefile文件
- EastFax传真服务器与单机传真软件什么区别
- 利用teigha制作dwg无单位块工具开发
- 加拿大计算机科学专业高中选课,加拿大高中选课攻略
- 10.5 Vue电商后台管理完善--订单详情页面显示商品信息,添加备注
- php date 格式时分秒,PHP 把秒数转为时分秒格式
- 项目源代码迁移到另一个gitlab的方法(保留原来的提交记录)
- new joiner
- 前端面试题总结(js、html、小程序、React、ES6、Vue、算法、全栈热门视频资源)...
- salesforce 和 salesforce platform 的License的区别
- 想拿高工资?Java面试资料集合,附赠课程+题库