54 POSIX 共享内存

POSIX 共享内存对应虚拟文件系统 /dev/shm,这是一个 tmpfs 文件系统,具有内核持久性。如果不满意默认的大小(书上说默认大小是 256M,但是我测试默认大小是内存的一半——在服务器、虚拟机、wsl 上都是这样;docker 容器共享内存的默认大小则是 64M),可以使用 mount -o remount,size=<num_bytes> ... 重新挂载。

回忆:PyTorch 的 dataloader 经常需要共享内存,因此创建的跑 PyTorch 程序的容器需要设置更大一点的共享内存限制。

POSIX 共享内存的使用方式很像一个文件,可以用 shm_open() 打开或者创建,用 shm_unlink() 来删除。

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

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

POSIX 共享内存对象要和 mmap() 一起用:先打开一个共享内存对象,然后将其文件描述符映射到内存的某处。这个时候 shm_open()mmap() 的关系很像 shmget()shmat() 的关系。

联想记忆:有些系统上没有提供匿名映射,因此创建匿名映射需要打开文件 /dev/zero 然后创建文件映射。

另外注意到共享内存对象刚创建时大小是 0,因此我们有必要在创建共享内存对象之后ftruncate() 修改它的大小,新增的部分将被填充为 0。

联想记忆:如果是普通文件,用 ftruncate() 增大文件大小时,多出来的部分会被 0 填充,新增的页面会形成空洞,并不会真正占用磁盘空间。

SUSv3 规定 O_TRUNC + O_RDONLY 打开共享内存对象是未定义的。在 Linux 中这样做依然会将文件截断。

shm_open() 返回的文件描述符有 close-on-exec 标记,不能跨越 exec()。这个标志虽然可以后期更改,但是 exec() 载入的新的进程映像很难知道到底哪些继承的文件描述符和共享内存段关联。通过环境变量或者命令行指定 fd 参数都需要修改被载入程序的源码,这大概也是为什么默认有 FD_CLOEXEC 标记。参考: https://stackoverflow.com/a/48191050/

既然 shm_open() 要和 mmap() 一起用,为什么不直接用 mmap()

单靠 mmap() 无法解决不写入文件的命名共享内存问题。要不写入文件,就得匿名,但是这样无关进程就不能共享内存了。而 shm_open() 通过虚拟文件系统解决了这个问题。

另外一方面,基于文件的共享内存在重启仍保留已经同步到磁盘的内容,而其他共享内存手段的信息在重启后会丢失。所以两者各有长处。