2

这样理解mmap,挺有意思!

 1 year ago
source link: https://os.51cto.com/article/713717.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

这样理解mmap,挺有意思!-51CTO.COM

这样理解mmap,挺有意思!
作者:李纳克斯 2022-07-11 13:09:26
mmap有很多用途,有人用它来实现进程间通信,有人用它搬运数据,对于我们嵌入式工程师来说,我们可以用它来点灯。嘿嘿,想不到吧!
14553bb04fb8c4643f34162ccbdbe679ea4819.jpg

大概雍正皇帝怎么也不会想到,自己在西历2022年的男生和女生眼里,会是截然不同的两种形象。

以我对身边同学朋友的观察,男生们大多爱看《雍正王朝》,他们眼中的雍正,大约是个推行了“火耗归公”、“摊丁入亩”等遏制贪腐,减轻税收之类政策的改革家,是个经历了九子夺嫡的惊心动魄、腹黑深沉的政治家,是个登基后也兢兢业业,熬夜加班996的工作狂。

而女生们大多爱看《甄嬛传》,她们眼里的雍正,是“大胖橘”,是“大猪蹄子”,是被后宫一众妃嫔玩弄于股掌之中,戴了N顶绿帽,最后还被钮钴禄甄嬛气死的渣男。

b50abc262f82f4336f60725b5c32d0f08b6e56.jpg

我没完整的看过甄嬛传,但是有幸在吃饭的时候陪我家那位看过几集。

正所谓后宫佳丽三千人,铁杵磨成绣花针... (不是妃嫔太多了,皇帝就一个,难免会互相争风吃醋。位份高的贵妃的仗势欺人,一些小妃嫔无法正面回击,自然会用点别的奇淫技巧,扎小人就是其中一个出现频率较高的方法。

据我总结,扎小人这个技术的核心思想是:用户这边由于无法扎到正主,只能拿个自己身边的布片等物品模拟一个小人出来,在上面画上正主的经筋脉络,写上名字,施以某种魔法,然后用针扎自己手边的小人的某个穴道,远程那位正主的对应部位就会受到同样的折磨。

b3bb91258f4a790317a7519997e2280ae855da.gif

虽然有点神乎其技,令人羡慕而不可得。但是在linux内核开发里面,却可以用mmap的机制实现类似的效果。

mmap的核心思想是:用户这边由于在用户态无法直接操作寄存器的物理地址,于是通过mmap方法进行内存映射,将物理地址映射到用户态的虚拟地址上,然后用户通过读写自己手边的虚拟地址,就可以实现对物理地址的读取/写入。

两者的共同点是,由于无法直接操作目标,所以通过某种方法,将自己能操作的事物和目标建立一种映射关系,从而达到如臂使指,指哪打哪,打哪哪疼的效果。

只要能建立起对目标的映射,我们借此映射能做什么文章,自然有很多想象空间。所以mmap有很多用途,有人用它来实现进程间通信,有人用它搬运数据,对于我们嵌入式工程师来说,我们可以用它来点灯。嘿嘿,想不到吧!

且听我慢慢道来。

599848739251d84d81f509d8c6d697f335905e.jpg

作为一个嵌入式工程师,花式点灯是必备技能。无论是写裸机代码操作GPIO口,还是通过物联网云端远程控制LED,从硬件的角度讲,核心原理都是找到连接LED的GPIO口,让它输出一个电信号。而从软件的角度讲,最终目的就是找到这个GPIO口对应寄存器的地址,根据实际的电路要求,让CPU给它写入一个1或者0。

裸机开发的时候,我们可以直接找到物理地址进行操作。而在Linux系统里却略有不同。因为在操作系统里有内核空间的存在,我们写的程序都是运行在用户态的,需要经过内核来对硬件进行驱动,无法直接操作物理地址。

你当然可以选择为这个LED写一个驱动,从而在用户空间通过read,write来操作它的状态。不过有些同学一听要写驱动,就想吟一首蜀道难来表达自己的望而却步。所以没了解过驱动的同学你也可以选择用一种更直接的方式:mmap。

就好像你可以选择给贵妃下药来控制她,不过下药这种方式需要精通药理、掌控时机,成本较高,难度较大。只要能达到目的,有时候扎个小人或许更加经济适用。

我在上家公司的时候用ARM Cortex-A9芯片做过一个项目,开发过程大概是先和硬件同事约定好一个协议,然后我通过GPIO口的输入输出模拟出这个协议,通过它对寄存器进行读写配置,驱动硬件ADC采样,然后将采回来的数据通过DMA传输,最终到应用层进行分析处理。其中驱动GPIO口的部分就用了mmap。

项目很大,做了半年多,想完全讲明白也不现实,不过我们可以从驱动GPIO口这个点切入,体会一下软件驱动硬件中间这玄妙的过程。聪明的你一定可以举一反三。

作为一个软件工程师,拿到板子的时候,硬件工程师一般会给你一份文档,类似这样:

17105c802f7ba8937dc0266bf67c9a620bfdaa.jpg

这个文档会指明,如果想操作这个GPIO口的话,你需要用GPIO外设的基地址加上偏移地址找到对应的寄存器地址,再用位操作给指定的bit写入命令。

不过我由于FPGA也会一些,所以我们公司里FPGA的Block Diagram都是我来建的。建好FPGA的硬件工程后做一下综合,从Address Map里就能看到我想用的GPIO口地址了。如图:

5148c4b95b87ebd50f8261524fe04dc5592684.jpg
a6538bc87452f586aff590f3eb08fe4dd4d4f6.jpg

无论怎样,你现在拿到这个所谓的硬件寄存器地址了,接下来我们就可以拿小人扎它了。

以上图我拿到的0x43C00000为例,这是寄存器的地址,那我能否直接在应用程序里把0x43C00000赋值给一个指针,然后对它进行读写呢?

在玩裸机的时候确实是这样的。但是上面说了,Linux系统有虚拟内存的存在,就不能这么做了。因为理论上我可以在系统里开100个进程,这100个进程里都有0x43C00000这个地址,那这100个地址哪个是真正的寄存器地址呢?可能都不是。因为进程里的0x43C00000是虚拟的,它真正对应的物理地址在哪里,没人知道。要想把虚拟地址和物理地址对应起来,就得用mmap进行内存映射。

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口输出电信号,于是板卡上的灯成功的闪烁起来,类似奥特曼体力不支时的能量灯。

d37d98f93f12668556a1851b7874da6c26e593.gif

多说两句,除了用来操作GPIO/字符设备外,mmap还有个常用的场景是操作块设备。它和传统的用read,write的区别,最关键的是省一次拷贝。

比如要读取磁盘上某个文件的数据,用read write的话,由于会涉及到系统调用,进程是无法直接访问内核的,所以在read系统调用返回前,内核需要将数据从内核复制到进程指定的buffer里。

b8a355d6958ef976b3d7900f693311f32a597d.jpg

但如果用mmap的话,那么这段数据会首先拷贝到内存中作为页缓存(即page cache)。用mmap将这段内存映射到用户空间,则进程可以通过指针直接读写page cache,不再需要多余的系统调用和内存拷贝。

48ddc0c213ad06dfbe7005cfb3b649a698d137.jpg

不过虽然少了一次拷贝,但mmap会触发缺页中断(page fault),相比于内存拷贝而言,缺页中断的开销更大。所以性能而言mmap大部分情况下并不会比read/write要好。

说到页缓存,我在上家公司开发项目的时候,还被脏页延迟这玩意坑过。篇幅所限,页缓存涉及到的缺页中断,脏页,延迟写回,sync强制写回等内容,我们下次再详细聊聊。

好了,于是我们学会用mmap点亮一个灯了。想象一下接下来的场景:

你跟公司研发部最漂亮的女同事说,

“hi,领导那边给了我们组一个新任务,你写个驱动控制一下LED吧?”

“啊?驱动这么难,我不会啦”

“哦没事,那你用mmap吧”

“诶你怎么骂人呢!”


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK