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

6140阅读 0评论2013-08-11 星闪夜空
分类:LINUX

 

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

1head.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       @通过宏pgtblr4设置成页表的基地址(物理地址)

       /*页表将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中。

      

   /*

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

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

    */

   mov r6, pc, lsr #20             @通过PC值的高12(右移20),得到kernelsection。并存储在r6中,因为当前是通过运行时地址得到的kernelsection,因而是物理地址。

       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开始的内存也临时地做相应的映射就不会有问题了。


    由于笔者能力有限,文中难免存在不足,如果发现错误之处,希望能帮助我及时指正。

上一篇:移植madplay
下一篇:Linux伙伴算法