当前位置:网站首页>这样理解mmap,挺有意思!
这样理解mmap,挺有意思!
2022-07-01 09:39:00 【嵌入式Linux,】
大概雍正皇帝怎么也不会想到,自己在西历2022年的男生和女生眼里,会是截然不同的两种形象。
1
以我对身边同学朋友的观察,男生们大多爱看《雍正王朝》,他们眼中的雍正,大约是个推行了“火耗归公”、“摊丁入亩”等遏制贪腐,减轻税收之类政策的改革家,是个经历了九子夺嫡的惊心动魄、腹黑深沉的政治家,是个登基后也兢兢业业,熬夜加班996的工作狂。
而女生们大多爱看《甄嬛传》,她们眼里的雍正,是“大胖橘”,是“大猪蹄子”,是被后宫一众妃嫔玩弄于股掌之中,戴了N顶绿帽,最后还被钮钴禄甄嬛气死的渣男。
我没完整的看过甄嬛传,但是有幸在吃饭的时候陪我家那位看过几集。
正所谓后宫佳丽三千人,铁杵磨成绣花针... (不是
妃嫔太多了,皇帝就一个,难免会互相争风吃醋。位份高的贵妃的仗势欺人,一些小妃嫔无法正面回击,自然会用点别的奇淫技巧,扎小人就是其中一个出现频率较高的方法。
据我总结,扎小人这个技术的核心思想是:用户这边由于无法扎到正主,只能拿个自己身边的布片等物品模拟一个小人出来,在上面画上正主的经筋脉络,写上名字,施以某种魔法,然后用针扎自己手边的小人的某个穴道,远程那位正主的对应部位就会受到同样的折磨。
虽然有点神乎其技,令人羡慕而不可得。但是在linux内核开发里面,却可以用mmap的机制实现类似的效果。
2
mmap的核心思想是:用户这边由于在用户态无法直接操作寄存器的物理地址,于是通过mmap方法进行内存映射,将物理地址映射到用户态的虚拟地址上,然后用户通过读写自己手边的虚拟地址,就可以实现对物理地址的读取/写入。
两者的共同点是,由于无法直接操作目标,所以通过某种方法,将自己能操作的事物和目标建立一种映射关系,从而达到如臂使指,指哪打哪,打哪哪疼的效果。
只要能建立起对目标的映射,我们借此映射能做什么文章,自然有很多想象空间。所以mmap有很多用途,有人用它来实现进程间通信,有人用它搬运数据,对于我们嵌入式工程师来说,我们可以用它来点灯。嘿嘿,想不到吧!
且听我慢慢道来。
3
作为一个嵌入式工程师,花式点灯是必备技能。无论是写裸机代码操作GPIO口,还是通过物联网云端远程控制LED,从硬件的角度讲,核心原理都是找到连接LED的GPIO口,让它输出一个电信号。而从软件的角度讲,最终目的就是找到这个GPIO口对应寄存器的地址,根据实际的电路要求,让CPU给它写入一个1或者0。
裸机开发的时候,我们可以直接找到物理地址进行操作。而在Linux系统里却略有不同。因为在操作系统里有内核空间的存在,我们写的程序都是运行在用户态的,需要经过内核来对硬件进行驱动,无法直接操作物理地址。
你当然可以选择为这个LED写一个驱动,从而在用户空间通过read,write来操作它的状态。不过有些同学一听要写驱动,就想吟一首蜀道难来表达自己的望而却步。所以没了解过驱动的同学你也可以选择用一种更直接的方式:mmap。
就好像你可以选择给贵妃下药来控制她,不过下药这种方式需要精通药理、掌控时机,成本较高,难度较大。只要能达到目的,有时候扎个小人或许更加经济适用。
我在上家公司的时候用ARM Cortex-A9芯片做过一个项目,开发过程大概是先和硬件同事约定好一个协议,然后我通过GPIO口的输入输出模拟出这个协议,通过它对寄存器进行读写配置,驱动硬件ADC采样,然后将采回来的数据通过DMA传输,最终到应用层进行分析处理。其中驱动GPIO口的部分就用了mmap。
项目很大,做了半年多,想完全讲明白也不现实,不过我们可以从驱动GPIO口这个点切入,体会一下软件驱动硬件中间这玄妙的过程。聪明的你一定可以举一反三。
4
作为一个软件工程师,拿到板子的时候,硬件工程师一般会给你一份文档,类似这样:
这个文档会指明,如果想操作这个GPIO口的话,你需要用GPIO外设的基地址加上偏移地址找到对应的寄存器地址,再用位操作给指定的bit写入命令。
不过我由于FPGA也会一些,所以我们公司里FPGA的Block Diagram都是我来建的。建好FPGA的硬件工程后做一下综合,从Address Map里就能看到我想用的GPIO口地址了。如图:
无论怎样,你现在拿到这个所谓的硬件寄存器地址了,接下来我们就可以拿小人扎它了。
以上图我拿到的0x43C00000为例,这是寄存器的地址,那我能否直接在应用程序里把0x43C00000赋值给一个指针,然后对它进行读写呢?
在玩裸机的时候确实是这样的。但是上面说了,Linux系统有虚拟内存的存在,就不能这么做了。因为理论上我可以在系统里开100个进程,这100个进程里都有0x43C00000这个地址,那这100个地址哪个是真正的寄存器地址呢?可能都不是。因为进程里的0x43C00000是虚拟的,它真正对应的物理地址在哪里,没人知道。要想把虚拟地址和物理地址对应起来,就得用mmap进行内存映射。
5
mmap的函数接口定义如下:
void mmap(void addr,size_t length,
int prot,int flags,
int fd,off_t offset);
这里面参数比较多。其中addr一般指定为NULL,prot则用于设置映射区域的权限,比如是否可读可写;flags则用于指定是共享映射还是私有映射;而fd,offset,length这三个参数表示将fd对应的文件,从offset位置起,将长度为length的内容映射到进程的地址空间。
需要注意mmap的操作单元是页,即最后映射的offset参数必须是内存页大小的整数倍,而Linux系统内存页大小一般为4096字节。
一个我在程序中的调用示例:
#define AXI_GPIO_BASEADDR 0x43C00000
int memfd = open("/dev/mem", O_RDWR
| O_SYNC);
if (-1 == memfd) {
printf("Can't open /dev/mem\n");
return -1;
}
unsigned int* led_gpio =
(unsigned int*)(mmap(
NULL, MMAP_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED, memfd,
AXI_GPIO_BASEADDR));
调用mmap后,我们拿到一个指针,通过这个指针对指向的地址做任何操作,对应的寄存器物理地址也会有相同的效果。于是我们将它循环赋值0101,相应的寄存器控制的GPIO口输出电信号,于是板卡上的灯成功的闪烁起来,类似奥特曼体力不支时的能量灯。
6
多说两句,除了用来操作GPIO/字符设备外,mmap还有个常用的场景是操作块设备。它和传统的用read,write的区别,最关键的是省一次拷贝。
比如要读取磁盘上某个文件的数据,用read write的话,由于会涉及到系统调用,进程是无法直接访问内核的,所以在read系统调用返回前,内核需要将数据从内核复制到进程指定的buffer里。
但如果用mmap的话,那么这段数据会首先拷贝到内存中作为页缓存(即page cache)。用mmap将这段内存映射到用户空间,则进程可以通过指针直接读写page cache,不再需要多余的系统调用和内存拷贝。
不过虽然少了一次拷贝,但mmap会触发缺页中断(page fault),相比于内存拷贝而言,缺页中断的开销更大。所以性能而言mmap大部分情况下并不会比read/write要好。
说到页缓存,我在上家公司开发项目的时候,还被脏页延迟这玩意坑过。篇幅所限,页缓存涉及到的缺页中断,脏页,延迟写回,sync强制写回等内容,我们下次再详细聊聊。
7
好了,于是我们学会用mmap点亮一个灯了。想象一下接下来的场景:
你跟公司研发部最漂亮的女同事说,
“hi,领导那边给了我们组一个新任务,你写个驱动控制一下LED吧?”
“啊?驱动这么难,我不会啦”
“哦没事,那你用mmap吧”
“诶你怎么骂人呢!”
边栏推荐
- CSDN一站式云服务开放内测,诚邀新老用户来抢鲜
- 4hutool实战:DateUtil-格式化时间[通俗易懂]
- High precision factorial
- dotnet 控制台 使用 Microsoft.Maui.Graphics 配合 Skia 进行绘图入门
- uniapp微信小程序组件按需引入
- [unity rendering] customized screen post-processing
- Analysis and solution of JS this loss
- 嵌入式开发用到的一些工具
- 利用闭包实现私有变量
- Introduction to expressions and operators in C language
猜你喜欢
Nacos service configuration and persistence configuration
华为帐号多端协同,打造美好互联生活
吃一个女富豪的瓜。。。
CSDN一站式云服务开放内测,诚邀新老用户来抢鲜
Construction of esp8266 FreeRTOS development environment
Problems caused by delete and delete[]
Hololens2 development -6-eyetracking and speech recognition
Implementation and application of queue
Dotnet console uses microsoft Maui. Getting started with graphics and skia
集成积木报表报错 org.apache.catalina.core.StandardContext.filterStart 启动过滤器异常
随机推荐
Closure implementation iterator effect
node. How to implement the SQL statement after JS connects to the database?
Packetdrill script analysis guide
PHP array functions (merge, split, append, find, delete, etc.)
Analysis and solution of JS this loss
Tree structure --- binary tree 1
Sd-wan notes
Click the screen with your finger to simulate F11 and enter the full screen
LVGL V8.2字符串显示在Keil MDK上需要注意的事项(以小熊派为例)
In terms of use
好高的佣金,《新程序员》合伙人计划来袭,人人皆可参与!
持续进阶,软通动力稳步推动云智能战略
js函数arguments对象
JS scope chain and closure
MT7628K eCos开发入门
Network partition notes
123. how to stop a thread?
架构实战营 模块九:设计电商秒杀系统
SQL学习笔记(04)——数据更新、查询操作
Youqitong PE toolbox [vip] v3.7.2022.0106 official January 22 Edition