linux内核启动过程分析(一) arm

时间:2022-03-12 15:57:09

转载:http://chxxxyg.blog.163.com/blog/static/150281193201072603030285/



文件linux/arch/arm/boot/compressed/head.S是linux内核启动过程执行的第一个文件。
  .align
start:
  .type start,#function  #function //type指定start这个符号是函数类型
  .rept 8   //重复8次 mov r0, r0,
  mov r0, r0 //空操作,让前面所取指令得以执行。
  .endr

  b 1f  //跳转

/*

魔数0x016f2818是在bootloader中用于判断zImage的存在,

而zImage的判别的magic number为0x016f2818,这个也是内核和bootloader约定好的。

*/
  .word 0x016f2818  
  .word start   
  .word _edata   

////r1和r0中分别存放着由bootloader传递过来的architecture ID和指向标记列表的指针。
1:  mov r7, r1  
  mov r8, r2   

/*

这也标志着u-boot将系统完全的交给了OS,bootloader生命终止。之后会读取

cpsr并判断是否处理器处于supervisor模式——从u-boot进入kernel,系统已经处于SVC32模式;

而利用angel进入则处于user模式,还需要额外两条指令。之后是再次确认中断关闭,并完成cpsr写入

*/

#ifndef __ARM_ARCH_2__
  mrs r2, cpsr  @ get current mode
  tst r2, #3   @ not user?
  bne not_angel
  mov r0, #0x17  //0x17是angel_SWIreason_EnterSVC半主机操作
  swi 0x123456  //0x123456是arm指令集的半主机操作编号
not_angel:
  mrs r2, cpsr  
  orr r2, r2, #0xc0  
  msr cpsr_c, r2 ////这里将cpsr中I、F位分别置“1”,关闭IRQ和FIQ
#else
  teqp pc, #0x0c000003  @ turn off interrupts
#endif

/*

LC0表是链接文件arch/arm/boot/compressed/vmlinux.lds

(这个lds文件是由arch/arm/boot/compressed/vmlinux.lds.in生成的)的各段入口。

ENTRY(_start)
SECTIONS
{
  /DISCARD/ : {
    *(.ARM.exidx*)
    *(.ARM.extab*)
  }

  . = TEXT_START;
  _text = .;

  .text : {

………………

展开如下表:

假定zImage在内存中的初始地址为0x30008000(这个地址由bootloader决定,位置不固定)1、初始状态

linux内核启动过程分析(一)  arm 链接文件arch/arm/boot/compressed/vmlinux.lds中的连接地址都是位置无关的,即都是以0地址为偏移的。 而此时内核已被bootloader搬移到了SDRAM中。链接地址应该加上这个偏移。

 */

  .text

//指令adr是基于PC的值来获取标号LC0 的地址的,由于内核已被搬移,标号LC0的地址不是以地址0为偏移的

//这中间就存在一个固定的地址偏移,在s3c2410中是0x30008000.
  adr r0, LC0  
  ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}
  subs r0, r0, r1  //这里获得当前运行地址与链接地址的偏移量,存入r0中为0x30008000.

 //如果不需要重定位,即内核没有进行过搬移,就跳转。如果内核代码是存于NANDflash中的

//是需要搬移的。
  beq not_relocated 

  add r5, r5, r0 //修改内核映像基地址此时r5=0x30008000
  add r6, r6, r0 //修改got表的起始和结束位置
  add ip, ip, r0

#ifndef CONFIG_ZBOOT_ROM
//S3C2410平台是需要一下调整的,将bss段以及堆栈的地址都进行调整
  add r2, r2, r0
  add r3, r3, r0
  add sp, sp, r0

//修改GOT(全局偏移表)表。根据当前的运行地址,修正该表  
1:  ldr r1, [r6, #0]  @ relocate entries in the GOT
  add r1, r1, r0  @ table.  This fixes up the
  str r1, [r6], #4  @ C references.
  cmp r6, ip
  blo 1b
#else

//S3C2410平台不会进入该分支,只对got表中在bss段以外的符号进行重定位
1:  ldr r1, [r6, #0]  @ relocate entries in the GOT
  cmp r1, r2   @ entry < bss_start ||
  cmphs r3, r1   @ _end < entry
  addlo r1, r1, r0  @ table.  This fixes up the
  str r1, [r6], #4  @ C references.
  cmp r6, ip
  blo 1b
#endif

//下面的代码,如果运行当前运行地址和链接地址相等,则不需进行重定位。直接清除bss段

not_relocated: mov r0, #0
1:  str r0, [r2], #4    //清BSS段,所有的arm程序都需要做这些的
  str r0, [r2], #4
  str r0, [r2], #4
  str r0, [r2], #4
  cmp r2, r3
  blo 1b


  bl cache_on  //打开cache

  mov r1, sp   @ malloc space above stack
  add r2, sp, #0x10000 //分配一段解压函数需要的内存缓冲

/*

head.S调用misc.c中的decompress_kernel刚解压完内核后,还没有进行重定位。

linux内核启动过程分析(一)  arm

*/
  cmp r4, r2

//r4为内核执行地址,此时为0X30008000,r2此时为用户栈定,即解压函数所需内存缓冲的开始处,

//显然r4 < r2所以不会跳转。
  bhs wont_overwrite 
  sub r3, sp, r5  //r5是内核映像的开始地址0X30008000,sp为用户栈的栈顶,他们之差就是映像大小。

//将这个大小值乘以4,因为映像解压后不会超过解压前的4倍大小。

//r4为解压后内核开始地址,此时为0X30008000,r5为解压前映存放的开始位置,此时也为0X30008000。

//下面的判断是,看解压后的内核是不是会覆盖未解压的映像。显然是覆盖的,所以是不会跳转的。

  add r0, r4, r3, lsl #2 
  cmp r0, r5
  bls wont_overwrite

  mov r5, r2   //此时r2为解压函数缓冲区的尾部地址。
  mov r0, r5  //r0为映像解压后存放的起始地址,解压后的内核就紧接着存放在解压函数缓冲区的尾部。
  mov r3, r7 //r7中存放的是architecture ID,对于SMDK2410这个值为193; 

/*

解压函数是用C语言实现的在文件arch/arm/boot/compressed/misc.c中。

解压函数的是个参数是有r0~r3传入的。r0~r3的值在上面已初始化,再对照上面表格就很清楚了。

decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p,int arch_id)

  output_start:指解压后内核输出的起始位置,此时它的值参考上面的图表,紧接在解压缓冲区后;

  free_mem_ptr_p:解压函数需要的内存缓冲开始地址;

 ulg free_mem_ptr_end_p:解压函数需要的内存缓冲结束地址,共64K;

arch_id :architecture ID,对于SMDK2410这个值为193; 

*/
  bl decompress_kernel

 

//解压完毕后,内核长度返回值存放在r0寄存器里。在内核末尾空出128字节的栈空间用,

//并且使其长度128字节对齐。

  add r0, r0, #127 + 128 
  bic r0, r0, #127  
/*
 * r0     = 解压后内核的长度
 * r1-r3  = 没使用

 * r4     = 内核执行地址
 * r5     = 解压后内核的起始地址,如上面初始化 mov r5, r2

 * r6     = 处理器ID
 * r7     = 体系结构 ID
 * r8     = 标记列表地址

 * r9-r14 = corrupted
 */

/*

上面只是将内核临时解压到了一个位置,下面还要将它重定位到0X30008000处。

标号reloc_start下面有一段重定位内核的程序。为了在内核重定位的过程中不至于将这段用于

重定位的代码给覆盖了,就先将这段用于内核重定位的代码搬到另一个地方,如下表。

linux内核启动过程分析(一)  arm

 */
  add r1, r5, r0   //r1就是解压后内核代码的结束位置,下面就是将这段重定位代码搬移到r1地址处。
  adr r2, reloc_start //重定位代码起始地址
  ldr r3, LC1  //用于内核重定位的代码的长度
  add r3, r2, r3 //重定位代码的结束地址
1:  ldmia r2!, {r9 - r14}  //将这段重定位代码搬移到r1地址处,如上表。
  stmia r1!, {r9 - r14}
  ldmia r2!, {r9 - r14}
  stmia r1!, {r9 - r14}
  cmp r2, r3
  blo 1b
  add sp, r1, #128  //改变堆栈指针位置。

//搬移完成后刷新cache,因为代码地址变化了不能让cache再命中被内核覆盖的老地址。

  bl cache_clean_flush
  add pc, r5, r0  //r0 + r5 就是被搬移后的内核重定位代码的开始位置,reloc_start。下面将会讲到。

 

//如果内核映像没有被bootloader搬移过,上面程序就会跳到此处。

wont_overwrite: mov r0, r4
  mov r3, r7
  bl decompress_kernel
  b call_kernel

//这个表与文件arch/arm/kernel/vmlinux.lds.S

//这个lds文件是由arch/arm/boot/compressed/vmlinux.lds.in生成的)

  .type LC0, #object  //
LC0:  .word LC0   @ r1
  .word __bss_start  @ r2
  .word _end   @ r3
  .word zreladdr  @ r4
  .word _start   @ r5
  .word _got_start  @ r6
  .word _got_end  @ ip
  .word user_stack+4096  @ sp
LC1:  .word reloc_end - reloc_start
  .size LC0, . - LC0

 

下面是将解压后的内核代码重定位。

linux内核启动过程分析(一)  arm

 //将解压后的内核代码搬到0X30008000处。

  .align 5
reloc_start: add r9, r5, r0  // r0 + r5就是解压后内核代码的结束位置加128字节栈空间。
  sub r9, r9, #128  @ do not copy the stack
  debug_reloc_start
  mov r1, r4 //r4为内核执行地址,即为0X30008000。
1:
  .rept 4  //将解压后的内核搬到r1处,即0X30008000处。

  ldmia r5!, {r0, r2, r3, r10 - r14} @ relocate kernel
  stmia r1!, {r0, r2, r3, r10 - r14}
  .endr

  cmp r5, r9
  blo 1b
  add sp, r1, #128  @ relocate the stack
  debug_reloc_end

//清除并关闭cache,清零r0,将machine ID存入r1,atags指针存入r2,再跳入0x30008000执行真正的内核Image

call_kernel: bl cache_clean_flush
  bl cache_off
  mov r0, #0   @ must be zero
  mov r1, r7   @ restore architecture number
  mov r2, r8   @ restore atags pointer
  mov pc, r4   @ call kernel

 

//内核映像zImage的组成,它是由一个压缩后的内核piggy.o,连接上一段初始化及解压功能的代码

//(head.o misc.o),组成的。

//此时内核解压已经完成。内核启动要执行的第二个文件就是arch/arm/kernel/head.S文件。