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

发布于 2024-12-03 12:25:18 字数 480 浏览 1 评论 0原文

我面临着一个非常棘手的问题。我试图让 2 个虚拟内存区域指向相同的物理内存。重点是不同的内存区域有不同的页面保护参数。

在这个论坛上,用户似乎有一个解决方案,但看起来有点老套,而且很明显可以在性能方面做更好的事情: http: //www.linuxforums.org/forum/programming-scripting/19491-map-two-virtual-memory-addres-same-physical-page.html

作为我也面临着同样的问题,我想在这里尝试一下,看看是否有人有更好的想法。不要害怕提及引擎盖背后的肮脏细节,这就是这个问题的目的。

提前致谢。

I'm facing a quite tricky problem. I'm trying to get 2 virtual memory areas pointing to the same physical memory. The point is to have different page protection parameters on different memory areas.

On this forum, the user seems to have a solution, but it seems kinda hacky and it's pretty clear that something better can be done performance-wise :
http://www.linuxforums.org/forum/programming-scripting/19491-map-two-virtual-memory-addres-same-physical-page.html

As I'm facing the same problem, I want to give a shot here to know if somebody has a better idea. Don't be afraid to mention the dirty details behind the hood, this is what this question is about.

Thank by advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

感情废物 2024-12-10 12:25:18

从 Linux 内核 3.17(2014 年 10 月发布)开始,您可以使用 memfd_create 系统调用来创建由匿名内存支持的文件描述符。然后对同一区域进行多次 mmap,如上面答案中所述。

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

Since Linux kernel 3.17 (released in October 2014) you can use memfd_create system call to create a file descriptor backed by anonymous memory. Then mmap the same region several times, as mentioned in the above answers.

Note that glibc wrapper for the memfd_create system call was added in glibc 2.27 (released in February 2018). The glibc manual also describes how the descriptor returned can be used to create multiple mappings to the same underlying memory.

耀眼的星火 2024-12-10 12:25:18

我正在尝试让 2 个虚拟内存区域指向同一物理内存。

mmap同一文件中的同一区域两次,或使用系统V 共享内存(不需要在内存中映射文件)。

I'm trying to get 2 virtual memory area pointing on the same physical memory.

mmap the same region in the same file, twice, or use System V shared memory (which does not require mapping a file in memory).

情话墙 2024-12-10 12:25:18

我想如果你不喜欢 Sys V 共享内存,你可以使用 POSIX 共享内存对象。它们不是很流行,但至少可以在 Linux 和 BSD 上使用。

一旦您通过 shm_open 获得 fd,您就可以立即调用 shm_unlink。那么其他进程就不能附加到同一共享内存,并且您可以对其进行多次mmap。不过仍然有一小段比赛时间。

I suppose if you dislike Sys V shared memrory you could use POSIX shared memory objects. They're not very popular but available on Linux and BSDs at least.

Once you get an fd with shm_open you could immediately call shm_unlink. Then no other process can attach to the same shared memory, and you can mmap it multiple times. Still a small race period available though.

云胡 2024-12-10 12:25:18

根据@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.

As suggested by @PerJohansson, I wrote & tested following code, it works well on linux, using mmap with MAP_SHARED|MAP_FIXED flag, we can map the same physical page allocated by POSIX shm object multiple times and continuously into very large virtual memory.

#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;
}

You can observe very low cache miss rate on memory allocated in such method:

$ 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.
﹉夏雨初晴づ 2024-12-10 12:25:18

如果您是 root,则可以 mmap("/dev/mem", ...) 但较新的内核中有一些警告,请参阅 访问 mmaped /dev/mem?

If you are root, you can mmap("/dev/mem", ...) but there are caveats in the newer kernels, see accessing mmaped /dev/mem?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文