在 Linux內核源碼分析之setup_arch (二) 中介紹了當前啓動階段的內存分配函數memblock_alloc,該內存分配函數在本篇將要介紹paging_init中用於頁表和內存的分配,paging_init函數大體流程以下圖所示。linux
該函數根據具體的CPU架構對靜態定義的mem_types數組中定義的屬性進行調整。數組
該函數的做用是把頁目錄項清零,源碼大體以下。首先是把虛擬地址範圍[0, MODULES_VADDR]的頁目錄項清零,若是內核是模塊區域以XIP方式運行的,則跳過內核部分的頁目錄,而後繼續對區域[addr,PAGE_OFFSET]的頁目錄項清零,此時用戶空間的頁目錄項已經所有清零;最後,把除了第一塊內存條以外的內核空間[__phys_to_virt(end), VMALLOC_START]對應的頁目錄項清零。架構
/* arch/arm/mm/mmu.c */ static inline void prepare_page_table(void) { unsigned long addr; phys_addr_t end; /* <--(1)--> */ for (addr = 0; addr < MODULES_VADDR; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr)); #ifdef CONFIG_XIP_KERNEL addr = ((unsigned long)_etext + PMD_SIZE - 1) & PMD_MASK; #endif /* <--(2)--> */ for ( ; addr < PAGE_OFFSET; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr)); end = memblock.memory.regions[0].base + memblock.memory.regions[0].size; if (end >= lowmem_limit) end = lowmem_limit; /* <--(3)--> */ for (addr = __phys_to_virt(end); addr < VMALLOC_START; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr)); }
該函數將物理內存地址小於lowmem_limit的內存映射到內核空間,實際的內存映射工做在create_mapping中完成。app
/* arch/arm/mm/mmu.c */ static void __init map_lowmem(void) { ... for_each_memblock(memory, reg) { start = reg->base; end = start + reg->size; if (end > lowmem_limit) end = lowmem_limit; if (start >= end) break; map.pfn = __phys_to_pfn(start); map.virtual = __phys_to_virt(start); map.length = end - start; map.type = MT_MEMORY; create_mapping(&map, false); } }
create_mapping函數的大體流程以下圖所示,這裏須要提一下,linux內核使用的是四級頁表,即PGD、PUD、PMD、PTE;而ARM32使用的是二級頁表,即PMD、PTE。同時因爲內存管理是以頁爲單位進行的,若是按照ARM硬件MMU的分頁機制,一個PMD對應的PTE並不能徹底佔用完整個頁,爲了不內存浪費,會在軟件層面上將兩個PMD對應的PTE放在一個頁內,具體細節能夠參考文件arch/arm/include/asm/pgtable-2level.h中的註釋部分。最終會調用alloc_init_pte函數對指定範圍的內存區域進行映射,其中的early_pte_alloc函數最終也會去調用 Linux內核源碼分析之setup_arch (二) 中介紹的memblock_alloc函數來分配內存,最後將PTE所在頁寫入到PMD中便可完成映射。函數
/* arch/arm/mm/mmu.c */ static void __init alloc_init_pte(...) { pte_t *start_pte = early_pte_alloc(pmd); pte_t *pte = start_pte + pte_index(addr); do { set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0); pfn++; } while (pte++, addr += PAGE_SIZE, addr != end); early_pte_install(pmd, start_pte, type->prot_l1); }
該函數大體流程以下圖所示,首先調用early_alloc分配一個頁,而後調用early_trap_init將向量表複製到新的頁內,最後調用create_mapping將這個頁映射到0xffff0000處,若是mdesc->map_io存在,還會對設備相關的IO進行映射。源碼分析
這個函數很是簡單,把大小爲2MB的區間[PKMAP_BASE,PAGE_OFFSET]映射到內核空間。ui
/* arch/arm/mm/mmu.c */ static void __init kmap_init(void) { #ifdef CONFIG_HIGHMEM pkmap_page_table = early_pte_alloc_and_install(pmd_off_k(PKMAP_BASE), PKMAP_BASE, _PAGE_KERNEL_TABLE); #endif }
本文主要介紹了內核啓動階段頁表初始化部分的內容,其中,build_mem_type_table負責根據不一樣CPU架構對mem_types進行調整,prepare_page_table負責將待初始化區域的頁目錄項清零,而後經過map_lowmem創建低端內存區域的頁表映射,最後調用devicemaps_init創建對向量表和設備IO的映射。至此,除了bootmem_init函數沒有分析以外,paging_init基本算是分析完了,bootmem_init的分析將在下一篇中給出。3d