1. 今日內容(第二階段(二)–初始化備用記憶體域列表zonelists) 我們之前講了在memblock完成之後, 記憶體初始化開始進入第二階段, 第二階段是一個漫長的過程, 它執行了一系列複雜的操作, 從體繫結構相關信息的初始化慢慢向上層展開, 其主要執行瞭如下操作 特定於體繫結構的設置 在完成了基 ...
1. 今日內容(第二階段(二)–初始化備用記憶體域列表zonelists)
我們之前講了在memblock完成之後, 記憶體初始化開始進入第二階段, 第二階段是一個漫長的過程, 它執行了一系列複雜的操作, 從體繫結構相關信息的初始化慢慢向上層展開, 其主要執行瞭如下操作
特定於體繫結構的設置
在完成了基礎的記憶體結點和記憶體域的初始化工作以後, 我們必須剋服一些硬體的特殊設置
- 在初始化記憶體的結點和記憶體區域之前, 內核先通過pagging_init初始化了內核的分頁機制, 這樣我們的虛擬運行空間就初步建立, 並可以完成物理地址到虛擬地址空間的映射工作.
在arm64架構下, 內核在start_kernel()
->setup_arch()中通過arm64_memblock_init( )完成了memblock的初始化之後, 接著通過setup_arch()->paging_init()開始初始化分頁機制
paging_init負責建立只能用於內核的頁表, 用戶空間是無法訪問的. 這對管理普通應用程式和內核訪問記憶體的方式,有深遠的影響
- 在分頁機制完成後, 內核通過setup_arch()->bootmem_init開始進行記憶體基本數據結構(記憶體結點pg_data_t, 記憶體域zone和頁幀)的初始化工作, 就是在這個函數中, 內核開始從體繫結構相關的部分逐漸展開到體繫結構無關的部分, 在zone_sizes_init->free_area_init_node中開始, 內核開始進行記憶體基本數據結構的初始化, 也不再依賴於特定體繫結構無關的層次
bootmem_init
始化記憶體數據結構包括記憶體節點, 記憶體域和頁幀page
|
|---->arm64_numa_init();
| 支持numa架構
|
|---->zone_sizes_init(min, max);
來初始化節點和管理區的一些數據項
|
|---->free_area_init_node
| 初始化記憶體節點
|
|---->free_area_init_core
| 初始化zone
|
|---->memmap_init
| 初始化page頁面
|
|---->memblock_dump_all();
| 初始化完成, 顯示memblock的保留的所有記憶體信息
至此,bootmem_init已經完成了節點和管理區的關鍵數據已完成初始化, 內核在後面為記憶體管理做得一個準備工作就是將所有節點的管理區都鏈入到zonelist中,便於後面記憶體分配工作的進行.
內核在start_kernel()–>build_all_zonelist()中完成zonelist的初始化
2 後備記憶體域列表zonelists
內核setup_arch的最後通過bootmem_init中完成了記憶體數據結構的初始化(包括記憶體結點pg_data_t, 記憶體管理域zone和頁面信息page), 數據結構已經基本準備好了, 在後面為記憶體管理做得一個準備工作就是將所有節點的管理區都鏈入到zonelist中, 便於後面記憶體分配工作的進行.
2.1 回到start_kernel函數(已經完成的工作)
asmlinkage __visible void __init start_kernel(void)
{
setup_arch(&command_line);
build_all_zonelists(NULL, NULL);
page_alloc_init();
/*
* These use large bootmem allocations and must precede
* mem_init();
* kmem_cache_init();
*/
mm_init();
kmem_cache_init_late();
kmemleak_init();
setup_per_cpu_pageset();
rest_init();
}
下麵內核開始通過start_kernel()->build_all_zonelists來設計記憶體的組織形式
2.2 後備記憶體域列表zonelist
記憶體節點pg_data_t中將記憶體節點中的記憶體區域zone按照某種組織層次存儲在一個zonelist中, 即pglist_data->node_zonelists成員信息
// http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=4.7#L626
typedef struct pglist_data
{
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[MAX_ZONELISTS];
}
內核定義了記憶體的一個層次結構關係, 首先試圖分配廉價的記憶體,如果失敗,則根據訪問速度和容量,逐漸嘗試分配更昂貴的記憶體.
高端記憶體最廉價, 因為內核沒有任何部分依賴於從該記憶體域分配的記憶體, 如果高端記憶體用盡, 對內核沒有副作用, 所以優先分配高端記憶體
普通記憶體域的情況有所不同, 許多內核數據結構必須保存在該記憶體域, 而不能放置到高端記憶體域, 因此如果普通記憶體域用盡, 那麼內核會面臨記憶體緊張的情況
DMA記憶體域最昂貴,因為它用於外設和系統之間的數據傳輸。
舉例來講,如果內核指定想要分配高端記憶體域。它首先在當前結點的高端記憶體域尋找適當的空閑記憶體段,如果失敗,則查看該結點的普通記憶體域,如果還失敗,則試圖在該結點的DMA記憶體域分配。如果在3個本地記憶體域都無法找到空閑記憶體,則查看其他結點。這種情況下,備選結點應該儘可能靠近主結點,以最小化訪問非本地記憶體引起的性能損失。
2.3 build_all_zonelists初始化zonelists
內核在start_kernel中通過build_all_zonelists完成了記憶體結點及其管理記憶體域的初始化工作, 調用如下
build_all_zonelists(NULL, NULL);
build_all_zonelists建立記憶體管理結點及其記憶體域的組織形式, 將描述記憶體的數據結構(結點, 管理域, 頁幀)通過一定的演算法組織在一起, 方便以後記憶體管理工作的進行. 該函數定義在mm/page_alloc.c?v4.7, line 5029
2.4 build_all_zonelists函數
/*
* Called with zonelists_mutex held always
* unless system_state == SYSTEM_BOOTING.
*
* __ref due to (1) call of __meminit annotated setup_zone_pageset
* [we're only called with non-NULL zone through __meminit paths] and
* (2) call of __init annotated helper build_all_zonelists_init
* [protected by SYSTEM_BOOTING].
*/
void __ref build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
{
/* 設置zonelist中節點和記憶體域的組織形式
* current_zonelist_order變數標識了當前系統的記憶體組織形式
* zonelist_order_name以字元串存儲了系統中記憶體組織形式的名稱 */
set_zonelist_order();
if (system_state == SYSTEM_BOOTING) {
build_all_zonelists_init();
} else {
#ifdef CONFIG_MEMORY_HOTPLUG
if (zone)
setup_zone_pageset(zone);
#endif
/* we have to stop all cpus to guarantee there is no user
of zonelist */
stop_machine(__build_all_zonelists, pgdat, NULL);
/* cpuset refresh routine should be here */
}
vm_total_pages = nr_free_pagecache_pages();
/*
* Disable grouping by mobility if the number of pages in the
* system is too low to allow the mechanism to work. It would be
* more accurate, but expensive to check per-zone. This check is
* made on memory-hotadd so a system can start with mobility
* disabled and enable it later
*/
if (vm_total_pages < (pageblock_nr_pages * MIGRATE_TYPES))
page_group_by_mobility_disabled = 1;
else
page_group_by_mobility_disabled = 0;
pr_info("Built %i zonelists in %s order, mobility grouping %s. Total pages: %ld\n",
nr_online_nodes,
zonelist_order_name[current_zonelist_order],
page_group_by_mobility_disabled ? "off" : "on",
vm_total_pages);
#ifdef CONFIG_NUMA
pr_info("Policy zone: %s\n", zone_names[policy_zone]);
#endif
}
3 設置結點初始化順序
在build_all_zonelists開始, 首先內核通過set_zonelist_order函數設置了zonelist_order
,如下所示, 參見mm/page_alloc.c?v=4.7, line 5031
void __ref build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
{
set_zonelist_order();
/* ....... */
}
3.1 zonelist
前面我們講解記憶體管理域時候講解到, 系統中的所有管理域都存儲在一個多維的數組zone_table. 內核在初始化記憶體管理區時, 必須要建立管理區表zone_table. 參見mm/page_alloc.c?v=2.4.37, line 38
/*
*
* The zone_table array is used to look up the address of the
* struct zone corresponding to a given zone number (ZONE_DMA,
* ZONE_NORMAL, or ZONE_HIGHMEM).
*/
zone_t *zone_table[MAX_NR_ZONES*MAX_NR_NODES];
EXPORT_SYMBOL(zone_table);
- MAX_NR_NODES為系統中記憶體結點的數目
- MAX_NR_ZONES為系統中單個記憶體結點所擁有的最大記憶體區域數目
3.2 記憶體域初始化順序zonelist_order
NUMA系統中存在多個節點, 每個節點對應一個struct pglist_data結構, 每個結點中可以包含多個zone, 如: ZONE_DMA, ZONE_NORMAL, 這樣就產生幾種排列順序, 以2個節點2個zone為例(zone從高到低排列, ZONE_DMA0表示節點0的ZONE_DMA,其它類似).
Legacy方式, 每個節點只排列自己的zone;
Node方式, 按節點順序依次排列,先排列本地節點的所有zone,再排列其它節點的所有zone。
Zone方式, 按zone類型從高到低依次排列各節點的同相類型zone
可通過啟動參數”numa_zonelist_order”來配置zonelist order,內核定義了3種配置, 這些順序定義在mm/page_alloc.c?v=4.7, line 4551
// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4551
/*
* zonelist_order:
* 0 = automatic detection of better ordering.
* 1 = order by ([node] distance, -zonetype)
* 2 = order by (-zonetype, [node] distance)
*
* If not NUMA, ZONELIST_ORDER_ZONE and ZONELIST_ORDER_NODE will create
* the same zonelist. So only NUMA can configure this param.
*/
#define ZONELIST_ORDER_DEFAULT 0 /* 智能選擇Node或Zone方式 */
#define ZONELIST_ORDER_NODE 1 /* 對應Node方式 */
#define ZONELIST_ORDER_ZONE 2 /* 對應Zone方式 */
註意
在非NUMA系統中(比如UMA), 由於只有一個記憶體結點, 因此ZONELIST_ORDER_ZONE和ZONELIST_ORDER_NODE選項會配置相同的記憶體域排列方式, 因此, 只有NUMA可以配置這幾個參數
全局的current_zonelist_order變數標識了系統中的當前使用的記憶體域排列方式, 預設配置為ZONELIST_ORDER_DEFAULT, 參見mm/page_alloc.c?v=4.7, line 4564
// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4564
/* zonelist order in the kernel.
* set_zonelist_order() will set this to NODE or ZONE.
*/
static int current_zonelist_order = ZONELIST_ORDER_DEFAULT;
static char zonelist_order_name[3][8] = {"Default", "Node", "Zone"};
而zonelist_order_name方式分別對應了Legacy方式, Node方式和Zone方式. 其zonelist_order_name[current_zonelist_order]就標識了當前系統中所使用的記憶體域排列方式的名稱”Default”, “Node”, “Zone”.
巨集 | zonelist_order_name巨集 | 排列方式 | 描述 |
---|---|---|---|
ZONELIST_ORDER_DEFAULT | Default | 由系統智能選擇Node或Zone方式 | |
ZONELIST_ORDER_NODE | Node | Node方式 | 按節點順序依次排列,先排列本地節點的所有zone,再排列其它節點的所有zone |
ZONELIST_ORDER_ZONE | Zone | Zone方式 | 按zone類型從高到低依次排列各節點的同相類型zone |
3.3 set_zonelist_order設置排列方式
內核就通過通過set_zonelist_order函數設置當前系統的記憶體域排列方式current_zonelist_order, 其定義依據系統的NUMA結構還是UMA結構有很大的不同.
// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4571
#ifdef CONFIG_NUMA
/* The value user specified ....changed by config */
static int user_zonelist_order = ZONELIST_ORDER_DEFAULT;
/* string for sysctl */
#define NUMA_ZONELIST_ORDER_LEN 16
char numa_zonelist_order[16] = "default";
// http://lxr.free-electrons.com/source/mm/page_alloc.c#L4571
static void set_zonelist_order(void)
{
if (user_zonelist_order == ZONELIST_ORDER_DEFAULT)
current_zonelist_order = default_zonelist_order();
else
current_zonelist_order = user_zonelist_order;
}
#else /* CONFIG_NUMA */
// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7#L4892
static void set_zonelist_order(void)
{
current_zonelist_order = ZONELIST_ORDER_ZONE;
}
其設置的基本流程如下
- 如果系統當前系統是非NUMA結構的, 則系統中只有一個結點, 配置ZONELIST_ORDER_NODE和ZONELIST_ORDER_ZONE結果相同. 那麼set_zonelist_order函數被定義為直接配置當前系統的記憶體域排列方式current_zonelist_order為ZONE方式(與NODE效果相同)
- 如果系統是NUMA結構, 則設置為系統指定的方式即可
- 當前的排列方式為ZONELIST_ORDER_DEFAULT, 即系統預設方式, 則current_zonelist_order則由內核交給default_zonelist_order採用一定的演算法選擇一個最優的分配策略, 目前的系統中如果是32位則配置為ZONE方式, 而如果是64位系統則設置為NODE方式
- 當前的排列方式不是預設方式, 則設置為user_zonelist_order指定的記憶體域排列方式
3.4 default_zonelist_order函數選擇最優的配置
在UMA結構下, 記憶體域使用NODE和ZONE兩個排列方式會產生相同的效果, 因此系統不用特殊指定, 直接通過set_zonelist_order函數, 將當前系統的記憶體域排列方式current_zonelist_order配置為為ZONE方式(與NODE效果相同)即可
但是NUMA結構下, 預設情況下(當配置了ZONELIST_ORDER_DEFAULT), 系統需要根據系統自身的環境信息選擇一個最優的配置(NODE或者ZONE方式), 這個工作就由default_zonelist_order函數了來完成. 其定義在mm/page_alloc.c?v=4.7, line 4789
#if defined(CONFIG_64BIT)
/*
* Devices that require DMA32/DMA are relatively rare and do not justify a
* penalty to every machine in case the specialised case applies. Default
* to Node-ordering on 64-bit NUMA machines
*/
static int default_zonelist_order(void)
{
return ZONELIST_ORDER_NODE;
}
#else
/*
* On 32-bit, the Normal zone needs to be preserved for allocations accessible
* by the kernel. If processes running on node 0 deplete the low memory zone
* then reclaim will occur more frequency increasing stalls and potentially
* be easier to OOM if a large percentage of the zone is under writeback or
* dirty. The problem is significantly worse if CONFIG_HIGHPTE is not set.
* Hence, default to zone ordering on 32-bit.
*/
static int default_zonelist_order(void)
{
return ZONELIST_ORDER_ZONE;
}
#endif /* CONFIG_64BIT */
3.5 user_zonelist_order用戶指定排列方式
在NUMA結構下, 系統支持用戶指定記憶體域的排列方式, 用戶以字元串的形式操作numa_zonelist_order(default, node和zone), 最終被內核轉換為user_zonelist_order, 這個變數被指定為字元串numa_zonelist_order指定的排列方式, 他們定義在mm/page_alloc.c?v4.7, line 4573, 註意只有在NUMA結構中才需要這個配置信息.
#ifdef CONFIG_NUMA
/* The value user specified ....changed by config */
static int user_zonelist_order = ZONELIST_ORDER_DEFAULT;
/* string for sysctl */
#define NUMA_ZONELIST_ORDER_LEN 16
char numa_zonelist_order[16] = "default";
#else
/* ......*/
#endif
而接受和處理用戶配置的工作, 自然是交給我們強大的proc文件系統來完成的, 可以通過/proc/sys/vm/numa_zonelist_order動態改變zonelist order的分配方式。
內核通過setup_numa_zonelist_order讀取並處理用戶寫入的配置信息
- 接收到用戶的信息後用__parse_numa_zonelist_order處理接收的參數
- 如果前面用__parse_numa_zonelist_order處理的信息串成功, 則將對用的設置信息寫入到字元串numa_zonelist_order中
參見mm/page_alloc.c?v=4.7, line 4578
/*
* interface for configure zonelist ordering.
* command line option "numa_zonelist_order"
* = "[dD]efault - default, automatic configuration.
* = "[nN]ode - order by node locality, then by zone within node
* = "[zZ]one - order by zone, then by locality within zone
*/
static int __parse_numa_zonelist_order(char *s)
{
if (*s == 'd' || *s == 'D') {
user_zonelist_order = ZONELIST_ORDER_DEFAULT;
} else if (*s == 'n' || *s == 'N') {
user_zonelist_order = ZONELIST_ORDER_NODE;
} else if (*s == 'z' || *s == 'Z') {
user_zonelist_order = ZONELIST_ORDER_ZONE;
} else {
pr_warn("Ignoring invalid numa_zonelist_order value: %s\n", s);
return -EINVAL;
}
return 0;
}
static __init int setup_numa_zonelist_order(char *s)
{
int ret;
if (!s)
return 0;
ret = __parse_numa_zonelist_order(s);
if (ret == 0)
strlcpy(numa_zonelist_order, s, NUMA_ZONELIST_ORDER_LEN);
return ret;
}
early_param("numa_zonelist_order", setup_numa_zonelist_order);
4 build_all_zonelists_init完成記憶體域zonelists的初始化
build_all_zonelists函數在通過set_zonelist_order設置了zonelists中結點的組織順序後, 首先檢查了ssytem_state標識. 如果當前系統處於boot階段(SYSTEM_BOOTING), 就開始通過build_all_zonelists_init函數初始化zonelist
build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
{
/* 設置zonelist中節點和記憶體域的組織形式
* current_zonelist_order變數標識了當前系統的記憶體組織形式
* zonelist_order_name以字元串存儲了系統中記憶體組織形式的名稱 */
set_zonelist_order();
if (system_state == SYSTEM_BOOTING) {
build_all_zonelists_init();
4.1 system_state系統狀態標識
其中system_state
變數是一個系統全局定義的用來表示系統當前運行狀態的枚舉變數, 其定義在include/linux/kernel.h?v=4.7, line 487
/* Values used for system_state */
extern enum system_states
{
SYSTEM_BOOTING,
SYSTEM_RUNNING,
SYSTEM_HALT,
SYSTEM_POWER_OFF,
SYSTEM_RESTART,
} system_state;
- 如果系統system_state是SYSTEM_BOOTING, 則調用build_all_zonelists_init初始化所有的記憶體結點
- 否則的話如果定義了冷熱頁CONFIG_MEMORY_HOTPLUG且參數zone(待初始化的記憶體管理域zone)不為NULL, 則調用setup_zone_pageset設置冷熱頁
if (system_state == SYSTEM_BOOTING)
{
build_all_zonelists_init();
}
else
{
#ifdef CONFIG_MEMORY_HOTPLUG
if (zone)
setup_zone_pageset(zone);
#endif
4.2 build_all_zonelists_init函數
build_all_zonelists函數在如果當前系統處於boot階段(system_state == SYSTEM_BOOTING), 就開始通過build_all_zonelists_init函數初始化zonelist
build_all_zonelists_init函數定義在mm/page_alloc.c?v=4.7, line 5013
static noinline void __init
build_all_zonelists_init(void)
{
__build_all_zonelists(NULL);
mminit_verify_zonelist();
cpuset_init_current_mems_allowed();
}
build_all_zonelists_init將將所有工作都委托給__build_all_zonelists完成了zonelists的初始化工作, 後者又對系統中的各個NUMA結點分別調用build_zonelists.
函數__build_all_zonelists定義在mm/page_alloc.c?v=4.7, line 4959
/* return values int ....just for stop_machine() */
static int __build_all_zonelists(void *data)
{
int nid;
int cpu;
pg_data_t *self = data;
/* ...... */
for_each_online_node(nid) {
pg_data_t *pgdat = NODE_DATA(nid);
build_zonelists(pgdat);
}
/* ...... */
}
for_each_online_node遍歷了系統中所有的活動結點.
由於UMA系統只有一個結點,build_zonelists只調用了一次, 就對所有的記憶體創建了記憶體域列表.
NUMA系統調用該函數的次數等同於結點的數目. 每次調用對一個不同結點生成記憶體域數據
4.3 build_zonelists初始化每個記憶體結點的zonelists
build_zonelists(pg_data_t *pgdat)完成了節點pgdat上zonelists的初始化工作, 它建立了備用層次結構zonelists. 由於UMA和NUMA架構下結點的層次結構有很大的區別, 因此內核分別提供了兩套不同的介面.
如下所示
// http://lxr.free-electrons.com/source/mm/page_alloc.c?v=4.7
4571 #ifdef CONFIG_NUMA
4586 static int __parse_numa_zonelist_order(char *s)
4601 static __init int setup_numa_zonelist_order(char *s)
4619 int numa_zonelist_order_handler(struct ctl_table *table, int write,
4620 void __user *buffer, size_t *length,
4678 static int find_next_best_node(int node, nodemask_t *used_node_mask)
4730 static void build_zonelists_in_node_order(pg_data_t *pgdat, int node)
4746 static void build_thisnode_zonelists(pg_data_t *pgdat)
4765 static void build_zonelists_in_zone_order(pg_data_t *pgdat, int nr_nodes)
4789 #if defined(CONFIG_64BIT)
4795 static int default_zonelist_order(void)
4799 #else
4808 static int default_zonelist_order(void)
4812 #endif /* CONFIG_64BIT */
4822 static void build_zonelists(pg_data_t *pgdat)
4872 #ifdef CONFIG_HAVE_MEMORYLESS_NODES
4879 int local_memory_node(int node)
4888 #endif
4890 #else /* CONFIG_NUMA */
4897 static void build_zonelists(pg_data_t *pgdat)
4892 static void set_zonelist_order(void)
4931 #endif /* CONFIG_NUMA */
header 1 | header 2
row 1 col 1 | row 1 col 2
row 2 col 1 | row 2 col 2
函數 | NUMA | UMA |
---|---|---|
build_zonelists | build_zonelists -=> mm/page_alloc.c?v=4.7, line 4822 | build_zonelists -=> mm/page_alloc.c?v=4.7, line 4897 build_zonelists_node -=> mm/page_alloc.c?v=4.7, line 4531 |
我們以UMA結構下的build_zonelists為例, 來講講內核是怎麼初始化備用記憶體域層次結構的, UMA結構下的build_zonelists函數定義在mm/page_alloc.c?v=4.7, line 4897, 如下所示
node_zonelists的數組元素通過指針操作定址, 這在C語言中是完全合法的慣例。實際工作則委托給build_zonelist_node。在調用時,它首先生成本地結點內分配記憶體時的備用次
內核在build_zonelists中按分配代價從昂貴到低廉的次序, 迭代了結點中所有的記憶體域. 而在build_zonelists_node中, 則按照分配代價從低廉到昂貴的次序, 迭代了分配代價不低於當前記憶體域的記憶體域.
首先我們來看看build_zonelists_node函數, 該函數定義在mm/page_alloc.c?v=4.7, line 4531
/*
* Builds allocation fallback zone lists.
*
* Add all populated zones of a node to the zonelist.
*/
static int build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist, int nr_zones)
{
struct zone *zone;
enum zone_type zone_type = MAX_NR_ZONES;
do {
zone_type--;
zone = pgdat->node_zones + zone_type;
if (populated_zone(zone)) {
zoneref_set_zone(zone,
&zonelist->_zonerefs[nr_zones++]);
check_highest_zone(zone_type);
}
} while (zone_type);
return nr_zones;
}
備用列表zonelists的各項是藉助於zone_type參數排序的, 該參數指定了最優先選擇哪個記憶體域, 該參數的初始值是外層迴圈的控制變數i.
我們知道其值可能是ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA或ZONE_DMA32之一.
nr_zones表示從備用列表中的哪個位置開始填充新項. 由於列表中尚沒有項, 因此調用者傳遞了0.
內核在build_zonelists中按分配代價從昂貴到低廉的次序, 迭代了結點中所有的記憶體域. 而在build_zonelists_node中, 則按照分配代價從低廉到昂貴的次序, 迭代了分配代價不低於當前記憶體域的記憶體域.
在build_zonelists_node的每一步中, 都對所選的記憶體域調用populated_zone, 確認zone->present_pages大於0, 即確認記憶體域中確實有頁存在. 倘若如此, 則將指向zone實例的指針添加到zonelist->zones中的當前位置. 後備列表的當前位置保存在nr_zones.
在每一步結束時, 都將記憶體域類型zone_type減1.換句話說, 設置為一個更昂貴的記憶體域類型. 例如, 如果開始的記憶體域是ZONE_HIGHMEM, 減1後下一個記憶體域類型是ZONE_NORMAL.
考慮一個系統, 有記憶體域ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA。在第一次運行build_zonelists_node時, 實際上會執行下列賦值
zonelist->zones[0] = ZONE_HIGHMEM;
zonelist->zones[1] = ZONE_NORMAL;
zonelist->zones[2] = ZONE_DMA;
我們以某個系統為例, 圖中示範了一個備用列表在多次迴圈中不斷填充的過程. 系統中共有四個結點
其中
A=(NUMA)結點0 0=DMA記憶體域
B=(NUMA)結點1 1=普通記憶體域
C=(NUMA)結點2 2=高端記憶體域
D=(NUMA)結點3
第一步之後, 列表中的分配目標是高端記憶體, 接下來是第二個結點的普通和DMA記憶體域.
內核接下來必須確立次序, 以便將系統中其他結點的記憶體域按照次序加入到備用列表.
現在我們回到build_zonelists函數, UMA架構下該函數定義在mm/page_alloc.c?v=4.7, line 4897, 如下所示
static void build_zonelists(pg_data_t *pgdat)
{
int node, local_node;
enum zone_type j;
struct zonelist *zonelist;
/* ...... */
for (node = local_node + 1; node < MAX_NUMNODES; node++) {
if (!node_online(node))
continue;
j = build_zonelists_node(NODE_DATA(node), zonelist, j);
}
for (node = 0; node < local_node; node++) {
if (!node_online(node))
continue;
j = build_zonelists_node(NODE_DATA(node), zonelist, j);
}
zonelist->_zonerefs[j].zone = NULL;
zonelist->_zonerefs[j].zone_idx = 0;
}
第一個迴圈依次迭代大於當前結點編號的所有結點. 在我們的例子中,有4個結點編號副本為0、1、2、3,此時只剩下結點3。新的項通過build_zonelists_node被加到備用列表。此時j的作用就體現出來了。在本地結點的備用目標找到之後,該變數的值是3。該值用作新項的起始位置。如果結點3也由3個記憶體域組成,備用列表在第二個迴圈之後的情況如圖3-9的第二步所示
第二個for迴圈接下來對所有編號小於當前結點的結點生成備用列表項。在我們的例子中,這些結點的編號為0和1。 如果這些結點也有3個記憶體域,則迴圈完畢之後備用列表的情況如下圖下半部分所示
備用列表中項的數目一般無法準確知道,因為系統中不同結點的記憶體域配置可能並不相同。因此 列表的最後一項賦值為空指針,顯式標記列表結束。
對總數N個結點中的結點m來說,內核生成備用列表時,選擇備用結點的順序總是:m、m+1、 m+2、…、N1、0、1、…、m1。這確保了不過度使用任何結點。例如,對照情況是:使用一個獨立 於m、不變的備用列表
4.4 setup_pageset初始化per_cpu緩存
前面講解記憶體管理域zone的時候, 提到了per-CPU緩存, 即冷熱頁. 在組織每個節點的zonelist的過程中, setup_pageset初始化了per-CPU緩存(冷熱頁面)
static void setup_pageset(struct per_cpu_pageset *p, unsigned long batch)
{
pageset_init(p);
pageset_set_batch(p, batch);
}
在此之前free_area_init_node初始化記憶體結點的時候, 內核就輸出了冷熱頁的一些信息, 該工作由zone_pcp_init完成, 該函數定義在mm/page_alloc.c?v=4.7, line 5029
static __meminit void zone_pcp_init(struct zone *zone)
{
/*
* per cpu subsystem is not up at this point. The following code
* relies on the ability of the linker to provide the
* offset of a (static) per cpu variable into the per cpu area.
*/
zone->pageset = &boot_pageset;
if (populated_zone(zone))
printk(KERN_DEBUG " %s zone: %lu pages, LIFO batch:%u\n",
zone->name, zone->present_pages,
zone_batchsize(zone));
}
5 總結
5.1 start_kernel啟動流程
start_kernel()
|---->page_address_init()
| 考慮支持高端記憶體
| 業務:初始化page_address_pool鏈表;
| 將page_address_maps數組元素按索引降序插入
| page_address_pool鏈表;
| 初始化page_address_htable數組.
|
|---->setup_arch(&command_line);
|
|---->setup_per_cpu_areas();
| 為per-CPU變數分配空間
|
|---->build_all_zonelist()
| 為系統中的zone建立後備zone的列表.
| 所有zone的後備列表都在
| pglist_data->node_zonelists[0]中;
|
| 期間也對per-CPU變數boot_pageset做了初始化.
|
|---->page_alloc_init()
|---->hotcpu_notifier(page_alloc_cpu_notifier, 0);
| 不考慮熱插拔CPU
|
|---->pidhash_init()
| 詳見下文.
| 根據低端記憶體頁數和散列度,分配hash空間,並賦予pid_hash
|
|---->vfs_caches_init_early()
|---->dcache_init_early()
| dentry_hashtable空間,d_hash_shift, h_hash_mask賦值;
| 同pidhash_init();
| 區別:
| 散列度變化了(13 - PAGE_SHIFT);
| 傳入alloc_large_system_hash的最後參數值為0;
|
|---->inode_init_early()
| inode_hashtable空間,i_hash_shift, i_hash_mask賦值;
| 同pidhash_init();
| 區別:
| 散列度變化了(14 - PAGE_SHIFT);
| 傳入alloc_large_system_hash的最後參數值為0;
|
5.2 pidhash_init配置高端記憶體
void pidhash_init(void)
|---->pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash),
| 0, 18, HASH_EARLY|HASH_SMALL, &pidhash_shift, NULL, 4096);
| 根據nr_kernel_pages(低端記憶體的頁數),分配哈希數組,以及各個哈希
| 數組元素下的哈希鏈表的空間,原理如下:
| number = nr_kernel_pages;
| number >= (18 - PAGE_SHIFT) 根據散列度獲得數組元素個數
| number = roundup_pow_of_two(number);
| pidhash_shift = max{x | 2**x <= number}
| size = number * sizeof(*pid_hash);
| 使用點陣圖分配器分配size空間,將返回值付給pid_hash;
|
|---->pidhash_size = 1 << pidhash_shift;
|
|---->for(i = 0; i < pidhash_size; i++)
| INIT_HLIST_HEAD(&pid_hash[i]);
5.3 build_all_zonelists初始化每個記憶體節點的zonelists
void build_all_zonelists(void)
|---->set_zonelist_order()
|---->current_zonelist_order = ZONELIST_ORDER_ZONE;
|
|---->__build_all_zonelists(NULL);
| Memory不支持熱插拔, 為每個zone建立後備的zone,
| 每個zone及自己後備的zone,形成zonelist
|
|---->pg_data_t *pgdat = NULL;
| pgdat = &contig_page_data;(單node)
|
|---->build_zonelists(pgdat);
| 為每個zone建立後備zone的列表
|
|---->struct zonelist *zonelist = NULL;
| enum zone_type j;
| zonelist = &pgdat->node_zonelists[0];
|
|---->j = build_zonelists_node(pddat, zonelist, 0, MAX_NR_ZONES - 1);
| 為pgdat->node_zones[0]建立後備的zone,node_zones[0]後備的zone
| 存儲在node_zonelist[0]內,對於node_zone[0]的後備zone,其後備的zone
| 鏈表如下(只考慮UMA體系,而且不考慮ZONE_DMA):
| node_zonelist[0]._zonerefs[0].zone = &node_zones[2];
| node_zonelist[0]._zonerefs[0].zone_idx = 2;
| node_zonelist[0]._zonerefs[1].zone = &node_zones[1];
| node_zonelist[0]._zonerefs[1].zone_idx = 1;
| node_zonelist[0]._zonerefs[2].zone = &node_zones[0];
| node_zonelist[0]._zonerefs[2].zone_idx = 0;
|
| zonelist->_zonerefs[3].zone = NULL;
| zonelist->_zonerefs[3].zone_idx = 0;
|
|---->build_zonelist_cache(pgdat);
|---->pdat->node_zonelists[0].zlcache_ptr = NULL;
| UMA體繫結構
|
|---->for_each_possible_cpu(cpu)
| setup_pageset(&per_cpu(boot_pageset, cpu), 0);
|詳見下文
|---->vm_total_pages = nr_free_pagecache_pages();
| 業務:獲得所有zone中的present_pages總和.
|
|---->page_group_by_mobility_disabled = 0;
| 對於代碼中的判斷條件一般不會成立,因為頁數會最夠多(記憶體較大)