1. 程式人生 > >arm linux核心啟動過程詳解

arm linux核心啟動過程詳解

         可以結合《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下面的各種指令碼,實現程式啟動。