Как предотвратить ошибки страницы после выхода ребенка?

Хороший способ создать моментальный снимок процесса — использовать fork() для создания дочернего процесса. Память дочернего процесса будет копией родительского процесса.

Вместо того, чтобы жадно копировать всю память, ОС просто помечает страницы как копирование при записи: страницы будут клонированы, если произойдет событие записи в них одного из процессов. Это экономит и время, и место, что здорово.

В случае завершения дочернего процесса режим копирования при записи должен быть деактивирован. Однако я получаю ошибки страниц для всего массива — есть ли способ оптимизировать эти ошибки страниц? например аналогично тому, как 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);
}

person João Neto    schedule 28.05.2019    source источник
comment
Для ясности: сбой страницы не означает, что страница скопирована. При ошибке страницы ядро ​​проверяет, нужно ли копировать страницу; если он обнаружит, что у программы есть единственная копия (refcount = 1), он пропустит эту копию. Ошибка страницы — это то, как ядро ​​запускает проверку.   -  person Andrew Sun    schedule 29.05.2019
comment
Но в идеале должна быть возможность сообщить ядру о ложной тревоге. остановить проверку всех страниц.   -  person João Neto    schedule 29.05.2019