POSIX shm_open
还是神奇的gem5。
这次是在学习底层实现的时候,看到的船新POSIX API:
if (sharedBackstore.empty()) {
shm_fd = -1;
map_flags = MAP_ANON | MAP_PRIVATE;
} else {
DPRINTF(AddrRanges, "Sharing backing store as %s\n",
sharedBackstore.c_str());
shm_fd = shm_open(sharedBackstore.c_str(), O_CREAT | O_RDWR, 0666);
if (shm_fd == -1)
panic("Shared memory failed");
if (ftruncate(shm_fd, range.size()))
panic("Setting size of shared memory failed");
map_flags = MAP_SHARED;
}
...
uint8_t* pmem = (uint8_t*) mmap(NULL, range.size(),
PROT_READ | PROT_WRITE,
map_flags, shm_fd, 0);
由于这块代码是从gem5中内存模块复制出来的,其功能是配置内存模块维护被模拟系统数据所需的储存空间(初始化),可以预见的是单词分配的内存空间大小至少是MB,甚至GB级别的。第一眼看去有点眼花,还在想Linux的shared memory什么时候能够配置GB级别的共享内存块了?仔细一看才发现,这里用的是shm_open
而不是shmget
,只是名字长得像,功能有很大差别= =,前者在fnctl.h
,后者在shm.h
中定义。(P.S. 在APUE第三版上翻了老半天都没找到,我觉得应该不是我的问题)
比较一下shmget
和shm_open
的函数签名:
int shmget(key_t key, size_t size, int shmflg);
int shm_open(const char *name, int oflag, mode_t mode);
不同于shmget
在创建共享内存块的时候需要声明块大小,且存在比较小气的最大限制(Linux每块默认不大于4KB,MacOS和Solaris还是比较大气,大了几个数量级),shm_open
的语法更偏向于open
,创建的是一个默认大小为0的空文件,关于文件大小、文件内容需要开发者后续调用其它syscall来修改。
那么shm_open
实际上做了什么呢?实际上是创建了一个基于内存的文件描述符。根据man7上的文档介绍,Linux上的实现方法是建立了一个基于内存的tmpfs
,并挂载到/dev/shm
目录下。调用shm_open
的时候,会对应地在目录下用文件的形式进行相应的操作。
如上文所述,shm_open
以后,文件的大小为0,用常规方法往文件写入数据并不能如我们所愿,将文件“撑”大。这时需要使用ftruncate
系统调用解决该问题,由操作系统完成对文件大小的调整。
由于shm_open
系统调用与进程无关,在不同程序中可以使用相同的操作获取同一块共享内存,达到无关进程内存共享的目的。这与文件系统类似,第一次调用建立文件,后面的重复调用只需要修改文件引用值即可。由shm_open
创建的文件描述符多与mmap
结合运用,直接将fd
用于mmap
中,其余按照需要配置即可,本文开头的代码段给出了较好的示范。
当一个进程不再需要使用这段共享内存的时候,使用shm_unlink
函数,把获得的fd
作为参数传过去即可。当最后一个引用关闭之后,共享内存对象由系统销毁。
这里顺便说一下上面的代码段中,mmap
使用的一个小细节。当满足条件sharedBackstore.empty() == false
的时候,fd
设置为-1
之后,直接执行了mmap
操作。-1
显然是一个无效的文件描述符,但是为什么程序还能正常运行?
在StackOverflow上搜索后,万能网友的给出了答案:此时的fd
为-1
是允许的,而这是与mmap
的参数map_flags
中MAP_ANON
(或MAP_ANONYMOUS
)这个flag共用时的一个特殊情况。在声明了MAP_ANON
/MAP_ANONYMOUS
的情况下,该共享内存块仅对当前进程及其子进程有效,其他进程无法访问。某些POSIX实现要求这种情况下,fd
一定要传-1
,而这也只是特殊情况,并不代表在fd=-1
的情况下mmap
具有特殊的逻辑。