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()函數研究頁表映射的創建。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章