为啥我可以使用 POSIX 创建比 /dev/shm 上安装的大小更大的共享内存?

Posted

技术标签:

【中文标题】为啥我可以使用 POSIX 创建比 /dev/shm 上安装的大小更大的共享内存?【英文标题】:Why I can create a shared memory bigger than the size mounted on /dev/shm using POSIX?为什么我可以使用 POSIX 创建比 /dev/shm 上安装的大小更大的共享内存? 【发布时间】:2018-06-01 11:36:59 【问题描述】:

我正在尝试在 Ubuntu 16.04 中使用共享内存 IPC 来处理错误。 首先,我使用 df -h 检查了 /dev/shm 中的可用内存,有 500M 可用内存,所以我快速编写了一些代码,以检查如果我尝试创建一个大于安装大小的共享内存会发生什么。代码如下(经过多次修改,我知道不是很整洁):

#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <cstring>
#include <stdint.h>
#include <stddef.h>
#include <cerrno>

//static const size_t size = 4000000L;
static const size_t size = 701000000L;
//static const size_t size = 999999999L;

extern int errno;

static int open(const char* name, int oflag, mode_t mode)

   int shm = -1;

   /* Create/Open a shared memory zone */
    shm = shm_open(name, oflag, mode);
    if(shm == -1)
    /* Print error */
        std::cout << "!!!Error getting file descriptor while opening!!!" << std::endl;
        std::cout << "ERROR:"<< strerror(errno) << std::endl;
    
   return shm;


static void write_shm(void *addr, size_t size)

    size_t i = 0;
    uint32_t *shm_index = (uint32_t *)addr;

    /* 4 bytes to be written in memory */
    const char *test = "DEAD";

    /* Maximum allowed memory address*/
    ptrdiff_t max = (size+(ptrdiff_t)addr);

    for (i = 0; (ptrdiff_t)(shm_index + i) < max; i++)
    
        std:memcpy(&shm_index[i], (uint32_t*)test, sizeof(uint32_t));
    

static int adjust (int fd, size_t size)

     std::cout<<__func__<<": The size of the shared memory is: "<<size<< std::endl;
     int result = ftruncate(fd, size);
     std::cout<<__func__<< "ftruncate return: " <<result<< std::endl;
     errno = 0;
     std::cout << "errno: "<< std::strerror(errno) <<std::endl;
     if (result)
     /* Print error */;
        std::cout << "FUNCION!!!Error in ftruncate!!!" << std::endl;
     
     return result;


int main()

    const char *name = "vardb";
    int fd = -1;
    int oflag = O_CREAT | O_EXCL | O_RDWR;
    mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; // POSIX 1003.1 (Realtime Extensions)
    size_t sizeToUse = (size/sizeof(uint32_t)* sizeof(uint32_t));

    /* Let's try to get a file descriptor related to the shared memory*/
    fd = open(name, oflag, mode);
    if (fd == -1)
        return fd;

    /* Adjust the size of the shared memory to the expected one */
    int result = adjust(fd, sizeToUse);
    if (result)
        return -1;

    int prot = PROT_READ | PROT_WRITE;
    int flags = MAP_SHARED;

    /* Map the memory */
    void *addr = mmap(NULL, size, prot, flags, fd, 0);
    std::cout<<__func__<< "mmap return: " <<*(int *)addr<< std::endl;
    std::cout<<__func__<< "mmap mapped to this address: " <<addr<< std::endl;
    errno = 0;
    std::cout << "mmap errno: "<< std::strerror(errno) <<std::endl;

    struct stat fileStat;
    if(fstat(fd, &fileStat) < 0)    
        return 1;

    std::cout<<__func__<< "File Size: " <<fileStat.st_size<<" bytes"<<std::endl;

    /* Write all the shared memory previously reserved for the test */
    write_shm(addr, sizeToUse);


    /* Release the memory */
    munmap(addr, size);

    return 0;

我不会为了十六进制转储它而取消链接共享内存,因此这需要在重新启动程序之前手动删除它。

好吧,我要发布的是,当我创建一个比 /dev/shm 挂载大小更大的共享内存时,我没有收到任何错误......显然,当我尝试写出时,我得到了一个总线错误内存的可用范围,但我需要控制共享内存的创建...我无法理解系统如何让我创建这样的东西而不报告任何错误。

提前致谢。

最好的问候,

伊万。

【问题讨论】:

也许您定义的是要分配的最大值,这并不意味着将立即分配所有内存。当然,如果到时候分配的内存比可用的多,就会发生错误 选择一种语言(显然是 C++)并呈现一个实际的minimal reproducible example。现在问题中的代码大约是 C++(而不是 C),但似乎包含语法错误。无论如何,它似乎比我认为证明问题所需要的要大得多。 您在write_shm() 中似乎有一个错误。通过将&lt;=max 更改为&lt;max 进行更正。 (根据您的计算,max 表示该区域之外的第一个地址。) 大家好。我正在尝试使用 C++,但是由于共享内存库授予我 C 接口,我使用这些函数调用。关于 write_shm 错误:请让我们坚持下去。我仍在试图弄清楚如果没有报告错误,我如何控制 /dev/shm 中可以分配的内存量 【参考方案1】:

简短(且不令人满意的答案):如果/dev/shm 上的空间不足,您不能强制shm_open 失败。 (您可以通过使用setrlimit 显式设置进程文件大小限制来修改RLIMIT_FSIZE 来强制它失败,但这是一个不适用于单个文件系统的全局设置,因此几乎可以肯定它不是您想要的。)

当 Posix 标准化共享内存时,考虑了各种实现选项,并尝试为实现提供相当大的灵活性,只要它不使接口复杂化。特别是,许多 Unix 实现已经具有将文件对象直接映射到进程内存的机制,对于基于内存的文件系统,这种组合非常适合实现共享内存:

简单的共享内存显然是更通用的文件映射能力的一个特例。此外,文件映射接口的协议和实现也比较广泛。在这些系统中,可以使用相同的映射接口映射许多不同类型的对象(例如,文件、内存、设备等)。这种方法既可以最小化接口扩散,又可以最大化使用映射接口的程序的通用性。 (来自Posix rationale: Mapped file functions)

特别是,“……上述要求并不排除:[t]可共享内存对象使用实际文件系统上的实际文件来实现。” (Posix rationale: Shared memory objects)。尽管我不相信 Linux 库会这样做,但 Posix 甚至允许将 shm_open() 实现为包装普通 open() 调用的宏;对于简单地将共享内存映射到文件系统的实现(如 Linux),不需要对ftruncate() 系统接口进行特殊处理。

强调ftruncate() 调用的一个方面很重要(已添加重点):

如果文件大小增加,扩展区域将显示为它是零填充的。

许多文件系统允许“稀疏文件”。在稀疏文件中,完全用零填充的文件块根本不会映射到物理存储;如果应用程序读取其中一个块,它会收到一页零。如果一个块被修改并提交到磁盘,那么——并且只有这样——文件系统才会为该块分配存储空间。 [注1]

零填充块的延迟分配意味着ftruncate()在扩展文件的情况下,只需要更新文件的元数据,使其返回速度非常快。除非所需大小超过进程的文件大小限制(或文件系统不使用足够大的整数类型作为文件大小的文件系统限制),否则ftruncate() 不会产生错误。当无法分配物理存储(或专用内存缓冲区,在内存映射文件的情况下)时,将发生错误。

这完全符合 Linux 对内存分配的乐观态度:mmap 总是成功(只要地址空间可用),并且只有在实际使用内存时才会指出错误。 (但这种实现共享内存的特殊方法并不限于那些使用乐观内存分配的方法。)


注意事项

    我曾经通过在软盘上存储一个 2GB 的文件来证明这一点,但我想今天的许多读者甚至都不知道软盘是什么,更不用说它的实际容量了。

【讨论】:

不错的答案,但正如你所说,不令人满意。所以我可以祈祷不要要求比 /de/shm 上安装的数量更多的共享内存。我以为会考虑到这一点...非常感谢您的详细回答。 @Fulgor3:我理解你的desazón,但你的不是唯一的用例。首次创建共享内存段后,扩展共享内存段并非易事(所有消费者都需要重做 ftruncate 和 mmap,并且可能存在很多竞争条件),因此如果他们持有可变长度数据,通常只制作它们“绝对足够大”(即太大)并依靠操作系统让您通过不实际保留未使用的空间(直到您尝试使用它)来摆脱它。 @Fulgor3,从 Linux 文件映射的角度来看,/dev/shm 并没有什么特别之处。 mmap()-ing 来自 FS 中允许写入的任何位置的文件可用于在进程之间共享内存,甚至超出物理内存的容量,只要您不触及比 FS 中的可用空间更多的内存。不同之处在于tmpfs 由有限的交换空间支持,而不是由磁盘FS 数据块支持。如有疑问,请先致电statvfs(2) 确定/dev/shm 上的可用空间。【参考方案2】:

POSIX 标准上的共享内存 API 是不够的。在一个进程中,可以分配更多内存,然后操作系统可以交付。最后很有可能会出现总线错误。这会导致使用共享内存的应用程序不稳定,从安全角度来看是一个重大漏洞。

【讨论】:

以上是关于为啥我可以使用 POSIX 创建比 /dev/shm 上安装的大小更大的共享内存?的主要内容,如果未能解决你的问题,请参考以下文章

创建了我自己的二进制搜索版本,不明白为啥它比常规方法更快?

对象比相应的 .RData 文件大得多。为啥?可以手动做吗?

为啥在 Windows 上创建新进程比在 Linux 上更昂贵?

为啥创建 HashMap 比创建 Object[] 更快?

为啥这比函数有效?

Windows 用户的 Posix、unix、paths、vim 和其他噩梦