首页 > 解决方案 > 如何通过替换为空页面的映射来取消映射 mmap 文件

问题描述

/dev/nullLinux用户空间有没有办法用空页面(映射自,或者可能是单个空页面,在页面顶部重复映射)替换映射文件的页面(或某个逻辑地址范围内的mmap页面)从文件映射)?

对于上下文,我想找到此 JDK 错误的修复程序:

https://bugs.openjdk.java.net/browse/JDK-4724038

总结一下这个错误:在 JVM 可以垃圾收集包装 mmap 文件的文件之前,目前不可能在 Java 中取消映射文件MappedByteBuffer,因为强制取消映射文件可能会由于竞争条件而导致安全问题(例如,本机代码可能仍在尝试访问文件映射到的相同地址范围,并且操作系统可能已经将新文件映射到相同的逻辑地址范围)。

我正在寻找替换逻辑地址范围内的映射页面,然后取消映射文件。有没有办法做到这一点?

(如果您也知道在其他操作系统中执行此操作的方法,尤其是 Windows 和 Mac OS X,则可以加分。)

请注意,这不一定是原子操作。主要目标是将内存的取消映射(或将映射的文件内容替换为读取时为零的页面)与文件的关闭分开,因为这将解决 Linux 上的一系列问题(具有每个进程的文件描述符数量的下限)和 Windows(在映射文件时不能删除文件的事实)。

更新:另请参阅:在 Windows 中使用 SHARE 属性对文件进行内存映射(因此文件不会被锁定以防删除)

标签: linuxmemory-managementmmapvirtual-memory

解决方案


错误在 JDK 中保留这么久的原因基本上是因为取消映射内存和映射虚拟内存之间的竞争条件,一些其他内存最终可能会映射到那里(可能是本机代码)。我已经研究过操作系统 API,并且在系统调用级别不存在原子的内存操作,这些操作取消映射文件并将其他内容映射到同一地址。但是,有些解决方案会阻止整个过程,同时从其下方换出映射。

unmap 在没有保护的情况下在 finalize 中正常工作,因为 GC 首先证明了该对象是不可访问的,因此没有竞争。

高度 Linux 特定的解决方案:

1) vfork()

2)向父母发送停止信号

3)取消映射内存

4)在其位置映射零

5) 向父母发送一个 CONT 信号

6) _exit (解除对父线程的阻塞)

在 Linux 中,内存映射更改会传播到父代。

代码实际上看起来更像这样(vfork()是疯子):

int unmap(void *addr, int length)
{
    int wstatus;
    pid_t child;
    pid_t parent;
    int thread_cancel_state;
    signal_set signal_set;
    signal_set old_signal_set;

    parent = getpid();
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &thread_cancel_state);
    sigfillset(&signal_set);
    pthread_sigmask(SIG_SETMASK, &signal_set, &old_signal_set);
    if (0 == (child = vfork()) {
        int err = 0;
        kill(parent, SIGSTOP);
        if (-1 == munmap(addr, length))
            err = 1;
        else if ((void*)-1 == mmap(addr, length, PROT_NONE, MAP_ANONYMOUS, -1, 0);
            err = 1;
        kill(parent, SIGCONT);
        _exit(err);
    }
    if (child > 0)
        waitpid(child, &wstatus, 0);
    else
        wstatus = 255;

    pthread_sigmask(SIG_SETMASK, &old_signal_set, &signal_set);
    pthread_setcancelstate(thread_cancel_state, &thread_cancel_state);
    return (wstatus & 255) != 0;
}

在 Windows 下,您可以停止所有线程,但这个使用SuspendThread感觉是为此量身定制的。但是,枚举线程会很困难,因为您正在与CreateThread. 您必须运行枚举线程ntdll.dllAPI(相信我,您不能在这里使用 ToolHelp)以及SuspendThread除您自己之外的每一个,小心地仅使用 VirtualAlloc 分配内存,因为SuspendThread刚刚破坏了所有堆分配例程,您将不得不做所有在一个循环中,直到你找不到更多。

这里有一些文章,我觉得我不能准确地提炼出来:

http://forums.codeguru.com/showthread.php?200588-How-to-enumerate-threads-in-currently-running-process

我没有找到任何适用于 Mac OSX 的解决方案。


推荐阅读