А ещё я кого-то учу...
Zoned page frame allocator

Введение

Подсистема ядра, которая удовлетворяет запросы на выделение памяти называется зонированным аллокатором страничных кадров, — если переводить дословно. Давайте взглянем на небольшую диаграмму:

Компонент 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. Правда, в кэше обычно лишь небольшое количество страниц.

Выделение и освобождение страниц

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

  • struct page *alloc_pages(gfp_t gfp, unsigned order) — принимает GFP флаги и целое значение, означающую степень двойки, в качестве количества страниц. Возвращает указатель на дескриптор первой выделенной страницы.
  • alloc_page(gfp_mask) — макрос, который используется для выделения одной страницы, разворачивается в alloc_pages(gfp_mask, 0).
  • unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) — обёртка над alloc_pages, которая: А) маскирует gfp_mask с ~__GFP_HIGHMEM; Б) возвращает виртуальный адрес, на который спроецирована страница. Причём действие "А" проистекает из действия "Б" — мы не можем вернуть виртуальный адрес из ZONE_HIGHMEM, т.к. память в этой зоне не может быть спроецирована в линейное адресное пространство ядра непосредственно.
  • __get_free_page(gfp_mask) — макрос, который используется для выделения одной страницы, разворачивается в __get_free_pages((gfp_mask), 0).
  • __get_dma_pages(gfp_mask, order) — макрос, который используется для выделения страниц из ZONE_DMA, разворачивается в __get_free_pages((gfp_mask) | GFP_DMA, (order)).
  • unsigned long get_zeroed_page(gfp_t gfp_mask) — функция, которая используется для получения страницы, заполненной нулями, обёртка над __get_free_pages, дополняющая gfp_mask флагом __GFP_ZERO.

Как уже было упомянуто, аллокатор может принимать флаги, которые указывают на то, каким образом должна быть выделена память. Я не буду приводить здесь полный список, его можно найти в файле include/linux/gfp.h. Приведу основные:

флаг состоит из описание
GFP_ATOMIC __GFP_HIGH | __GFP_ATOMIC | __GFP_KSWAPD_RECLAIM Флаг полезен для обработчиков hardirq и других non-preemptive контекстов. Он указывает на то, что нельзя спать, можно использовать зарезервированную память и ZONE_HIGH.
GFP_KERNEL __GFP_RECLAIM | __GFP_IO | __GFP_FS Стандартный флаг для рутин ядра. Требует ZONE_NORMAL и ниже, позволяет отвлекаться на высвобождение памяти.
GFP_USER __GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL Флаг для выделения памяти для пользовательского пространства, которая также должна быть доступна ядру или аппаратной части. Обычно используется для аппаратных буферов, которые проецируются в пользовательское приложение, при этом доступные для DMA. Например, графические буферы.
GFP_HIGHUSER GFP_USER | __GFP_HIGHMEM Флаг аналогичен предыдущему, но позволяет также выделять память из ZONE_HIGHMEM. Полезен, когда у аппаратной части нет ограничений по поводу адресного пространства.
GFP_DMA __GFP_DMA Указывает на необходимость выделения памяти из ZONE_DMA. На данный момент является устаревшим, но не удаляется из ядра по той причине, что некоторые платформы ещё его используют, причём не всегда по назначению (см. комментарий в заголовочном файле)
GFP_DMA32 __GFP_DMA32 Флаг аналогичен предыдущему, но просит ZONE_DMA32. Надо иметь в виду, что kmalloc(..., GFP_DMA32) не вернёт память из ZONE_DMA32, потому что в нём не реализован страничный кэш для этой зоны.

Остальные актуальные вашему ядру флаги можно найти в том же заголовочном файле (include/linux/gfp.h). Также в нём можно найти определения и комментарии для флагов, в которые раскрываются вышеперечисленные (комбинированные) флаги. Обычно, их не используют напрямую, но поможет лучше понять значение комбинированных флагов.

Теперь можно перейти к следующему этапу жизни страниц памяти, относящемуся к данной теме — освобождению страниц памяти. Для освобождения страниц можно использовать следующие рутины ядра:

  • void __free_pages(struct page *page, unsigned int order) — функция позволяет освободить страницы памяти, выделенные с помощью alloc_pages(). Как она работает: сначала она декрементирует поле _refcount страницы с помощью функции put_page_testzero(), и если _refcount стал нулевым, то делает free_the_page(page, order), что приведёт к освобождению всех страниц указанного порядка; если же _refcount всё ещё положительный, то проверяется, что страницы не являются составными (compound pages)(по наличию флага PG_head у первой страницы) — в таком случае освобождаются все страницы указанного порядка, кроме первой, на которую и ссылается page(это очень редкий случай, сделано это для предотвращения утечек в коммите e320d3012d25 ("mm/page_alloc.c: fix freeing non-compound pages") ).
  • void free_pages(unsigned long addr, unsigned int order) — обёртка над __free_pages(). Она, в отличие от __free_pages(), принимает виртуальный адрес кадра страницы памяти, который, с помощью макроса virt_to_page(), переводит в указатель на дескриптор страницы памяти.
  • __free_page(page) — макрос, который освобождает одну страницу, то есть разворачивается в __free_pages(page, 0).
  • free_page(addr) — аналогичный макрос, разворачивается в free_pages(addr, 0).

2022-02-19 11:59