arm32頁表對映過程(一)
在完成前面memory size的初始化之後,下面就是頁表的映射了,具體過程如下:
start_kernel()->setup_arch()->paging_init()
void __init paging_init(const struct machine_desc *mdesc) { void *zero_page; build_mem_type_table();-------------------------------------(1) prepare_page_table();---------------------------------------(2) map_lowmem();-----------------------------------------------(3) memblock_set_current_limit(arm_lowmem_limit); dma_contiguous_remap(); early_fixmap_shutdown(); devicemaps_init(mdesc); kmap_init(); tcm_init(); top_pmd = pmd_off_k(0xffff0000); /* allocate the zero page. */ zero_page = early_alloc(PAGE_SIZE); bootmem_init(); empty_zero_page = virt_to_page(zero_page); __flush_dcache_page(NULL, empty_zero_page); }
(1)主要是mem_type的初始化。
(2)呼叫pmd_clear清除kernel初始化過程中建立的一級頁表項的內容。
(3)呼叫map_lowmem()重新建立從實體地址起始點到high_mem的起始點的一一對映。
先看prepare_page_table()函式:
static inline void prepare_page_table(void) { unsigned long addr; phys_addr_t end; /* * Clear out all the mappings below the kernel image. */ for (addr = 0; addr < MODULES_VADDR; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr));-------------------------------------(1) #ifdef CONFIG_XIP_KERNEL /* The XIP kernel is mapped in the module area -- skip over it */ addr = ((unsigned long)_etext + PMD_SIZE - 1) & PMD_MASK; #endif for ( ; addr < PAGE_OFFSET; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr));-------------------------------------(2) /* * Find the end of the first block of lowmem. */ end = memblock.memory.regions[0].base + memblock.memory.regions[0].size; if (end >= arm_lowmem_limit) end = arm_lowmem_limit; /* * Clear out all the kernel space mappings, except for the first * memory bank, up to the vmalloc region. */ for (addr = __phys_to_virt(end); addr < VMALLOC_START; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr));------------------------------------(3) }
(1)呼叫pmd_clear清理0~MODULES_VADDR所對應的一級頁表項內容。
(2)呼叫pmd_clear清理MODULES_VADDR~PAGE_OFFSET所對應的一級頁表項內容.
(3)呼叫pmd_clear清理第一個memblock尾~VMALLOC_START所對應的一級頁表項內容.這裡有個疑問就是為何沒有清理第一個memblock對應的頁表內容,個人認為應該是第一個memblock對應的都是kernel的text, data,stack,不能清理這一部分內容。
下面要說一下pmd_off_k()函式:
static inline pmd_t *pmd_off_k(unsigned long virt) { return pmd_offset(pud_offset(pgd_offset_k(virt), virt), virt); } #define pgd_index(addr) ((addr) >> PGDIR_SHIFT) #define PGDIR_SHIFT 21 #define pgd_offset(mm, addr) ((mm)->pgd + pgd_index(addr)) /* to find an entry in a kernel page-table-directory */ #define pgd_offset_k(addr) pgd_offset(&init_mm, addr)
我們可以看到pgd的值其實就是init_mm.pgd+index的值,而init_mm.pgd的初始化為swapper_pg_dir的變數,此變數幾位核心頁表的基地址,其定義在arch/arm/kernel/head.S中:
.globl swapper_pg_dir
.equ swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE
#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
TEXT_OFFSET := $(textofs-y)
textofs-y := 0x00008000
而我們知道PAGE_OFFSET的值為0xC0000000,固由上可知swapper_pg_dir的值為0xC0004000,所以系統的一級頁表位於從0xC0004000開始的位置,這裡通過打log發現一個問題:
當virt=0時,pgd_offset=0xC0004000,我們可以認為此地址存放的就是第0個PGD所對應的所有二級頁表的第0個的地址
當virt=1時,pgd_offset=0xC0004008,
當virt=2時,pgd_offset=0xC0004010,
以此類推。。。
我們發現每兩個pgd_offset之間都相差8位元組,經過仔細研究發現主要是pgd_t為unsigned long行的陣列,且陣列元素為2,固陣列的長度為8位元組,所以每兩個pgd的陣列的首地址之間的差距為8位元組。由以上定義可得,以及頁表範圍pgd範圍為31~21(一般Linux為31~20),也就是2048(一般Linux為4096)個,而每個大小為8(一般Linux為4)位元組,所以pgd table的總大小為16K,剛好是從0xC0004000~0xC0008000,此設計是為了保持和arm的MMU保持一致。而從0xC0008000開始存放的時kernel image。
typedef unsigned long pte_t;
typedef unsigned long pmd_t;
typedef unsigned long pgd_t[2];
另外由於用例的32系統為2級對映,固pgd=pud=pmd,即,pgd_offset_k的值為pmd_offset_k的值:
static inline pud_t * pud_offset(pgd_t * pgd, unsigned long address)
{
return (pud_t *)pgd;
}
static inline pmd_t *pmd_offset(pud_t *pud, unsigned long addr)
{
return (pmd_t *)pud;
}
以上為建立頁面對映前的準備工作,下面將從map_lowmem()函式研究頁表對映的建立。