linux 啟動流程
CPU 上電后最先執行的啟動代碼,通常確實是放在 arch
目錄下對應架構的啟動文件里。這是因為啟動代碼強相關于 CPU 架構和硬件細節,不同架構差異非常大。
具體說明
1. 為什么啟動代碼放在 arch
目錄?
- 啟動代碼要設置 CPU 狀態(如寄存器、MMU、異常向量表等)。
- 各架構 CPU 啟動流程不一樣,匯編語言代碼也差別大。
- 所以放在
arch/架構名/boot/
或類似目錄,便于管理。
2. 以常見架構為例
架構 | 啟動代碼位置示例 | 說明 |
---|---|---|
x86 | arch/x86/boot/ | 包含 real mode 到 protected mode 切換等代碼 |
ARM | arch/arm/boot/ | 包含內核入口匯編、initrd搬移等代碼 |
ARM64 | arch/arm64/kernel/head.S | 64位 ARM 啟動匯編代碼 |
MIPS | arch/mips/kernel/ | MIPS 啟動相關代碼 |
3. 啟動流程簡述
- CPU 上電后,會跳轉到一個固定的物理地址(Reset Vector)。
- 該地址對應的代碼就是架構相關的啟動代碼,一般就是
arch/.../boot/...
里的匯編。 - 啟動代碼初始化最基本的硬件,設置內存和CPU狀態。
- 然后跳轉進入 C 語言編寫的內核入口函數
start_kernel()
。
總結
- CPU 上電后的第一段執行代碼一定是架構相關的啟動代碼,放在
arch
目錄下。 - 這是因為啟動時的硬件初始化和上下文設置緊密依賴 CPU 架構。
- 通用內核代碼則放在
kernel/
、mm/
、fs/
等目錄。
核心原因是內核啟動前后環境差異巨大,必須用匯編代碼做早期硬件和處理器環境初始化,才能安全可靠地進入 start_kernel。
Linux 內核源碼里的 arch/
目錄是 不同 CPU 架構相關代碼的匯總目錄。
具體解釋
1. Linux 內核支持多種 CPU 架構
- 比如 x86、ARM、MIPS、PowerPC、RISC-V 等。
- 每種架構底層硬件接口和啟動流程差別很大。
- 因此,內核源碼會根據架構分目錄管理。
2. arch/
目錄的作用
arch/
目錄下每個子目錄對應一個 CPU 架構,比如:
arch/├── x86/ # x86 架構相關代碼├── arm/ # 32位 ARM 架構代碼├── arm64/ # 64位 ARM 架構代碼├── mips/ # MIPS 架構代碼├── riscv/ # RISC-V 架構代碼...
- 每個目錄里包含啟動匯編、CPU相關驅動、體系結構專屬的調度、內存管理、異常處理等代碼。
- 其他跟架構無關的內核通用代碼則在
kernel/
、mm/
、fs/
等目錄。
詳細解釋
1. CPU 上電后環境非常“原始”
- CPU 剛上電時,只處于物理地址模式,沒有開啟分頁(虛擬內存)
- 寄存器值不確定
- 堆棧、內存布局未初始化
- 特權級和CPU模式尚未設置
此時無法直接跳轉執行復雜的C代碼,因為C代碼依賴于正確的棧、數據段和運行環境。
2. 匯編入口(_start)做的事情
匯編代碼位于內核最開始的入口,做以下關鍵工作:
- 設置 CPU 特權級(例如切換到內核態)
- 配置段寄存器、堆棧指針
- 設置分頁機制(啟用虛擬內存)
- 清理未初始化的BSS段
- 設置全局指針、數據指針等CPU寄存器
- 關閉中斷或做其他硬件相關初始化
這些操作都只能用匯編完成,因為C環境尚未準備好。
3. start_kernel
的前提
start_kernel()
是用 C 語言寫的,它假設運行環境已經穩定:
- 棧已初始化
- 全局變量可以正常訪問
- CPU 處于正確模式
- 內存管理子系統已部分就緒
換句話說,start_kernel
是“內核的真正入口”,但不是“CPU的入口”。
4. 總結
階段 | 作用 | 為什么不能直接跳到 start_kernel |
---|---|---|
CPU 上電 → _start 匯編 | 初始化 CPU 寄存器、棧、分頁、特權級 | 需要底層硬件初始化,C代碼依賴這些,直接跳C會崩潰 |
_start → start_kernel | 進入內核 C 語言環境,啟動內核子系統 | 需要完整運行環境,匯編做的準備工作必須先完成 |
簡單比喻
匯編入口相當于“內核啟動的引導員”,負責把環境布置好;
start_kernel
是“內核真正的司機”,環境準備好了才上車啟動。
好的,講講 secondary_start_kernel
的作用和背景。
什么是 secondary_start_kernel
?
- 它是 Linux 內核在多核(SMP)系統中,非主CPU(Secondary CPU) 啟動時的入口函數。
- 主CPU(CPU0)執行
start_kernel
來啟動整個系統和初始化多核環境。 - 其他CPU啟動后,會跳到
secondary_start_kernel
,完成自己的初始化流程。
為什么需要 secondary_start_kernel
?
- 多核CPU啟動時,只有主核(CPU0)從復位向量啟動,執行完整的內核啟動流程。
- 其他核處于停頓狀態,需要被喚醒,并單獨執行一套簡化的初始化。
- 這些核不會執行
start_kernel
,而是執行secondary_start_kernel
,完成自己的CPU初始化和進入調度。
secondary_start_kernel
主要做什么?
- 初始化該CPU的本地數據結構(如CPU寄存器、TSS、IDT等)
- 設置該CPU的中斷和調度環境
- 把該CPU加入到內核的調度器中,開始運行任務
- 不重復執行主核完成的系統全局初始化
簡單流程示意
主核 (CPU0) 啟動 -> 執行 start_kernel -> 初始化系統 -> 喚醒其他核
其他核 (CPU1..N) 啟動 -> 執行 secondary_start_kernel -> 初始化本地CPU環境 -> 進入調度循環
位置與調用
secondary_start_kernel
通常定義在init/main.c
- 各架構啟動匯編在喚醒其他CPU時會跳轉到這個函數
- 是 SMP 初始化的關鍵環節
總結
函數名 | 作用 | 適用CPU |
---|---|---|
start_kernel | 主核啟動,系統初始化 | CPU0(主CPU) |
secondary_start_kernel | 非主核啟動,局部CPU初始化和進入調度 | 其他CPU(輔助CPU) |
好的,下面詳細介紹 initrd
在 ARM 和 x86 Linux 內核啟動流程中的具體實現細節,幫助你理解它是怎么工作的。
一、initrd 的啟動流程核心概念
- initrd 是內核啟動時內存中的一個臨時根文件系統映像。
- 內核啟動時先掛載它作為根文件系統,執行初始化工作,之后切換到真實根文件系統。
- 在啟動參數中通過內核命令行參數或 bootloader 傳遞 initrd 的位置和大小。
二、x86 啟動流程中的 initrd
1. Bootloader 階段
- Bootloader(如 GRUB)加載內核鏡像
vmlinuz
和 initrd 鏡像到內存。 - 將 initrd 起始地址和大小放入內核啟動參數(例如內核命令行中的
initrd=0x地址,大小
)。 - 將控制權交給內核。
2. 內核啟動匯編階段
- 在
arch/x86/boot/header.S
等文件,內核啟動匯編代碼初始化后,會調用內核的 C 入口start_kernel()
。 - 內核解析啟動參數,獲得 initrd 地址和大小。
3. 內核 C 語言初始化階段(init/do_mounts.c
)
- 內核根據啟動參數調用
setup_initrd()
(2.6 內核及類似版本)。 setup_initrd()
負責把 initrd 映像注冊為臨時塊設備(ramdisk),并掛載為根文件系統(initrd rootfs)。- 調用 initrd 內部的
/init
程序進行系統初始化。
4. 切換到真實根文件系統
- 初始化完成后,
pivot_root()
或switch_root()
將根文件系統切換到硬盤或其他設備的文件系統。 - initrd 被卸載,內核開始正常運行用戶空間系統。
三、ARM 啟動流程中的 initrd
1. Bootloader 階段
-
Bootloader(如 U-Boot)將內核鏡像(zImage/uImage)和 initrd 加載到內存。
-
U-Boot 通過寄存器(如 r0-r3)傳遞內核啟動參數:
- r2 寄存器通常保存 initrd 的起始地址。
- r3 寄存器保存 initrd 大小。
-
設備樹(DTB)也可能攜帶 initrd 信息。
2. ARM 匯編入口階段 (arch/arm/boot/bootp/init.S
等)
- 內核啟動匯編代碼讀取這些參數,做內存搬移(前面你看過的
init.S
代碼就是搬移 initrd 的例子)。 - 將 initrd 拷貝到合適內存區域,確保后續內核使用。
3. 內核 C 入口 (start_kernel
)
- 解析傳入的 initrd 地址和大小,調用
setup_initrd()
。 - initrd 作為臨時根文件系統掛載。
- 執行 initrd 內部初始化腳本。
4. 切換根文件系統
- 跟 x86 類似,切換到真實根文件系統,完成啟動。
四、內核中與 initrd 相關的關鍵函數
setup_arch()
:在架構相關初始化時調用,解析啟動參數,設置 initrd 信息。setup_initrd()
:注冊并掛載 initrd,準備臨時根文件系統。mount_root()
:掛載根文件系統時優先使用 initrd,后續切換根。pivot_root()
/switch_root()
:實現根文件系統的切換。
五、簡要總結流程圖
Bootloader 加載內核和 initrd --> 內核啟動匯編(解析initrd參數,搬移initrd) --> start_kernel(調用setup_initrd,掛載initrd) --> 執行initrd內的/init程序初始化 --> 切換到真實根文件系統(硬盤/網絡文件系統等)
六、相關文件位置(Linux 源碼)
功能 | 典型源碼路徑 |
---|---|
ARM 啟動匯編 | arch/arm/boot/bootp/init.S |
x86 啟動匯編 | arch/x86/boot/header.S |
initrd 處理 | init/do_mounts.c 、init/do_mounts.h |
根文件系統掛載 | fs/namespace.c |
當然可以,下面是將你提供的 Linux 啟動流程(包括 Bootloader、主核、從核、initrd、根文件系統切換等)融合成的一個 Mermaid 序列圖:
? Mermaid 序列圖(Linux 多核啟動流程 + initrd 啟動)
📌 說明
CPU0
是主核,從入口跳入start_kernel
,控制整個啟動過程。CPU1..N
是從核,由主核在適當時機喚醒,執行secondary_start_kernel
。Bootloader
通常負責將內核、initrd 以及設備樹加載到內存,并設置參數。initrd
是臨時根文件系統,內核通過參數掛載它,并執行其中的/init
。/init
會負責進一步初始化和掛載真實根文件系統(如 ext4、NFS 等)。- 最終系統進入真實根文件系統的
/sbin/init
,開始正常運行。