1. 前言
在 Linux核心原始碼分析之setup_arch (二) 中介紹了當前啟動階段的記憶體分配函式memblock_alloc,該記憶體分配函式在本篇將要介紹paging_init中用於頁表和記憶體的分配,paging_init函式大致流程如下圖所示。
2. paging_init
2.1 build_mem_type_table
該函式根據具體的CPU架構對靜態定義的mem_types陣列中定義的屬性進行調整。
2.2 prepare_page_table
該函式的作用是把頁目錄項清零,原始碼大致如下。首先是把虛擬地址範圍[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));
}
2.3 map_lowmem
該函式將實體記憶體地址小於lowmem_limit的記憶體對映到核心空間,實際的記憶體對映工作在create_mapping中完成。
/* 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);
}
2.4 devicemaps_init
該函式大致流程如下圖所示,首先呼叫early_alloc分配一個頁,然後呼叫early_trap_init將向量表複製到新的頁內,最後呼叫create_mapping將這個頁對映到0xffff0000處,如果mdesc->map_io存在,還會對裝置相關的IO進行對映。
2.5 kmap_init
這個函式非常簡單,把大小為2MB的區間[PKMAP_BASE,PAGE_OFFSET]對映到核心空間。
/* 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
}
3. 總結
本文主要介紹了核心啟動階段頁表初始化部分的內容,其中,build_mem_type_table負責根據不同CPU架構對mem_types進行調整,prepare_page_table負責將待初始化區域的頁目錄項清零,然後通過map_lowmem建立低端記憶體區域的頁表對映,最後呼叫devicemaps_init建立對向量表和裝置IO的對映。至此,除了bootmem_init函式沒有分析之外,paging_init基本算是分析完了,bootmem_init的分析將在下一篇中給出。