arm-linux内核start_kernel之前启动分析(2)- 页表的准备

来源:互联网 发布:淘宝格子铺网址 编辑:程序博客网 时间:2024/05/15 00:07

arm-linux内核start_kernel之前启动分析另外2篇博文链接地址如下:

http://blog.csdn.net/skyflying2012/article/details/41344377

http://blog.csdn.net/skyflying2012/article/details/48054417


今天接着第一篇继续分析,不过今天只分析stext中一条汇编,如下:

    bl  __create_page_tables

kernel版本:3.4.55

看看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_SIZE1:  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_SWAPPER1:  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=2    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    //从上面这次填页表的过程可以看出,16KB的页表以虚拟地址页号为寻址下标,覆盖整个虚拟的4G地址空间    /*     * Now setup the pagetables for our kernel direct     * mapped region.     */    //接下来以多个1M的线性映射页表,建立kernel整个镜像的线性映射,((0x80000000-0x80000000+kernel_end)-(0xc0000000-0xc0000000+kernel_end))    //开启MMU之后就实现了链接地址(0xc0008000)与运行地址(0xc0008000)的统一        //这里有一个小技巧,利用当前PC值作为内核物理地址起始,create_page_tables距离内核起始地址不超过1MB,因此移位之后就是内核起始的物理页号。    //arm的create_page_tables中,不管是turn_mmu_on还是这里,都是使用的当前pc值计算物理页号,    //这样的好处是,不管内核加载到什么物理地址,都可以迅速的建立正确的页表映射。并且不需要内核开发人员对这部分代码进行修改    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.     */    //将atags的1M地址空间做线性映射,方便start_kernel中对args进行分析    //据上篇博文分析,r2中存储着bootloader传来的atag基地址(我的板子在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#endif1:  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, lrENDPROC(__create_page_tables)    .ltorg    .align__turn_mmu_on_loc:    .long   .    .long   __turn_mmu_on    .long   __turn_mmu_on_end




create_page_table完成了3种地址映射的页表空间填写:

(1)turn_mmu_on所在1M空间的平映射
(2)kernel image的线性映射
(2)atags所在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)atags跟kernel入口是在同一1M空间内,bootparams的线性映射操作是否多余?
根据第二个问题的分析,kernel image可以加载到任何sdram地址空间的0x****8000即可。
atags地址是有bootloader中指定,然后告诉kernel。
那就有这样一种情况,加入sdram起始地址为0x80000000,atags起始地址为0x80000100。
但kernel image我加载到0x81008000,可以看出,这时atags跟kernel image就在不同一1M空间啦
atags单独的线性映射操作还是很有必要的。


这是我想到的关于create_page_table的3个疑问,大家如果有别的疑问,欢迎留言讨论,共同学习。


今天就分析到这,页表准备就绪,只待开启MMU!


2 0
原创粉丝点击