由其他内存支持的 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;
}
我不确定这个问题是否有意义,但假设我有一个指向某些内存的指针:
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;
}