背景 By 魯迅 By 高爾基 說明: 1. Kernel版本:4.14 2. ARM64處理器,Contex A53,雙核 3. 使用工具:Source Insight 3.5, Visio 1. 介紹 之前的系列記憶體管理文章基本上描述的是物理頁面的初始化過程,以及虛擬頁面到物理頁面的映射建立過程 ...
背景
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 ~ 10
11個鏈表數組,鏈表中的連續的頁面為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
,流程分析如下: