ARM linux kernel启动流程 head.S(一)

2019-07-12 16:56发布

本文的原地址为:http://blog.csdn.net/tommy_wxie/article/details/7238748 在此表示感谢

1. kernel运行的史前时期和内存布局

在arm平台下,zImage.bin压缩镜像是由bootloader加载到物理内存,然后跳到zImage.bin里一段程序,它专门于将被压缩的kernel解压缩到KERNEL_RAM_PADDR开始的一段内存中,接着跳进真正的kernel去执行。该kernel的执行起点是stext函数,定义于arch/arm/kernel/head.S。

在分析stext函数前,先介绍此时内存的布局如下图所示 
  在开发板tqs3c2440中,SDRAM连接到内存控制器的Bank6中,它的开始内存地址是0x30000000,大小为64M,即0x20000000。 ARM Linuxkernel将SDRAM的开始地址定义为PHYS_OFFSET。经bootloader加载kernel并由自解压部分代码运行后,最终kernel被放置到KERNEL_RAM_PADDR(=PHYS_OFFSET + TEXT_OFFSET,即0x30008000)地址上的一段内存,经此放置后,kernel代码以后均不会被移动。 在进入kernel代码前,即bootloader和自解压缩阶段,ARM未开启MMU功能。因此kernel启动代码一个重要功能是设置好相应的页表,并开启MMU功能。为了支持MMU功能,kernel镜像中的所有符号,包括代码段和数据段的符号,在链接时都生成了它在开启MMU时,所在物理内存地址映射到的虚拟内存地址。 以arm kernel第一个符号(函数)stext为例,在编译链接,它生成的虚拟地址是0xc0008000,而放置它的物理地址为0x30008000(还记得这是PHYS_OFFSET+TEXT_OFFSET吗?)。实际上这个变换可以利用简单的公式进行表示:va = pa – PHYS_OFFSET + PAGE_OFFSET。Arm linux最终的kernel空间的页表,就是按照这个关系来建立。 之所以较早提及arm linux 的内存映射,原因是在进入kernel代码,里面所有符号地址值为清一 {MOD}的0xCXXXXXXX地址,而此时ARM未开启MMU功能,故在执行stext函数第一条执行时,它的PC值就是stext所在的内存地址(即物理地址,0x30008000)。因此,下面有些代码,需要使用地址无关技术。

2. 一览stext函数

这里的启动流程指的是解压后kernel开始执行的一部分代码,这部分代码和ARM体系结构是紧密联系在一起的,所以最好是将ARM ARCHITECTURE REFERENCE MANUL仔细读读,尤其里面关于控制寄存器啊,MMU方面的内容~ stext函数定义在Arch/arm/kernel/head.S,它的功能是获取处理器类型和机器类型信息,并创建临时的页表,然后开启MMU功能,并跳进第一个C语言函数start_kernel。 stext函数的在前置条件是:MMU, D-cache, 关闭; r0 = 0, r1 = machine nr, r2 = atags prointer.       前面说过解压以后,代码会跳到解压完成以后的vmlinux开始执行,具体从什么地方开始执行我们可以看看生成的vmlinux.lds(arch/arm/kernel/)这个文件: [cpp] view plain copy  print?
  1. 1. OUTPUT_ARCH(arm)    
  2. 2. ENTRY(stext)    
  3. 3. jiffies = jiffies_64;    
  4. 4. SECTIONS    
  5. 5. {    
  6. 6.  . = 0x80000000 + 0x00008000;    
  7. 7.  .text.head : {     
  8. 8.   _stext = .;    
  9. 9.   _sinittext = .;    
  10. 0.   *(.text.h    

很明显我们的vmlinx最开头的section是.text.head,这里我们不能看ENTRY的内容,以为这时候我们没有操作系统,根本不知道如何来解析这里的入口地址,我们只能来分析他的section(不过一般来说这里的ENTRY和我们从seciton分析的结果是一样的),这里的.text.head section我们很容易就能在arch/arm/kernel/head.S里面找到,而且它里面的第一个符号就是我们的stext: [cpp] view plain copy  print?
  1. # .section ".text.head", "ax"    
  2. #     
  3. # ENTRY(stext)   
  4. #     
  5. #  /* 设置CPU运行模式为SVC,并关中断 */    
  6. #     
  7. #   msr  cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode    
  8. #     
  9. #                                      @ and irqs disabled    
  10. #     
  11. #   mrc p15, 0, r9, c0, c0        @ get processor id    
  12. #     
  13. #   bl    __lookup_processor_type         @ r5=procinfo r9=cupid    
  14. #     
  15. # /* r10指向cpu对应的proc_info记录 */    
  16. #     
  17. #    movs  r10, r5                         @ invalid processor (r5=0)?    
  18. #     
  19. #   beq __error_p                    @ yes, error 'p'    
  20. #     
  21. #   bl    __lookup_machine_type            @ r5=machinfo    
  22. #     
  23. # /* r8 指向开发板对应的arch_info记录 */    
  24. #     
  25. #    movs  r8, r5                           @ invalid machine (r5=0)?    
  26. #     
  27. #   beq __error_a                    @ yes, error 'a'    
  28. #     
  29. # /* __vet_atags函数涉及bootloader造知kernel物理内存的情况,我们暂时不分析它。 */    
  30. #     
  31. #   bl    __vet_atags    
  32. #     
  33. # /*  创建临时页表 */    
  34. #     
  35. #   bl    __create_page_tables    
  36.     
  37. #   /*   
  38. #    
  39. #    * The following calls CPU specific code in a position independent   
  40. #    
  41. #    * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of   
  42. #    
  43. #    * xxx_proc_info structure selected by __lookup_machine_type   
  44. #    
  45. #    * above.  On return, the CPU will be ready for the MMU to be   
  46. #    
  47. #    * turned on, and r0 will hold the CPU control register value.   
  48. #    
  49. #    */    
  50. #     
  51. #  /* 这里的逻辑关系相当复杂,先是从proc_info结构中的中跳进__arm920_setup函数,   
  52. #    
  53. #   * 然后执__enable_mmu 函数。最后在__enable_mmu函数通过mov pc, r13来执行__switch_data,   
  54. #    
  55. #   * __switch_data函数在最后一条语句,鱼跃龙门,跳进第一个C语言函数start_kernel。   
  56. #    */    
  57. #     
  58. #   ldr   r13, __switch_data             @ address to jump to after    
  59. #     
  60. #                                      @ mmu has been enabled    
  61. #     
  62. #   adr  lr, __enable_mmu        @ return (PIC) address    
  63. #     
  64. #   add pc, r10, #PROCINFO_INITFUNC    
  65. #     
  66. # ENDPROC(stext)  

 这里的ENTRY这个宏实际我们可以在include/linux/linkage.h里面找到,可以看到他实际上就是声明一个GLOBAL Symbol,后面的ENDPROC和END唯一的区别是前面的声明了一个函数,可以在c里面被调用。 [cpp] view plain copy  print?
  1.  1. #ifndef ENTRY    
  2.  2. #define ENTRY(name) /    
  3.  3.   .globl name; /    
  4.  4.   ALIGN; /    
  5.  5.   name:    
  6.  6. #endif    
  7.  7. #ifndef WEAK    
  8.  8. #define WEAK(name)     /    
  9.  9.     .weak name;    /    
  10. 10.     name:    
  11. 11. #endif    
  12. 12. #ifndef END    
  13. 13. #define END(name) /    
  14. 14.   .size name, .-name    
  15. 15. #endif