Хороший способ создать моментальный снимок процесса — использовать fork()
a> для создания дочернего процесса. Память дочернего процесса будет копией родительского процесса.
Вместо того, чтобы жадно копировать всю память, ОС просто помечает страницы как копирование при записи: страницы будут клонированы, если произойдет событие записи в них одного из процессов. Это экономит и время, и место, что здорово.
В случае завершения дочернего процесса режим копирования при записи должен быть деактивирован. Однако я получаю ошибки страниц для всего массива — есть ли способ оптимизировать эти ошибки страниц? например аналогично тому, как MAP_POPULATE
позволяет избежать ошибок страницы при начальном доступе на страницы отображаемого региона.
Ниже приведен простой тест, демонстрирующий поведение, о котором я спрашиваю. Я проверяю ошибки страниц через perf stat -e minor-faults,major-faults ./a.out
.
Если дочерний процесс не создан (WITH_CHILD
установлен на false
), у меня очень мало ошибок страниц (около 125
и постоянно). Однако, просто создавая и собирая дочерний процесс, я получаю ошибки страницы во всем (около 131260
, пропорционально размеру массива). Поскольку страницы сопоставляются одним процессом, я не ожидаю возникновения каких-либо ошибок страниц! Почему?
Это продолжение ядра, копирующего страницы CoW после выхода из дочернего процесса.
#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);
}