如何在Linux上将两个虚拟地址映射到同一物理内存?

8
我面临一个相当棘手的问题。我试图让两个虚拟内存区域指向同一物理内存。关键在于在不同的内存区域上有不同的页面保护参数。
在这个论坛上,用户似乎有一个解决方案,但它似乎有点hacky,并且很明显可以通过性能更好的方式来完成: http://www.linuxforums.org/forum/programming-scripting/19491-map-two-virtual-memory-addres-same-physical-page.html 由于我也遇到了同样的问题,我想在这里尝试一下,看看是否有更好的想法。不要害怕提及引擎盖后面的肮脏细节,这就是这个问题的关键。
提前感谢您。
5个回答

8

自从Linux内核3.17发布(2014年10月)以来,您可以使用memfd_create系统调用创建一个由匿名内存支持的文件描述符。然后像上面的答案中提到的那样多次映射同一区域。

请注意,memfd_create系统调用的glibc包装器已经添加到glibc 2.27(2018年2月发布)。glibc手册还介绍了如何使用返回的描述符创建多个映射到相同底层内存的方法。


太好了!听起来就是正确的解决方案。我不得不承认我曾经失去了希望。谢谢! - deadalnix

3
我正在尝试让2个虚拟内存区域指向同一块物理内存。mmap可以在同一文件中两次映射相同的区域,或者使用System V共享内存(不需要在内存中映射文件)。

这甚至更糟。如果指定了MAP_SHARED,则需要IO,如果设置了MAP_PRIVATE,则写时复制将使技巧无效。该解决方案与线程中提出的建议解决方案具有相同的缺点:以不受控制的方式在进程之间共享内存。 - deadalnix
@deadalnix:在System V SHM上添加了一条注释。据我所知,在用户空间中没有其他方法可以做到这一点。 - Fred Foo
1
是的。但实际上没有其他方法。请注意,mmap 的实现可以非常智能,并且并不总是将数据写入磁盘。如果您在 /dev/shmmmap 一个文件,则根本不会进行磁盘 I/O。 - Fred Foo
好的,对于所有这些技巧都加一分。这总是有点“hacky”的方式。没有真正的解决方案,但还是谢谢。 - deadalnix
mmap(..., MAP_SHARED, ...)是否保证指向同一物理内存,还是只能通过页错误实现对所有映射器的更改可见 - Dima Tisnek
显示剩余3条评论

3
我想,如果你不喜欢Sys V共享内存,你可以使用POSIX共享内存对象。它们并不是很流行,但至少在Linux和BSD上可用。
一旦你使用shm_open获得一个文件描述符,你就可以立即调用shm_unlink。那么,没有其他进程可以附加到相同的共享内存,而且你可以多次mmap它。尽管如此,仍然存在一个小的竞争期。

哈哈,我并不讨厌SysV。我讨厌的是为了实现与内存共享无关的功能而启用内存共享。 - deadalnix

2

根据@PerJohansson的建议,我编写并测试了以下代码,在Linux上运行良好。通过使用带有MAP_SHARED | MAP_FIXED标志的mmap函数,我们可以将由POSIX shm对象分配的相同物理页面多次映射到非常大的虚拟内存中。

#include "stdio.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>        /* For mode constants */
#include <fcntl.h>           /* For O_* constants */


void * alloc_1page_mem(int size) {
    int fd;
    char * ptr_base;
    char * rptr;
    /* Create shared memory object and set its size */
    fd = shm_open("/myregion", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("error in shm_open");
        return NULL;
    }

    if (ftruncate(fd, 4096) == -1) {
        perror("error in ftruncate");
        return NULL;
    }

    // following trick reserves big enough holes in VM space
    ptr_base = rptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    munmap(rptr, size);

    for(int i=0; i<size; i+=4096) {
        rptr = mmap(rptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED|MAP_FIXED, fd, 0);
        if (rptr == MAP_FAILED) {
            perror("error in mmap");
            return NULL;
        }
        rptr += 4096;
    }
    close(fd);
    shm_unlink("/myregion");
    return ptr_base;
}

void check(int * p, int total_cnt){
    for (int i=0;i<4096/sizeof(int);i++) {
        p[i] = i;
    }

    int fail_cnt = 0;
    for (int k=0; k<total_cnt; k+= 4096/sizeof(int)) {
        for (int i=0;i<4096/sizeof(int);i++) {
            if (p[k+i] != i)
                fail_cnt ++;
        }
    }
    printf("fail_cnt=%d\n", fail_cnt);
}

int main(int argc, const char * argv[]) {
    const char * cmd = argv[1];
    int sum;
    int total_cnt = 32*1024*1024;
    int * p = NULL;
    if (*cmd++ == '1')
        p = alloc_1page_mem(total_cnt*sizeof(int));
    else
        p = malloc(total_cnt*sizeof(int));

    sum = 0;
    while(*cmd) {
        switch(*cmd++) {
            case 'c':
                check(p, total_cnt);
                break;
            case 'w':
                // save only 4bytes per cache line
                for (int k=0;k<total_cnt;k+=64/sizeof(int)){
                    p[k] = sum;
                }
                break;
            case 'r':
                // read only 4bytes per cache line
                for (int k=0;k<total_cnt;k+=64/sizeof(int)) {
                    sum += p[k];
                }
                break;
            case 'p':
                // prevent sum from being optimized
                printf("sum=%d\n", sum);
        }
    }

    return 0;
}


你可以在采用这种方法分配的内存中观察到非常低的缓存未命中率:
$ sudo perf stat -e mem_load_retired.l3_miss -- ./a.out 0wrrrrr
  # this produces L3 miss linearly increase with number of 'r' charaters
$ sudo perf stat -e mem_load_retired.l3_miss -- ./a.out 1wrrrrr
  # this produces almost constant L3 miss.

1
如果您是root用户,可以使用mmap("/dev/mem", ...),但在较新的内核中存在一些注意事项,请参见访问映射的/dev/mem?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接