背景
Read the fucking source code!--By 魯迅A picture is worth a thousand words.--By 高爾基
說明:
- Kernel版本:4.14
- ARM64處理器,Contex-A53,雙核
- 使用工具:Source Insight 3.5, Visio
1. 介紹
之前的系列內存管理文章基本上描述的是物理頁面的初始化過程,以及虛擬頁面到物理頁面的映射建立過程,從這篇文章開始,真正要涉及到頁面的分配了。接下來的文章會圍繞着分區頁框分配器(zoned page frame allocator)來展開,其中會包含大家熟知的Buddy System分析。
本文會先圍繞着涉及到的數據結構,以及大體的流程做一個整體的分析,后續會針對這個流程中的細節進行更詳細的拆解,我已經迫不及待了。
2. 數據結構
2.1 概述
先回顧一下(五)Linux內存管理zone_sizes_init的數據結構圖:

上述的結構體,描述的是下面這張圖:

Node ---> ZONE ---> Page的組織關系,其中Buddy System中,頁面都是以2的次冪來組織成鏈表,比如free_area[0],對應的是1個page鏈表,其中又根據不同的MIGRATE_xxxx類型來組織,如下圖:

ARM64中MAX_ORDER默認值為11,PAGE_SIZE=4K,因此總共有0 ~ 1011個鏈表數組,鏈表中的連續的頁面為2^0 ~ 2^10,對應大小為4K ~ 4M。
可以通過cat /proc/pagetypeinfo來查看下系統的頁面信息,如下圖:

可以通過cat /proc/zoneinfo來查看Node的ZONE計數信息:

2.2 Migrate類型
從上邊的圖中可以看到MIGRATE_xxx不同的遷移類型,表明頁面的移動屬性,並在可能的情況下通過將相同屬性的頁面分組在一起來抑制內存的連續碎片。
enum migratetype {
MIGRATE_UNMOVABLE,
MIGRATE_MOVABLE,
MIGRATE_RECLAIMABLE,
MIGRATE_PCPTYPES, /* the number of types on the pcp lists */
MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
/*
* MIGRATE_CMA migration type is designed to mimic the way
* ZONE_MOVABLE works. Only movable pages can be allocated
* from MIGRATE_CMA pageblocks and page allocator never
* implicitly change migration type of MIGRATE_CMA pageblock.
*
* The way to use it is to change migratetype of a range of
* pageblocks to MIGRATE_CMA which can be done by
* __free_pageblock_cma() function. What is important though
* is that a range of pageblocks must be aligned to
* MAX_ORDER_NR_PAGES should biggest page be bigger then
* a single pageblock.
*/
MIGRATE_CMA,
#endif
#ifdef CONFIG_MEMORY_ISOLATION
MIGRATE_ISOLATE, /* can't allocate from here */
#endif
MIGRATE_TYPES
};
MIGRATE_UNMOVABLE:無法移動和檢索的類型,用於內核分配的頁面,I/O緩沖區,內核堆棧等;MIGRATE_MOVABLE:當需要大的連續內存時,通過移動當前使用的頁面來盡可能防止碎片,用於分配用戶內存;MIGRATE_RECLAIMABLE:當沒有可用內存時使用此類型;MIGRATE_HIGHATOMIC:減少原子分配請求無法進行高階頁面分配的可能,內核會提前准備一個頁面塊;MIGRATE_CMA:頁面類型由CMA內存分配器單獨管理;MIGRATE_ISOLATE:內核會暫時更改為這種類型,以遷移使用中的系列活動頁面;
2.3 __GFP_xxx請求標志(gfp_mask)
__GFP_xxx為內部使用的標志,在include/linux/gfp.h文件中,外部不應該使用這些Flag,這些標志在頁面申請的時候使用,其中GFP表示get free page。
羅列部分如下:
__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:請求不重試內存分配請求;
2.4 ALLOC_xxxx分配標志(alloc_flags)
分配標志定義在mm/internal.h文件中,在頁面的分配函數中與gfp_mask分開使用,這些標志時用於內部函數的分配。
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區域進行分配;
2.5 struct alloc_context
在頁面分配的過程中,有一個結構叫struct alloc_context,這個結構用於存儲各個函數之間傳遞的參數。這種思想在平時的coding中是可以去借鑒的,比如有些人寫代碼很喜歡用全局變量,改成這種context的形式,在各個函數之間傳遞顯得更為優雅。直接看代碼吧:
/*
* Structure for holding the mostly immutable allocation parameters passed
* between functions involved in allocations, including the alloc_pages*
* family of functions.
*
* nodemask, migratetype and high_zoneidx are initialized only once in
* __alloc_pages_nodemask() and then never change.
*
* zonelist, preferred_zone and classzone_idx are set first in
* __alloc_pages_nodemask() for the fast path, and might be later changed
* in __alloc_pages_slowpath(). All other functions pass the whole strucure
* by a const pointer.
*/
struct alloc_context {
struct zonelist *zonelist;
nodemask_t *nodemask;
struct zoneref *preferred_zoneref;
int migratetype;
enum zone_type high_zoneidx;
bool spread_dirty_pages;
};
zonelist:用於分配頁面的區域列表;nodemask:指定Node,如果沒有指定,則在所有節點中進行分配;preferred_zone:指定要在快速路徑中首先分配的區域,在慢路徑中指定了zonelist中的第一個可用區域;migratetype:要分配的遷移頁面類型;high_zoneidx:將分配限制為小於區域列表中指定的高區域;spread_dirty_pages:臟區平衡相關;
3. build_all_zonelists
在上篇文章中描述到各個zone,實際上各個zone最終組織起來是在build_all_zonelists函數中實現的:

整體完成的工作也比較簡單,將所有Node中可用的zone全部添加到各個Node中的zonelist中,也就是對應的struct pglist_data結構體中的struct zonelist node_zonelists字段。
這一步之后,准備工作基本就緒,進行頁面申請的工作就可以開始了。
4. alloc_pages
下面的流程開始真正的頁面申請了,在內部的實現中通過__alloc_pages來實現的:

在頁面分配時,有兩種路徑可以選擇,如果在快速路徑中分配成功了,則直接返回分配的頁面;快速路徑分配失敗則選擇慢速路徑來進行分配。
4.1 Fast Path
快速路徑分配,是通過get_page_from_freelist來完成的,具體的流程及分析如下圖所示:

4.2 Slow Path
慢速路徑分配,最終也會調用get_page_from_freelist,流程分析如下:


