[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

[原文地址](https://blog.csdn.net/21cnbao/article/details/103470878)

共享单车、共享充电宝、共享雨伞,世间的共享有千万种,而我独爱共享内存。

早期的共享内存,着重于强调把同一片内存,map到多个进程的虚拟地址空间(在相应进程找到一个VMA区域),以便于CPU可以在各个进程访问到这片内存。

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

只要内存的拷贝(memcpy)仍然是一个占据内存带宽、CPU利用率的消耗大户存在,共享内存作为Linux进程间通信、计算机系统里各个不同硬件组件通信的最高效方法,都将持续繁荣。关于内存拷贝会大多程度地占据CPU利用率,这个可以最简单地尝试拷贝1080P,帧率每秒60的电影画面,我保证你的系统的CPU,蛋会疼地不行。

我早就想系统地写一篇综述Linux里面各种共享内存方式的文章了,但是一直被带娃这个事业牵绊,今日我决定顶着娃娃们的山呼海啸,也要写一篇文章不吐不快。

共享内存的方式有很多种,目前主流的方式仍然有:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

下面写一个最简单的程序来看共享内存的写端sw.c:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

编译和准备运行:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

下面运行sw和sr:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

可以看到used显著增大了(711632 -> 715908), shared显著地增大了(2264 -> 6360),而cached这一列也显著地增大326604->330716。

我们都知道cached这一列统计的是file-backed的文件的page cache的大小。理论上,共享内存属于匿名页,但是由于这里面有个非常特殊的tmpfs(/dev/shm指向/run/shm,/run/shm则mount为tmpfs):

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

POSIX共享内存

我对POSIX shm_open()、mmap () API系列的共享内存的喜爱,远远超过SYS V 100倍。原谅我就是一个懒惰的人,我就是讨厌ftok、shmget、shmat、shmdt这样的API。

上面的程序如果用POSIX的写法,可以简化成写端psw.c:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

编译和执行:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

坦白讲,mmap、munmap这样的API让我找到了回家的感觉,刚入行做Linux的时候,写好framebuffer驱动后,就是把/dev/fb0 mmap到用户空间来操作,所以mmap这样的 API,真的是特别亲切,像亲人一样。

当然,如果你不喜欢shm_open()这个API,你也可以用常规的open来打开文件,然后进行mmap。关键的是mmap,wikipedia如是说:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

请将这个free命令的结果与前2次的free结果的各个字段进行对照:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

这个进程的0,1,2和那个进程的0,1,2不是一回事。

某年某月的某一天,人们发现,一个进程其实想访问另外一个进程的fd。当然,这只是目的不是手段。比如进程A有2个fd指向2片内存,如果进程B可以拿到这2个fd,其实就可以透过这2个fd访问到这2片内存。这个fd某种意义上充当了一个中间媒介的作用。有人说,那还不简单吗,如果进程A:

fd = open();

open()如果返回100,把这个100告诉进程B不就可以了吗,进程B访问这个100就可以了。这说明你还是没搞明白fd是一个进程内部的东西,是不能跨进程的概念。你的100和我的100,不是一个东西。这些基本的东西你搞不明白,你搞别的都是白搭。

Linux提供一个特殊的方法,可以把一个进程的fd甩锅、踢皮球给另外一个进程(其实“甩锅”这个词用在这里不合适,因为“甩锅”是一种推卸,而fd的传递是一种分享)。我特码一直想把我的bug甩(分)锅(享)出去,却发现总是被人把bug甩锅过来。

那么如何甩(分)锅(享)fd呢br>

Linux里面的甩锅需要借助cmsg,用于在socket上传递控制消息(也称Ancillary data),使用SCM_RIGHTS,进程可以透过UNIX Socket把一个或者多个fd(file descriptor)传递给另外一个进程。

比如下面的这个函数,可以透过socket把fds指向的n个fd发送给另外一个进程:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

那么问题来了,如果在进程A中有一个文件的fd是100,发送给进程B后,它还是100吗能这么简单地理解,fd本身是一个进程级别的概念,每个进程有自己的fd的列表,比如进程B收到进程A的fd的时候,进程B自身fd空间里面自己的前面200个fd都已经被占用了,那么进程B接受到的fd就可能是201。数字本身在Linux的fd里面真地是一点都不重要,除了几个特殊的0,1,2这样的数字外。同样的,如果你把 cat /proc/interrupts 显示出的中断号就看成是硬件里面的中断偏移号码(比如ARM GIC里某号硬件中断),你会发现,这个关系整个是一个瞎扯。

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

我们透过memfd_create()创建了一个“文件”,但是它实际映射到一片内存,而且在/xxx/yyy/zzz这样的文件系统下没有路径!没有路径!没有路径!

所以,当你在Linux里面编程的时候,碰到这样的场景:需要一个fd,当成文件一样操作,但是又不需要真实地位于文件系统,那么,就请立即使用memfd_create()吧,它的manual page是这样描述的:

memfd_create

memfd_create() creates an anonymous file and returns a file descriptor that refers to it.  The file behaves like a regular file, and so can be modified, truncated, memory-mapped, and so on.
However, unlike a regular file, it lives in RAM and has a volatile backing storage.

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

下面的代码进程B透过相同的socket接受这2片内存对应的fd,之后通过read()读取每个文件的前256个字节并打印:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

封印

* SEAL_SHRINK: If set, the inode size cannot be reduced
* SEAL_GROW: If set, the inode size cannot be increased
* SEAL_WRITE: If set, the file content cannot be modified

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

dma_buf

dma_buf定义

The DMABUF framework provides a generic method for sharing buffers between multiple devices. Device drivers that support DMABUF can export a DMA buffer to userspace as a file descriptor (known as the exporter role), import a DMA buffer from userspace using a file descriptor previously exported for a different or the same device (known as the importer role), or both. 

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

上图中,进程A访问设备A并获得其使用的buffer的fd,之后通过socket把fd发送给进程B,而后进程B导入fd到设备B,B获得对设备A中的buffer的共享访问。如果CPU也需要在用户态访问这片buffer,则进行了mmap()动作。

为什么我们要共享DMA buffer象一个场景:你要把你的屏幕framebuffer的内容透过gstreamer多媒体组件的服务,变成h264的视频码流,广播到网络上面,变成流媒体播放。在这个场景中,我们就想尽一切可能的避免内存拷贝

技术上,管理framebuffer的驱动可以把这片buffer在底层实现为dma_buf,然后graphics compositor给这片buffer映射出来一个fd,之后透过socket发送fd 把这篇内存交给gstreamer相关的进程,如果gstreamer相关的“color space硬件转换”组件、“H264编码硬件组件”可以透过收到的fd还原出这些dma_buf的地址,则可以进行直接的加速操作了。比如color space透过接收到的fd1还原出framebuffer的地址,然后把转化的结果放到另外一片dma_buf,之后fd2对应这片YUV buffer被共享给h264编码器,h264编码器又透过fd2还原出YUV buffer的地址。

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

如果是multi plane的话,则需要导入多个fd:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

有的童鞋说,为嘛在一个进程里面设备A和B共享内存还需要fd来倒腾一遍呢直接设备A驱动弄个全局变量存buffer的物理地址,设备B的驱动访问这个全局变量不就好了吗只能说,你对Linux内核的只提供机制不提供策略,以及软件工程每个模块各司其责,高内聚和低耦合的理解,还停留在裸奔的阶段。在没有dma_buf等类似机制的情况下,如果用户空间仍然负责构建策略并连接设备A和B,人们为了追求代码的干净,往往要进行这样的内存拷贝:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

其中的vb2_dc_dmabuf_ops是一个struct dma_buf_ops,它含有多个成员函数:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

应用程序可以通过如下方式拿到底层的dma_buf的fd:

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

落花满天蔽月光,借一杯附荐凤台上。

全剧终

Linux阅码场原创精华文章汇总

更多精彩,尽在”Linux阅码场”,扫描下方二维码关注

[转载]宋宝华:世上最好的共享内存(Linux共享内存最透彻的一篇)

你的随手转发或点个在看是对我们最大的支持!

文章知识点与官方知识档案匹配,可进一步学习相关知识CS入门技能树Linux入门初识Linux24890 人正在系统学习中

来源:qq62

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年6月2日
下一篇 2021年6月2日

相关推荐