【问题标题】:How to provide extend-on-write functionality for memory mapped files in Linux?如何为 Linux 中的内存映射文件提供写时扩展功能?
【发布时间】:2011-10-19 15:25:47
【问题描述】:

我正在将一些代码从 AIX 移植到 Linux。部分代码使用shmat() system call 创建新文件。在可写模式下与SHM_MAP 一起使用时,可以将文件扩展至超出其原始长度(在我的情况下为零):

当一个文件被映射到一个段上时,通过访问该段来引用该文件。内存分页系统会自动处理物理 I/O。超出文件末尾的引用会导致文件以页面大小的增量进行扩展。文件不能扩展到下一个段边界之外。

(AIX 中的“段”是 256 MB 的地址空间块,而“页”通常是 4 KB。)

希望在 Linux 上做的事情如下:

  • 保留一大块地址空间(它不必像 256 MB 一样大,这些不是那么大的文件)
  • 设置页面保护位,以便在第一次访问之前未触及的页面时生成段错误
  • 在页面错误时,清除“导致页面错误”位并为页面分配已提交的内存,从而允许导致页面错误的写入(或读取)继续进行
  • 关闭共享内存区域后,将修改后的页面写入文件

我知道我可以在 Windows 上使用VirtualProtect 函数、PAGE_GUARD 内存保护位和structured exception handler 执行此操作。在Linux上做同样的相应方法是什么?是否有更好的方法在 Linux 上实现这种写时扩展功能?

我已经考虑过了:

  • 使用 mmap() 和一些固定的大尺寸,但我不知道应用程序代码写入了多少文件
  • 分配了一个很大的匿名共享内存区域,但我还是不知道该区域已经写入了多少
  • mmap() 本身似乎没有提供任何工具来扩展支持文件的长度

当然,我希望只对应用程序代码进行少量更改。

【问题讨论】:

    标签: linux posix aix


    【解决方案1】:

    非常类似于我曾经做过的作业。基本上我有一个“页面”列表和一个“框架”列表,以及相关信息。使用SIGSEGV 我会发现错误并根据需要更改内存保护位。我将包含您可能会觉得有用的部分。

    创建映射。最初它没有权限。

    int w_create_mapping(size_t size, void **addr)
    {
    
        *addr = mmap(NULL,
                size * w_get_page_size(),
                PROT_NONE,
                MAP_ANONYMOUS | MAP_PRIVATE,
                -1,
                0
        );
    
        if (*addr == MAP_FAILED) {
            perror("mmap");
            return FALSE;
        }
    
        return TRUE;
    }
    

    安装信号处理程序

    int w_set_exception_handler(w_exception_handler_t handler)
    {
        static struct sigaction sa;
        sa.sa_sigaction = handler;
        sigemptyset(&sa.sa_mask);
        sigaddset(&sa.sa_mask, SIGSEGV);
        sa.sa_flags = SA_SIGINFO;
    
        if (sigaction(SIGSEGV, &sa, &previous_action) < 0)
            return FALSE;
    
        return TRUE;
    }
    

    异常处理程序

    static void fault_handler(int signum, siginfo_t *info, void *context)
    {
        void *address;      /* the address that faulted */
    
        /* Memory location which caused fault */
        address = info->si_addr;
    
        if (FALSE == page_fault(address)) {
            _exit(1);
        }
    }
    

    增加保护

    int w_protect_mapping(void *addr, size_t num_pages, w_prot_t protection)
    {
        int prot;
    
        switch (protection) {
        case PROTECTION_NONE:
            prot = PROT_NONE;
            break;
        case PROTECTION_READ:
            prot = PROT_READ;
            break;
        case PROTECTION_WRITE:
            prot = PROT_READ | PROT_WRITE;
            break;
        }
    
        if (mprotect(addr, num_pages * w_get_page_size(), prot) < 0)
            return FALSE;
    
        return TRUE;
    }
    

    我无法公开所有内容,因为团队可能会再次使用相同的作业。

    【讨论】:

    • 太好了,谢谢!这在结构上与我之前在 Windows 上所做的非常相似。
    【解决方案2】:

    根据需要分配一个大缓冲区,然后使用 mprotect()* 系统调用将缓冲区的尾部设为只读,并为 SIGSEGV 注册一个信号处理程序,以记录之前写入的位置并使用 mprotect()再次启用写入。

    【讨论】:

      【解决方案3】:

      我自己也考虑过类似的事情,也没有找到任何方法让mmap() 扩展支持文件。

      目前,我计划尝试两种选择:

      • 手动管理文件大小,自己扩展,然后mremap()'ing
      • 创建一个稀疏文件,并希望 VM 在刷新脏页时分配所需的扇区。

      老实说,我认为稀疏文件行不通,但值得一试。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-28
        • 2016-02-23
        • 1970-01-01
        • 1970-01-01
        • 2021-11-30
        相关资源
        最近更新 更多