前言
Linux核心原始碼分析之setup_arch (三) 基本上把setup_arch主要的函式都分析了,由於距離上一篇時間比較久了,所以這裡重新貼一下大致的流程圖,本文主要分析的是bootmem_init函式。
程式碼分析
bootmem_init函式的結構如下:
find_limits通過儲存在meminfo中的記憶體條資訊得到低端記憶體和高階記憶體的頁框編號,分別放入到min、max_low、max_high中。
static void __init find_limits(unsigned long *min, unsigned long *max_low,
unsigned long *max_high)
{
...
*min = bank_pfn_start(&mi->bank[0]);
for_each_bank (i, mi)
if (mi->bank[i].highmem)
break;
*max_low = bank_pfn_end(&mi->bank[i - 1]);
*max_high = bank_pfn_end(&mi->bank[mi->nr_banks - 1]);
}
arm_bootmem_init對低端記憶體區域進行管理,流程圖如下:
在通過find_limits得到記憶體的起止頁框號之後,通過bootmem_bootmap_pages計算得到需要分配bitmap的大小,分配好bitmap之後呼叫init_bootmem_node將起止頁框號和bitmap資訊寫入到pgdat中。
/* arch/arm/mm/init.c */
static void __init arm_bootmem_init(unsigned long start_pfn,
unsigned long end_pfn)
{
...
boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn);
bitmap = memblock_alloc_base(boot_pages << PAGE_SHIFT, L1_CACHE_BYTES,
__pfn_to_phys(end_pfn));
node_set_online(0);
pgdat = NODE_DATA(0);
init_bootmem_node(pgdat, __phys_to_pfn(bitmap), start_pfn, end_pfn);
...
}
最後就是把memblock管理的記憶體移交給bootmem來管理,對於memblock中的空閒區域通過free_bootmem將bitmap中對應的bit置零,而已經使用的記憶體,即memblock中對應的reserved的區域使用reserve_bootmem將bitmap中對應bit置1。
/* arch/arm/mm/init.c */
static void __init arm_bootmem_init(...)
{
...
/* Free the lowmem regions from memblock into bootmem. */
for_each_memblock(memory, reg) {
...
free_bootmem(__pfn_to_phys(start), (end - start) << PAGE_SHIFT);
}
/* Reserve the lowmem memblock reserved regions in bootmem. */
for_each_memblock(reserved, reg) {
...
reserve_bootmem(__pfn_to_phys(start),
(end - start) << PAGE_SHIFT, BOOTMEM_DEFAULT);
}
}
在設定好bitmap之後,接下來開始分配對應的page結構體,在分配page結構體記憶體之前,先統計出各個zone區域內的記憶體空洞,存放在zhole_size中。
/* arch/arm/mm/init.c */
static void __init arm_bootmem_free(unsigned long min, unsigned long max_low,
unsigned long max_high)
{
...
for_each_memblock(memory, reg) {
...
if (start < max_low) {
unsigned long low_end = min(end, max_low);
zhole_size[0] -= low_end - start;
}
#ifdef CONFIG_HIGHMEM
if (end > max_low) {
unsigned long high_start = max(start, max_low);
zhole_size[ZONE_HIGHMEM] -= end - high_start;
}
#endif
}
...
free_area_init_node(0, zone_size, min, zhole_size);
}
統計好記憶體空洞之後開始分配page結構體所需要的記憶體空間,大致流程如下:
calculate_node_totalpages通過zones_size和zholes_size計算出記憶體頁總數和真正可用的記憶體頁數量,分別記錄在pgdat->node_spanned_pages和pgdat->node_present_pages中。
/* mm/page_alloc.c */
void __paginginit free_area_init_node(int nid, unsigned long *zones_size,
unsigned long node_start_pfn, unsigned long *zholes_size)
{
pg_data_t *pgdat = NODE_DATA(nid);
pgdat->node_id = nid;
pgdat->node_start_pfn = node_start_pfn;
calculate_node_totalpages(pgdat, zones_size, zholes_size);
alloc_node_mem_map(pgdat);
free_area_init_core(pgdat, zones_size, zholes_size);
}
alloc_node_mem_map根據pgdat->node_spanned_pages的大小確定需要分配的page結構體數量,這其中包括了記憶體空洞部分的區域,分配好之後將起始地址記錄到pgdat->node_mem_map中,同時也記錄在全域性變數mem_map中。
/* mm/page_alloc.c */
static void __init_refok alloc_node_mem_map(struct pglist_data *pgdat)
{
...
if (!pgdat->node_mem_map) {
...
start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1);
end = pgdat->node_start_pfn + pgdat->node_spanned_pages;
end = ALIGN(end, MAX_ORDER_NR_PAGES);
size = (end - start) * sizeof(struct page);
map = alloc_remap(pgdat->node_id, size);
if (!map)
map = alloc_bootmem_node_nopanic(pgdat, size);
pgdat->node_mem_map = map + (pgdat->node_start_pfn - start);
}
if (pgdat == NODE_DATA(0))
mem_map = NODE_DATA(0)->node_mem_map;
...
}
最後通過free_area_init_core初始化page結構體,大致流程如下:
主要作用是設定每個zone結構體的資訊,比如zone的可用空間大小等資訊,並把每個zone的page結構體初始化,記錄自己所屬的zone和node_id,同時把page結構體狀態設定為PG_reserved,這裡是無差別的設定的,至於空閒的記憶體頁會在後續的mm_init::mem_init::free_all_bootmem中重新釋放出來;另外,SetPageReserved是通過巨集宣告的,所以是無法找到該函式的,其定義在page-flags.h中。
總結
bootmem_init函式的作用是分配bitmap和page結構體所需要的空間,同時把已使用的和空閒的記憶體區域都標記到bitmap中,然後更新每個zone的記憶體資訊,並把屬於每個zone記憶體空間對應的page結構體進行初始化,且全部都設定為PG_reserved狀態。