c++共享内存通信如何实现

c++共享内存通信如何实现

  • 前言
  • mmap机制-对应cyber中共享内存通信模式中的PosixSegment
    • 小结
  • System V共享内存-对应cyber中XsiSegment
    • 小结
  • 参考链接

前言

现在很多对性能要求高的项目都会支持共享内存的进程间通信(IPC)方式,本文会以百度Apollo自动驾驶项目为例,展示两种c++中实现共享内存通信的方式(对应linux中两种不同的机制)。
共享内存实际上就是两个不相关的进程访问同一块逻辑内存,相应的肯定需要额外的同步机制来保证读写正确。采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

mmap机制-对应cyber中共享内存通信模式中的PosixSegment

c++共享内存通信如何实现_第1张图片
内存映射机制mmap是POSIX标准的系统调用,mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap(该调用在进程地址空间中解除一个映射关系)后才执行该操作。可以通过调用msync实现磁盘上文件内容与共享内存区的内容一致。
mmap用于共享内存有两种使用方式,分别是使用普通文件提供内存映射和使用特殊文件提供匿名内存映射(适用于有亲缘关系的进程之间)。下面分别介绍用法。

  1. 普通文件映射的步骤(cyber中方法)
  • 创建共享内存区域。调用shm_open函数(这是cyber中做法,实际上创建普通文件即可),创建内存文件(默认路径为Linux下sysv共享内存的默认挂载点/dev/shm),参数是文件名和读写权限等,返回一个int文件描述符。
  • 分配大小。调用ftruncate函数,给刚刚创建的文件设置大小即设置内存区域的大小。
  • 映射内存。调用mmap函数,将某块内存映射到刚刚的文件,接下去对该文件的操作都会反映到内存上,注意该函数使用时的参数,映射区域的特性flags需要像cyber中那样设置为MAP_SHARED,这样对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。如果设置为MAP_PRIVATE则对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
  • 读写数据,之后就可以通过直接读写对应内存比如memcpy或cyber中的string*->assign/append函数等来进行操作和通信。
  1. 匿名映射实现共享内存
  • 父进程中调用mmap映射一块内存,对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。
  • 父进程调用fork创建子进程,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。
  • 对这块内存进行读写就能实现通信。

小结

  1. mmap的原理是在打开一个文件时,如果发现page cache已经有相应的页面则直接返回该地址供读写,这样就能多个进程操作同一块内存区域。
  2. 对mmap返回地址的访问不完全取决于文件被映射部分大小,因为linux中采用页管理机制,所以映射文件的内存大小一定是页的整数倍,实际能访问的大小是>=文件被映射部分大小的。
    c++共享内存通信如何实现_第2张图片
  3. mmap有一个好处是当机器重启,因为mmap把文件保存在磁盘上,这个文件还保存了操作系统同步的映像。mmap映射的内存不是持久化的,如果进程关闭,映射随即失效,除非事先已经映射到了一个文件上。

System V共享内存-对应cyber中XsiSegment

c++共享内存通信如何实现_第3张图片
系统调用mmap()通过映射一个普通文件实现共享内存。系统V则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。也就是说,每个共享内存区域对应特殊文件系统shm中的一个文件。
进程间需要共享的数据被放在一个叫做IPC共享内存区域的地方,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。系统V共享内存通过shmget获得或创建一个IPC共享内存区域,并返回相应的标识符。内核在保证shmget获得或创建一个共享内存区,初始化该共享内存区相应的shmid_kernel结构注同时,还将在特殊文件系统shm中,创建并打开一个同名文件,并在内存中建立起该文件的相应dentry及inode结构,新打开的文件不属于任何一个进程(任何进程都可以访问该共享内存区)。所有这一切都是系统调用shmget完成的。
该结构中最重要的一个域应该是shm_file,它存储了将被映射文件的地址。每个共享内存区对象都对应特殊文件系统shm中的一个文件,一般情况下,特殊文件系统shm中的文件是不能用read()、write()等方法访问的,当采取共享内存的方式把其中的文件映射到进程地址空间后,可直接采用访问内存的方式对其访问。
在创建了一个共享内存区域后,还要将它映射到进程地址空间,系统调用shmat()完成此项功能。由于在调用shmget()时,已经创建了文件系统shm中的一个同名文件与共享内存区域相对应,因此,调用shmat()的过程相当于映射文件系统shm中的同名文件过程,原理与mmap()大同小异。
下面以cyber中使用的步骤为例:

  • 调用shmget用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。
  • 调用shmat把共享内存区域映射到调用进程的地址空间中去,这样,进程就可以方便地对共享区域进行访问操作。这个函数返回一个指针,之后就可以直接操作内存了。
  • 直接操作内存区域进行读写。
  • 遇到错误时可以调用shmdt()用来解除进程对共享内存区域的映射。shmctl可以用来控制共享内存,cyber中用到了IPC_RMID参数来删除共享内存段。

小结

  1. 系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。 系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的,文件系统shm的安装点在交换分区上,系统重新引导后,所有的内容都丢失。
  2. 系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。

参考链接

C/C++ 使用mmap/munmap函数分配内存
共享内存上,下
mmap映射区和shm共享内存的区别总结

你可能感兴趣的:(OS相关,操作系统,嵌入式,linux,c++)