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

Ism*_*awi 2 c linux mmap virtual-memory

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

char *mem;
size_t len;
Run Code Online (Sandbox Code Playgroud)

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

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

我考虑的一种方法是使用fmemopenand then mmap,但是没有与 的结果关联的文件描述符fmemopen

Mar*_*lli 7

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

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

我能想到的在不进行任何复制的情况下完成这项工作的唯一方法是手动打开并检查/proc/[PID]/pagemap以获取与您想要“别名”的页面相对应的物理页面的页框编号,然后打开并映射/dev/mem到相应的偏移量。虽然这在理论上可行,但它需要 root 权限,并且在任何合理的 Linux 发行版上很可能都是不可能的,因为内核通常配置为CONFIG_STRICT_DEVMEM=y,这限制了/dev/mem. 例如,在 x86 上,它不允许读取系统 RAM /dev/mem ,而只允许读取内存映射的 PCI 区域。此外,请注意,为了使其正常工作,您想要“别名”的页面需要锁定以将其保留在 RAM 中。

无论如何,如果您能够/愿意这样做,这里是一个如何工作的示例(我在这里假设 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;
}
Run Code Online (Sandbox Code Playgroud)

userfaultfd(2)

除了我上面描述的之外,据我所知,没有一种方法可以在不复制的情况下从用户空间执行您想要的操作。IE 没有办法简单地告诉内核“将第二个虚拟地址映射到现有虚拟地址的同一内存”。userfaultfd(2)但是,您可以通过系统调用和注册用于页面错误的用户空间处理程序ioctl_userfaultfd(2),我认为这总体上是您的最佳选择。

整个机制类似于内核对真实内存页所做的操作,只是错误由用户定义的用户空间处理程序线程处理。这基本上仍然是一个实际的副本,但对于错误线程来说是原子的,并为您提供了更多控制权。一般来说,它也可能表现得更好,因为复制是由您控制的,因此只有在需要时才可以完成(即在第一次读取故障时),而在正常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;
}
Run Code Online (Sandbox Code Playgroud)