Linux内核启动代码之__create_page_tables函数分析

 在分析__create_page_tables函数之前,需要知道以下的知识。

1、head.S首先确定了processor type和 machine type,之后就是创建页表。通过前面的两步,我们已经确定了processor type 和 machine type。此时,一些特定寄存器的值如下所示:
r8 = machine info       (struct machine_desc的基地址)
r9 = cpu id             (通过cp15协处理器获得的cpu id)
r10 = procinfo          (struct proc_info_list的基地址)

2、由于CPU要开启MMU进入虚地址执行模式,因此必须先通过__create_page_tables建立一个临时的page table(将来这个table会被抛弃,重新建立)。

3、函数中出现的宏及其解释

默认值
定义
KERNEL_RAM_VADDR
0xC0008000
   
内核在内存中的虚拟地址
PAGE_OFFSET
0xC0000000
内核虚拟地址空间的起始地址

TEXT_OFFSET
0x00008000
内核起始位置相对于内存起始位置的偏移
PHYS_OFFSET

构架相关
物理内存的起始地址

4、因为在ARM数据处理指令中,当参与操作的第二操作数为立即数时,每个立即数都是采用一个8位的常数循环右移偶数位而间接得到,所以           

   add  r0, r4,  #(KERNEL_START & 0xff000000) >> 18
   str    r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!

这样分开写是由于arm的立即数只能是8位表示。

.type      __create_page_tables, %function
__create_page_tables:
       pgtbl      r4       @通过宏pgtbl将r4设置成页表的基地址(物理地址)

       /*页表将4GB的地址空间分成若干个1MB的段(section),因此页表包含4096个页表项(section entry)。每个页表项是32bits(4 bytes),因而页表占用4096*4=16k的内存空间。下面的代码是将这16k的页表清0。

   */
       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] @获得proc_info_list的__cpu_mm_mmu_flags的值,并存储到r7中。
   /*

*下面三行设置了kernel的section的页表项,将起始物理地址为0x30008000

*的1M内存空间映射到虚拟地址为0x30008000。

    */
   mov r6, pc, lsr #20             @通过PC值的高12位(右移20位),得到kernel的section。并存储在r6中,因为当前是通过运行时地址得到的kernel的section,因而是物理地址。
       orr   r3, r7, r6, lsl #20         @得到页表需要设置的值。
       str   r3, [r4, r6, lsl #2]        @设置页表:mem[r4+r6*4]=r3,因为页表的每一项是32bits(4字节),所以要乘以4(<<2)。
       /*

        * 因为KERNEL_START是内核的起始虚拟地址(0xC0008000),KERNEL_END为内核的结束虚拟地址,所以下面的代码实际上是将物理 地址为kernel的起始地址(0x30008000)的一段内存空间(大小为内核映像文件的大小)映射到虚拟地址0xC0008000。

        */
       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
/*

 * 如果是XIP技术的内核,上面的映射只能映射内核代码和只读数据部分
 * 这里我们再映射一些RAM来作为.data and .bss空间。

 */
#ifdef CONFIG_XIP_KERNEL
       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

       /*
        *下面的代码用来设置RAM中起始地址为0x30000000、大小为1M虚拟地址的页表,之所以要设置这个页表项的原因是该区域起始地址为0x30000100存
     *储着boot params。因此需要为它建立map,这样开启MMU后就可以访
     *问这些参数了。
       */
       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]
       mov       pc, lr    @建好页表后返回

通过__create_page_tables函数可知,启动时虚拟地址和物理地址映射关系如下图所示:




问题:为什么要把物理地址0x30008000开始的一段内存映射到虚拟地址0x30008000?
    这是因为当开启分页机制后,在执行以某一个符号为转移目标的指令之前,PC还是使用的物理地址取指令,所以把0x30008000开始的内存也临时地做相应的映射就不会有问题了。
本文版权归属:铣挖机www.no1tool.com 转载请注明,肆意删除链接,我们将保留追责权利。

posted on 2013-08-29 15:10  软件哥  阅读(788)  评论(0编辑  收藏  举报