arm-linux kernel启动过程分析(2)-start_kernel之前第二步
今天接着分析,不过今天只分析stext中一条汇编,如下:
bl __create_page_tables
看看kernel启动初期,开启MMU之前如何初始化页表。
#ifdef CONFIG_ARM_LPAE /* LPAE requires an additional page for the PGD */ #define PG_DIR_SIZE 0x5000 #define PMD_ORDER 3 #else #define PG_DIR_SIZE 0x4000 #define PMD_ORDER 2 #endif ..... .macro pgtbl, rd, phys add \rd, \phys, #TEXT_OFFSET - PG_DIR_SIZE .endm ..... __create_page_tables: //据上篇博文分析,r8存储着sdram的物理起始地址(我的板子0x80000000) //pgtbl宏获取0x80008000之下16K的地址空间作为页表空间 //arm页表一页是4 bytes,完成虚拟地址空间4GB中1MB的映射, //一共需要4 x 4096 bytes的页表空间 //可以看出,单页完成的是虚拟地址和物理地址高12位的转换。 //低20位的地址(1M内的地址)偏移是一致的。 pgtbl r4, r8 @ page table address /* * Clear the swapper page table */ //按照16bytes一页将16K页表空间清空 mov r0, r4 mov r3, #0 add r6, r0, #PG_DIR_SIZE 1: str r3, [r0], #4 str r3, [r0], #4 str r3, [r0], #4 str r3, [r0], #4 teq r0, r6 bne 1b //如果定义CONFIG_ARM_LPAE,在PGD与PMD之前还要再加一级页表,这里不详解这种情景 #ifdef CONFIG_ARM_LPAE /* * Build the PGD table (first level) to point to the PMD table. A PGD * entry is 64-bit wide. */ mov r0, r4 add r3, r4, #0x1000 @ first PMD table address orr r3, r3, #3 @ PGD block type mov r6, #4 @ PTRS_PER_PGD mov r7, #1 << (55 - 32) @ L_PGD_SWAPPER 1: str r3, [r0], #4 @ set bottom PGD entry bits str r7, [r0], #4 @ set top PGD entry bits add r3, r3, #0x1000 @ next PMD table subs r6, r6, #1 bne 1b add r4, r4, #0x1000 @ point to the PMD tables #endif //据上篇博文分析,r10中存储该CPU的processor_type_list(处理器信息结构体),获取该CPU的mmuflags 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(). */ //首先建立包含turn_mmu_on函数的1M空间的平映射(virt addr = phy addr) //turn_mmu_on距stext不远,所以实际完成0x8000000-0x81000000空间的平映射 //老方法,上篇博文分析过,获取phy到virt的offset adr r0, __turn_mmu_on_loc ldmia r0, {r3, r5, r6} sub r0, r0, r3 @ virt->phys offset //获取turn_mmu_on的首尾物理地址 add r5, r5, r0 @ phys __turn_mmu_on add r6, r6, r0 @ phys __turn_mmu_on_end //因1页映射1M空间,所以SECTION_SHIFT为20 //右移20位后,r5,r6代表该段地址空间在页表中下标(第几页) mov r5, r5, lsr #SECTION_SHIFT mov r6, r6, lsr #SECTION_SHIFT //r5左移20位,获取该页基地址,或上CPU的mmuflags,存在r3中 1: orr r3, r7, r5, lsl #SECTION_SHIFT @ flags + kernel base //将r3值存储在页表空间(r4起始)的(r5<<4)的页表中 //因一页用4bytes表示,所以PMD_ORDER=4 str r3, [r4, r5, lsl #PMD_ORDER] @ identity mapping //r5与r6之前相距多个1M,则需要填写多个页表。 //因turn_mmu_on函数很短,所以肯定在1M内,该处r5=r6 cmp r5, r6 addlo r5, r5, #1 @ next section blo 1b /* * Now setup the pagetables for our kernel direct * mapped region. */ //这里建立kernel整个镜像的线性映射,((0x80000000-0x80000000+kernel_end)-(0xc0000000-0xc0000000+kernel_end)) //开启MMU之后就实现了链接地址(0xc0008000)与运行地址(0xc0008000)的统一 mov r3, pc mov r3, r3, lsr #SECTION_SHIFT orr r3, r7, r3, lsl #SECTION_SHIFT //将该1M空间的物理起始地址存储到页表中相应虚拟地址页中 add r0, r4, #(KERNEL_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER) str r3, [r0, #((KERNEL_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]! ldr r6, =(KERNEL_END - 1) add r0, r0, #1 << PMD_ORDER add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER) 1: cmp r0, r6 add r3, r3, #1 << SECTION_SHIFT strls r3, [r0], #1 << PMD_ORDER bls 1b #ifdef CONFIG_XIP_KERNEL /* * Map some ram to cover our .data and .bss areas. */ add r3, r8, #TEXT_OFFSET orr r3, r3, r7 add r0, r4, #(KERNEL_RAM_VADDR & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER) str r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> (SECTION_SHIFT - PMD_ORDER)]! ldr r6, =(_end - 1) add r0, r0, #4 add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER) 1: cmp r0, r6 add r3, r3, #1 << 20 strls r3, [r0], #4 bls 1b #endif /* * Then map boot params address in r2 or the first 1MB (2MB with LPAE) * of ram if boot params address is not specified. */ //将包含boot params的1M地址空间做线性映射,方便start_kernel中对args进行分析 //据上篇博文分析,r2中存储着bootloader传来的params基地址(我的板子在0x80000100) //所以该1M空间是0x80000000-0x81000000,映射到0xc0000000-0xc1000000 mov r0, r2, lsr #SECTION_SHIFT movs r0, r0, lsl #SECTION_SHIFT moveq r0, r8 sub r3, r0, r8 add r3, r3, #PAGE_OFFSET add r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER) orr r6, r7, r0 str r6, [r3] //如果需要早期串口输出进行调试,在这里进行I/O空间的映射,从而实现可以对串口控制器的操作,这里不详解了。 #ifdef CONFIG_DEBUG_LL #if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING) /* * Map in IO space for serial debugging. * This allows debug messages to be output * via a serial console before paging_init. */ addruart r7, r3, r0 mov r3, r3, lsr #SECTION_SHIFT mov r3, r3, lsl #PMD_ORDER 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 #SECTION_SHIFT ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags orr r3, r7, r3, lsl #SECTION_SHIFT #ifdef CONFIG_ARM_LPAE mov r7, #1 << (54 - 32) @ XN #else orr r3, r3, #PMD_SECT_XN #endif 1: str r3, [r0], #4 #ifdef CONFIG_ARM_LPAE str r7, [r0], #4 #endif add r3, r3, #1 << SECTION_SHIFT cmp r0, r6 blo 1b #else /* CONFIG_DEBUG_ICEDCC || CONFIG_DEBUG_SEMIHOSTING */ /* we don't need any serial debugging mappings */ ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags #endif #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 >> (SECTION_SHIFT - PMD_ORDER) 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 >> (SECTION_SHIFT - PMD_ORDER) orr r3, r7, #0x02000000 str r3, [r0] add r0, r4, #0xd8000000 >> (SECTION_SHIFT - PMD_ORDER) str r3, [r0] #endif #endif #ifdef CONFIG_ARM_LPAE sub r4, r4, #0x1000 @ point to the PGD table #endif mov pc, lr ENDPROC(__create_page_tables) .ltorg .align __turn_mmu_on_loc: .long . .long __turn_mmu_on .long __turn_mmu_on_endcreate_page_table完成了3种地址映射的页表空间填写:
(1)turn_mmu_on所在1M空间的平映射
(2)kernel image的线性映射
(2)bootparams所在1M空间的线性映射
物理地址空间和虚拟地址空间映射关系图如下:
对于这3种地址空间映射,我觉得分别有3个值得思考的地方:
(1)为什么turn_mmu_on要做平映射?
turn_mmu_on我会在下一篇博文中分析,主要是完成开启MMU的操作。
那为什么将turn_mmu_on处做一个平映射?
可以想象,执行开启MMU指令之前,CPU取指是在0x80008000附近turn_mmu_on中。
如果只是做kernel image的线性映射,执行开启MMU指令后,CPU所看到的地址就全变啦。
turn_mmu_on对于CPU来说在0xc0008000附近,0x80008000附近对于CPU来说已经不可预知了。
但是CPU不知道这些,它只管按照地址一条条取指令,执行指令。
所以不做turn_mmu_on的平映射(virt addr = phy addr),turn_mmu_on在开启MMU后的运行是完全不可知。
完成turn_mmu_on的平映射,我们可以在turn_mmu_on末尾MMU已经开启稳定后,修改PC到0xc0008000附近,就可以解决从0x8xxxxxxx到0xcxxxxxxx的跳转。
(2)kernel image加载地址为什么会在0x****8000?
分析了kernel image线性映射部分,这个就好理解了,
kernel编译链接时的入口地址在0xc0008000(PAGE_OFFSET + TEXT_OFFSET),但其物理地址不等于其链接的虚拟地址,image的线性映射实现其运行地址等于链接地址。
kernel的每一页表映射1M,所以入口处在(0x80000000-->0xc0000000)映射页表中完成映射。物理地址和虚拟地址的1M内偏移必须一致呀。
kernel定义的TEXT_OFFSET = 0x8000.所以加载的物理地址必须为0x****8000.
这样,开启MMU后,访问0xc0008000附近指令,MMU根据TLB才能正确映射找到0x****8000附近的指令。
(3)bootparams跟kernel入口是在同一1M空间内,bootparams的线性映射操作是否多余?
根据第二个问题的分析,kernel image可以加载到任何sdram地址空间的0x****8000即可。
bootparams地址是有bootloader中指定,然后告诉kernel的。
那就有这样一种情况,加入sdram起始地址为0x80000000,bootparams起始地址为0x80000100。
但kernel image我加载到0x81008000,可以看出,这时bootparams跟kernel image就在不同一1M空间啦
bootparams单独的线性映射操作还是很有必要的。
这是我想到的关于create_page_table的3个疑问,大家如果有别的疑问,欢迎留言讨论,共同学习。
今天就分析到这,TLB已经准备就绪,只待开启MMU!
start_kernel之前剩余部分汇编如何开启mmu,下篇再分析。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。