Linux共享内存API简介

共享内存允许不同的进程共享一个给定的存储区,因为这些数据不需要在进程之间复制,所以这是最快的一种IPC。不同进程之间进行数据的读写也需要进行同步,通常可以使用信号量、互斥量进行同步。

基本API

shmget

[IPC基础]01-Linux共享内存API简介_IPC

在Linux环境中,对开始申请的共享内存空间进行了初始化,初始值为0x00。
如果用shmget创建了一个新的消息队列对象时,则shmid_ds结构成员变量的值设置如下:
shm_lpid、shm_nattach、shm_atime、shm_dtime设置为0。
msg_ctime设置为当前时间。
shm_segsz设成创建共享内存的大小。
shmflg的读写权限放在shm_perm.mode中。
shm_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。

shmat

[IPC基础]01-Linux共享内存API简介_共享内存_02

shmdt

[IPC基础]01-Linux共享内存API简介_共享内存_03

shmctl

[IPC基础]01-Linux共享内存API简介_IPC_04

shm_open/shm_unlink/mmap/munmap

shm_open和shm_unlink

​shm_open​​​创建并打开一个新的或者打开一个已存在的​​POSIX​​​共享内存文件。通过​​mmap​​​将​​shm_open​​返回的文件描述符映射到内存中,可以让没有亲缘关系的进程访问该共享内存。

#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */

// name:共享内存文件名,只是名字,不要使用绝对路径
// oflag:取值 O_RDONLY | O_RDWR | O_CREAT | O_EXCL | O_TRUNC
// mode:文件访问权限,比如0666,需要和umask相与得到真实的权限
// 成功返回文件描述符,失败返回-1
int shm_open(const char *name, int oflag, mode_t mode);

// 删除由shm_open创建的共享内存文件
int shm_unlink(const char *name);

// 需要连接rt库,比如 gcc -lrt xxx

mmap和munmap

​mmap​​​操作提供了一种机制,让用户程序直接访问设备内存,这种机制相比较在用户空间和内核空间互相拷贝数据效率更高。在要求高性能的应用中比较常用。​​mmap​​​映射内存必须是页面大小的整数倍,面向流的设备不能进行​​mmap​​​,​​mmap​​​的实现和硬件有关。​​mmap()​​​系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用​​read()​​​,​​write()​​等操作。

#include <sys/mman.h>

// addr:一般为NULL,表示由内核自行选择虚拟地址空间进行内存映射
// length:映射的内存大小,以字节为单位,不足一个内存页的向上取整
// prot:访问权限, PROT_EXEC | PROT_READ | PROT_WRITE | PROT_NONE
// flags:指定映射对象的类型,映射选项和映射页是否可以共享,具体见https://baike.baidu.com/item/mmap/1322217?fromModule=lemma_search-box
// MAP_FIXED:使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上
// MAP_SHARED:与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
// MAP_PRIVATE:建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
// flags由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用
// fd:要映射的文件描述符,如果为-1则必须指定flags参数中的MAP_ANON,表明进行匿名映射(此时只能具有亲缘关系的进程间通信)
//
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);

// 一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。
int munmap(void *addr, size_t length);

​mmap​​的一般用法:

  1. 使用普通文件提供的内存映射,适用于任何进程之间的通信;此时需要打开或创建一个文件,然后再调用​​mmap​
  2. 使用特殊文件提供匿名内存映射,适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用​​mmap​​​,然后再调用​​fork​​​。在​​fork​​​之后,子进程集成父进程匿名映射后的地址空间,同样也继承​​mmap​​返回的地址,这样父子进程就可以通过映射区域进行通信了。

文件一旦被映射后,调用mmap()的进程对返回地址的访问是对某一内存区域的访问,暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义,只有在调用了munmap()后或者msync()时,才把内存中的相应内容写回磁盘文件,所写内容仍然不能超过文件的大小。