- Memory management в ядре Linux. Семинар в Яндексе
- Задачи подсистемы управления памятью и компоненты, из которых она состоит
- Аппаратные возможности платформы x86_64
- Как описывается в ядре физическая и виртуальная память?
- API подсистемы управления памятью
- Высвобождение ранее занятой памяти (memory reclaim)
- Модель «LRU»
- Инструменты мониторинга
- Memory cgroups
- Compaction
- Управление памятью в Linux
- Как устроена память в Linux?
- Основные принципы управления памятью в Linux
- Особенности управления памятью в Linux
- Заключение
Memory management в ядре Linux. Семинар в Яндексе
Привет! Меня зовут Роман Гущин. В Яндексе я занимаюсь ядром Linux. Некторое время назад я провел для системных администраторов семинар, посвященный общему описанию подсистемы управления памятью в Linux, а также некоторым проблемам, с которыми мы сталкивались, и методам их решения. Большая часть информации описывает «ванильное» ядро Linux (3.10), но некоторая часть специфична для ядра, использующегося в Яндексе. Вполне возможно, семинар окажется интересен не только системным администраторам, но и всем, кто хочет узнать, как в Linux устроена работа с памятью.
- Задачи и компоненты подсистемы управления памятью;
- Аппаратные возможности платформы x86_64;
- Как описывается в ядре физическая и виртуальная память;
- API подсистемы управления памятью;
- Высвобождение ранее занятой памяти;
- Инструменты мониторинга;
- Memory Cgroups;
- Compaction — дефрагментация физической памяти.
Задачи подсистемы управления памятью и компоненты, из которых она состоит
Основная задача подсистемы — выделение физической памяти ядру и userspace-процессам, а также высвобождение и перераспределение в тех случаях, когда вся память занята.
- Buddy allocator занимается менеджментом пула свободной памяти.
- Page replacent («LRU» reclaim model) решает, у кого отобрать память, когда закончилась свободная.
- PTE management — блок управления таблицами трансляции.
- Slub kernel allocator — внутренний ядерный аллокатор.
- и др.
Аппаратные возможности платформы x86_64
Схема NUMA подразумевает, что к каждому физическому процессору присоединен некоторый объем памяти, к которому он может обращаться быстрее всего. Обращение к участкам памяти других процессоров происходит значительно медленнее.
Как описывается в ядре физическая и виртуальная память?
Физическая память в ядре описывается тремя структурами: ноды (pg_data_t), зоны (struct zone), страницы (struct page). Виртуальная память у каждого процесса своя и описывается при помощи структуры struct mm_struct. Они, в свою очередь, делятся на регионы (struct vm_area_struct).
API подсистемы управления памятью
Ядро взаимодействует с подсистемой memory management при помощи таких функцций функций, как __get_free_page(), kmalloc(), kfree(), vmalloc(). Они отвечают за выделение свободных страниц, больших и малых участков памяти, а также их высвобождение. Существует целое семейство подобных функций, отличающихся небольшими особенностями, например, будет ли занулена область при высвобождении.
Пользовательские программы взаимодействуют с mm-подсистемой при помощи функций mmap(), munmap(), brk(), mlock(), munlock(). Также есть функции posix_fadvice() и madvice(), которые могут давать ядру «cоветы». Но учитывать их в своих эвристиках оно строго говоря не обязано.
Высвобождение ранее занятой памяти (memory reclaim)
Система всегда старается поддерживать некоторый объем свободной памяти (free pool). Таким образом, память выделяется гораздо быстрее, т.к. не приходится высвобождать ее в тот момент, когда она уже действительно нужна.
Те страницы в памяти, которые используются постоянно (системные библиотеки и т.п), называются working set. Вытеснение их из памяти приводит к замедлению работы всей системы. Общая скорость потребления памяти в системе называется memory pressure. Эта величина может очень сильно колебаться в зависимости от того, насколько загружена система.
Всю незанятую ядром память в системе можно поделить на две части: анонимная память и файловая. Отличаются они тем, что про первую мы точно знаем, что каждый ее кусок соответствует какому-либо файлу, и его можно туда сбросить.
Модель «LRU»
LRU расшифровывается как least recently used. Это абстракция, которая предлагает выкидывать страницы, к которым мы дольше всего не обращались. Реализовать ее в Linux полноценно невозможно, т.к. все что нам известно — было ли когда-либо обращение к той или иной странице. Чтобы как-то отслеживать частоту обращений к страницам используются списки active, inactive и unevictable. В последнем находятся залоченные пользователем страницы, которые не будут выбрасываться из памяти ни при каких условиях.
Существуют четкие правила перемещения между списками inactive и active. Под воздействием memory pressure, страницы из неактивного списка могут быть либо выброшены из памяти, либо перейти в активный. Страницы из активного списка перемещаются в неактивный, если к ним давно не было обращений.
Инструменты мониторинга
Утилита top демонстрирует статистику потребления памяти в системе. Програмка vmtouch — показывает какая часть определенного файла находится в памяти. Исчерпывающую информацию по количеству файловых, активных и неактивных страниц можно найти в /proc/vmstat. Статистика buddy allocator есть в /proc/buddyinfo, а статистика slub allocator, соответственно, в /proc/slabinfo. Часто бывает полезно посмотреть на perf top, где отлично видны все проблемы с фрагментацией.
Memory cgroups
Сигруппы зародились из желания выделить группу из нескольких процессов, объединить их логически и ограничить их суммарное потребление памяти определенным. При этом, если они достигнут своего лимита, память должна высвобождаться именно из выделенного им объема. В этом случае нужно освободить память, принадлежащую именно этой сигруппе (это называется target reclaim). Если в системе просто закончилась память и нужно пополнить free pool — это называется global reclaim. C точки зрения аккаунтинга каждая страница принадлежит только одной сигруппе: той, которая ее первой прочитала.
Compaction
Compaction — это механизм дефрагментации физической памяти. Он достаточно подробно описывается в этой статье. Механизм этот был достаточно долго сломан, примерно с версии 3.3 до версии 3.7. Это проявлялось в том, что на некоторых машинах с мощным фрагментирующим моментом спустя две недели работы все процессоры были заняты исключительно compaction и никакого полезного действия не совершали.
Управление памятью в Linux
Данная тема, на первый взгляд, не является важной для системного администрирования. А скорее более полезна тем, кто занимается разработкой, отладкой или тестированием программного обеспечения (ПО) под Linux. Однако, понимание того, как устроена и функционирует система управления памятью в Linux (даже на базовом уровне). Для любого системного администратора также может быть полезным. В первую очередь для анализа производительности системы. А также для поиска решений для её увеличения и/или оптимизации.
Как устроена память в Linux?
Базовой единицей в организации памяти для систем UNIX/Linux является страница памяти. Обладающая размером от 4 Кбайт, которому соответствует объём физического пространства в оперативной или виртуальной (область подкачки на диске или другом устройстве хранения) памяти. При запуске процессов, они запрашивают у системы (т. е. у ядра посредством соответствующих системных вызовов) память для своей работы. А в ответ на это ядро выделяет для них достаточное количество страниц памяти. Виртуальная память или как её ещё называют, «резервное ЗУ» (резервное запоминающее устройство) для страниц памяти. Которые содержат, к примеру, исходный текст исполняемого приложения, представляют собой обычные исполняемые файлы на диске. Равно как и для других файлов данных резервным ЗУ являются сами файлы. Информация о том как взаимосвязаны страницы физической и виртуальной памяти хранится в соответствующих таблицах страниц памяти.
Для работы с памятью в Linux (как и в других UNIX-подобных системах) характерно такое явление как «страничный обмен» (paging). Оно заключается в том, что ядро выделяет процессам столько памяти, сколько им необходимо. В том смысле, чтобы её (памяти) всегда хватало. Это достигается за счёт расширения физической памяти за счёт виртуальной, т. е. «подкачки». Поскольку выполнение процессов должно происходить в реальной физической памяти. То ядро постоянно перемещает страницы памяти процессов между физической и виртуальной памятью. Забегая вперёд, следует отметить, что в виртуальной памяти хранятся «неактивные» страницы. Которые не задействованы процессом в данный момент, но необходимые ему для полноценной работы впоследствии.
Основные принципы управления памятью в Linux
Первое, на что следует обратить внимание, это то, что ядро старается управлять памятью таким образом, чтобы недавно используемые процессом страницы находились в физической памяти. И в свою очередь, «неактивные» или редко используемые страницы перемещаются и хранятся в виртуальной памяти в области «подкачки». Такой механизм распределения памяти называется LRU (least recently used) — замещение наиболее редко используемых страниц.
Вторым важнейшим аспектом в работе памяти является использование кеш-буфера страниц. Это вытекает из работы алгоритма LRU, который довольно сложен в своей реализации. Поскольку следить за всеми обращениями к страницам — это в некоторых случаях, довольно ощутимые потери в производительности системы. Использование же страничного кеш-буфера куда проще в своей реализации при тех же самых результатах. К тому же данный подход имеет огромный модернизационный потенциал (в отличие от LRU) и алгоритмы анализа содержимого кеш-буфера (для определения, какие страницы должны быть перемещены из виртуальной памяти) постоянно совершенствуются. Что заметно сказывается на производительности и эффективности управления памятью.
Когда процессу не хватает памяти, то ядро начинает искать «занятые» страницы. Которые можно использовать для «голодающего» процесса. Обычно такими страницами являются те, что давно не были использованы. Ядро проверяет их на предмет модификации каким-либо процессом. Для этого существуют определённые признаки, при последнем обращении и если изменения были, то такие страницы помечаются ядром как «грязные». Т. е. такие, которые ещё нужны процессам. Для повторного использования памяти такие страницы сначала обязательно переносятся в виртуальную память. Все же остальные страницы являются «чистыми». И поэтому ядро их использует для предоставления другим или «голодающим» процессам.
Особенности управления памятью в Linux
Когда происходит обращение к страницам памяти, которые некоторое или долгое время не использовалис, т. е. к «неактивным» страницам. То ядро выполняет с ними несколько важных задач:
- возвращает ссылки на эти страницы в соответствующей таблице страниц;
- сбрасывает в нулевое значение время «неиспользования» этих страниц;
- помечает эти страницы как «активные».
Со страницами, находящимися в виртуальной памяти не всё так однозначно. Дело в том, что для того, чтобы «активизировать» такие страницы, они должны быть предварительно прочитаны с диска.
Системное ядро комплектуется специализированными модулями. Которые содержат алгоритмы и даже целые технологии. С помощью которых система довольно эффективно «предсказывает», сколько может потребоваться памяти при разной степени активности и загруженности процессов. Эти алгоритмы имеют своей целью обеспечение процессов свободной памятью с максимальной эффективностью. Т. е. так, чтобы процессам как можно реже приходилось простаивать в «ожидании» выгрузки очередной страницы в свободную память. Таким образом, наблюдая за состоянием страничного обмена во время рабочей нагрузки системы, можно делать выводы о том, нужна ли ей дополнительная память. Если страничный обмен интенсивный — то однозначно следует установить дополнительные модули ОЗУ.
Если же происходит так, что процессам не хватает ни реальной физической, ни виртуальной памяти. Т. е. когда память полностью исчерпана, то система начинает завершать (а точнее уничтожать) целые процессы. Либо запрещает создание новых. Конечно в этом случае в первую очередь уничтожаются наиболее «безболезненные» для системы процессы. Однако в таких случаях даже «на глаз» и по собственным ощущениям видно что она большую часть времени тратит на управление памятью, а не на выполнение рабочих задач.
В Linux можно настроить параметр, который задаёт, насколько быстро ядро должно «отбирать»страницы памяти у процессов. Которым они менее нужны для процессов, которым они на данный момент необходимы. Этот параметр содержится в файле /proc/sys/vm/swappiness и по-умолчанию равен 60. Если задать его меньшим значением (например 0). То ядро будет забирать страницы процесса в самую последнюю очередь. Используя вместо этого любые другие варианты. Если это значение в пределах между 60 и 100. То страницы будут отбираться у процессов с более высокой вероятностью. Вариант с изменением данного параметра на самом деле говорит о том, что необходимо либо снизить нагрузку на систему. Адаптировав её для других менее производительных задач, либо увеличить объём ОЗУ.
Заключение
В заключение следует отметить, что схема работы и управления с памятью в Linux не так уж и сложна. Гораздо более сложнее специфические задачи. Такие как анализ содержимого кеш-буфера страниц и его интеллектуальное использование. Но это уже работа программистов и разработчиков. В свою очередь понимание основ управления памятью помогает лучше распоряжаться ресурсами системы. Что для системных администраторов очень важно.
Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.