五:为kernel建立临时页表
前面提及到,kernel里面的所有符号在链接时,都使用了虚拟地址值。在完成基本的初始化后,kernel代码将跳到第一个C语言函数start_kernl来执行,在哪个时候,这些虚拟地址必须能够对它所存放在真正内存位置,否则运行将为出错。为此,CPU必须开启MMU,但在开启MMU前,必须为虚拟地址到物理地址的映射建立相应的面表。在开启MMU后,kernel指并不马上将PC值指向start_kernl,而是要做一些C语言运行期的设置,如堆栈,重定义等工作后才跳到start_kernel去执行。在此过程中,PC值还是物理地址,因此还需要为这段内存空间建立va = pa的内存映射关系。当然,本函数建立的所有页表都会在将来paging_init销毁再重建,这是临时过度性的映射关系和页表。
在介绍__create_table_pages前,先认识一个macro pgtbl,它将KERNL_RAM_PADDR – 0x4000的值赋给rd寄存器,从下面的使用中可以看它,该值是页表在物理内存的基础,也即页表放在kernel开始地址下的16K的地方。
.macro pgtbl, rd
ldr \rd, =(KERNEL_RAM_PADDR - 0x4000)
.endm
[cpp]
/* * Setup the initial page tables. We only setup the barest * amount which are required to get the kernel running, which * generally means mapping in the kernel code. * * r8 = machinfo * r9 = cpuid * r10 = procinfo * * Returns: * r0, r3, r5-r7 corrupted * r4 = physical page table address */ __create_page_tables: pgtbl r4 @ page table address /* * Clear the 16K level 1 swapper page table */ mov r0, r4 mov r3, #0 add r6, r0, #0x4000 1: str r3, [r0], #4 str r3, [r0], #4 str r3, [r0], #4 str r3, [r0], #4 teq r0, r6 bne 1b ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags /* * Create identity mapping to cater for __enable_mmu. * This identity mapping will be removed by paging_init(). */ adr r0, __enable_mmu_loc ldmia r0, {r3, r5, r6} sub r0, r0, r3 @ virt->phys offset add r5, r5, r0 @ phys __enable_mmu add r6, r6, r0 @ phys __enable_mmu_end mov r5, r5, lsr #20 mov r6, r6, lsr #20 1: orr r3, r7, r5, lsl #20 @ flags + kernel base str r3, [r4, r5, lsl #2] @ identity mapping teq r5, r6 addne r5, r5, #1 @ next section bne 1b /* * Now setup the pagetables for our kernel direct * mapped region. */ mov r3, pc mov r3, r3, lsr #20 orr r3, r7, r3, lsl #20 add r0, r4, #(KERNEL_START & 0xff000000) >> 18 str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! ldr r6, =(KERNEL_END - 1) add r0, r0, #4 add r6, r4, r6, lsr #18 1: cmp r0, r6 add r3, r3, #1 << 20 strls r3, [r0], #4 bls 1b #ifdef CONFIG_XIP_KERNEL /* * Map some ram to cover our .data and .bss areas. */ orr r3, r7, #(KERNEL_RAM_PADDR & 0xff000000) .if (KERNEL_RAM_PADDR & 0x00f00000) orr r3, r3, #(KERNEL_RAM_PADDR & 0x00f00000) .endif add r0, r4, #(KERNEL_RAM_VADDR & 0xff000000) >> 18 str r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]! ldr r6, =(_end - 1) add r0, r0, #4 add r6, r4, r6, lsr #18 1: cmp r0, r6 add r3, r3, #1 << 20 strls r3, [r0], #4 bls 1b #endif /* * Then map first 1MB of ram in case it contains our boot params. */ add r0, r4, #PAGE_OFFSET >> 18 orr r6, r7, #(PHYS_OFFSET & 0xff000000) .if (PHYS_OFFSET & 0x00f00000) orr r6, r6, #(PHYS_OFFSET & 0x00f00000) .endif str r6, [r0] #ifdef CONFIG_DEBUG_LL #ifndef CONFIG_DEBUG_ICEDCC /* * Map in IO space for serial debugging. * This allows debug messages to be output * via a serial console before paging_init. */ addruart r7, r3 mov r3, r3, lsr #20 mov r3, r3, lsl #2 add r0, r4, r3 rsb r3, r3, #0x4000 @ PTRS_PER_PGD*sizeof(long) cmp r3, #0x0800 @ limit to 512MB movhi r3, #0x0800 add r6, r0, r3 mov r3, r7, lsr #20 ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags orr r3, r7, r3, lsl #20 1: str r3, [r0], #4 add r3, r3, #1 << 20 teq r0, r6 bne 1b #else /* CONFIG_DEBUG_ICEDCC */ /* we don't need any serial debugging mappings for ICEDCC */ ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags #endif /* !CONFIG_DEBUG_ICEDCC */ #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS) /* * If we're using the NetWinder or CATS, we also need to map * in the 16550-type serial port for the debug messages */ add r0, r4, #0xff000000 >> 18 orr r3, r7, #0x7c000000 str r3, [r0] #endif #ifdef CONFIG_ARCH_RPC /* * Map in screen at 0x02000000 & SCREEN2_BASE * Similar reasons here - for debug. This is * only for Acorn RiscPC architectures. */ add r0, r4, #0x02000000 >> 18 orr r3, r7, #0x02000000 str r3, [r0] add r0, r4, #0xd8000000 >> 18 str r3, [r0] #endif #endif mov pc, lr ENDPROC(__create_page_tables)
里面涉及的代码主要就是建立虚拟地址与物理地址的转换,尤其是右移20位和18位两个地方与页表目录项的地址关系比较复杂。执行完该函数后,虚拟内存和物理内存的映射关系如下图所示: