还是神奇的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第三版上翻了老半天都没找到,我觉得应该不是我的问题

比较一下shmgetshm_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_flagsMAP_ANON(或MAP_ANONYMOUS)这个flag共用时的一个特殊情况。在声明了MAP_ANON/MAP_ANONYMOUS的情况下,该共享内存块仅对当前进程及其子进程有效,其他进程无法访问。某些POSIX实现要求这种情况下,fd一定要传-1,而这也只是特殊情况,并不代表在fd=-1的情况下mmap具有特殊的逻辑。

标签: none

添加新评论