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表是什麼?
GOT(Global 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.lds中zImage連線地址為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,上一節中提到該值為0x40008000,0x40000000為實體記憶體起始地址,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結束時關閉mmu和cache。初始化頁表沒有真正的使用價值,只是為了開啟解壓縮程式碼使用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. 不滿足上面條件的就會自我覆蓋,需要搬運當前映像到不會覆蓋的地方執行。通常是這種情況,zImage和Image同一個地址。uboot將zImage搬運到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 //如果delta為0,無須對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_data:r4的地址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_VADDR前16K,所以KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000,vmlinux.lds是0xC0008000(虛擬地址)
*/
#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 儲存id到processor_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 */
//建立所有記憶體管理區連結串列(DMA,NORMAL,HIGHMEM 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();
//解析命令列各個引數,mem、rootfs等
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_bootmem,alloc_bootmem_low alloc_bootmem_pages等申請低端記憶體的函式申請記憶體。
mm_init();
/* 初始化每個處理器的可執行佇列。同時通過init_idle(current,smp_processor_id());設定系統初始化程序即0號idle程序,當前的執行都可以算作是在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_data和per_cpu_rcu_bh_data
tick_init();
//初始化當前處理器的時間向量,開啟TIMER_SOFTIRQ定時器軟中斷
init_timers();
//高精度定時器初始化
hrtimers_init();
//軟中斷初始化,開啟TASKLET_SOFTIRQ和HI_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();
//建立1號init程序
kernel_thread(kernel_init, NULL, CLONE_FS| CLONE_SIGHAND);
numa_default_policy();
//建立2號kthreadd程序
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下面的各種指令碼,實現程式啟動。