Linux中多种内存共享机制及其应用探究

2023-06-22 14:08汪敏
无线互联科技 2023年4期

汪敏

摘要:Linux操作系统广泛运用在服务器运维、嵌入式软件设计和移动端应用开发中,而对于Linux进程间通信机制的理解和研究又决定了软件之间数据通信的效率。Linux有多种方式实现进程之间的通信,包括管道、消息队列、信号量、socket、内存共享等,其中内存共享是效率最高的一种方式,实现了不同进程对同一块物理内存的访问,不需要陷入内核态中进行内核空间和用户空间的数据拷贝,大大提高了通信效率。文章介绍了在Linux操作系统中内存共享的4种不同实现方式,同时介绍了不同实现方式在原理和应用场景上的区别。

关键词:Linux操作系统:内存共享:进程间通信:dmabuf框架

中图分类号:TP31

文献标志码:A

0 引言

Linux是一种开源的操作系统,可以自由裁剪系统功能、性能高效安全,具有强大的技术社区的运维,在嵌入式软件设计、服务器运维、移动端应用开发等场景中有着广泛的运用。内存共享作为一种高效的进程间通信机制[1-5],决定着Linux应用开发的效率。目前,内存共享机制主要有4种,它们在技术实现和应用场景上面有很多的不同,特别是Linux3.3版本中加入的DMABUF框架不仅解决了进程之间大量数据的通信问题,还支持多设备之间的数据高效共享,从而避免了内存拷贝问题的存在,在图形开发、多媒体领域有着越来越多的运用。

1 内存共享简介

内存共享机制是将不同进程的虚拟地址空间映射到同一片物理内存上,多个进程可以共享同一片物理内存。Linux操作系统存在多种进程间通信的方式,包括管道、消息队列、信号等。不同于管道、消息队列需要在内核空间和用户空间进行多次数据拷贝,内存共享仅需要两次数据拷贝,大大提高了进程之间通信的效率,适合通信数据量比较大的场景。

在Linux操作系统中,提供了内存共享机制的多种实现方式。存在内存共享机制多种实现方式的原因是多方面的,既有多种历史版本不同实现方式的原因,又有针对多媒体、图形领域等特定场景不同实现方式的原因。内存共享作为最高效的进程间通信和计算机硬件组件间数据通信的一种方式,值得研究人员深入研究。目前,Linux操作系统中主要存在4种内存共享的实现方式,有System V内存共享、POSIX内存共享、通过memfd_create()实现的内存共享和基于DMABUF框架实现的内存共享。下面分别介绍这4种实现方式的区别以及具体的应用场景。

2 Linux的内存管理

Linux操作系统采用的是虚拟内存管理技术,该技术使Linux中进程的所有操作都是基于虚拟地址展开的。只有在需要访问内存资源的时候,才会进行虚拟地址和物理地址的映射,通过slab分配器和伙伴系统申请实际的物理内存。虚拟内存管理技术有很多好处,首先,扩展了Linux中进程的地址空间,每个进程都有了4G的虚拟地址空间:其次,进程不直接访问物理内存,提高了操作系统的安全性和稳定性;最后,每个进程拥有了独立的地址空间,易于用户程序的开发。Linux中进程在执行的时候,先加载部分程序,将剩余部分留在外部磁盘中;当执行过程中需要的部分在外部磁盘的时候,发生中断:内核将需要的部分加载到内存中。对Linux内存管理机制的深入理解有助于编写更加高效的内存共享程序,特别是能够理解mmap()函数的工作方式,对于文件或者其他对象映射进入调用进程的虚拟地址空间将会有更加透彻的认识。

3 System V内存共享

System V内存共享可以设置多个进程共享物理内存的同一块区域,这一共享的内存区域将成为进程用户空间的一个部分。“写进程”将数据复制到共享内存区域,“读进程”将从共享内存区域中读出数据。不同于消息队列,在消息队列中要求“写进程”将数据从用户空间的缓冲区复制到内核空间中,也要求“读进程”将数据从内核空间复制到用户空间的缓冲区中。也不同于管道,“写进程”的写入数据缓存在内核中,“读进程”从内核缓存中读取数据,同时数据交互的过程遵循先进先出的原则。System V内存共享机制数据拷贝不经过内核空间,意味着更高的通信效率。由于存在多个进程对同一片内存区域数据的读写,在实际使用中通常使用信号量来完成System V内存共享机制的数据同步过程。

System V内存共享机制的一般过程如下:首先,调用shmget()创建新的共享内存段,返回共享内存标识符;然后,使用shmat()附着共享内存段,将该内存段成为调用进程的虚拟内存的一部分,shmat()返回的addr值可以供程序使用.addr值是该共享内存段起始点的指针,共享内存段使用完毕后,调用shmdt()分离共享内存,调用该函数后,该共享内存段将无法被继续引用;最后,调用shmctl()删除共享内存段。如图1所示,用free和ipcs命令查看当前内存运行情况和进程间通信的状态。

4 POSIX内存共享

Svstem V内存共享是基于标识符实现的,不同于通用的Linux l/0模型使用文件描述符的方式,这意味着Svstem V内存共享需要一整套单独的系统调用,导致了System V内存共享在易用性方面的劣势,正因为这样.POSIX.1b设置了新的内存共享方式。Linux在实现POSIX内存共享机制时,使用文件系统来标识共享内存对象,将共享对象创建为/dev/shm目录下面的一个文件,/dev/shm目录下使用tmpfs专用文件系统,该文件系统下的内存对象具有持久性,共享内存对象将一直存活,直到系统关闭之后才会丢失。

mmap()函数经常将文件映射為进程虚拟地址空间的一部分[6-10].完成映射之后,可以通过操作内存地址完成文件内容的修改。如果操作的内容不在内存页上面,则会从相应的外部磁盘中来加载该页完成文件的最终修改过程。mmap()函数经常被使用在POSIX内存共享机制中.POSIX内存共享机制一般过程如下:先调用shm_open()创建且打开一个命名的共享内存对象,shm_open()函数将返回一个引用共享内存对象的文件描述符,共享内存对象初始长度为0。调用mmap()函数传人文件描述符参数,同时指定flags为MAP—SHARED.mmap()函数将该内存对象映射为进程的虚拟地址空间。可以使用既有的Linux系统调用(例如fchmod(),fstat())进行共享内存对象的操作和查看。在使用完共享内存对象后,可以使用shm_unlink()函数删除共享内存对象。删除单个的共享内存对象不会影响现有的共享内存对象的映射关系,但是后续shm_open()打开这个共享内存对象会失败。当所有的进程都删除共享对象后,对象将最终被删除。

Svstem V和POSIX内存共享机制在使用上有着很多差异。首先是在持久化上的区别,POSIX内存共享对象与文件相映射意味着共享内存中的数据能够持久化地保存:其次是在標识内存共享对象上的差异,由于使用了独立的标识符模型.Svstem V需要单独的系统调用实现内存共享机制,而POSIX内存共享机制可以使用标准的Linux I/O模型实现内存共享操作;再次是共享内存大小的创建时间,System V在使用shmget()的时候就确定了共享内存的大小,而POSIX内存共享可以通过ftruncate()独立调整对象的大小,然后重新建立新的映射,在灵活性方面.POSIX内存共享机制要更加高效:最后是支持程度方面,由于发展历史的原因.System V的支持厂商更多一些,但是目前来看.Linux通用的发行版本都已经支持POSIX内存共享机制。从以上System V和POSIX内存共享机制的区别可以看出,大部分情况下.POSIX内存共享机制应该作为使用进程间通信机制的首选。

5 通过memfd_create()实现的内存共享

在进行进程间通信的编程过程中,经常会出现这样的场景,就是A进程想要访问B进程的文件描述符,要访问B进程文件描述符的原因是因为该文件描述符指向需要访问的内存,这样A进程就可以通过B进程的文件描述符访问B进程的内存。下一步就是需要完成文件描述在A、B进程之间的共享,文件描述符是进程级别的概念,不同的进程有不同的文件描述符列表。文件描述符不能作为一个变量直接传输给A进程,原因是open()函数返回的数字仅代表了在B进程中文件描述符的引用,即使是传输给A进程,A进程也不能通过该数字访问B进程的内存。这时候就需要通过cmsg在socket上传输控制信息,完成文件描述的传递过程。

memfd_create()函数主要的功能是创建一个匿名文件,然后返回这个匿名文件的文件描述符,这样就可以通过这个函数直接返回一个匿名内存文件的文件描述符。同时使用SCM_RIGHTS,进程就可以透过socket将文件描述符传递给另外一个进程,进而可以实现以fd为中间媒介,沟通两个不相关的进程共享同一片内存区域。

通过memfd_creace()函数实现进程之间的内存共享,最大的好处是这种编程模型比较清晰、简单和通用。进程之间共享内存的大小、数量、目的地都简单可控,只要使用socket来传递需要的文件描述符即可。例如在网络视频播放领域,可以将收到的视频画面多创建几个缓存,然后单独将缓存的fd递交给另外的进程进行解码,即可实现视频画面高效的处理。

6 基于DMABUF框架的内存共享

DMABUF框架主要解决的是多个不同设备之间的缓存共享问题,而缓存共享问题的本质是避免内存拷贝。作为一个通用的框架,在2012年2月汇入Linux3.3版本,解决了长期困扰Linux驱动开发人员的“缓存共享”问题。

DMABUF框架的一般应用如图2所示,缓存使用者1通过访问缓存分配者获得缓存的文件描述符,获取文件描述符之后,通过socket将文件描述符发送给缓存使用者2。缓存使用者2在收到文件描述符之后,将文件描述符信息导入缓存使用者3(内核空间)中,此时缓存使用者3就获得了缓存的共享访问能力。

通过DMABUF框架实现缓存共享有很多好处,例如,在网络流媒体场景中,要实现内存零拷贝,需要先将帧缓存里面的内容通过多媒体组件来转变为视频码流,在转变为视频码流之后,再广播到网络上。这一系列的过程中,需要避免出现内存拷贝的过程,这样才能够保障流媒体传输的效率。在类似的场景中.DMABUF框架有着很多的运用,特别是多进程、多组件之间协同处理数据的时候,更加需要考虑数据处理的效率问题。

从设计思想上来看,内核一般只提供机制而不提供具体的策略,具体的策略应该由用户空间来构建,上面DMABUF框架的一般使用流程正是遵循的这样的原则。基于DMABUF框架的内存共享方式以文件描述符为句柄,实现了设备间、进程间的缓存共享访问,从而避免了内存拷贝问题。

7 内存共享的应用场景

7.1 生产者和消费者内存共享场景

生产者、消费者模型作为经典的模型,经常被用在各种进程间通信的案例中来介绍,这里主要介绍的是大数据量的生产者和消费者模型的构建过程。生产者主要负责数据的产出,消费者主要负责数据的处理过程。在大数据量的生产者、消费者模型中使用共享内存有很多好处。首先,具有内存共享的生产者消费者模型支持数据的异步传输。在使用共享内存前,生产者只能一直等待消费者处理完成数据之后,才能生产数据;在使用了内存共享机制后,生产者可以将消费者来不及处理的数据放置到共享内存中,实现了生产者和消费者异步处理数据的过程。其次,内存共享支持了生产者和消费者之间的交互解耦。生产者和消费者之间不需要直接交互,避免了依赖关系的生成。最后,支持数据处理流量控制的实现。当出现消费者处理数据过快或者过慢的时候,可以增加或者减少生产者线程同时配合信号量,完成消费者和生产者之间数据的处理节奏。

7.2 无关进程间内存共享场景

无关进程之间经常需要进行只读文件的共享过程,例如配置文件、图像帧缓存等只读数据。例如,有一个500 k大小的配置文件,本地机器有80个进程需要进行配置文件的读取过程,那么一共就需要分配4M的内存空间进行数据的读取过程,这对于计算机来说,是巨大的内存资源的浪费。如果使用了内存共享技术,就可以由一个进程完成在共享内存中加载配置文件的过程,同时将这份共享的配置文件同步给其他进程,就实现了多个进程使用同一份只读配置文件的过程。无关进程间的内存共享更多的应用在多媒体领域中,多媒体领域经常会存在多个画面的缓存。为了实现内存的零拷贝,会更多地使用内存共享技术。

7.3 父子进程间内存共享场景

管道是父子进程间经常使用的一种通信方式,但是使用管道作为父子进程间的通信方式有兩方面的弊端。第一,通信的数据量比较小,管道经常作为shell中两个命令间数据沟通快捷的方式而存在,同时交互的数据量比较小,不像共享内存那样可以按照需求来分配通信数据量的大小;第二,管道写入的数据都是缓存在内核中,严格遵守先进先出的原则,需要在内核空间和用户空间来回切换,才能完成数据的拷贝过程,因此通信的效率不高。基于这两个原因,在对通信数据量大小有要求的情况下,建议使用共享内存的方式来完成父子进程间的数据通信过程。

8 结语

本文主要讨论了System V内存共享、POSIX内存共享、基于memfd_create()内存共享和基于DMABUF框架内存共享4种进程间通信方式的优缺点以及应用场景,前3种更加强调进程之间的内存共享功能,而基于DMABUF框架则更加突出设备间的缓存共享功能,虽然基于DMABUF框架也支持多进程之间的内存共享。目前广泛应用于图形编程领域的内存共享方式,已经从进程虚拟地址空间映射方式转变为以文件描述符为句柄共享内存的方式,这种趋势的转变,也让decoder.GPU等可以跨越不同的进程来访问内存。

参考文献

[1]黄茹.浅析Linux环境下的进程间通信机制[J].科技信息,2014( 14):96-97.

[2]姚珉.理解Linux环境下SYSTEM V进程间通信[J].电脑开发与应用,2005( 10):50-52.

[3]陈浩.Linux下用户态和内核态内存共享的实现[J].电脑编程技巧与维护,2011(4):25-27.

[4]张华,孙传伟,李靖谊.基于Linux的同步共享内存的研究与实现[J].湖南工业职业技术学院学报,2004(4):19-21.39.

[5]刘斌,朱程荣.Linux内核与用户空间通信机制研究[J].电脑知识与技术,2012( 16):3816-3817.3849.

[6]杨宇音,李志淮.Linux中用户空间与内核空间的通信实现[J].微机发展,2005(5):75-76,130.

[7]李妍.消息队列在Linux线程或进程间通信中的应用[J].电子世界,2019 (17):146-147.

[8]许豪,陈可.Linux下进程间通信机制的探讨[J].科技与创新,2016(3):83.

[9]黄向平,彭明田,杨永凯.基于内存映射文件的高性能库存缓存系统[J].电子技术应用,2020(7):113-117. 126.

[10]黄向平,彭明田,杨永凯.基于内存映射文件的复杂对象快速读取方法[J].计算机技术与发展,2020(3):82-87.

(编辑沈强)