从零到负一

Linux编程基础5 - 进程间通信(POSIX共享内存)

2020/06/26

Linux下有两种类型的共享内存系统调用,一种是基于System V的,另一种是基于POSIX的。上一篇文章Linux编程基础4 - 进程间通信(共享内存) 说的就是System V下的共享内存系统调用;之前介绍过的mmap()函数就是基于POSIX的,除了mmap()外,shm_open()shm_unlink()也是基于POSIX的函数。这篇文章主要记录POSIX下共享内存系统调用的使用。

mmap()

mmap()主要作用是将文件映射到进程的内存空间,从而进程可以快速地读取文件内容(不用再使用write()read()经过磁盘、内核缓冲区、用户空间缓冲区等);同时,我们也可以指定多个进程映射同一个文件,这样也实现了共享内存的作用。mmap()一般适合临时性的大文件映射,当进程结束时,映射自动就被销毁了(System V需要手动销毁)。

mmap()的函数原型如下(munmap()用于销毁映射):

1
2
3
4
// 成功返回共享内存的地址,失败返回(void *)-1
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
// 成功返回0,失败返回-1
int munmap(void *addr, size_t length);

mmap()函数参数比较多,addr是映射地址一般填NULL,内核会自动分配地址进行映射;length是需要映射的长度;prot是该空间的范围权限有PROT_READPROT_WRITE等;flags是标志位,用的比较多的是MAP_SHAREDMAP_PRIVATE,它们的作用下面会讲;fd是文件描述符;offset是文件映射起始地址的偏移量,它的值必须是页大小的整数倍。

上面提到MAP_SHAREDMAP_PRIVATE,这两个标志位有什么不同呢?前者对进程内部映射区的修改会更新文件,其他同样映射到该文件的进程也可以看到修改后的文件(修改文件同样也会修改映射区?);对于后者进程使用COW(copy on write),只修改内部映射区,不会修改文件,因此其它进程是看不到文件修改的(修改文件是否会影响映射区是没有定义的行为)。

上面有提到offset必须是页大小的整数倍,因为Linux内核是通过页的方式管理内存空间的,因此mmap()的操作是按页进行操作的。文件会按照文件大小 / 页大小 + 1的形式被映射到进程,因此mmap()实际会使用比文件大小更多的内存空间。对于mmap()的参数,虽然只有offset被要求是页的整数倍,但addr在函数内部会向上转化成页大小的整数倍。

除了实现文件到进程空间的映射,mmap()还可以通过匿名映射实现父进程和子进程之间的通信(匿名映射还可以用来实现malloc())。

匿名映射

当我们设置flagsAP_ANONYMOUS或者MAP_ANON时,mmap()将实现匿名映射。在该设置下,fd通常会被忽略,在有些系统中需要设置fd = -1。在匿名映射下,进程将内存空间而不是文件映射到进程空间,并且这个空间除了子进程外的其他进程是不能访问的,因此匿名映射可以用作父子进程间的通信。

如果要用mmap()来实现任意进程间的通信,我们还需要使用另外两个系统调用 - shm_open()shm_unlink()(当然还需要额外的函数实现进程同步)。

shm_open()用于创建或者打开一个共享内存空间,这个空间的地址在/dev/shm/xxx,其中/xxx就是shm_open()创建共享内存空间时的参数。shm_open()shm_unlink()函数原型如下:

1
2
3
4
// 成功返回文件描述符,失败返回-1
int shm_open(const char *name, int oflag, mode_t mode);
// 成功返回0,失败返回-1
int shm_unlink(const char *name);

name是创建文件的名字,建议使用/xxx的方式命名。oflag是标志位,用于设置可读、可写、创建文件等;modeopenmode一样,后9bit是设置文件的访问权限。

shm_unlink()用于解除共享内存空间和文件描述符的映射,当没有进程映射到该内存空间,内核就会销毁它。

CATALOG
  1. 1. mmap()
    1. 1.1. 匿名映射
  2. 2. shm_open()和shm_unlink()