由其他内存支持的 mmap 内存?

mmap memory backed by other memory?

我不确定这个问题是否有意义,但假设我有一个指向某些内存的指针:

char *mem;
size_t len;

是否可以以某种方式将 mem 的内容映射到另一个地址作为只读映射?即我想获得一个指针 mem2 使得 mem2 != mem 和访问 mem2[i] 实际上读取 mem[i] (不进行复制)。

我的最终目标是获取不连续的内存块,并通过将它们彼此相邻映射使它们看起来是连续的。

我考虑的一种方法是使用 fmemopen,然后使用 mmap,但是没有与 fmemopen.

的结果关联的文件描述符

一般情况 - 无法控制第一个映射

/proc/[PID]/pagemap + /dev/mem

我能想到的 无需任何复制 的唯一方法是手动打开并检查相应偏移处的 /proc/[PID]/pagemap to get the Page Frame Number of the physical page corresponding to the page you want to "alias", and then opening and mapping /dev/mem。虽然这在理论上可行,但它需要 root 权限,并且很可能 not 在任何合理的 Linux 发行版上都是可能的,因为内核通常配置为 CONFIG_STRICT_DEVMEM=y这对 /dev/mem 的使用有严格的限制。例如在 x86 上它不允许从 /dev/mem 读取 RAM(只允许读取内存映射的 PCI 区域)。请注意,为了使其正常工作,您需要锁定要“别名”的页面以将其保存在 RAM 中。

无论如何,这是一个如何工作的例子如果你able/willing这样做(我在这里假设 x86 64 位):

#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>

/* Get the physical address of an existing virtual memory page and map it. */

int main(void) {
    FILE *fp;
    char *endp;
    unsigned long addr, info, physaddr, val;
    long off;
    int fd;
    void *mem;
    void *orig_mem;

    // Suppose that this is the existing page you want to "alias"
    orig_mem = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
    if (orig_mem == MAP_FAILED) {
        perror("mmap orig_mem failed");
        return 1;
    }

    // Write a dummy value just for testing
    *(unsigned long *)orig_mem = 0x1122334455667788UL;

    // Lock the page to prevent it from being swapped out
    if (mlock(orig_mem, 0x1000)) {
        perror("mlock orig_mem failed");
        return 1;
    }

    fp = fopen("/proc/self/pagemap", "rb");
    if (!fp) {
        perror("Failed to open \"/proc/self/pagemap\"");
        return 1;
    }

    addr = (unsigned long)orig_mem;
    off  = addr / 0x1000 * 8;

    if (fseek(fp, off, SEEK_SET)) {
        perror("fseek failed");
        return 1;
    }

    // Get its information from /proc/self/pagemap
    if (fread(&info, sizeof(info), 1, fp) != 1) {
        perror("fread failed");
        return 1;
    }

    physaddr = (info & ((1UL << 55) - 1)) << 12;

    printf("Value: %016lx\n", info);
    printf("Physical address: 0x%016lx\n", physaddr);

    // Ensure page is in RAM, should be true since it was mlock'd
    if (!(info & (1UL << 63))) {
        fputs("Page is not in RAM? Strange! Aborting.\n", stderr);
        return 1;
    }

    fd = open("/dev/mem", O_RDONLY);
    if (fd == -1) {
        perror("open(\"/dev/mem\") failed");
        return 1;
    }

    mem = mmap(NULL, 0x1000, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, fd, physaddr);
    if (mem == MAP_FAILED) {
        perror("Failed to mmap \"/dev/mem\"");
        return 1;
    }

    // Now `mem` is effecively referring to the same physical page that
    // `orig_mem` refers to.

    // Try reading 8 bytes (note: this will just return 0 if
    // CONFIG_STRICT_DEVMEM=y).
    val = *(unsigned long *)mem;

    printf("Read 8 bytes at physaddr 0x%016lx: %016lx\n", physaddr, val);

    return 0;
}

userfaultfd(2)

除了我上面描述的以外,AFAIK 没有办法在不复制的情况下从用户空间做你想做的事情。 IE。没有办法简单地告诉内核“将第二个虚拟地址映射到现有地址的同一内存”。但是,您可以通过 userfaultfd(2) syscall and ioctl_userfaultfd(2) 为页面错误注册一个用户空间处理程序,我认为这总体上是您最好的选择。

整个机制类似于内核对真实内存页面所做的,只是错误是由用户定义的用户空间处理程序线程处理的。这仍然是一个实际的副本,但对于故障线程来说是原子的,并为您提供更多控制权。它通常也可能表现得更好,因为复制由您控制,因此只能完成 if/when 需要(即在第一次读取错误时),而在这种情况下正常 mmap + 复制你 总是 进行复制,无论以后是否访问该页面。

userfaultfd(2) 的手册页中有一个很好的示例程序,我在上面链接了它,所以我不打算在这里复制粘贴它。它涉及一个或多个页面,应该让您了解整个 API.

更简单的情况 - 控制第一个映射

如果您 控制了您想要“别名”的第一个映射,那么您可以简单地创建一个共享映射。您正在寻找的是memfd_create(2)。您可以使用它来创建一个匿名文件,然后可以使用不同的权限mmap编辑多次。

这是一个简单的例子:

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>

int main(void) {
        int memfd;
        void *mem_ro, *mem_rw;

        // Create a memfd
        memfd = memfd_create("something", 0);
        if (memfd == -1) {
                perror("memfd_create failed");
                return 1;
        }

        // Give the file a size, otherwise reading/writing will fail
        if (ftruncate(memfd, 0x1000) == -1) {
                perror("ftruncate failed");
                return 1;
        }

        // Map the fd as read only and private
        mem_ro = mmap(NULL, 0x1000, PROT_READ, MAP_PRIVATE, memfd, 0);
        if (mem_ro == MAP_FAILED) {
                perror("mmap failed");
                return 1;
        }

        // Map the fd as read/write and shared (shared is needed if we want
        // write operations to be propagated to the other mappings)
        mem_rw = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, memfd, 0);
        if (mem_rw == MAP_FAILED) {
                perror("mmap failed");
                return 1;
        }

        printf("ro mapping @ %p\n", mem_ro);
        printf("rw mapping @ %p\n", mem_rw);

        // This write can now be read from both mem_ro and mem_rw
        *(char *)mem_rw = 123;

        // Test reading
        printf("read from ro mapping: %d\n", *(char *)mem_ro);
        printf("read from rw mapping: %d\n", *(char *)mem_rw);

        return 0;
}