Подсистема ядра, которая удовлетворяет запросы на выделение памяти называется зонированным аллокатором страничных кадров, — если переводить дословно. Давайте взглянем на небольшую диаграмму:
Компонент 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)
.