arm linux内核启动过程详解

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

         可以结合《hi3536uboot引导内核全过程》一文一起看

1、uImage生成过程

(1)vmlinux

根目录下vmlinux为kernel未经过任何处理的原始可执行文件。根据arch/arm/kernel/vmlinux.lds连接文件生成:

         . = PAGE_OFFSET + TEXT_OFFSET; =0xC0000000 + 0x8000    内核运行时虚拟起始地址(3G/1G内核情况,如果2G/2G则为0x80000000+0x8000)

#definePAGE_OFFSET     UL(CONFIG_PAGE_OFFSET)

textofs-y  := 0x00008000

TEXT_OFFSET:= $(textofs-y)   (arch/arm/Makefile文件中)

 

(2)uImage依赖关系

在arch/arm/boot/Makefile:

$(obj)/uImage:  $(obj)/zImage

$(obj)/zImage:  $(obj)/compressed/vmlinux

$(obj)/compressed/vmlinux:$(obj)/Image

$(obj)/Image:vmlinux     

 

         在arch/arm/boot/compressed/Makefile:

$(obj)/vmlinux:$(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.lzma.o $(addprefix $(obj)/,$(OBJS))

$(obj)/piggy.lzma:$(obj)/../Image

$(obj)/piggy.lzma.o:  $(obj)/piggy.lzma

compressed/vmlinux:根据当前目录下的vmlinux.lds生成,其中有一个重要字段:

. =TEXT_START; (=0,why?

arch/arm/boot/compressed/Makefile

/* Decompressors running fromRAM should not define ZTEXTADDR. Decompressors running directly from ROM or Flash must define ZTEXTADDR*/

TEXT_START = $(ZTEXTADDR) = 0

BSS_START = $(ZBSSADDR) =ALIGN(8)

.piggydata: {

    *(.piggydata)

  }

存放的就是上面的piggy.lzma,其arch/arm/boot/compressed/piggy.lzma.S内容如下:

.section.piggydata,#alloc

    .globl input_data

input_data:

    .incbin"arch/arm/boot/compressed/piggy.lzma"

    .globl input_data_end

input_data_end:

在自解压判断和解压时就会用到上面红色地址。

 

(3)uImage生成流程

//根据vmlinux.lds生成

根目录vmlinux ——>    

         //去掉vmlinux调试信息

         Arch/arm/boot/Image ——> 

                   //lzma压缩末尾4个字节代表Image大小,汇编代码通过input_data_end获取

                   Arch/arm/boot/compressed/piggy.lzma——>

                            //根据vmlinux.lds,将压缩内核、引导代码、自解压代码等重新组成新的vmlinux

                            Arch/arm/boot/compressed/piggy.lzma.o+head.o+misc.o+ decompress.o——>                                              

Arch/arm/boot/compressed/vmlinux——>

                                              //将新的vmlinux通过objcopy生成zImage

                                               Arch/arm/boot/zImage——>

                                                        //添加64字节头生成uImage

                                                        Arch/arm/boot/uImage

 

arch/arm/mach-hi3536/Makefile.boot

zreladdr-y  := 0x40008000     //需要和uboot保持一致

params_phys-y   := 0x00000100 //需要和uboot保持一致

initrd_phys-y   := 0x00800000

 

arch/arm/boot/Makefile

/* Note:the following conditions must always be true:

   ZRELADDR == virt_to_phys(PAGE_OFFSET +TEXT_OFFSET)

   PARAMS_PHYS must be within 4MB of ZRELADDR

   INITRD_PHYS must be in RAM */

ZRELADDR    :=$(zreladdr-y) = 0x40008000       //zImage解压后最终Image的起始地址,uboot引导时zImage一般也存放在这个地址,所以后面涉及到解压前当前程序搬运问题。

PARAMS_PHYS:= $(params_phys-y) = 0x00000100 //内核参数地址

INITRD_PHYS:= $(initrd_phys-y) = 0x00800000

 

2、内核代码自解压过程

(1)arch/arm/boot/compressed/vmlinux.lds

连接文件内容

OUTPUT_ARCH(arm)

ENTRY(_start)

SECTIONS       

{

  /DISCARD/ : {

    *(.ARM.exidx*)

    *(.ARM.extab*)

    /*         

     * Discard any r/w data - this produces alink error if we have any,

     * which is required for PICdecompression.  Local data generates

     * GOTOFF relocations, which prevents itbeing relocated independently

     * of the text/got segments.

     */        

    *(.data)

  }

 

  . = 0;      // TEXT_START连接地址0x0,因为从RAM直接启动 

  _text = .;

 

  .text : {

    _start = .;    

    *(.start)   //入口地址

    *(.text)

    *(.text.*)

    *(.fixup)      

    *(.gnu.warning)

    *(.glue_7t)

    *(.glue_7)

  }

  .rodata : {

    *(.rodata)

    *(.rodata.*)

  }

  .piggydata : {

    *(.piggydata)   //存放piggy.lzma字段

  }

 

  . = ALIGN(4);

  _etext = .;

 

  .got.plt     : { *(.got.plt) }

  _got_start = .;

  .got         : { *(.got) }

  _got_end = .;

 

  /* ensure the zImage file size is always amultiple of 64 bits */

  /* (without a dummy byte, ld just ignores theempty section) */

  .pad         : { BYTE(0); . = ALIGN(8); }

  _edata = .;   //结束地址

 

  . = ALIGN(8);

  __bss_start = .;

  .bss         : { *(.bss) }

  _end = .;

 

  . = ALIGN(8);     /* the stack must be 64-bit aligned */

  .stack       : { *(.stack) }

 

  .stab 0      : { *(.stab) }

  .stabstr 0        : { *(.stabstr) }

  .stab.excl 0      : { *(.stab.excl) }

  .stab.exclstr 0   : { *(.stab.exclstr) }

  .stab.index 0     : { *(.stab.index) }

  .stab.indexstr 0  : { *(.stab.indexstr) }

  .comment 0        : { *(.comment) }

}

上面的.got,那么GOT表是什么?

GOTGlobal Offset Table)表中每一项都是本运行模块要引用的一个全局变量或函数的地址。可以用GOT表来间接引用全局变量、函数,也可以把GOT表的首地址作为一个基准,用相对于该基准的偏移量来引用静态变量、静态函数

 

(2)arch/arm/boot/compressed/head.S

以下是对该自解压代码分析:

        .section ".start", #alloc,#execinstr           //以下代码存放在vmlinux.lds.start字段

/*

 * sort out different calling conventions

 */

        .align

        .arm                //总是进入arm状态

start:

        .type  start,#function  //定义start函数

        .rept  7

        mov r0, r0

        .endr

   ARM(    mov r0, r0      )

   ARM(    b   1f      )

 THUMB(    adr r12, BSYM(1f)   )

 THUMB(    bx  r12     )

 

        .word  0x016f2818      @Magic numbers tohelp the loader

        .word  start           //加载和运行zImage的绝对地址(编译时确定),在vmlinux.ldszImage连接地址为0

        .word  _edata          //zImage的结束地址,在vmlinux.lds中可以看到

 THUMB(    .thumb          )

1:

        mrs r9, cpsr

#ifdefCONFIG_ARM_VIRT_EXT

        bl __hyp_stub_install  @ get into SVCmode, reversibly

#endif

        mov r7, r1  //保存机器码到R7,这个由uboot传递的第二个参数

mov r8, r2  //保存参数列表指针到R8,这个由uboot传递的第三个参数

                  

                  

ldr r4, =zreladdr       //最终Image的执行起始地址存放在R4,上一节中提到该值为0x400080000x40000000为物理内存起始地址,0x8000为偏移

#ifdefined(CONFIG_ARCH_HI3536) \

    || defined(CONFIG_ARCH_HI3536_SLAVE) \

    || defined(CONFIG_ARCH_HI3521A)

/*

 * set SMP bit ACTLR register to enable I cacheand D cache

 */

                mrc     p15, 0, r0, c1, c0, 1

                orr     r0, #(1 << 6)

                mcr     p15, 0, r0, c1, c0, 1

#endif

        bl cache_on 

/*开始cache以及MMU,并且会做一些页表初始化的工作,然后在head.S结束时关闭mmucache。初始化页表没有真正的使用价值,只是为了打开解压缩代码使用D-cache而打开的,所以这里的页表中映射的虚拟地址和物理地址的关系是等价映射的(也就是将物理地址映射到等值的虚拟地址上)。*/

restart:    adr r0, LC0 

/* 获取LC0的运行地址,依次将相应的连接地址放入到下面的寄存器中。这里之所以是restart主要是判断解压后是否会覆盖当前镜像,如果会就先搬运当前镜像到解压结束地址+堆栈之后,再从这里开始运行解压内核。*/

        ldmia  r0, {r1, r2, r3, r6, r10, r11, r12}

        ldr sp, [r0, #28]     //此时r0中存放的还是LC0的运行地址,所以加28后正好是LC0数组中的.L_user_stack_end的值(栈的结束地址),在head.S的结尾定义

{

        .type  LC0, #object

LC0:        .word  LC0         @ r1

        .word  __bss_start     @ r2

        .word  _end            @ r3

        .word  _edata          @ r6 //zImage镜像的结束地址

        .word  input_data_end - 4  @ r10在上一节中提到过这个地址存放的就是image大小

        .word  _got_start      @ r11

        .word  _got_end        @ ip

        .word  .L_user_stack_end   @ sp

        .size  LC0, . - LC0

}

        /* 编译时连接地址从0x0开始,这里计算出当前实际运行的物理地址 */

        sub r0, r0, r1      @ 计算偏移量

        add r6, r6, r0      @ _edata 实际运行地址

        add r10, r10, r0    @ 获取Image大小存放的实际物理地址

 

        /*

         内核编译系统将Image的大小4字节以小端形式添加到压缩数据的末尾。这里是将解压后的内核数据大小正确的放入R9寄存器里。

         */

        ldrb   r9, [r10, #0]

        ldrb   lr, [r10, #1]

        orr r9, r9, lr, lsl #8

        ldrb   lr, [r10, #2]

        ldrb   r10, [r10, #3]

        orr r9, r9, lr, lsl #16

        orr r9, r9, r10, lsl #24

 

/* malloc获取的内存空间位于重定向的栈指针之上(64K max */

        add sp, sp, r0  //使用r0修正sp,得到堆栈的实际结束物理地址,为什么是结束地址?因为栈是向下生长的。

        add r10, sp, #0x10000 //执行这句之前sp中存放的是栈的结束地址,执行后,r10中存放的是堆空间的结束物理地址

                  

                   …

                  

/* 检查解压是否会自我覆盖的情况

 *   r4  = 最终执行地址(解压后内核起始地址)

 *   r9  = 解压后的内核大小

 *   r10 = 当前执行映像的结束地址,包括bss/stack/malloc空间

 * 判断是否会自我覆盖的的情况:

 *   A. r4 - 16k页目录 >= r10 -> OK,即最终执行地址在当前映像之后

 *   B. r4 + 内核大小 <=wont_overwrite的地址(当前位置PC -> OK 即最终执行地址在当前映像之前

*    C. 不满足上面条件的就会自我覆盖,需要搬运当前映像到不会覆盖的地方运行。通常是这种情况,zImageImage同一个地址。ubootzImage搬运到0x40008000运行,上面的r4最终执行地址也是该地址。

 */

        add r10, r10, #16384

        cmp r4, r10

        bhs wont_overwrite

        add r10, r4, r9        

        adr r9, wont_overwrite 

        cmp r10, r9

        bls wont_overwrite

 

只有发生自我覆盖时才会运行这里,否则直接运行wont_overwrite开始的地方。

/*

 * 将当前运行映像搬运到解压后的内核结束地址后面

 *   r6  =_edata

 *   r10 = 解压后内核结束地址

 * 由于拷贝时当前映像向后执行移动,为了防止源数据和目标数据重叠,从后向前拷贝代码。

 */

        /*

         * Bump to the next 256-byte boundarywith the size of

         * the relocation code added. Thisavoids overwriting

         * ourself when the offset is small.

         */

        add r10, r10, #((reloc_code_end -restart + 256) & ~255)

        bic r10, r10, #255

 

        /* Get start of code we want to copyand align it down. */

        adr r5, restart  //获取需要搬运的当前映像起始位置,并32B对齐

        bic r5, r5, #31

 

/*Relocate the hyp vector base if necessary */

#ifdefCONFIG_ARM_VIRT_EXT

        mrs r0, spsr

        and r0, r0, #MODE_MASK

        cmp r0, #HYP_MODE

        bne 1f

 

        bl __hyp_get_vectors

        sub r0, r0, r5

        add r0, r0, r10

        bl __hyp_set_vectors

1:

#endif

        sub r9, r6, r5     @ r6-r5 = 拷贝大小

        add r9, r9, #31    @ 对齐

        bic r9, r9, #31     @ ... of 32 bytes

        add r6, r9, r5             //r6=当前映像需要搬运的结束地址

        add r9, r9, r10          //当前映像搬运后的结束地址

                  

                   //搬运当前映像,不包含bss/stack/malloc空间

1:      ldmdb  r6!, {r0 - r3, r10 - r12, lr}

        cmp r6, r5

        stmdb  r9!, {r0 - r3, r10 - r12, lr}

        bhi 1b

/* r6存放当映像和目标映像地址偏移量,修正SP和实现代码跳转 */

        sub r6, r9, r6

#ifndefCONFIG_ZBOOT_ROM

        修正sp地址,修正后sp指向重定向后的代码的栈区的结束地址(栈向下生长),栈区后面紧跟的就是堆空间

        add sp, sp, r6

#endif

 

        bl  cache_clean_flush  //清除缓存

 

        adr r0, BSYM(restart)   //获得restart的运行地址
        add r0, r0, r6                    //获得重定向后的代码的restart的物理地址
        mov pc, r0           //跳到重定向后的代码的restart处开始执行
 
/* 在上面的跳转之后,程序又从restart开始。
* 但这次在检查自我覆盖的时候,新的执行位置必然满足上面B情况
* 所以必然直接跳到了下面的wont_overwrite执行
*/

wont_overwrite:

/*

 *如果delta(当前映像地址与编译时的地址偏移)为0, 我们运行的地址就是编译时确定的地址。但我们不一致,所以需要重定向。

 *   r0  =delta

 *   r2  =BSS start

 *   r3  =BSS end

 *   r4  =kernel execution address

 *   r5  =appended dtb size (0 if not present)

 *   r7  =architecture ID

 *   r8  =atags pointer

 *   r11 = GOT start

 *   r12 = GOT end

 *   sp  =stack pointer

 */

        orrs   r1, r0, r5

        beq not_relocated  //如果delta0,无须对GOT表项和BSS进行重定位,否则下面进行重新定向

 

        add r11, r11, r0 //重定位GOT start
        add r12, r12, r0 //重定位GOT end

       

        add r2, r2, r0 //重定位BSS start
        add r3, r3, r0 //重定位BSS end

 

        //重定位所有GOT表的入口项

1:      ldr r1, [r11, #0]       @ relocate entries in the GOT

        add r1, r1, r0        @This fixes up C references

        cmp r1, r2          @ if entry >= bss_start &&

        cmphs  r3, r1      @       bss_end > entry

        addhi  r1, r1, r5    @    entry += dtb size

        str r1, [r11], #4       @ next entry

        cmp r11, r12

        blo 1b

 

        // 重定向bbs

        add r2, r2, r5

        add r3, r3, r5

 

//到这里,当前映像搬运和重定向完成,开始内核解压

not_relocated:  mov r0, #0

1:      str r0, [r2], #4        @ clear bss

        str r0, [r2], #4

        str r0, [r2], #4

        str r0, [r2], #4

        cmp r2, r3

        blo 1b

 

/*

 * C运行环境充分建立,设置部分指针,开始内核解压

 *   r4  = 解压后内核执行地址0x40008000

 *   r7  = 机器码

 *   r8  = 内核参数列表指针

 */

        mov r0, r4

        mov r1, sp          @ malloc space above stack

        add r2, sp, #0x10000    @ 64k max

        mov r3, r7

        bl decompress_kernel  //r0/r1/r2/r3分别是参数传递给decompress_kernel,该函数是C语言解压函数。

{

ret = do_decompress(input_data, input_data_end - input_data,

               output_data, error);

input_data:上一节arch/arm/boot/compressed/piggy.lzma.S中存放piggy.lzma的起始地址

output_datar4的地址0x40008000

}

        bl cache_clean_flush    //清除缓存

        bl cache_off  //关闭cache

        mov r1, r7          @ restore architecture number

        mov r2, r8          @ restore atags pointer

 

        mrs r0, spsr        @ Get saved CPU boot mode

        and r0, r0, #MODE_MASK

        cmp r0, #HYP_MODE       @ if not booted in HYP mode...

        bne __enter_kernel      @ 进入解压后的内核运行

{

__enter_kernel:

        mov r0, #0          @ must be 0

 ARM(       mov pc, r4  )      @ call kernel r4=解压后内核执行地址0x40008000

 THUMB(     bx r4  )       @ 跳转到0x40008000开始执行Image内核,入口为stext,细节见下文。

}

 

        adr r12, .L__hyp_reentry_vectors_offset

        ldr r0, [r12]

        add r0, r0, r12

        bl __hyp_set_vectors

        __HVC(0)            @ otherwise bounce to hyp mode

        b  .           @ should never bereached

        .align 2

.L__hyp_reentry_vectors_offset:.long   __hyp_reentry_vectors - .

 

3、内核启动代码分析

(1)arch/arm/kernel/head.S

/*

 * swapper_pg_dir是初始化页表的虚拟地址

 * 页表放在KERNEL_RAM_VADDR16K,所以KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000vmlinux.lds0xC0008000(虚拟地址)

 */

#defineKERNEL_RAM_VADDR    (PAGE_OFFSET +TEXT_OFFSET) = 0xC0008000

#if(KERNEL_RAM_VADDR & 0xffff) != 0x8000

#errorKERNEL_RAM_VADDR must start at 0xXXXX8000

#endif

 

#definePG_DIR_SIZE 0x4000 //页表大小16K

#definePMD_ORDER   2      //占据4个页

 

    .globl swapper_pg_dir

    .equ   swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE

 

    .macro pgtbl, rd, phys

    add \rd, \phys, #TEXT_OFFSET - PG_DIR_SIZE

    .endm

 

/*

 真正内核入口点:

 * 上一节解压后即跳转到这里,要求:

 *  MMU = off, D-cache = off, I-cache = dont care,r0 = 0,

 *  r1 = machine nr, r2 = atags or dtb pointer.

* Seelinux/arch/arm/tools/mach-types 完整的机器码列表 for r1.

 */

    .arm

 

    __HEAD

ENTRY(stext)

 

 THUMB( adr r9, BSYM(1f)    )   @Kernel is always entered in ARM.

 THUMB( bx r9      )   @ If this is a Thumb-2 kernel,

 THUMB( .thumb          )  @ switch to Thumb now.

 THUMB(1:           )

 

#ifdefCONFIG_ARM_VIRT_EXT

    bl __hyp_stub_install

#endif

    @ ensure svc mode and all interrupts masked

    safe_svcmode_maskall r9

 

    mrc p15, 0, r9, c0, c0      @ get processor id

bl __lookup_processor_type     @ r5=procinfo r9=cupid判断是否是内核支持的cpu类型(即ARM核,这里为armv7

{

arch/arm/kernel/head-common.S中定义:

__lookup_processor_type

.long   __proc_info_begin

  .long   __proc_info_end 获取所有支持列表

这里armv7则由arch/arm/mm/proc-v7.S文件生成列表

}

    movs   r10, r5             @ invalidprocessor (r5=0)?

 THUMB( it eq )        @ force fixup-ablelong branch encoding

    beq __error_p           @ yes, error 'p' //不匹配就出错,执行不下去

 

#ifndefCONFIG_XIP_KERNEL

    adr r3, 2f

    ldmia  r3, {r4, r8}

    sub r4, r3, r4          @ (PHYS_OFFSET - PAGE_OFFSET)

    add r8, r8, r4          @ PHYS_OFFSET     //计算物理起始地址

#else

    ldr r8, =PHYS_OFFSET        @ always constant in this case

#endif

   

    /*

     * r1 = machine no, r2 = atags or dtb,

     * r8 = phys_offset, r9 = cpuid, r10 =procinfo

     */

    bl __vet_atags      //判断r2的合法性

#ifdefCONFIG_SMP_ON_UP

    bl __fixup_smp     //修正smp内核在单处理器上的运作

#endif

#ifdefCONFIG_ARM_PATCH_PHYS_VIRT

    bl __fixup_pv_table     

#endif

bl __create_page_tables     //创建页表,比较复杂,不理解!

 

    /*

     * The following calls CPU specific code ina position independent

     * manner. See arch/arm/mm/proc-*.S for details. r10 = base of

     * xxx_proc_info structure selected by__lookup_processor_type

     * above. On return, the CPU will be ready for the MMU to be

     * turned on, and r0 will hold the CPUcontrol register value.

     */

    ldr r13, =__mmap_switched //mmu使能后的跳转地址放入LR,后面执行完后跳转到该函数执行

    adr lr, BSYM(1f)            @ return (PIC) address

    mov r8, r4              @ set TTBR1 to swapper_pg_dir

 ARM(  add pc, r10, #PROCINFO_INITFUNC )

 THUMB( add r12, r10, #PROCINFO_INITFUNC    )

 THUMB( mov pc, r12             )

1:  b  __enable_mmu         //使能mmu

ENDPROC(stext)

   .ltorg                                                                        

#ifndefCONFIG_XIP_KERNEL

2:  .long  .

    .long  PAGE_OFFSET

#endif

 

(2)arch/arm/kernel/head-common.S

 

/* Thefollowing fragment of code is executed with the MMU on in MMU mode,

 * and uses absolute addresses; this is notposition independent.

 * r0  = cp#15 control register

 * r1  = machine ID

 * r2  = atags/dtb pointer

 * r9  = processor ID

 */

    __INIT

__mmap_switched:

    adr r3, __mmap_switched_data     //依次将__mmap_switched_data的连接地址加载

 

    ldmia  r3!, {r4, r5, r6, r7}

    cmp r4, r5              @ Copy data segment if needed

1:  cmpne  r5, r6

    ldrne  fp, [r4], #4

    strne  fp, [r5], #4

    bne 1b

 

    mov fp, #0              @ Clear BSS (and zero fp)

1:  cmp r6, r7

    strcc  fp, [r6],#4

    bcc 1b

 

 ARM(  ldmia   r3, {r4, r5, r6, r7, sp})

 THUMB( ldmia  r3, {r4, r5, r6, r7}    )

 THUMB( ldr sp, [r3, #16]       )

    str r9, [r4]            @ Save processor ID 保存idprocessor_id变量里

    str r1, [r5]            @ Save machine type 保存机器码到__machine_arch_type变量里

    str r2, [r6]            @ Save atags pointer 保存参数列表到__atags_pointer变量

    cmp r7, #0

    bicne  r4, r0, #CR_A           @ Clear'A' bit

    stmneia r7, {r0, r4}            @ Save control register values

    b   start_kernel     //跳转到C语言启动内核的代码处

ENDPROC(__mmap_switched)

   

.align 2

    .type  __mmap_switched_data, %object

__mmap_switched_data:

    .long  __data_loc          @ r4

    .long  _sdata              @ r5

    .long  __bss_start         @ r6

    .long  _end                @ r7

    .long  processor_id            @ r4

    .long  __machine_arch_type     @ r5

    .long  __atags_pointer         @ r6

#ifdefCONFIG_CPU_CP15

    .long  cr_alignment            @ r7

#else

    .long  0               @ r7

#endif

    .long  init_thread_union + THREAD_START_SP @ sp //start_kernel使用的内核堆栈,也可以认为是空闲任务0使用的内核堆栈8K

.size  __mmap_switched_data, . - __mmap_switched_data

 

(3)init/main.c—start_kernel

该函数涉及对内核所需要的各种初始化。

asmlinkage void __init start_kernel(void)

{

   char * command_line;

   extern const struct kernel_param __start___param[], __stop___param[];

 

    /*

     *Need to run as early as possible, to initialize the

     *lockdep hash:

     */

   lockdep_init();

   smp_setup_processor_id();

   debug_objects_early_init();

 

    /*

     *Set up the the initial canary ASAP:

     */

   boot_init_stack_canary();

 

   cgroup_init_early();

 

   local_irq_disable();

    early_boot_irqs_disabled= true;

 

/*

 *Interrupts are still disabled. Do necessary setups, then

 *enable them

 */

   boot_cpu_init();

   page_address_init();

         //打印内核信息

   pr_notice("%s", linux_banner);      

         //重要,见后面一节(4)详细说明

   setup_arch(&command_line);

   mm_init_owner(&init_mm, &init_task);

   mm_init_cpumask(&init_mm);

         //保存未操作过的命令行内容

   setup_command_line(command_line);

   setup_nr_cpu_ids();

   setup_per_cpu_areas();

   smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */

         //建立所有内存管理区链表(DMANORMALHIGHMEM 3种管理区)

   build_all_zonelists(NULL, NULL);

   page_alloc_init();

 

   pr_notice("Kernel command line: %s\n", boot_command_line);

  //分析系统能够辨别的一些早期参数,以未做任何修改的boot_command_line为基础

parse_early_param();

//解析命令行各个参数,memrootfs

   parse_args("Booting kernel", static_command_line,__start___param,

          __stop___param - __start___param,

          -1, -1, &unknown_bootoption);

 

   jump_label_init();

 

    /*

     *These use large bootmem allocations and must precede

     *kmem_cache_init()

     */

   setup_log_buf(0);

    //顺序扫描进程链表并检查进程描述符的pid字段是可行但是相当低效,为了加速查找,引入了4个散列表。为每种hash表申请空间,并将内存虚拟基址分别存放在pid_hash数组中。

pidhash_init();

   vfs_caches_init_early();

/* 将放在__start_ex_table__stop_ex_table之间的*(_ex_table)区域中的structexception_table_entry型全局结构变量按照insn成员变量值从小到大排序。

*/

   sort_main_extable();

   trap_init();

         //内存初始化(包括slab分配器初始化),释放前面标志为保留的所有页面,这个函数结束后就不能在使用alloc_bootmemalloc_bootmem_low alloc_bootmem_pages等申请低端内存的函数申请内存。

   mm_init();

 

/* 初始化每个处理器的可运行队列。同时通过init_idle(current,smp_processor_id());设置系统初始化进程即0idle进程,当前的运行都可以算作是在idle这个进程,使用init_task(静态分配的第一个任务描述符)init_thread_union(静态分配的第一个指向任务描述符的堆栈结构体)(init/init_task.c

*/

   sched_init();

    //禁止内核抢占,因为这是调度还没准备好,直到cpu_idle为止

   preempt_disable();

    if(WARN(!irqs_disabled(), "Interrupts were enabled *very* early, fixingit\n"))

       local_irq_disable();

   idr_init_cache();

   perf_event_init();

   rcu_init();

   tick_nohz_init();

   radix_tree_init();

    /*init some links before init_ISA_irqs() */

   early_irq_init();

         //初始化中断,调用的是平台中断接口machine_desc->init_irq();hi3536_gic_init_irq)。

   init_IRQ();

         //初始化当前cpu的读、拷贝、更新数据结构全局变量per_cpu_rcu_dataper_cpu_rcu_bh_data

   tick_init();

         //初始化当前处理器的时间向量,打开TIMER_SOFTIRQ定时器软中断

   init_timers();

         //高精度定时器初始化

   hrtimers_init();

         //软中断初始化,打开TASKLET_SOFTIRQHI_SOFTIRQ tasklet所需要的软中断

   softirq_init();

   timekeeping_init();

         //初始化体系结构硬件定时器

   time_init();

         //用来对系统剖析的,在系统调试的时候有用

   profile_init();

   call_function_init();

   WARN(!irqs_disabled(), "Interrupts were enabled early\n");

   early_boot_irqs_disabled = false;

         //使能IRQ中断

   local_irq_enable();

 

   kmem_cache_init_late();

 

   //初始化系统控制台结构,该函数执行后调用printk函数将log_buf中所有符合打印几倍的信息打印到控制台。

   console_init();

    if(panic_later)

       panic(panic_later, panic_param);

 

   lockdep_info();

 

    /*

     *Need to run this when irqs are enabled, because it wants

     *to self-test [hard/soft]-irqs on/off lock inversion bugs

     *too:

     */

   locking_selftest();

 

#ifdef CONFIG_BLK_DEV_INITRD

    if(initrd_start && !initrd_below_start_ok &&

       page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {

       pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disablingit.\n",

           page_to_pfn(virt_to_page((void *)initrd_start)),

           min_low_pfn);

       initrd_start = 0;

    }

#endif

   page_cgroup_init();

   debug_objects_mem_init();

   kmemleak_init();

   setup_per_cpu_pageset();

         //cpu系统的BogMIPS数值,即处理器每秒执行的指令数

   numa_policy_init();

    if(late_time_init)

       late_time_init();

   sched_clock_init();

   calibrate_delay();

         //为了循环使用PID编号,内容通过pidmap管理已经分配的pid和空闲PID

   pidmap_init();

         //为匿名逆序内存区域链表结构分配一个高速缓存内存

   anon_vma_init();

#ifdef CONFIG_X86

    if(efi_enabled(EFI_RUNTIME_SERVICES))

       efi_enter_virtual_mode();

#endif

   thread_info_cache_init(); //do nothing

   cred_init();

         //创建进程相关的初始化

   fork_init(totalram_pages);

   proc_caches_init();

   buffer_init();

   key_init();

   security_init();

   dbg_late_init();

   vfs_caches_init(totalram_pages);

   signals_init();

    /*rootfs populating might need page-writeback */

   page_writeback_init();

#ifdef CONFIG_PROC_FS

   proc_root_init();

#endif

   cgroup_init();

   cpuset_init();

   taskstats_init_early();

   delayacct_init();

 

   check_bugs();

   //add by hlb

   gs_proc_init();

 

   acpi_early_init(); /* before LAPIC and SMP init */

   sfi_init_late();

 

    if(efi_enabled(EFI_RUNTIME_SERVICES)) {

       efi_late_init();

       efi_free_boot_services();

    }

 

   ftrace_init();

 

         //重要,见后面一节(5)详细说明

   rest_init();

}

 

(4)arch/arm/kernel/setup.c——setup_arch

void __init setup_arch(char **cmdline_p)

{

   struct machine_desc *mdesc;

 

         //获取arm处理相关信息

   setup_processor();

   mdesc = setup_machine_fdt(__atags_pointer);

    //获取uboot传递进来的参数列表,解析各个tags,其中一个重要的是boot_command_line

if (!mdesc)

       mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);

   machine_desc = mdesc;

   machine_name = mdesc->name;

 

         //设置DMA内存管理区,当前没有用

   setup_dma_zone(mdesc);

 

    if(mdesc->restart_mode)

       reboot_setup(&mdesc->restart_mode);

        

         //初始化init_mm内存描述符,后面的值在vmlinux.lds中编译时确定

   init_mm.start_code = (unsigned long) _text;

   init_mm.end_code   = (unsignedlong) _etext;

   init_mm.end_data   = (unsignedlong) _edata;

   init_mm.brk    = (unsigned long)_end;

 

    //cmd_line传递出去,后续解析使用

   strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);

   *cmdline_p = cmd_line;

 

   parse_early_param();

 

   sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]),meminfo_cmp, NULL);

   sanity_check_meminfo();

   arm_memblock_init(&meminfo, mdesc);

 

         //为系统内存创建页表,初始化内存

   paging_init(mdesc);

   request_standard_resources(mdesc);

 

    ….

}

 

(5)init/main.c——rest_init

该函数创建init内核进程(1号进程)和kthreadd内核进程(2号进程),原来的为0号系统启动进程进入空闲状态。

static noinline void __init_refokrest_init(void)

{

    intpid;

 

   rcu_scheduler_starting();

   

         //创建1init进程            

   kernel_thread(kernel_init, NULL, CLONE_FS| CLONE_SIGHAND);

   numa_default_policy();

         //创建2kthreadd进程

    pid= kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

   rcu_read_lock();

   kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);

   rcu_read_unlock();

   complete(&kthreadd_done);

       

    //初始化当前任务到空闲进程

   init_idle_bootup_task(current);

         //禁止调度抢占

   schedule_preempt_disabled();

    //进入空闲内核

   cpu_startup_entry(CPUHP_ONLINE);

}

 

kernel_init内核进程主要运行/sbin/init命令,完成内核态到用户态的切换,执行/etc下面的各种脚本,实现程序启动。