Linux內核源碼分析之setup_arch (三)

1. 前言

Linux內核源碼分析之setup_arch (二) 中介紹了當前啓動階段的內存分配函數memblock_alloc,該內存分配函數在本篇將要介紹paging_init中用於頁表和內存的分配,paging_init函數大體流程以下圖所示。linux

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中完成。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);
}

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]映射到內核空間。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
}

3. 總結

本文主要介紹了內核啓動階段頁表初始化部分的內容,其中,build_mem_type_table負責根據不一樣CPU架構對mem_types進行調整,prepare_page_table負責將待初始化區域的頁目錄項清零,而後經過map_lowmem創建低端內存區域的頁表映射,最後調用devicemaps_init創建對向量表和設備IO的映射。至此,除了bootmem_init函數沒有分析以外,paging_init基本算是分析完了,bootmem_init的分析將在下一篇中給出。3d

相關文章
相關標籤/搜索