如何在Linux中为内存映射文件提供扩展写入功能?

时间:2011-08-04 03:51:24

标签: linux posix aix

我正在努力将一些代码从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()本身似乎没有提供任何延长支持文件长度的工具

当然,我只想对应用程序代码进行最小的更改即可。

3 个答案:

答案 0 :(得分:5)

非常类似于我曾经做过的家庭作业。基本上我有一个“页面”列表和一个“框架”列表,以及相关信息。使用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;
}

由于团队可能会再次使用相同的作业,所以我无法公开全部可用。

答案 1 :(得分:3)

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

答案 2 :(得分:0)

我自己也考虑过类似的事情,并且没有找到mmap()扩展后备文件的方法。

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

  • 手动管理文件大小,自己进行扩展并随后mremap()进行
  • 创建一个稀疏文件,并希望VM在刷新脏页时分配所需的扇区。
老实说,我不认为稀疏文件会起作用,但值得一试。