ARM架构内核启动分析-head.S(1.3、stext分析之内存临时页表建立)

来源:互联网 发布:淘宝app历史版本 编辑:程序博客网 时间:2024/04/29 20:56

1.2.4、创建临时页表:

对于创建临时页表,使用的是arm的L1主页表,L1主页表也称为段页表(section page table,说白了就是采用段式管理而不是页式管理),它将4GB的地址空间分成若干个1MB的段(section),因此L1页表包含4096个页表项(section entry);每个页表项是32 bits(4 bytes), 所以L1页表占用 4096*4 = 16k的内存空间;

L1页表,实际是给4G的虚拟地址空间中每个1M空间的段,映射其对应的物理内存地址是哪里,通过在对应的页表项写入物理地址值及MMU相关内容实现,在临时页表中,并没有写满所有表项,因为这时需考虑的只有内核代码。

另外注意在此时,R8存储machine_desc的物理地址;R9存储CPU ID,R10存储procinfo。

         /*创建临时页表*/

         bl      __create_page_tables

         __create_page_tables:

                  pgtbl         r4                                 @ page table address   

通过宏pgtblr4设置成页表的物理地址,KERNEL_RAM_PADDR - 0x4000 = 0x4000,即内核的前面是页表,head.S文件前面的代码有pgtbl宏声明的地方如下:

.macro     pgtbl, rd                                  

定义了一个宏pgtbl等于寄存器rd的值

ldr    \rd, =(KERNEL_RAM_PADDR - 0x4000)              

(KERNEL_RAM_PADDR - 0x4000)这个地址值赋给rd寄存器,一般这个地址用来存放临时页表,这也就让宏pgtbl的值为(KERNEL_RAM_PADDR - 0x4000 = 0x4000)

         /*

          * Clear the 16K level 1 swapper page table

          */

                   /*1、下面是把页表清零,页表是在内核之前16KB位置*/

                  mov r0, r4         

R0也保存页表起始物理地址

                  mov r3, #0         

R3为0

                  add  r6, r0, #0x4000

R6保存页表结尾地址

1:      str    r3, [r0], #4   

R3的值(0)存储在R0指向地址处,并且R0加4即指向的地址加4

                  str    r3, [r0], #4   

不断这样操作,意即不断清零R0到R6地址处即页表,直到R0指向地址和R6相同

                  str    r3, [r0], #4

                  str    r3, [r0], #4

                  teq   r0, r6

                  bne  1b

                   /*2、把R10保存的地址再加PROCINFO_MM_MMUFLAGS后,取该地址的值赋给R7,R10先前已保存了procinfo的地址,再加8就是proc_info_list结构变量的__cpu_mm_mmu_flags成员地址,取该成员的值赋给R7,这个成员的具体意义与arm MMU相关,需要仔细查看arm手册,这是下一阶段学习的重点!*/

                   ldr    r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

         /*

          * Create identity mapping for first MB of kernel to

          * cater for the MMU enable.  This identity mapping

          * will be removed by paging_init().  We use our current program

          * counter to determine corresponding section base address.

          */

                   /*3、下面这堆内容,其实是把L1页表中当前运行地址所在的段(当前以汇编代码运行的内核代码段,运行在物理内存起初的部分,取值应在0x0到0x00100000范围内,可以看看KERNEL_RAM_PADDR值为0x00008000,可见从L1页表的分段角度,在第一个段即地址0x0-0x00100000段范围内)的表项写入映射关系,事实上重点是理解为什么要把这里也做映射:

           之所以要为这个物理地址的段也做映射,是因为现在MMU没开启,过一会就会开启MMU,按说内核代码段的内容在做了映射后,CPU给MMU的PC值是虚拟地址,但由于CPU流水线的预取指功能,还是会有一些PC值还不是虚拟地址而仍然是物理地址,这样的话如果MMU内部没有相应的映射关系,将不知道访问实际物理内存哪部分,导致出现问题,所以这里需要把这部分物理地址在L1页表的相应表项也写入,本质是PA = PA(很多参考文章写PA = VA,其实道理是一样的,但那样表述容易给人以误解,因为开启MMU后,CPU发出的访问按说都是虚拟地址,但确实有一些其实还是物理地址,如果不做映射将让MMU无从知道该访问哪里,所以这里写L1页表的相关物理页表项,其实是做PA = PA的映射给MMU)*/

         mov r6, pc                                              

R6获取到PC值

         /*然后获取到kernel的section的物理地址保存在R6,R6 = 0x00008xxx(PC当前值)*/

         mov r6, r6, lsr #20                     @ start of kernel section  

通过pc值的高12位(右移20位),得到kernel的section,并存储到r6中,                                                     因为当前是通过运行时地址得到的kernel的section,因而是物理地址,                                                        所以R6 = 0x00008xxx << 20 = 0

    /*然后获取到写页表应该写什么值,并保存在R3,R3 = R7 + 0 = R7 = PROCINFO_MM_MMUFLAGS,R6刚才已保存了当前正在执行的内核代码(PC)所在的段值,这里左移20位得到该段地址起始值(这里即0x00000000,含义即物理地址的kernel base值),      再加上R7即PROCINFO_MM_MMUFLAGS,这个就是要写入L1页表表项的内容(具体为什么填入这个和MMU有关,还需后续仔细研究!)*/

         orr    r3, r7, r6, lsl #20                @ flags + kernel base      

flags + kernel base,得到页表中需要设置的值保存在R3

         /*接下来实际写页表,每个L1页表的表项长度为4字节,为了定位这个页表项在L1页表的位置或者说算出其在L1页表的偏移,所以左移2位即乘以4得出偏移值,再加上基地址R4,得出写在哪个L1页表的表项*/

         str    r3, [r4, r6, lsl #2]               @ identity mapping     

R4已保存了页表物理起始地址,R6保存了页表物理结束地址,上一步R3也保存了需要设置页表的值,这里把R3的值写入从R4+R6*4 = R4字节空间处,即写页表的第一个条目的值为R3值。

         /*

          * Now setup the pagetables for our kernel direct

          * mapped region.

          */

         /*4、下面这堆内容,核心目的是为kernel镜像(即内核代码段)做L1页表映射,即KERNL_START到KERNEL_END建立内存映射,这两个宏是编译时产生的,所以它们是虚拟地址,而内核代码段在物理内存的实际位置是从物理内存起初的部分(L1页表角度看就是在第一个段内),后续要开启MMU了,即CPU发给MMU的地址将是虚拟地址了,所以这里要先把内核代码做映射,映射方式方法和前面是一样的,把虚拟的0xc0008000到KERNEL_END(一般内核代码长度为3-4M,所以KERNEL_END值估计为0xc03XXXXX),映射到物理地址的0x0到0x3段内(假定内核代码为3-4M长度大小)*/

         add  r0, r4,  #(KERNEL_START & 0xff000000) >> 18    

R4已保存了页表物理起始地址,对于(KERNEL_START & 0xff000000),这是为了获取高12bit位的段值(为什么不直接以0xfff00000去与?),

所谓右移18位,其实就是先右移20位获取到段值,然后左移2位取得所在表项在L1页表的偏移值(因为每个表项占4个字节),

最后加上R4即L1页表物理基地址,得出该表项实际的物理地址保存在R0(实际值为0x20004000+0xC00*4 = 0x20007000)

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

R3即内核物理起始地址段值和PROCINFO_MM_MMUFLAGS的或运算结果即映射结果写入内核起始虚拟地址所在的L1表项,

对于我们这里就是写入的是0x0与PROCINFO_MM_MMUFLAGS的或运算结果,这里的疑问是:为什么不一开始就用0xfff00000去与运算,而是分两次?

         ldr    r6, =(KERNEL_END - 1)                          

r6为内核代码的尾部虚拟地址

         add  r0, r0, #4                                     

R0保存下一个即将要填写的L1页表项的物理地址

         add  r6, r4, r6, lsr #18                             

R6本身是内核代码的尾部虚拟地址,右移18位(右移20再左移2,先获取段值然后算出所在L1页表项的偏移),再加上R4值,最后R6值是内核代码结尾的所在L1页表的表项的物理地址

1:      cmp r0, r6

         add  r3, r3, #1 << 20                               

R0是内核代码起始处所在L1页表项物理地址,R6是内核代码结尾处所在L1页表项物理地址,R3是内核代码起始处对应的物理地址映射值;

         strls r3, [r0], #4                               

之后的内核代码对应的物理地址映射值将是其段值相应加1,即0xc00-0x000、0xc01-0x001、0xc02-0x002、0xc03-0x003.....

         bls    1b                                             

对于L1页表项,其实是写第0xc00、0xc01、0xc02、0xc03个条目的表项内容(物理地址在0x20007000、0x20007004、0x20007008、0x2000700c)

接下来的一段由宏CONFIG_XIP_KERNEL定制的代码无需关注;

/ *

* Then map first 1MB of ram in case it contains our boot params.

*/

/*5、通常kernel的启动参数由bootloader放到了物理内存的第1个M上,所以需要为RAM上的第1个M建立映射下面这堆内容,核心目的就是确保RAM的第1个M建立映射;

 事实上上面已为PHYS_OFFSET + TEXT_OFFSET建立了映射(如果TEXT_OFFSET小于0x00100000即1MB的话),我们这里TEXT_OFFSET值为0x8000,即也为SDRAM的第一个M建立了映射;

         说白了,这里的用意就是说无论上面代码如何,均为SDRAM的第一个M建立映射,事实上是为了考虑TEXT_OFFSET值大于0x00100000即1MB的情况,即内核起始代码超出了第一个段的范围的情况*/

         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]

接下来的一堆宏定制的代码无需关注,最后是返回stext,这时临时页表建立成功,但仅仅是建立成功还未真正使用,真正使用还在后面。

/*6、返回函数stext调用__create_page_tables的地方*/

         mov pc, lr

临时页表创建后:

R4: 页表起始处物理地址

      R8: machine info,struct machine_desc的基地址

      R9: cpu id

      R10: procinfo,struct proc_info_list的基地址

并且映射了如下部分的页表映射: 物理内存第一MB(自己映射自己,因为开启MMU后仍有一些访问是以物理地址发到MMU)、内核代码段(基本是0xc00段到0xc03段,映射至0x0段到0x003段), 后续准备开启MMU!*/