Linux的啟動(dòng)代碼真的挺大,從匯編到C,從Makefile到LDS文件,需要理解的東西很多。畢竟Linux內(nèi)核是由很多人,花費(fèi)了巨大的時(shí)間和精力寫出來(lái)的。而且直到現(xiàn)在,這個(gè)世界上仍然有成千上萬(wàn)的程序員在不斷完善Linux內(nèi)核的代碼。
Linux內(nèi)核啟動(dòng)及文件系統(tǒng)加載過(guò)程
當(dāng)u-boot開(kāi)始執(zhí)行bootcmd命令,就進(jìn)入Linux內(nèi)核啟動(dòng)階段,與u-boot類似,普通Linux內(nèi)核的啟動(dòng)過(guò)程也可以分為兩個(gè)階段,但針對(duì)壓縮了的內(nèi)核如uImage就要包括內(nèi)核自解壓過(guò)程了。本文以linux-2.6.37版源碼為例分三個(gè)階段來(lái)描述內(nèi)核啟動(dòng)全過(guò)程。第一階段為內(nèi)核自解壓過(guò)程,第二階段主要工作是設(shè)置ARM處理器工作模式、使能MMU、設(shè)置一級(jí)頁(yè)表等,而第三階段則主要為C代碼,包括內(nèi)核初始化的全部工作
Linux內(nèi)核啟動(dòng)流程
arch/arm/kernel/head-armv.S
該文件是內(nèi)核最先執(zhí)行的一個(gè)文件,包括內(nèi)核入口ENTRY(stext)到start_kernel間的初始化代碼,
主要作用是檢查CPU ID, Architecture Type,初始化BSS等操作,并跳到start_kernel函數(shù)。在執(zhí)行前,處理器應(yīng)滿足以下?tīng)顟B(tài):
r0 - should be 0
r1 - unique architecture number
MMU - off
I-cache - on or off
D-cache – off
1 /* 部分源代碼分析 */
2 /* 內(nèi)核入口點(diǎn) */
3 ENTRY(stext)
4 /* 程序狀態(tài),禁止FIQ、IRQ,設(shè)定SVC模式 */
5 mov r0, #F_BIT | I_BIT | MODE_SVC@ make sure svc mode
6 /* 置當(dāng)前程序狀態(tài)寄存器 */
7 msr cpsr_c, r0 @ and all irqs disabled
8 /* 判斷CPU類型,查找運(yùn)行的CPU ID值與Linux編譯支持的ID值是否支持 */
9 bl __lookup_processor_type
10 /* 跳到__error */
11 teq r10, #0 @ invalid processor?
12 moveq r0, #‘p’ @ yes, error ‘p’
13 beq __error
14 /* 判斷體系類型,查看R1寄存器的Architecture Type值是否支持 */
15 bl __lookup_architecture_type
16 /* 不支持,跳到出錯(cuò) */
17 teq r7, #0 @ invalid architecture?
18 moveq r0, #‘a(chǎn)’ @ yes, error ‘a(chǎn)’
19 beq __error
20 /* 創(chuàng)建核心頁(yè)表 */
21 bl __create_page_tables
22 adr lr, __ret @ return address
23 add pc, r10, #12 @ initialise processor
24 /* 跳轉(zhuǎn)到start_kernel函數(shù) */
25 b start_kernel
Linux內(nèi)核啟動(dòng)第一階段stage1
這里所以說(shuō)的第一階段stage1就是內(nèi)核解壓完成并出現(xiàn)Uncompressing Linux.。.done,booting the kernel.之后的階段。該部分代碼實(shí)現(xiàn)在arch/arm/kernel的 head.S中,該文件中的匯編代碼通過(guò)查找處理器內(nèi)核類型和機(jī)器碼類型調(diào)用相應(yīng)的初始化函數(shù),再建 立頁(yè)表,最后跳轉(zhuǎn)到start_kernel()函數(shù)開(kāi)始內(nèi)核的初始化工作。檢測(cè)處理器類型是在匯編子函數(shù)__lookup_processor_type中完成的,通過(guò)以下代碼可實(shí)現(xiàn)對(duì)它的調(diào)用:bl__lookup_processor_type(在文件head-commom.S實(shí)現(xiàn))。__lookup_processor_type調(diào)用結(jié)束返回原程序時(shí),會(huì)將返回結(jié)果保存到寄存器中。其中r5寄存器返回一個(gè)用來(lái)描述處理器的結(jié)構(gòu)體地址,并對(duì)r5進(jìn)行判斷,如果r5的值為0則說(shuō)明不支持這種處理器,將進(jìn)入__error_p。r8保存了頁(yè)表的標(biāo)志位,r9 保存了處理器的ID 號(hào),r10保存了與處理器相關(guān)的struct proc_info_list結(jié)構(gòu)地址。Head.S核心代碼如下:
檢測(cè)機(jī)器碼類型是在匯編子函數(shù)__lookup_machine_type (同樣在文件head-common.S實(shí)現(xiàn)) 中完成的。與__lookup_processor_type類似,通過(guò)代碼:“bl __lookup_machine_type”來(lái)實(shí)現(xiàn)對(duì)它的調(diào) 用。該函數(shù)返回時(shí),會(huì)將返回結(jié)構(gòu)保存放在r5、r6 和r7三個(gè)寄存器中。其中r5寄存器返回一個(gè)用來(lái)描述機(jī)器(也就是開(kāi)發(fā)板)的結(jié)構(gòu)體地址,并對(duì)r5進(jìn)行判斷,如果r5的值為0則說(shuō)明不支持這種機(jī)器(開(kāi)發(fā)板),將進(jìn)入__error_a,打印出內(nèi)核不支持u-boot傳入的機(jī)器碼的錯(cuò)誤如圖2。r6保存了I/O基地址,r7 保存了 I/O的頁(yè)表偏移地址。 當(dāng)檢測(cè)處理器類型和機(jī)器碼類型結(jié)束后,將調(diào)用__create_page_tables子函數(shù)來(lái)建立頁(yè)表,它所要做的工作就是將 RAM 基地址開(kāi)始的1M 空間的物理地址映射到 0xC0000000開(kāi)始的虛擬地址處。對(duì)本項(xiàng)目的開(kāi)發(fā)板DM3730而言,RAM掛接到物理地址0x80000000處,當(dāng)調(diào)用__create_page_tables 結(jié)束后 0x80000000 ~ 0x80100000物理地址將映射到 0xC0000000~0xC0100000虛擬地址處。當(dāng)所有的初始化結(jié)束之后,使用如下代碼來(lái)跳到C 程序的入口函數(shù)start_kernel()處,開(kāi)始之后的內(nèi)核初始化工作: bSYMBOL_NAME(start_kernel) 。
Linux內(nèi)核啟動(dòng)第二階段stage2
從start_kernel函數(shù)開(kāi)始
Linux內(nèi)核啟動(dòng)的第二階段從start_kernel函數(shù)開(kāi)始。start_kernel是所有Linux平臺(tái)進(jìn)入系統(tǒng)內(nèi)核初始化后的入口函數(shù),它主要完成剩余的與 硬件平臺(tái)相關(guān)的初始化工作,在進(jìn)行一系列與內(nèi)核相關(guān)的初始化后,調(diào)用第一個(gè)用戶進(jìn)程- init 進(jìn)程并等待用戶進(jìn)程的執(zhí)行,這樣整個(gè) Linux內(nèi)核便啟動(dòng)完畢。該函數(shù)位于init/main.c文件中,主要工作流程如圖3所示:
該函數(shù)所做的具體工作有 :
1) 調(diào)用setup_arch()函數(shù)進(jìn)行與體系結(jié)構(gòu)相關(guān)的第一個(gè)初始化工作;對(duì)不同的體系結(jié)構(gòu)來(lái)說(shuō)該函數(shù)有不同的定義。對(duì)于ARM平臺(tái)而言,該函數(shù)定義在 arch/arm/kernel/setup.c。它首先通過(guò)檢測(cè)出來(lái)的處理器類型進(jìn)行處理器內(nèi)核的初始化,然后 通過(guò)bootmem_init()函數(shù)根據(jù)系統(tǒng)定義的meminfo結(jié)構(gòu)進(jìn)行內(nèi)存結(jié)構(gòu)的初始化,最后調(diào)用 paging_init()開(kāi)啟MMU,創(chuàng)建內(nèi)核頁(yè)表,映射所有的物理內(nèi)存和IO空間。
2) 創(chuàng)建異常向量表和初始化中斷處理函數(shù);
3) 初始化系統(tǒng)核心進(jìn)程調(diào)度器和時(shí)鐘中斷處理機(jī)制;
4) 初始化串口控制臺(tái)(console_init);
ARM-Linux 在初始化過(guò)程中一般都會(huì)初始化一個(gè)串口做為內(nèi)核的控制臺(tái),而串口Uart驅(qū)動(dòng)卻把串口設(shè)備名寫死了,如本例中l(wèi)inux2.6.37串口設(shè)備名為ttyO0,而不是常用的ttyS0。有了控制臺(tái)內(nèi)核在啟動(dòng)過(guò)程中就可以通過(guò)串口輸出信息以便開(kāi)發(fā)者或用戶了解系統(tǒng)的啟動(dòng)進(jìn)程。
5) 創(chuàng)建和初始化系統(tǒng)cache,為各種內(nèi)存調(diào)用機(jī)制提供緩存,包括;動(dòng)態(tài)內(nèi)存分配,虛擬文件系統(tǒng)(VirtualFile System)及頁(yè)緩存。
6) 初始化內(nèi)存管理,檢測(cè)內(nèi)存大小及被內(nèi)核占用的內(nèi)存情況;
7) 初始化系統(tǒng)的進(jìn)程間通信機(jī)制(IPC); 當(dāng)以上所有的初始化工作結(jié)束后,start_kernel()函數(shù)會(huì)調(diào)用rest_init()函數(shù)來(lái)進(jìn)行最后的初始化,包括創(chuàng)建系統(tǒng)的第一個(gè)進(jìn)程-init進(jìn)程來(lái)結(jié)束內(nèi)核的啟動(dòng)。
掛載根文件系統(tǒng)并啟動(dòng)init
Linux內(nèi)核啟動(dòng)的下一過(guò)程是啟動(dòng)第一個(gè)進(jìn)程init,但必須以根文件系統(tǒng)為載體,所以在啟動(dòng)init之前,還要掛載根文件系統(tǒng)。
評(píng)論
查看更多