首页 > 代码库 > Linux学习之源码1:入口流程

Linux学习之源码1:入口流程

有地方看到,启动流程是arch/arm/boot/compressed/head.s ----->调用arch/arm/boot/compressed/misc.c的decompress_kernel()
函数解压内核。---->arch/arm/kernel/head-common.S初始化 ---->init/main.c的asmlinkage void __init start_kernel(void) 

注意在arch/arm/kernel/smp.c文件中有一个启动多核处理器的函数 void __init smp_prepare_boot_cpu(void),被init/main.c的asmlinkage void __init start_kernel(void)调用

有人说是ARM Linux的启动代码有两处,一处是经过压缩的,一处是没有经过压缩的,压缩的最终还是会调用没有压缩的,没有压缩的入口在arch/arm/kernel/head.S文件中。看起来是这样的,第二种启动流程是在boot/compressed文件夹中,证实是压缩启动流程. 

以32位arm为例,在3.9.7内核版本上分析。

1、  压缩启动流程

/arch/arm/boot/compressed/head.S

1)首先定义了一系列的宏,如writeb、loadsp、kputc、kphex、debug_reloc_start、debug_reloc_end;

2)使用r0、r7、r8分别保存r0、r1、r2的值(分别为0、MachineType、ATAG指针),r9是cpsr的值; 

3)r2保存cpsr,判断最后2bit是否0,即判断是否user mode,不是的话则设置为svc模式,并把r9中的cpsr保存到spsr;

4)把最终的kernel文件的地址放在r4中,一种方式是通过pc&0xf8000000 + TEXT_OFFSET;一种方式是zreladdr;TEXT_OFFSET = ??

   zreladdr在makefile中定义,但是来龙去脉不清楚。

5)跳转到cache_on

6)adr      r0, LC0  //将LC0标签的地址赋给r0,LC0标签处的内容如下图所示

ldmia   r0, {r1, r2, r3, r6, r10, r11, r12}  //将r0地址开始的7个word数据分别保存到r1, r2, r3, r6, r10, r11, r12中

设置堆栈,是LC0开始第8个word的值

    /*
   * We might be running at a different address.  We need
   * to fix up various pointers.
   */
  sub r0, r0, r1  @ calculate the delta offset
  add r6, r6, r0  @ _edata
  add r10, r10, r0  @ inflated kernel size location

计算kernel size存放的地址并保存在r10中

 LC0:  .word LC0   @ r1
  .word __bss_start  @ r2
  .word _end   @ r3
  .word _edata   @ r6
  .word input_data_end - 4 @ r10 (inflated size location)
  .word _got_start  @ r11
  .word _got_end  @ ip
  .word .L_user_stack_end @ sp
  .size LC0, . - LC0

7)从r10开始的地址依次读取4个字节,拼成32bit存放在r9中,即是kernel size(解压缩的大小)的值;

8)分配空间在堆栈之上,堆栈最大64KB(0x10000),将sp + 0x10000的值赋给r10;

9)初始化dtb的大小为0,并保存在r5中;

10)如果有device trees (dtb) appended to zImage,一些处理,暂时先不分析

截止目前,各寄存器的内容如下图所示:

 /*
 *   r0  = delta
 *   r2  = BSS start
 *   r3  = BSS end
 *   r4  = final kernel address
 *   r5  = appended dtb size (still unknown)
 *   r6  = _edata
 *   r7  = architecture ID
 *   r8  = atags/device tree pointer
 *   r9  = size of decompressed image
 *   r10 = end of this image, including  bss/stack/malloc space if non XIP
 *   r11 = GOT start
 *   r12 = GOT end
 *   sp  = stack pointer
 *
 * if there are device trees (dtb) appended to zImage, advance r10 so that the
 * dtb data will get relocated along with the kernel if necessary.
 */

11)判断是否会地址覆盖,

r10 = r10 + 16KB;

r4 > r10则不会覆盖,否则r10 = r4 + r9,r10 < r9则不会覆盖否则就会覆盖

覆盖时的处理这里先不分析

12)orrs   r1, r0, r5   //orr逻辑与,r5为0,当r0及delta也为0,跳转到not_relocated

    beq    not_relocated //否则就需要对地址进行跳转,跳转部分我们暂时先不看了

 13)清零bss段,从r2开始的地址到r3开始的地址

 14)解压缩C代码环境准备及解压缩

     decompress_kernel(

unsigned long output_start,  //(r4中的值,移送到r0)

unsigned long free_mem_ptr_p, //(sp的值,移送到r1)

               unsigned long free_mem_ptr_end_p, //(sp值+64KB,移送到r2)

               int arch_id //(r7中的值,移送到r3)

);

分别准备4个参数,保存在r0~r3中,然后调用decompress_kernel函数

        15)刷cache、关闭cache,分别调用cache_clean_flush、cache_off

        16)再把r7、r8中的architecture number、atags pointer恢复到r1、r2中

            跳转到__enter_kernel标签,在这里,将r0恢复到0,r4中的解压缩内核地址移送到pc中,跳转到r4中,完成从压缩内核流程向非压缩内核流程转换。

2、非压缩启动流程

       代码入口是在arch\arm\kernel\head.s,然后跳转到main.c中的start_kernel。

       确保SVC模式和中断关闭

       |

       获取cpuid

       |

       判断ATAG,貌似只判断了ATAG_CORE

       |

       __create_page_tables,创建页表

       |

       将__mmap_switched地址保存在r13中

       |

       跳转到__enable_mmu,

       在__turn_mmu_on的最后跳转到r13中,即跳转到__mmap_switched

       |

       在__mmap_switched的最后 b start_kernel

 这段GCC汇编看的好痛苦,有些在as文档中都找不到,也不知道是什么意思,所以还只是个大概。

http://linux.chinaunix.net/bbs/thread-1021226-1-1.html中有详细的描述这段汇编。