IPC之共享内存

共享内存区介绍

进程间通讯有很多方法,其中最快的形式要数共享内存。

从实现标准上来说,共享内存可分为POSIX共享内存区和System V 共享内存区,从概念上来是是类似的。

两个进程直接访问同一段共享内存。减少了数据的复制次数和上下文切换成本,

但是这种快是有代价的,那就是没有内核来帮你维护数据同步,两个进程同时能够访问这段内存,会造成数据同步问题,这个时候需要使用到锁来解决这个问题,不过这篇文章暂时不涉及到锁,我们详细的来了解下内存映射的概念。

从实现标准上来说,共享内存可分为POSIX共享内存区和System V 共享内存区,从概念上来是是类似的。


POSIX共享内存区

每个进程都拥有操作系统虚拟给自己的一段连续的私有内存区域,但是多个进程要共享一段内存就需要操作系统从空闲内存池中分配,分配好以后,需要连接它的进程进行申请连接,这个过程就叫做共享内存映射。映射完成后,每个进程就可以像访问自己的私有内存一样去访问共享内存区域,从而跟其他进程进行通讯。请看下图:

多进程共享内存概念图

上图很清晰的描述了共享内存的原理,但是该怎么操作才能申请共享内存呢?接下来我会详细描述这个过程。

进程分为有亲缘关系进程和无亲缘关系进程,它们的通讯方式也不尽相同。 有亲缘关系的进程间共享内存有三种办法。

  1. 使用内存映射文件,open函数打开。
  2. BSD系列的系统提供了匿名内存映射标识MAP_ANONYMOUS直接映射内存。
  3. 打开/dev/zero设备文件匿名映射。

/dev/zero在类UNIX系统中是一个特殊的设备文件,当你读它的时候,它会提供无限的空字符。 Link 维基百科

无亲缘关系的进程间共享内存有两种办法:

  1. 内存映射文件,open函数打开。
  2. 共享内存区对象,通过shm_open 函数打开一个POSIX IPC名称。

函数的使用

使用POSIX共享内存区要用到shm_open,shm_unlink,ftruncate,mmap,munmap等函数,接下来我为大家详细说明。

shm_open

创建或打开一个IPC对象。

函数原型:

1
2
3
4
5
#include >sys/mman.h>

int shm_open(const char *name, int oflag, mode_t mode);

//返回: 若成功则为非负描述符,若出错则为-1

参数:

  • name 这是一个"Posix IPC名字",符合已有的路径名规则,必须是斜杠符/开头,可以是真实的路径名,也可以不是。
  • oflag 可选标识有O_RDONLY,O_RDWR,O_CREAT,O_EXCL,O_NONBLOCK,O_TRUNC,其中O_RDONLY,O_RDWR两个标识必须有一个存在。
  • mode 创建一个新的消息队列,信号量或共享内存区对象时,需要设置这个权限位参数,权限位参数值如下:
常值 说明
S_IRUSR 用户(属主)读
S_IWUSR 用户(属主)写
S_IRGRP (属)组成员读
S_IWGRP (属)组成员读写
S_IROTH 其他用户读
S_IWOTH 其他用户写

删除一个IPC对象。跟所有其他unlink函数一样,删除一个IPC对象不会影响对于其底层支撑对象的已存在引用,只有该对象的所有引用全部关闭,这个对象才会清除。删除一个IPC对象仅仅是为了防止后续对其调用。

函数原型:

1
2
3
4
5
#include >sys/mman.h>

int shm_unlink(const char *name);

//返回: 若成功则为非负描述符,若出错则为-1

ftruncate

该函数可以调整指定对象的大小,无论是普通文件还是共享内存区对象都可以调节。只是针对这两个类型的操作稍微有些不同。

  • 对于普通文件。如果该文件的大小大于length参数,那么额外的数据就会被丢弃掉。
  • 对于一个共享内存对象,该函数把这个对象的大小设置成length字节。 函数原型:
1
2
3
4
5
#include <unistd.h>

int ftruncate(int fd, off_t length);

//返回: 若成功则为非负描述符,若出错则为-1

fstat

获取指定对象的信息。

函数原型:

1
2
3
4
5
#include <unistd.h>

int fstat(int fd, struct stat *buf);

//返回: 若成功则为非负描述符,若出错则为-1
0%