Повторные незначительные ошибки страницы по тому же адресу после вызова mlockall()
Эта проблема
В ходе попыток уменьшить / исключить появление незначительных сбоев страниц в приложении, я обнаружил запутанное явление; а именно, я неоднократно запускаю незначительные сбои страниц при записи на один и тот же адрес, хотя я думал, что предпринял достаточные шаги для предотвращения сбоев страниц.
Фон
По совету здесь я позвонил mlockall
заблокировать все текущие и будущие страницы в памяти.
В моем исходном сценарии использования (который включал довольно большой массив) я также предварительно обрабатывал данные, записывая их в каждый элемент (или, по крайней мере, на каждую страницу) согласно совету здесь; хотя я понимаю, что этот совет предназначен для пользователей, использующих ядро с исправлением RT, общая идея принудительной записи в систему для предотвращения подкачки COW / запроса должна оставаться применимой.
Я думал что mlockall
может использоваться для предотвращения незначительных сбоев страницы. В то время как справочная страница, кажется, только гарантирует, что не будет никаких серьезных сбоев, различные другие ресурсы (например, выше) заявляют, что она может также использоваться для предотвращения незначительных сбоев страниц.
Документация ядра, кажется, также указывает на это. Например, unevictable-lru.txt и pagemap.txt утверждают, что mlock()
Редактируемые страницы являются неизбежными и, следовательно, не пригодными для восстановления.
Несмотря на это, я продолжал вызывать несколько незначительных сбоев страниц.
пример
Я создал чрезвычайно урезанный пример, чтобы проиллюстрировать проблему:
#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 минуту.
Запуск его на изолированном процессоре не оказывает никакого влияния; ни один не работает с более высоким приоритетом. Однако запуск с приоритетом в реальном времени устраняет ошибки страницы.
Вопросы
- Ожидается ли такое поведение?
1a. Чем объясняется время? - Можно ли это предотвратить?
Версии
Я использую 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
зонд был сломан для моей комбинации ядра и системной карты, потому что он ссылался на переменную, которой больше не было в ядре handle_mm_fault
функция, но исправить было тривиально)
2 ответа
Упоминание @fche о прозрачных огромных страницах поставило меня на правильный путь.
Менее небрежное чтение документации ядра, с которой я связан в этом вопросе, показывает, что mlock
не мешает ядру перенести страницу в новый фрейм страницы; действительно, есть целый раздел, посвященный миграции заблокированных страниц. Таким образом, просто позвонив mlock()
не гарантирует, что вы не будете испытывать незначительные ошибки страницы
С некоторым запозданием я вижу, что этот ответ цитирует тот же отрывок и частично отвечает на мой вопрос.
Одна из причин, по которой ядро может перемещать страницы, - это сжатие памяти, при котором ядро освобождает большой непрерывный блок страниц, чтобы можно было выделить "огромную страницу". Прозрачные огромные страницы могут быть легко отключены; см., например, этот ответ.
Мой конкретный контрольный пример был результатом некоторых изменений балансировки NUMA, внесенных в ядро 3.13.
Цитирую статью LWN, связанную там:
Планировщик будет периодически сканировать адресное пространство каждого процесса, отменяя все разрешения на доступ к страницам, которые в данный момент находятся в оперативной памяти. В следующий раз, когда затронутый процесс попытается получить доступ к этой памяти, произойдет сбой страницы. Планировщик перехватит эту ошибку и восстановит доступ к рассматриваемой странице...
Такое поведение планировщика можно отключить, задав для политики NUMA процесса явное использование определенного узла. Это можно сделать с помощью numactl
в командной строке (например, numactl --membind=0
) или звонок в libnuma
библиотека.
РЕДАКТИРОВАТЬ: документация sysctl прямо заявляет о балансировке NUMA:
Если целевая рабочая нагрузка уже связана с узлами NUMA, эту функцию следует отключить.
Это может быть сделано с sysctl -w kernel.numa_balancing=0
Могут быть и другие причины для миграции страниц, но этого вполне достаточно для моих целей.
Просто рассуждаем здесь, но, возможно, вы видите какое-то нормальное отслеживание использования страниц ядра (может быть, даже KSM, THP или cgroup), в котором оно пытается определить, сколько страниц активно используется. Проверьте функцию mark_page_accessed, например