Linux內核啟動流程-迅為IMX6ULL開發板(一)


在前面的章節介紹了ubootLinux內核的一些相關內容。在來看Linux內核的大致啟動流程,Linux內核的啟動流程要比uboot復雜的多,涉及到的內容也更多,因此在本章節大致簡單的了解一下Linux內核的啟動流程。有興趣的用戶可以參考其他書籍或資料進行深入了解。

嵌入式linux內核的啟動全過程主要分為三個階段。第一階段為內核自解壓過程,第二階段主要工作是設置ARM處理器工作模式、使能MMU、設置一級頁表等,而第三階段則主要為C代碼,包括內核初始化的全部工作下面分別進行簡單介紹。

基於迅為-IMX6ULL開發板

30.1 Linux內核啟動(一):Linux內核自解壓過程

Linux內核有兩種映像格式:一種是非壓縮內核,叫Image,另一種是它的壓縮版本,叫zImage。zImage是Image經過壓縮形成的,所以它的大小比Image小。但為了能使用zImage,必須在它的開頭加上解壓縮的代碼,將zImage解壓縮之后才能執行,因此它的執行速度比Image要慢一些

內核壓縮和解壓縮代碼都在目錄kernel/arch/arm/boot/compressed,編譯完成后將產生head.omisc.opiggy.gzip.ovmlinuxdecompress.o這幾個文件,head.o是內核的頭部文件,負責初始設置;misc.o將主要負責內核的解壓工作,它在head.o之后;piggy.gzip.o是一個中間文件,其實是一個壓縮的內核(kernel/vmlinux),只不過沒有和初始化文件及解壓文件鏈接而已;vmlinux是沒有(zImage是壓縮過的內核)壓縮過的內核,就是由piggy.gzip.ohead.omisc.o組成的,而decompress.o是為支持更多的壓縮格式而新引入的。

uboot完成系統引導將Linux內核加載到內存之后,調用do_bootm_linux(),這個函數將跳轉到kernel的起始位置。如果kernel沒有被壓縮,就可以啟動了。如果kernel被壓縮過,則要進行解壓,在壓縮過的kernel頭部有解壓程序。壓縮過的kernel入口第一個文件源碼位置在arch/arm/boot/compressed/head.S。它將調用函數decompress_kernel(),這個函數在文件arch/arm/boot/compressed/misc.c中,decompress_kernel()又調用arch_decomp_setup()進行設置,然后調用gunzip()將內核放於指定的位置。

函數decompress_kernel實現的功能:解壓縮代碼位於kernel/lib/inflate.cinflate.c是從gzip源程序中分離出來的,包含了一些對全局數據的直接引用,在使用時需要直接嵌入到代碼中。gzip壓縮文件時總是在前32K字節的范圍內尋找重復的字符串進行編碼, 在解壓時需要一個至少為32K字節的解壓緩沖區,它定義為window[WSIZE]inflate.c使用get_byte()讀取輸入文件,它被定義成宏來提高效率。輸入緩沖區指針必須定義為inptrinflate.c中對之有減量操作。inflate.c調用flush_window()來輸出window緩沖區中的解壓出的字節串,每次輸出長度用outcnt變量表示。在flush_window()中,還必須對輸出字節串計算CRC並且刷新crc變量。在調用gunzip()開始解壓之前,調用makecrc()初始化CRC計算表。最后gunzip()返回0表示解壓成功。在內核啟動時一般會看到這樣的輸出:

UncompressingLinux...done, booting the kernel.

當然有的內核沒有這樣的輸出,是沒有這一條打印語句。

30.2 Linux內核啟動(二):ARM處理器相關設置

Linux內核自解壓完成后,開始執行內核代碼。內核的入口函數由鏈接腳本vmlinux.lds決定,需要編譯內核源碼,才會生成腳本文件。

首先分析Linux內核的鏈接腳本文件arch/arm/kernel/vmlinux.lds,通過鏈接腳本可以找到Linux內核的第一行程序是從哪里開始執行的。vmlinux.lds文件部分代碼如下:

492 OUTPUT_ARCH(arm)

493 ENTRY(stext)

494 jiffies = jiffies_64;

495 SECTIONS

496 {

497 /*

498 * XXX: The linker does not define how output sections are

499 * assigned to input sections when there are multiple statements

500 * matching the same input section name. There is no documented

501 * order of matching.

502 *

503 * unwind exit sections must be discarded before the rest of the

504 * unwind sections get included.

505 */

506 /DISCARD/ : {

507 *(.ARM.exidx.exit.text)

508 *(.ARM.extab.exit.text)

509

......

645 }

493行的 ENTRY 指明了Linux內核入口函數為stext,因此要分析Linux內核第二階段的啟動流程,就得先從文件 arch/arm/kernel/head.S stext處開始分析。 

30.2.1 Linux內核入口函數stext

stextLinux內核的入口地址,在文件arch/arm/kernel/head.S中有如下內容:

/*

* Kernel startup entry point.

* ---------------------------

*

* This is normally called from the decompressor code. The requirements

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

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

.....

*/

以上代碼中的內容顯示。Linux內核啟動之前要求如下:

① 關閉MMU

② 關閉D-cache

③ 不用關心I-cache

④ r0 = 0

⑤ r1 = machine nr(機器ID號)

⑥ r2 = atags或者設備樹(dts)首地址

Linux內核的入口點stext其實相當於內核的入口函數,stext函數代碼如下:

80 ENTRY(stext)

......

91 @ ensure svc mode and all interrupts masked

92 safe_svcmode_maskall r9

93

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

95 bl __lookup_processor_type @ r5=procinfo r9=cpuid

96 movs r10, r5 @ invalid processor (r5=0)?

97 THUMB( it eq ) @ force fixup-able long branch encoding

98 beq __error_p @ yes, error 'p'

99

......

107

108 #ifndef CONFIG_XIP_KERNEL

......

113 #else

114 ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case

115 #endif

116

117 /*

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

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

120 */

121 bl __vet_atags

......

128 bl __create_page_tables

129

130 /*

131 * The following calls CPU specific code in a position independent

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

133 * xxx_proc_info structure selected by __lookup_processor_type

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

135 * turned on, and r0 will hold the CPU control register value.

136 */

137 ldr r13, =__mmap_switched @ address to jump to after

138 @ mmu has been enabled

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

140 mov r8, r4 @ set TTBR1 to swapper_pg_dir

141 ldr r12, [r10, #PROCINFO_INITFUNC]

142 add r12, r12, r10

143 ret r12

144 1: b __enable_mmu

145 ENDPROC(stext)

92行,調用函數safe_svcmode_maskall確保CPU處於SVC模式,並且關閉了所有的中斷。safe_svcmode_maskall定義在文件arch/arm/include/asm/assembler.h 中。

94行,讀處理器IDID值保存在r9寄存器中。

95行,__lookup_processor_type調用結束返回原程序時,會將返回結果保存到寄存器中。其中r5寄存器返回一個用來描述處理器的結構體地址,並對r5進行判斷,如果r5的值為0則說明不支持這種處理器,將進入__error_p procinfo  proc_info_list      Linux內核將每種處理器都抽象為一個 proc_info_list 結構體,每種處理器都對應一個procinfoproc_info_list在文件arch/arm/include/asm/procinfo.h 中的定義如下:

struct proc_info_list {

unsigned int cpu_val;

unsigned int cpu_mask;

unsigned long __cpu_mm_mmu_flags; /* used by head.S */

unsigned long   __cpu_io_mmu_flags; /* used by head.S */

unsigned long __cpu_flush; /* used by head.S */

const char *arch_name;

const char *elf_name;

unsigned int elf_hwcap;

const char *cpu_name;

struct processor *proc;

struct cpu_tlb_fns *tlb;

struct cpu_user_fns *user;

struct cpu_cache_fns *cache;

};

 121 行,調用函數__vet_atags 驗證 atags 或設備樹(dtb)的合法性。函數__vet_atags 定義在文件 arch/arm/kernel/head-common.S 中。

 128 行,調用函數__create_page_tables 創建頁表。

 137 行,將函數__mmap_switched 的地址保存到 r13 寄存器中。__mmap_switched 定義在文件 arch/arm/kernel/head-common.S__mmap_switched 最終會調用 start_kernel 函數。

 144  ,調用 __enable_mmu   使  MMU  __enable_mmu     arch/arm/kernel/head.S 中。__enable_mmu 最終會通過調用__turn_mmu_on 來打開 MMU__turn_mmu_on 最后會執行 r13 里面保存的__mmap_switched 函數。

30.2.2 _mmap_switched 函數

__mmap_switched 函數定義在文件arch/arm/kernel/head-common.S中,函數代碼如下:

81 __mmap_switched:

82 adr r3, __mmap_switched_data

83

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

85 cmp r4, r5 @ Copy data segment if needed

86 1: cmpne r5, r6

87 ldrne fp, [r4], #4

88 strne fp, [r5], #4

89 bne 1b

90

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

92 1: cmp r6, r7

93 strcc fp, [r6],#4

94 bcc 1b

95

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

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

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

99 str r9, [r4] @ Save processor ID

100 str r1, [r5] @ Save machine type

101 str r2, [r6] @ Save atags pointer

102 cmp r7, #0

103 strne r0, [r7] @ Save control register values

104 b start_kernel

105 ENDPROC(__mmap_switched)

 104 行最終調用 start_kernel 來啟動 Linux 內核,start_kernel 函數定義在文件 init/main.c中。

start_kernel 函數開始Linux內核啟動進入到下一個階段。

下面內容請關注:Linux內核啟動流程-迅為IMX6ULL開發板(二)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM