创建进程快照的一种好方法是使用fork()
创建一个子进程。子进程的内存将是父进程的副本。
操作系统并没有急切地复制所有内存,而是只是将页面标记为写时复制:如果其中一个进程写入了该事件,则将克隆这些页面。这样既节省时间又节省空间。
如果子进程退出,则应禁用写时复制行为。但是,我遇到了整个阵列的页面错误-有什么方法可以优化这些页面错误吗?例如类似于MAP_POPULATE
如何避免首次访问映射区域的页面时出现页面错误。
下面有一个简单的基准,可以演示我要询问的行为。我通过perf stat -e minor-faults,major-faults ./a.out
检查页面错误。
如果未创建子进程(WITH_CHILD
设置为false
),则页面错误很少(大约125
和常量)。但是,仅通过创建并获得子进程,我就会在所有内容中遇到页面错误(大约131260
,与数组大小成比例)。 由于页面是通过单个进程映射的,所以我不会期望发生任何页面错误!为什么呢?
这是Kernel copying CoW pages after child process exit的后续行动。
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <array>
#include <cassert>
#include <cstring>
#include <iostream>
#define ARRAY_SIZE 536870912 // 512MB
#define WITH_CHILD true
using inttype = uint64_t;
constexpr uint64_t NUM_ELEMS() {
return ARRAY_SIZE / sizeof(inttype);
}
int main() {
// allocate array
void *arraybuf = mmap(nullptr, ARRAY_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);
assert(arraybuf != nullptr);
std::array<inttype, NUM_ELEMS()> *array =
new (arraybuf) std::array<inttype, NUM_ELEMS()>();
#if WITH_CHILD
// spawn checkpointing process
int pid = fork();
assert(pid != -1);
// child process -- do nothing, just exit
if (pid == 0) {
exit(0);
}
// wait for child thread to exit
assert(waitpid(pid, nullptr, 0) == pid);
#endif
// write to array -- this shouldnt generate page faults, right? :(
std::fill(array->begin(), array->end(), 0);
// cleanup
munmap(array, ARRAY_SIZE);
}