Данная заметка должна поднять завесу тайны над сущностью ядра Linux, которая называется “generic net estimator”. Зачем это нужно, и как этим пользоваться?
Ладно, дальше всё равно будет каша, потом структурирую.
Кликни по мне, чтобы перейти к полному тексту...В данной заметке речь пойдёт об отображении кадров страниц памяти из ZONE_HIGHMEM
на линейное адресное пространство ядра. Как было упомянуто в Управление памятью#Зоны памяти, ядро не может непосредственно адресовать память из ZONE_HIGHMEM
. Чтобы ядро могло получить доступ до этой памяти и нужно её отображение в адресное пространство ядра.
На современных ПК, в том числе смартфонах, вряд ли уже можно встретить ZONE_HIGHMEM
, т.к. 64-битные платформы не имеют ограничение по адресному пространству. Однако, представленный механизм имеет место в Linux v5.17, т.к. 32-битные платформы из нашей жизни не исчезли: кто знает, сколько Aarch32-/MIPS32-роутеров, работающих на Linux, пересекла эта страница, прежде чем попасть в браузер.
Подсистема ядра, которая удовлетворяет запросы на выделение памяти называется зонированным аллокатором страничных кадров, — если переводить дословно. Давайте взглянем на небольшую диаграмму:
Компонент Zone allocator
получает запросы на выделение и освобождение динамической памяти. В случае с выделением, этот компонент сначала определит зону, которая могла бы удовлетворить запрос. Допустим, мы хотим выделить память под кольцевой буфер сетевой карты, то есть мы передали флаг GFP_DMA
. Тогда, Zone allocator
может взять память только из зоны ZONE_DMA
(т.к. флаг GFP_DMA
требует выделения памяти из зоны ZONE_DMA
). Если бы мы не передали этот флаг, то Zone allocator
сначала поискал бы свободную память в зоне ZONE_NORMAL
(но не в зоне ZONE_HIGHMEM
, т.к. мы не указали флаг __GFP_HIGHMEM
, который указывает, что память может быть выделена в ZONE_HIGHMEM
, но не требует этого).
Внутри каждой зоны есть компонент Buddy allocator
, который и управляет страницами памяти (см. Buddy аллокатор). Однако, для увеличения быстродействия системы, запрос может быть удовлетворён страницами из кэша, который отражён на диаграмме компонентном Per-CPU page frame cache
. Правда, в кэше обычно лишь небольшое количество страниц.
Пока не вижу её будущего, но не могу записать это в ту, которую сейчас пишу. В общем, я сломался об исходный код, а оказалось, что всё макросами генерится.
Кликни по мне, чтобы перейти к полному тексту...В этой заметке будут опущены тонкости трансляции адресов. Также в ней опущены тонкости первичной инициализации памяти (выделение двух страниц, статические данные и т.п.). Память, которая не относится к первичной инициализации называется динамической. Именно управление ею и будет рассмотрено в данной заметке.
Управление динамической памятью — важная составляющая эффективности работы системы. Памятью пользуются не только процессы, но и ядро ОС(файловый кэш, буферы периферийных устройств, и т.п.). Данная заметка будет как раз про то, как ядро управляет памятью для своих нужд.
Кликни по мне, чтобы перейти к полному тексту...Существует проблема при управлении памятью, которая заключается в том, что выделения и освобождения участков разных размеров приводят к фрагментации свободной памяти. Таким образом, несмотря на достаточный объём свободной памяти, может быть невозможным выделение непрерывного участка памяти требуемого размера — назовём это негативным эффектом фрагментации.
Для того, чтобы избежать негативного эффект от фрагментации, можно:
Оба варианта дорогие. У второго это по формулировке видно, а первый так или иначе потребует трансляции адресов (хорошо, если аппаратной) и управление этой трансляцией для каждого выделенного кусочка памяти.
Ядро предпочло второй вариант. На это есть несколько причин:
Разработчики Linux назвали свой алгоритм "buddy memory allocation" в честь так называемой "системы приятелей". Система приятелей заключается в том, что два человека могут объединить усилия в каком-либо деле, тем самым контролируя друг друга.
В рамках этого алгоритма все свободные страницы формируются в группы по 1,2,4,8..1024 (и т.д., в зависимости от аппаратной платформы) блоков. Начальный адрес каждой группы является произведением размера этой группы. Например, для группы по 16 страниц начальным адресом будет 16 * 2^12
, где 2^12
— типичный размер страницы в 4 КБ. Понятно, что речь идёт о смещение относительно некоторого начала памяти для buddy allocator.
Приведу пример работы этого алгоритма:
Допустим, мы хотим выделить 1 МБ непрерывной физической памяти, то есть 256 страниц. Первым делом алгоритм проверит наличие свободного блока по 256 страниц. Если такой блок есть, то алгоритм отдаёт его, и работа закончена. Если такого блока нет, то алгоритм будет искать свободный блок по 512 страниц. Если такой блок есть, то алгоритм разобьёт его на два блока по 256 страниц, один отдаст пользователю, второй сохранит в группе других блоков по 256 страниц. Если и по 512 страниц блока не нашлось, тогда алгоритм попробует найти блок по 1024 страницы. Таким образом, блок по 1024 страницы будет разбит на три блока: один по 512, два по 256. Как только алгоритм дошёл до группы наибольшего размера и не нашёл свободной памяти, способной удовлетворить запрос, алгоритм безуспешно завершает свою работу и сигнализирует об ошибке.
Обратное действие — освобождение памяти — отсылает к названию алгоритма. Если мы хотим освободить участок непрерывной памяти размером в 1 МБ, то алгоритм попытается найти рядом свободный блок такого же размера и слить их в один блок. Таким образом, если рядом с только что освобождённым блоком по 256 страниц имеется ещё один блок по 256 страниц, то алгоритм объединит их в один блок по 512 страниц. В свою очередь, новообразованный блок по 512 страниц также может быть объединён с соседом в блок по 1024 страницы, если таковой сосед, конечно, имеется.
Формально описать правила для "приятелей", которых следует объединить, можно так:
2 * [размер блока] * [размер страницы]
.