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

时间:2018-06-01 11:37:00

标签: c++ c posix ipc shared-memory

我正在尝试使用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;
}

我没有取消链接共享内存以便进行hexdump,所以这需要在重新启动程序之前手动删除它。

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

提前致谢。

致以最诚挚的问候,

伊万。

1 个答案:

答案 0 :(得分:2)

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

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

  

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

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

重点强调ftruncate()电话的一个方面(强调添加):

  

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

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

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

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

注释

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