我們現在知道物理內存是以頁框為最小單位存在的,那麼內核中分配頁框的方法是什麼呢?
頁框分配在內核裡的機制我們叫做分區頁框分配器(zoned page frame allocator),在linux系統中,分區頁框分配器管理著所有物理內存,無論你是內核還是進程,都需要請求分區頁框分配器,這時才會分配給你應該獲得的物理內存頁框。當你所擁有的頁框不再使用時,你必須釋放這些頁框,讓這些頁框回到管理區頁框分配器當中。
有時候目標管理區不一定有足夠的頁框去滿足分配,這時候系統會從另外兩個管理區中獲取要求的頁框,但這是按照一定規則去執行的,如下:
如果要求從DMA區中獲取,就只能從ZONE_DMA區中獲取。
如果沒有規定從哪個區獲取,就按照順序從 ZONE_NORMAL -> ZONE_DMA 獲取。
如果規定從HIGHMEM區獲取,就按照順序從 ZONE_HIGHMEM -> ZONE_NORMAL -> ZONE_DMA 獲取。
內核中根據不同的分配需求有6個函數接口來請求頁框,最終都會調用到__alloc_pages_nodemask。
可以看頁框分配器的核心函數是__alloc_pages_nodemask,在講這個函數之前我們先看下兩個標誌:
gfp_mask
__GFP_DMA:請求在ZONE_DMA區域中分配頁面;
__GFP_HIGHMEM:請求在ZONE_HIGHMEM區域中分配頁面;
__GFP_MOVABLE:ZONE_MOVALBE可用時在該區域分配頁面,同時表示頁面分配後可以在內存壓縮時進行遷移,也能進行回收;
__GFP_RECLAIMABLE:請求分配到可恢復頁面;
__GFP_HIGH:高優先級處理請求;
__GFP_IO:請求在分配期間進行 I/O 操作;
__GFP_FS:請求在分配期間進行文件系統調用;
__GFP_ZERO:請求將分配的區域初始化為 0;
__GFP_NOFAIL:不允許請求失敗,會無限重試;
__GFP_NORETRY:請求不重試內存分配請求;
alloc_flags
ALLOC_WMARK_MIN:僅在最小水位water mark及以上限制頁面分配;
ALLOC_WMARK_LOW:僅在低水位water mark及以上限制頁面分配;
ALLOC_WMARK_HIGH:僅在高水位water mark及以上限制頁面分配;
ALLOC_HARDER:努力分配,一般在gfp_mask設置了__GFP_ATOMIC時會使用;
ALLOC_HIGH:高優先級分配,一般在gfp_mask設置了__GFP_HIGH時使用;
ALLOC_CPUSET:檢查是否為正確的 cpuset;
ALLOC_CMA:允許從 CMA 區域進行分配;
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,
nodemask_t *nodemask)
{
page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);//fastpath分配頁面:從pcp(per_cpu_pages)和夥伴系統中正常的分配內存空間
......
page = __alloc_pages_slowpath(alloc_mask, order, &ac);//slowpath分配頁面:如果上面沒有分配到空間,調用下面函數慢速分配,允許等待和回收
......
}
在頁面分配時,有兩種路徑可以選擇,如果在快速路徑中分配成功了,則直接返回分配的頁面;快速路徑分配失敗則選擇慢速路徑來進行分配。總結如下:
正常分配(或叫快速分配):
如果分配的是單個頁面,考慮從per CPU緩存中分配空間,如果緩存中沒有頁面,從夥伴系統中提取頁面做補充。
分配多個頁面時,從指定類型中分配,如果指定類型中沒有足夠的頁面,從備用類型鍊表中分配。最後會試探保留類型鍊表。
慢速(允許等待和頁面回收)分配:
當上面兩種分配方案都不能滿足要求時,考慮頁面回收、殺死進程等操作後在試。