文章目錄
Linux Memory初始化過程
start_kernel()
首先我們來看看Linux的系統啓動函數中的start_kernel()中與memory相關的處理步驟:
asmlinkage void __init start_kernel(void)
{
...........
setup_arch(&command_line);
.......................
build_all_zonelists();//create zone fallback order
page_alloc_init();//register callback
...............
mem_init(); //boot memory retire,create buddy allocator
kmem_cache_init();//init slab allocator
...........
anon_vma_init();//create a vma slab
.................
}
從start_kernel()函數,memory相關的步驟,可以大致分爲三大塊:
- setup_arch(): boot memory allocator初始化 和直接映射和固定映射的頁表創建,然後再 啓動分頁單元,再初始化每個Node下的Zone
- mem_init(): 將boot memory allocator退休,將已經在boot memory階段使用的memory標示爲reserve,將未使用的memory以page爲單位釋放給buddy allocator
- keme_cache_init():初始化slab allocator的原始slab:cache_cache,在此過後,slab allocator開始使用。
setup_arch()
在setup_arch() 函數下,做了好多事情,下面來詳細講述下:
boot memeory allocator初始化
下面是boot memory allocator的初始化核心函數,每個node下都有一個boot memory allocator,
然後再初始化boot memory allocator結構pgdat->bdata:
node_boot_start:當前bootmem_data結構中所描述的memory塊的起始物理地址。
node_low_pfn:當前bootmem_data結構中所描述memory塊的結束pfn。換句話說,就是該node中ZONE_NORMAL的結束物理地址。
node_bootmem_map:代表page分配狀態的bitmap的地址。
last_offset:在某個page中上次分配完成之後的偏移。如果爲0,則代表該page全部被分配完了。
last_pos:上次分配所使用的page的PFN。將last_pos和last_offset聯合起來,可以測試多個分配是否可以合併到一個page中進行而不是使用一個全新的page
last_success:上次分配所對應的物理地址
static unsigned long __init init_bootmem_core (pg_data_t *pgdat,
unsigned long mapstart, unsigned long start, unsigned long end)
{
bootmem_data_t *bdata = pgdat->bdata;
unsigned long mapsize = ((end - start)+7)/8;//node_bootmem_map 大小,字節對齊
pgdat->pgdat_next = pgdat_list;//將node插入到list中
pgdat_list = pgdat;
mapsize = (mapsize + (sizeof(long) - 1UL)) & ~(sizeof(long) - 1UL);
bdata->node_bootmem_map = phys_to_virt(mapstart << PAGE_SHIFT);
bdata->node_boot_start = (start << PAGE_SHIFT);
bdata->node_low_pfn = end;
/*
* Initially all pages are reserved - setup_arch() has to
* register free RAM areas explicitly.
*/
memset(bdata->node_bootmem_map, 0xff, mapsize);
return mapsize;
}
boot memory是使用node_bootmem_map中的每一bit代表一個page,如果bit爲0,則爲被分配使用,如果bit是1,則已經被分配使用。
所以boot memory被初始化之後,已經被使用的物理page被標示爲reserve,比如boot memory allocator自己使用的內存,以及以及high memory不可使用。
頁表初始化
當boot memory allocator初始化之後,就要開始初始化頁表項,這些頁表項需要用到boot memory allocator來分配內存。
void __init paging_init(void)
{
pagetable_init();
load_cr3(swapper_pg_dir);
.........
__flush_tlb_all();
kmap_init();
zone_sizes_init();
}
static void __init pagetable_init (void)
{
unsigned long vaddr;
pgd_t *pgd_base = swapper_pg_dir;
.........
kernel_physical_mapping_init(pgd_base); //分配直接映射的page table
remap_numa_kva();
/*
* Fixed mappings, only the page table structure has to be
* created - mappings will be set by set_fixmap():
*/
vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
page_table_range_init(vaddr, 0, pgd_base);//分配高端內存中固定映射需要的頁表
permanent_kmaps_init(pgd_base);
.........
}
頁表的初始化,被分爲兩個階段:
第一階段,啓動初始化階段,在內核靜態代碼中有靜態定義swapper_pg_dir 爲全局pgd,其中內核代碼使用物理地址1MB ~ 9MB的這8Mb空間,因此在內核啓動階段,會生成臨時內核頁表項:pg0和pg1,其中pg0 對應swapper_pg_dir中的第0項和第768項,pg1位第1項和第769項,因此內核啓動階段靜態代碼在分頁機制爲啓動之前就可以使用臨時內核頁表所映射的虛擬地址了
第二階段,虛擬地址低端內存的部分以及虛擬地址高端內存固定映射的頁表分配。
其中kernel_physical_mapping_init() 映射低端內存的頁表項
而page_table_range_init()分配用用映射高端內存固定映射的部分的頁表項。
其中pmd和pte的物理頁框的分配用到了boot memory allocator的函數:alloc_bootmem_low_pages()
Zone初始化以及buddy allocator初始化
zone_sizes_init()函數循環初始化每一個node中的各個zone,根據配置項中的配置得知每個node的物理頁框範圍,然後再計算出各個node下各個zone的物理頁框起始位置,和大小,再調用free_area_init_node()來初始化zone.
void __init free_area_init_node(int nid, struct pglist_data *pgdat,
unsigned long *zones_size, unsigned long node_start_pfn,
unsigned long *zholes_size)
{
pgdat->node_id = nid;
pgdat->node_start_pfn = node_start_pfn;
calculate_zone_totalpages(pgdat, zones_size, zholes_size);
if (!pfn_to_page(node_start_pfn))
node_alloc_mem_map(pgdat); //如果是NUMA,則需要動態分配mem_map的空間,如果是UMA,則直接使用contig_page_data.node_mem_map靜態定義的物理地址,alloc_bootmem_node()分配n個struct page大小
free_area_init_core(pgdat, zones_size, zholes_size);
}
static void __init free_area_init_core(struct pglist_data *pgdat,
unsigned long *zones_size, unsigned long *zholes_size)
{
unsigned long i, j;
const unsigned long zone_required_alignment = 1UL << (MAX_ORDER-1);
int cpu, nid = pgdat->node_id;
unsigned long zone_start_pfn = pgdat->node_start_pfn;
pgdat->nr_zones = 0;
init_waitqueue_head(&pgdat->kswapd_wait);
for (j = 0; j < MAX_NR_ZONES; j++) {
struct zone *zone = pgdat->node_zones + j;
...........
zone->spanned_pages = size;
zone->present_pages = realsize;
zone->name = zone_names[j];
spin_lock_init(&zone->lock);
spin_lock_init(&zone->lru_lock);
zone->zone_pgdat = pgdat;
zone->free_pages = 0;
zone->temp_priority = zone->prev_priority = DEF_PRIORITY;
........................
zone->zone_mem_map = pfn_to_page(zone_start_pfn);
zone->zone_start_pfn = zone_start_pfn;
if ((zone_start_pfn) & (zone_required_alignment-1))
printk("BUG: wrong zone alignment, it will crash\n");
memmap_init(size, nid, j, zone_start_pfn);//將物理頁框對應的struct page初始化
zone_start_pfn += size;
zone_init_free_lists(pgdat, zone, zone->spanned_pages);//初始化buddy allocator的free area數組
}
}
free_pages:當前zone中free page的總個數
pages_min,pages_low,page_high:該zone中的水位線,具體在下一小節進行說明。
free_area:buddy allocator使用的free area bitmaps
wait_table:等待某個page被釋放的進程的等待隊列鏈表。這個隊列對於wait_on_page()和unlock_page()函數來說非常重要。
wait_table_size:wait_table中等待隊列的個數。
wait_table_bits:wait_table_size == (1 << wait_table_bits)
zone_pgdat:指向當前zone所屬的node
zone_mem_map:當前zone中的第一個page的地址,該page輸入全局數組mem_map
size:當前zone中pages 的個數
初始化每個node中mem_map數組中各個zone的struct *page,struct page中的flag字段用來記錄當前page數據那個zone,初始化的時候,全部被標記爲reserve,當boot memory retire的時候,會把要釋放的物理frame再設置爲可用狀態。
void __init memmap_init_zone(unsigned long size, int nid, unsigned long zone,
unsigned long start_pfn)
{
struct page *start = pfn_to_page(start_pfn);
struct page *page;
for (page = start; page < (start + size); page++) {
set_page_zone(page, NODEZONE(nid, zone));
set_page_count(page, 0);
reset_page_mapcount(page);
SetPageReserved(page);
INIT_LIST_HEAD(&page->lru);
#ifdef WANT_PAGE_VIRTUAL
/* The shift won't overflow because ZONE_NORMAL is below 4G. */
if (!is_highmem_idx(zone))
set_page_address(page, __va(start_pfn << PAGE_SHIFT));
#endif
start_pfn++;
}
}
初始化每個Zone中的buddy allocator所使用的free_area[].map所需要的空間
void zone_init_free_lists(struct pglist_data *pgdat, struct zone *zone, unsigned long size)
{
int order;
for (order = 0; ; order++) {
unsigned long bitmap_size;
INIT_LIST_HEAD(&zone->free_area[order].free_list);
if (order == MAX_ORDER-1) {
zone->free_area[order].map = NULL;
break;
}
bitmap_size = pages_to_bitmap_size(order, size);
zone->free_area[order].map =
(unsigned long *) alloc_bootmem_node(pgdat, bitmap_size);
}
}
mem_init()
主要是調用free_all_bootmem_core()來釋放low memory到buddy allocator,set_highmem_pages_init()來釋放high memory到buddy allocator。
其中free_all_bootmem_core()的核心步驟如下:
(1)boot memory allocator中未分配的pages:
清除struct page中的PG_reserve的標記位
將count設置爲1
調用__free_pages()將memory移交給buddy allocator來構建它的free lists
(2) 釋放bitmap中使用過的pages並將這些pages移交給buddy allocator。
kmem_cache_init()
初始化slab allocator的第一個object:cache_cache