前言:linux的啟動代碼真的挺大,從彙編到C,從Makefile到LDS文件,需要理解的東西很多。畢竟Linux内核是由很多人,花費了巨大的時間和精力寫出來的。而且直到現在,這個世界上仍然有成千上萬的程序員在不斷完善Linux内核的代碼。
嵌入式進階教程分門别類整理好了,看的時候十分方便,由于内容較多,這裡就截取一部分圖吧。
需要的朋友私信【内核】即可領取。
内核學習地址:Linux内核源碼/内存調優/文件系統/進程管理/設備驅動/網絡協議棧-學習視頻教程-騰訊課堂
Linux内核啟動及文件系統加載過程當u-boot開始執行bootcmd命令,就進入Linux内核啟動階段,與u-boot類似,普通Linux内核的啟動過程也可以分為兩個階段,但針對壓縮了的内核如uImage就要包括内核自解壓過程了。本文以linux-2.6.37版源碼為例分三個階段來描述内核啟動全過程。第一階段為内核自解壓過程,第二階段主要工作是設置ARM處理器工作模式、使能MMU、設置一級頁表等,而第三階段則主要為C代碼,包括内核初始化的全部工作。
Linux内核啟動流程arch/ARM/kernel/head-armv.S該文件是内核最先執行的一個文件,包括内核入口ENTRY(stext)到start_kernel間的初始化代碼,主要作用是檢查CPU ID, Architecture Type,初始化BSS等操作,并跳到start_kernel函數。
在執行前,處理器應滿足以下狀态:
r0 - should be 0
r1 - unique architecture number
MMU - off
I-cache - on or off
D-cache – off
- /* 部分源代碼分析 */
- /* 内核入口點 */
- ENTRY(stext)
- /* 程序狀态,禁止FIQ、IRQ,設定SVC模式 */
- mov r0, #F_BIT | I_BIT | MODE_SVC@ make sure svc mode
- /* 當前程序狀态寄存器 */
- msr cpsr_c, r0 @ and all irqs disabled
- /* 判斷CPU類型,查找運行的CPU ID值與Linux編譯支持的ID值是否支持 */
- bl __lookup_processor_type
- /* 跳到__error */
- teq r10, #0 @ invalid processor?
- moveq r0, #‘p’ @ yes, error ‘p’
- beq __error
- /* 判斷體系類型,查看R1寄存器的Architecture Type值是否支持 */
- bl __lookup_architecture_type
- /* 不支持,跳到出錯 */
- teq r7, #0 @ invalid architecture?
- moveq r0, #‘a’ @ yes, error ‘a’
- beq __error
- /* 創建核心頁表 */
- bl __create_page_tables
- adr lr, __ret @ return address
- add pc, r10, #12 @ iniTIalise processor
- /* 跳轉到start_kernel函數 */
- b start_kernel
這裡所以說的第一階段stage1就是内核解壓完成并出現Uncompressing Linux.。.done,booTIng the kernel.之後的階段。該部分代碼實現在arch/arm/kernel的 head.S中,該文件中的彙編代碼通過查找處理器内核類型和機器碼類型調用相應的初始化函數,再建 立頁表,最後跳轉到start_kernel()函數開始内核的初始化工作。
檢測處理器類型是在彙編子函數__lookup_processor_type中完成的,通過以下代碼可實現對它的調用:bl__lookup_processor_type(在文件head-commom.S實現)。__lookup_processor_type調用結束返回原程序時,會将返回結果保存到寄存器中。其中r5寄存器返回一個用來描述處理器的結構體地址,并對r5進行判斷,如果r5的值為0則說明不支持這種處理器,将進入__error_p。r8保存了頁表的标志位,r9 保存了處理器的ID 号,r10保存了與處理器相關的struct proc_info_list結構地址。
Head.S核心代碼如下:
Linux内核啟動第二階段stage2
從start_kernel函數開始
Linux内核啟動的第二階段從start_kernel函數開始。start_kernel是所有Linux平台進入系統内核初始化後的入口函數,它主要完成剩餘的與 硬件平台相關的初始化工作,在進行一系列與内核相關的初始化後,調用第一個用戶進程- init 進程并等待用戶進程的執行,這樣整個 Linux内核便啟動完畢。該函數位于init/main.c文件中,主要工作流程如圖3所示:
該函數所做的具體工作有 :
- 調用setup_arch()函數進行與體系結構相關的第一個初始化工作;對不同的體系結構來說該函數有不同的定義。對于ARM平台而言,該函數定義在 arch/arm/kernel/setup.c。它首先通過檢測出來的處理器類型進行處理器内核的初始化,然後 通過bootmem_init()函數根據系統定義的meminfo結構進行内存結構的初始化,最後調用 paging_init()開啟MMU,創建内核頁表,映射所有的物理内存和IO空間。
- 創建異常向量表和初始化中斷處理函數;
- 初始化系統核心進程調度器和時鐘中斷處理機制;
- 初始化串口控制台(console_init);
ARM-Linux 在初始化過程中一般都會初始化一個串口做為内核的控制台,而串口Uart驅動卻把串口設備名寫死了,如本例中linux2.6.37串口設備名為ttyO0,而不是常用的ttyS0。有了控制台内核在啟動過程中就可以通過串口輸出信息以便開發者或用戶了解系統的啟動進程。
創建和初始化系統cache,為各種内存調用機制提供緩存,包括;動态内存分配,虛拟文件系統(VirtualFile System)及頁緩存。
初始化内存管理,檢測内存大小及被内核占用的内存情況;
初始化系統的進程間通信機制(IPC); 當以上所有的初始化工作結束後,start_kernel()函數會調用rest_init()函數來進行最後的初始化,包括創建系統的第一個進程-init進程來結束内核的啟動。
挂載根文件系統并啟動init
Linux内核啟動的下一過程是啟動第一個進程init,但必須以根文件系統為載體,所以在啟動init之前,還要挂載根文件系統。
,