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

转载自https://blog.csdn.net/skyflying2012/article/details/41447843


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之前如何初始化页表。此处分析过程我都写在对应的代码处,方便查看。


[cpp]  view plain  copy
  1. #ifdef CONFIG_ARM_LPAE  
  2.     /* LPAE requires an additional page for the PGD */  
  3. #define PG_DIR_SIZE 0x5000  
  4. #define PMD_ORDER   3  
  5. #else  
  6. #define PG_DIR_SIZE 0x4000  
  7. #define PMD_ORDER   2  
  8. #endif  
  9. .....  
  10.     .macro  pgtbl, rd, phys  
  11.     add \rd, \phys, #TEXT_OFFSET - PG_DIR_SIZE  
  12.     .endm  
  13. .....  
  14. __create_page_tables:  
  15.     //据上篇博文分析,r8存储着sdram的物理起始地址(我的板子0x80000000)  
  16.     //pgtbl宏获取0x80008000之下16K的地址空间作为页表空间  
  17.     //arm页表一页是4 bytes,完成虚拟地址空间4GB中1MB的映射,  
  18.     //一共需要4 x 4096 bytes的页表空间  
  19.     //可以看出,单页完成的是虚拟地址和物理地址高12位的转换。  
  20.     //低20位的地址(1M内的地址)偏移是一致的。  
  21.     pgtbl   r4, r8              @ page table address  
  22.     /* 
  23.      * Clear the swapper page table 
  24.      */  
  25.     //按照16bytes一页将16K页表空间清空  
  26.     mov r0, r4  
  27.     mov r3, #0  
  28.     add r6, r0, #PG_DIR_SIZE  
  29. 1:  str r3, [r0], #4  
  30.     str r3, [r0], #4  
  31.     str r3, [r0], #4  
  32.     str r3, [r0], #4  
  33.     teq r0, r6  
  34.     bne 1b  
  35.   
  36.     //如果定义CONFIG_ARM_LPAE,在PGD与PMD之前还要再加一级页表,这里不详解这种情景  
  37. #ifdef CONFIG_ARM_LPAE  
  38.     /* 
  39.      * Build the PGD table (first level) to point to the PMD table. A PGD 
  40.      * entry is 64-bit wide. 
  41.      */  
  42.     mov r0, r4  
  43.     add r3, r4, #0x1000         @ first PMD table address  
  44.     orr r3, r3, #3          @ PGD block type  
  45.     mov r6, #4              @ PTRS_PER_PGD  
  46.     mov r7, #1 << (55 - 32)     @ L_PGD_SWAPPER  
  47. 1:  str r3, [r0], #4            @ set bottom PGD entry bits  
  48.     str r7, [r0], #4            @ set top PGD entry bits  
  49.     add r3, r3, #0x1000         @ next PMD table  
  50.     subs    r6, r6, #1  
  51.     bne 1b  
  52.   
  53.     add r4, r4, #0x1000         @ point to the PMD tables  
  54. #endif  
  55.     //据上篇博文分析,r10中存储该CPU的processor_type_list(处理器信息结构体),获取该CPU的mmuflags  
  56.     ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags  
  57.   
  58.     /* 
  59.      * Create identity mapping to cater for __enable_mmu. 
  60.      * This identity mapping will be removed by paging_init(). 
  61.      */  
  62.     //首先建立包含turn_mmu_on函数1M空间的平映射(virt addr = phy addr)  
  63.     //turn_mmu_on距stext不远,所以实际完成0x8000000-0x81000000空间的平映射  
  64.       
  65.     //老方法,上篇博文分析过,获取phy到virt的offset  
  66.     adr r0, __turn_mmu_on_loc  
  67.     ldmia   r0, {r3, r5, r6}  
  68.     sub r0, r0, r3          @ virt->phys offset  
  69.     //获取turn_mmu_on的首尾物理地址  
  70.     add r5, r5, r0          @ phys __turn_mmu_on  
  71.     add r6, r6, r0          @ phys __turn_mmu_on_end  
  72.     //因1页映射1M空间,所以SECTION_SHIFT为20  
  73.     //右移20位后,r5,r6代表该段地址空间的物理地址页号,因为是平映射,也代表了页表中的须知下标,即虚拟地址页号!  
  74.     mov r5, r5, lsr #SECTION_SHIFT  
  75.     mov r6, r6, lsr #SECTION_SHIFT  
  76.   
  77.     //r5左移20位,获取该页基地址,或上CPU的mmuflags,存在r3中  
  78. 1:  orr r3, r7, r5, lsl #SECTION_SHIFT  @ flags + kernel base  
  79.     //将r3值存储在页表空间(r4起始)的(r5<<4)的页表中  
  80.     //因一页用4bytes表示,所以PMD_ORDER=2  
  81.     str r3, [r4, r5, lsl #PMD_ORDER]    @ identity mapping  
  82.     //r5与r6之前相距多个1M,则需要填写多个页表。  
  83.     //因turn_mmu_on函数很短,所以肯定在1M内,该处r5=r6  
  84.     cmp r5, r6  
  85.     addlo   r5, r5, #1          @ next section  
  86.     blo 1b  
  87.     //从上面这次填页表的过程可以看出,16KB的页表以虚拟地址页号为寻址下标,覆盖整个虚拟的4G地址空间  
  88.   
  89.     /* 
  90.      * Now setup the pagetables for our kernel direct 
  91.      * mapped region. 
  92.      */  
  93.     //接下来以多个1M的线性映射页表,建立kernel整个镜像的线性映射,((0x80000000-0x80000000+kernel_end)-(0xc0000000-0xc0000000+kernel_end))  
  94.     //开启MMU之后就实现了链接地址(0xc0008000)与运行地址(0xc0008000)的统一  
  95.       
  96.     //这里有一个小技巧,利用当前PC值作为内核物理地址起始,create_page_tables距离内核起始地址不超过1MB,因此移位之后就是内核起始的物理页号。  
  97.     //arm的create_page_tables中,不管是turn_mmu_on还是这里,都是使用的当前pc值计算物理页号,  
  98.     //这样的好处是,不管内核加载到什么物理地址,都可以迅速的建立正确的页表映射。并且不需要内核开发人员对这部分代码进行修改  
  99.     mov r3, pc  
  100.     mov r3, r3, lsr #SECTION_SHIFT  
  101.     orr r3, r7, r3, lsl #SECTION_SHIFT  
  102.     //将该1M空间的物理起始地址存储到页表中相应虚拟地址页中  
  103.     add r0, r4,  #(KERNEL_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)  
  104.     str r3, [r0, #((KERNEL_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]!  
  105.     ldr r6, =(KERNEL_END - 1)  
  106.     add r0, r0, #1 << PMD_ORDER  
  107.     add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)  
  108. 1:  cmp r0, r6  
  109.     add r3, r3, #1 << SECTION_SHIFT  
  110.     strls   r3, [r0], #1 << PMD_ORDER  
  111.     bls 1b  
  112. #ifdef CONFIG_XIP_KERNEL  
  113.     /* 
  114.      * Map some ram to cover our .data and .bss areas. 
  115.      */  
  116.     add r3, r8, #TEXT_OFFSET  
  117.     orr r3, r3, r7  
  118.     add r0, r4,  #(KERNEL_RAM_VADDR & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)  
  119.     str r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> (SECTION_SHIFT - PMD_ORDER)]!  
  120.     ldr r6, =(_end - 1)  
  121.     add r0, r0, #4  
  122.     add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)  
  123. 1:  cmp r0, r6  
  124.     add r3, r3, #1 << 20  
  125.     strls   r3, [r0], #4  
  126.     bls 1b  
  127. #endif  
  128.   
  129.     /* 
  130.      * Then map boot params address in r2 or the first 1MB (2MB with LPAE) 
  131.      * of ram if boot params address is not specified. 
  132.      */  
  133.     //将atags的1M地址空间做线性映射,方便start_kernel中对args进行分析  
  134.     //据上篇博文分析,r2中存储着bootloader传来的atag基地址(我的板子在0x80000100)  
  135.     //所以该1M空间是0x80000000-0x81000000,映射到0xc0000000-0xc1000000  
  136.     mov r0, r2, lsr #SECTION_SHIFT  
  137.     movs    r0, r0, lsl #SECTION_SHIFT  
  138.     moveq   r0, r8  
  139.     sub r3, r0, r8  
  140.     add r3, r3, #PAGE_OFFSET  
  141.     add r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)  
  142.     orr r6, r7, r0  
  143.     str r6, [r3]  
  144.   
  145. //如果需要早期串口输出进行调试,在这里进行I/O空间的映射,从而实现可以对串口控制器的操作,这里不详解了。  
  146. #ifdef CONFIG_DEBUG_LL  
  147. #if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING)  
  148.     /* 
  149.      * Map in IO space for serial debugging. 
  150.      * This allows debug messages to be output 
  151.      * via a serial console before paging_init. 
  152.      */  
  153.     addruart r7, r3, r0  
  154.   
  155.     mov r3, r3, lsr #SECTION_SHIFT  
  156.     mov r3, r3, lsl #PMD_ORDER  
  157.   
  158.     add r0, r4, r3  
  159.     rsb r3, r3, #0x4000         @ PTRS_PER_PGD*sizeof(long)  
  160.     cmp r3, #0x0800         @ limit to 512MB  
  161.     movhi   r3, #0x0800  
  162.     add r6, r0, r3  
  163.     mov r3, r7, lsr #SECTION_SHIFT  
  164.     ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags  
  165.     orr r3, r7, r3, lsl #SECTION_SHIFT  
  166. #ifdef CONFIG_ARM_LPAE  
  167.     mov r7, #1 << (54 - 32)     @ XN  
  168. #else  
  169.     orr r3, r3, #PMD_SECT_XN  
  170. #endif  
  171. 1:  str r3, [r0], #4  
  172. #ifdef CONFIG_ARM_LPAE  
  173.     str r7, [r0], #4  
  174. #endif  
  175.     add r3, r3, #1 << SECTION_SHIFT  
  176.     cmp r0, r6  
  177.     blo 1b  
  178.   
  179. #else /* CONFIG_DEBUG_ICEDCC || CONFIG_DEBUG_SEMIHOSTING */  
  180.     /* we don't need any serial debugging mappings */  
  181.     ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags  
  182. #endif  
  183. #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)  
  184.     /* 
  185.      * If we're using the NetWinder or CATS, we also need to map 
  186.      * in the 16550-type serial port for the debug messages 
  187.      */  
  188.     add r0, r4, #0xff000000 >> (SECTION_SHIFT - PMD_ORDER)  
  189.     orr r3, r7, #0x7c000000  
  190.     str r3, [r0]  
  191. #endif  
  192. #ifdef CONFIG_ARCH_RPC  
  193.     /* 
  194.      * Map in screen at 0x02000000 & SCREEN2_BASE 
  195.      * Similar reasons here - for debug.  This is 
  196.      * only for Acorn RiscPC architectures. 
  197.      */  
  198.     add r0, r4, #0x02000000 >> (SECTION_SHIFT - PMD_ORDER)  
  199.     orr r3, r7, #0x02000000  
  200.     str r3, [r0]  
  201.     add r0, r4, #0xd8000000 >> (SECTION_SHIFT - PMD_ORDER)  
  202.     str r3, [r0]  
  203. #endif  
  204. #endif  
  205. #ifdef CONFIG_ARM_LPAE  
  206.     sub r4, r4, #0x1000     @ point to the PGD table  
  207. #endif  
  208.     mov pc, lr  
  209. ENDPROC(__create_page_tables)  
  210.     .ltorg  
  211.     .align  
  212. __turn_mmu_on_loc:  
  213.     .long   .  
  214.     .long   __turn_mmu_on  
  215.     .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!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值