1. 程式人生 > >arm32頁表對映過程(一)

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()函式研究頁表對映的建立。