Повторяющиеся второстепенные ошибки страницы по тому же адресу после вызова mlockall ()

Проблема

Пытаясь уменьшить / исключить возникновение незначительных ошибок страниц в приложении, я обнаружил сбивающий с толку феномен; а именно, я неоднократно вызываю незначительные ошибки страниц для записи на один и тот же адрес, хотя я думал, что предпринял достаточные шаги для предотвращения ошибок страниц.

Задний план

Согласно совету, здесь, я вызвал mlockall, чтобы заблокировать все текущие и будущие страницы в памяти.

В моем первоначальном варианте использования (который включал довольно большой массив) я также предварительно обработал данные, записав их в каждый элемент (или, по крайней мере, на каждую страницу) в соответствии с советом здесь; хотя я понимаю, что этот совет предназначен для пользователей, использующих ядро ​​с исправлением RT, общая идея принудительной записи для предотвращения подкачки COW / запроса должна оставаться применимой.

Я думал, что mlockall можно использовать для предотвращения мелких ошибок страницы. В то время как страница руководства только кажется, гарантирует, что не будет никаких серьезных ошибок, различные другие ресурсы (например, выше) заявляют, что ее также можно использовать для предотвращения незначительных ошибок страницы.

Документация к ядру, кажется, тоже указывает на это. Например, unevictable-lru.txt и pagemap.txt заявляют, что mlock() ' ed страницы невозможно удалить, и поэтому они не подходят для повторного использования.

Несмотря на это, я продолжал вызывать несколько мелких ошибок страниц.

Пример

Я создал чрезвычайно урезанный пример, чтобы проиллюстрировать проблему:

#include <sys/mman.h> // mlockall
#include <stdlib.h> // abort

int main(int , char **) {
  int x;

  if (mlockall(MCL_CURRENT | MCL_FUTURE)) abort();

  while (true) {
    asm volatile("" ::: "memory"); // So GCC won't optimize out the write
    x = 0x42;
  }
  return 0;
}

Здесь я неоднократно пишу по одному и тому же адресу. Легко увидеть (например, через cat /proc/[pid]/status | awk '{print $10}'), что у меня по-прежнему возникают незначительные ошибки страниц после завершения инициализации.

Запуская модифицированную версию * сценария pfaults.stp, включенного в systemtap-doc, я регистрировал время каждой ошибки страницы, адрес, вызвавший ошибку, адрес инструкции, которая вызвала ошибку, была ли она основной / второстепенной, а также чтение / запись. После начальных ошибок при запуске и mlockall все ошибки были идентичны: попытка записи в x вызвала незначительную ошибку записи.

Интервал между последовательными ошибками страниц демонстрирует поразительную картину. Для одного конкретного прогона интервалы были в секундах: 2, 4, 4, 4.8, 8.16, 13.87, 23.588, 40.104, 60, 60, 60, 60, 60, 60, 60, 60, 60, ... Это выглядит (приблизительно) экспоненциальным откатом с абсолютным потолком в 1 минуту.

Запуск его на изолированном процессоре не оказывает никакого влияния; также не выполняется работа с более высоким приоритетом. Однако работа с приоритетом в реальном времени устраняет ошибки страниц.

Вопросы

  1. Ожидается ли такое поведение?
    1a. Что объясняет время?
  2. Можно ли это предотвратить?

Версии

Я использую Ubuntu 14.04 с ядром 3.13.0-24-generic и версией Systemtap 2.3/0.156, Debian version 2.3-1ubuntu1 (trusty). Код, скомпилированный с gcc-4.8, без дополнительных флагов, хотя уровень оптимизации, похоже, не имеет значения (при условии, что директива asm volatile остается на месте; в противном случае запись полностью оптимизируется)

Я буду рад включить дополнительные сведения (например, точный сценарий stap, исходный результат и т. Д.), Если они окажутся актуальными.


* На самом деле, зонд vm.pagefault был сломан для моей комбинации ядра и systemtap, потому что он ссылался на переменную, которой больше не существовало в функции ядра handle_mm_fault, но исправление было тривиальным)


person Tom    schedule 02.06.2014    source источник


Ответы (2)


Упоминание @fche о прозрачных огромных страницах направило меня на правильный путь.

Менее небрежное чтение документации ядра, на которую я ссылался в вопросе, показывает, что mlock не предотвращает перенос страницы ядром в новый страничный фрейм; действительно, есть целый раздел, посвященный перенос заблокированных страниц. Таким образом, простой вызов mlock() не гарантирует, что вы не столкнетесь с незначительными ошибками страницы

С некоторым опозданием я вижу, что этот ответ цитирует тот же отрывок и частично отвечает на мой вопрос.

Одна из причин, по которой ядро ​​может перемещать страницы, - это уплотнение памяти, в результате чего ядро ​​освобождает большой непрерывный блок страниц, поэтому можно выделить «огромную страницу». Прозрачные огромные страницы можно легко отключить; см. например этот ответ.

Мой конкретный тестовый пример стал результатом некоторых изменений балансировки NUMA, внесенных в ядро ​​3.13.

Цитата из ссылки на LWN:

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

Такое поведение планировщика можно отключить, настроив политику NUMA процесса на явное использование определенного узла. Это можно сделать с помощью numactl в командной строке (например, numactl --membind=0) или вызова библиотеки libnuma.

РЕДАКТИРОВАТЬ: В документации к sysctl прямо говорится о балансировке NUMA:

Если целевая рабочая нагрузка уже привязана к узлам NUMA, эту функцию следует отключить.

Это можно сделать с помощью sysctl -w kernel.numa_balancing=0

Могут быть и другие причины миграции страницы, но для моих целей этого было достаточно.

person Tom    schedule 03.06.2014

Просто размышления здесь, но, возможно, вы видите обычное отслеживание использования страниц ядра (возможно, даже KSM, THP или cgroup), при котором он пытается определить, сколько страниц активно используется. Проверьте функцию mark_page_accessed, например

person fche    schedule 03.06.2014