如何通过替换为空页面的映射来取消映射 mmap 文件

How to unmap an mmap'd file by replacing with a mapping to empty pages

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

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

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

总结错误:目前无法取消映射 Java 中的文件,直到 JVM 可以垃圾收集包装 mmap 文件的 MappedByteBuffer,因为强行取消映射文件可能由于竞争条件而引起安全问题(例如,本机代码可能仍在尝试访问文件映射到的相同地址范围,并且 OS 可能已经将新文件映射到相同的逻辑地址范围).

我想替换逻辑地址范围内的映射页面,然后取消映射文件。有什么办法可以做到这一点?

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

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

更新:另见:Memory-mapping a file in Windows with SHARE attribute (so file is not locked against deletion)

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

unmap 在没有守卫的情况下在 finalize 中正常工作,因为 GC 已经证明 object 首先是不可访问的,所以没有竞争。

高度Linux具体解决方案:

1) vfork()

2) 发送 parent 停止信号

3) 取消映射内存

4) 在其位置映射零

5) 发送 parent 一个 CONT 信号

6) _exit(解锁 parent 线程)

在 Linux 中,内存映射更改传播到 parent。

代码实际上看起来更像这样(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.dll API(你不能在这里使用 ToolHelp 相信我)和 SuspendThread 除了你自己的,小心地只使用 VirtualAlloc 分配内存因为 SuspendThread 刚刚打破了所有的堆分配例程,你将不得不在一个循环中完成所有这些,直到你找不到更多。

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

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

我没有找到 Mac OSX.

的任何解决方案

在 Linux 上,您可以使用 mmapMAP_FIXED 将映射替换为您想要的任何映射。如果替换整个映射,将删除对该文件的引用。