【发布时间】:2013-06-02 15:54:43
【问题描述】:
我有一个通过mmap 和MAP_ANONYMOUS 获得的内存段。
我怎样才能分配第二个相同大小的内存段来引用第一个内存段并在 Linux 中进行复制写入(目前正在运行 Linux 2.6.36)?
我想要和fork 完全一样的效果,只是不创建新进程。我希望新映射保持在同一进程中。
整个过程必须在原始页面和复制页面上都是可重复的(好像父子页面会继续fork)。
我不想分配整个段的直接副本的原因是因为它们有数 GB 大,而且我不想使用可以在写时复制共享的内存。
我尝试过的:
mmap 共享段,匿名。
在复制 mprotect 时将其设置为只读,并使用 remap_file_pages 创建第二个映射也是只读的。
然后使用libsigsegv拦截写入尝试,手动复制页面然后mprotect两者都进行读写。
成功了,但很脏。我实际上是在实现自己的虚拟机。
遗憾的是,当前的 Linux 不支持 mmaping /proc/self/mem,否则 MAP_PRIVATE 映射可以解决问题。
写时复制机制是 Linux VM 的一部分,必须有一种方法可以在不创建新进程的情况下使用它们。
请注意: 我在 Mach VM 中找到了合适的机制。
以下代码在我的 OS X 10.7.5 上编译并具有预期的行为:
Darwin 11.4.2 Darwin Kernel Version 11.4.2: Thu Aug 23 16:25:48 PDT 2012; root:xnu-1699.32.7~1/RELEASE_X86_64 x86_64 i386
gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#ifdef __MACH__
#include <mach/mach.h>
#endif
int main() {
mach_port_t this_task = mach_task_self();
struct {
size_t rss;
size_t vms;
void * a1;
void * a2;
char p1;
char p2;
} results[3];
size_t length = sysconf(_SC_PAGE_SIZE);
vm_address_t first_address;
kern_return_t result = vm_allocate(this_task, &first_address, length, VM_FLAGS_ANYWHERE);
if ( result != ERR_SUCCESS ) {
fprintf(stderr, "Error allocating initial 0x%zu memory.\n", length);
return -1;
}
char * first_address_p = first_address;
char * mirror_address_p;
*first_address_p = 'a';
struct task_basic_info t_info;
mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
task_info(this_task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
task_info(this_task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
results[0].rss = t_info.resident_size;
results[0].vms = t_info.virtual_size;
results[0].a1 = first_address_p;
results[0].p1 = *first_address_p;
vm_address_t mirrorAddress;
vm_prot_t cur_prot, max_prot;
result = vm_remap(this_task,
&mirrorAddress, // mirror target
length, // size of mirror
0, // auto alignment
1, // remap anywhere
this_task, // same task
first_address, // mirror source
1, // Copy
&cur_prot, // unused protection struct
&max_prot, // unused protection struct
VM_INHERIT_COPY);
if ( result != ERR_SUCCESS ) {
perror("vm_remap");
fprintf(stderr, "Error remapping pages.\n");
return -1;
}
mirror_address_p = mirrorAddress;
task_info(this_task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
results[1].rss = t_info.resident_size;
results[1].vms = t_info.virtual_size;
results[1].a1 = first_address_p;
results[1].p1 = *first_address_p;
results[1].a2 = mirror_address_p;
results[1].p2 = *mirror_address_p;
*mirror_address_p = 'b';
task_info(this_task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
results[2].rss = t_info.resident_size;
results[2].vms = t_info.virtual_size;
results[2].a1 = first_address_p;
results[2].p1 = *first_address_p;
results[2].a2 = mirror_address_p;
results[2].p2 = *mirror_address_p;
printf("Allocated one page of memory and wrote to it.\n");
printf("*%p = '%c'\nRSS: %zu\tVMS: %zu\n",results[0].a1, results[0].p1, results[0].rss, results[0].vms);
printf("Cloned that page copy-on-write.\n");
printf("*%p = '%c'\n*%p = '%c'\nRSS: %zu\tVMS: %zu\n",results[1].a1, results[1].p1,results[1].a2, results[1].p2, results[1].rss, results[1].vms);
printf("Wrote to the new cloned page.\n");
printf("*%p = '%c'\n*%p = '%c'\nRSS: %zu\tVMS: %zu\n",results[2].a1, results[2].p1,results[2].a2, results[2].p2, results[2].rss, results[2].vms);
return 0;
}
我想要在 Linux 中同样的效果。
【问题讨论】:
-
您可以使用 btrfs 并将其文件复制与写时复制功能一起使用...但是,您将在 FS 中获得不必要的数据副本。应该可以工作,但不完全是高性能。
-
修补内核是不可能的吗?
-
@thejh 不幸的是:(。该代码旨在可部署在我没有 root 权限的机器上。出于同样的原因和性能,部署另一个文件系统也不是一种选择。
/dev/shm(tmpfs) 是我愿意使用文件支持的内存。 -
@ChrisStratton 新的复制映射可以放在我的虚拟地址空间的任何位置并返回一个指针。原点映射应该留在原处。请检查马赫代码中的
vm_remap调用。这正是我想要的语义——只是在 Linux 中。
标签: c linux virtual-memory