一般情况-无法控制第一次映射
/proc/[PID]/pagemap
+
/dev/mem
我能想到的唯一办法
不进行任何复制
是通过手动打开和检查
/proc/[PID]/pagemap
获取要“别名”的页面对应的物理页面的页面帧号,然后打开并映射
/dev/mem
在相应的偏移处。虽然这在理论上可行,但它需要root权限,而且最有可能
不
在任何合理的Linux发行版上都是可能的,因为内核通常配置为
CONFIG_STRICT_DEVMEM=y
这对使用施加了严格的限制
/dev/mem
例如,在x86上,它不允许从
/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;
}
userfaultfd(2)
除了我上面描述的,AFAIK没有一种方法可以在不复制的情况下从用户空间中做你想做的事情。也就是说,没有一种方法可以简单地告诉内核“将第二个虚拟地址映射到现有地址的同一内存中”。但是,您可以通过注册用户空间处理程序来处理页面错误
userfaultfd(2)
系统调用和
ioctl_userfaultfd(2)
,我认为这是你最好的投篮。
整个机制类似于内核对真实内存页的处理,只是故障由用户定义的用户空间处理程序线程处理。这仍然是一个实际的副本,但对故障线程来说是原子性的,并为您提供了更多的控制。一般来说,它也可能表现得更好,因为复制是由您控制的,因此只能完成
如果/何时
需要(即在第一次读取故障时),而在正常情况下
mmap
+复制你
总是
无论页面以后是否会被访问,都要进行复制。
手册页中有一个很好的示例程序
用户错误fd(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;
}