在完成前面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()函數研究頁表映射的創建。