一、概述
之前學習了uboot的啟動流程,現在接著學習uboot的啟動流程,關於 kernel 的啟動流程分析的大佬也是很多的,這裡還是透過流程的圖的方式進行記錄,為了像我一樣的新手,直觀的瞭解 kernel 的啟動流程。
在 kernel 啟動之前已將完成了 uboot 的啟動,看到此筆記的小夥伴應該都知道,還不瞭解的可以看我之前的筆記:UBOOT 啟動流程
二、kernel 檔案目錄介紹
在瞭解 kernel 啟動之前,先了解一下原始碼的目錄,透過下表可以初步瞭解原始碼目錄的作用,想要了解更細一點的,可以看著兩位博友的筆記:
- kernel目錄介紹:https://www.jianshu.com/p/c9053d396fcb
- kernel 目錄 解析:https://www.cnblogs.com/yuanfang/p/1920895.html
名稱 | 描述 |
---|---|
arch | 架構相關目錄 |
block | 塊裝置相關目錄 |
crypto | 加密相關目錄 |
Documentation | 文件相關目錄 |
drivers | 驅動相關目錄 |
fs | 檔案系統相關目錄 |
include | 標頭檔案相關目錄 |
init | 初始化相關目錄 |
ipc | 程式間通訊相關目錄 |
kernel | 核心相關目錄 |
lib | 庫相關目錄 |
LICENSES | 許可相關目錄 |
mm | 記憶體管理相關目錄 |
net | 網路相關目錄 |
samples | 例程相關目錄 |
scripts | 指令碼相關目錄 |
security | 安全相關目錄 |
sound | 音訊處理相關目錄 |
tools | 工具相關目錄 |
usr | 與 initramfs 相關的目錄,用於生成 initramfs |
virt | 提供虛擬機器技術(KVM) |
.config | Linux 最終使用的配置檔案 |
.gitignore | git 工具相關檔案 |
.mailmap | 郵件列表 |
.version | 與版本有關 |
.vmlinux.cmd | cmd 檔案,用於生成 vmlinux |
COPYING | 版權宣告 |
CREDITS | Linux 貢獻者 |
Kbuild | Makefile 會讀取此檔案 |
Kconfig | 圖形化配置介面的配置檔案 |
MAINTAINERS | 維護者名單 |
Makefile | Linux 頂層 Makefile |
Module.xx、modules.xx | 一系列檔案,和模組有關 |
README | Linux 描述檔案 |
System.map | 符號表 |
vmlinux | 編譯出來的、未壓縮的 ELF 格式 Linux 檔案 |
vmlinux.o | 編譯出來的 vmlinux.o 檔案 |
三、映象檔案
編譯完成後,會生成 vmlinux、Image,zImage、uImage 檔案,這裡透過對不它們的區別,便可瞭解每個檔案的作用
-
vmlinux
vmlinux 是編譯出來的最原始的核心檔案,沒有經過壓縮,所以檔案比較大。 -
Image
Image 是 Linux 核心映象檔案,僅包含可執行的二進位制資料。是使用 objcopy 取消掉 vmlinux 中的一些其他資訊,比如符號表等,雖然也沒有壓縮,但是檔案比vmlinux小了很多。 -
zImage
zImage 是經過 gzip 壓縮後的 Image,一般燒寫的都是 zImage 映象檔案 -
uImage
uImage 是老版本 uboot 專用的映象檔案,uImag 是在 zImage 前面加了一個長度為 64位元組的“頭”,這個頭資訊描述了該映象檔案的型別、載入位置、生成時間、大小等資訊。
四、kernel 彙編啟動階段
-
vmlinux.lds
vmlinux.lds 是連結指令碼,透過分析 kernel 頂層 Makefile 檔案可知,映象檔案的打包是從 vmlinux.lds 連結指令碼開始的,vmlinux.lds 檔案位置在 arch/arm/kernel 目錄下,在檔案中使用了ENTRY(stext) 指定了入口 為 stext。 -
stext
stext 在檔案 arch/arm/kernel/head.S 中,主要完成了 kernel 的彙編啟動階段。 -
safe_svcmode_maskall
safe_svcmode_maskall 在檔案arch/arm/include/asm/assembler.h 中,主要作用,確保cpu處於SVC模式,並且關閉所有終端 -
__lookup_processor_type
__lookup_processor_type 在檔案 arch/arm/kernel/head-common.S 檔案中,主要作用是檢查當前系統是否支援此 CPU,如果支援的就獲取procinfo資訊。procinfo 是proc_info_list 型別的結構體 , proc_info_list 在檔案arch/arm/include/asm/procinfo.h 中
-
__lookup_machine_type
__lookup_machine_type檢測是否支援當前單板。 -
__vet_atags
__vet_atags 在檔案 arch/arm/kernel/head-common.S 中,主要作用是驗證 atags 或裝置樹(dtb)的合法性。 -
__fixup_smp
__fixup_smp 在當前檔案中,主要作用是處理多核,透過宏CONFIG_SMP_ON_UP 開啟。 -
__create_page_tables
__create_page_tables 在檔案檔案 arch/arm/kernel/head.S中,主要作用是建立頁表。 -
__mmap_switched
__mmap_switched 在檔案 arch/arm/kernel/head-common.S 中,主要作用是將函式__mmap_switched的地址儲存到 r13 暫存器中,最終會呼叫start_kernel 函式 -
__enable_mmu
__enable_mmu 在檔案 arch/arm/kernel/head.S 中,主要作用是透過呼叫 _turn_mmu_on 來開啟 MMU, _turn_mmu_on 最後會執行 r13 裡面儲存的_mmap_switched 函式。
五、kernel 初始化階段
start_kernel 函式透過呼叫眾多的子函式來完成 Linux 啟動之前的一些初始化工作,由於start_kernel 函式里面呼叫的子函式太多,而這些子函式又很複雜,因此我們簡單的來看一下一些重要的子函式。start_kernel 函式如下所示:
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
/* 設定任務棧結束魔術數,用於棧溢位檢測 */
set_task_stack_end_magic(&init_task);
/* 跟 SMP 有關(多核處理器),設定處理器 ID。
* 有很多資料說 ARM 架構下此函式為空函式,
* 是因為那時候 ARM 還沒有多核處理器。
*/
smp_setup_processor_id();
/* 做一些和 debug 有關的初始化 */
debug_objects_early_init();
/* Set up the the initial canary ASAP: */
boot_init_stack_canary();
/* cgroup 初始化,cgroup 用於控制 Linux 系統資源*/
cgroup_init_early();
/* 關閉當前 CPU 中斷 */
local_irq_disable();
early_boot_irqs_disabled = true;
/****** 中斷關閉期間做一些重要的操作,然後開啟中斷 ******/
/* 跟 CPU 有關的初始化 */
boot_cpu_init();
/* 頁地址相關的初始化 */
page_address_init();
/* 列印 Linux 版本號、編譯時間等資訊 */
pr_notice("%s", linux_banner);
/* 架構相關的初始化,此函式會解析傳遞進來的
* ATAGS 或者裝置樹(DTB)檔案。會根據裝置樹裡面
* 的 model 和 compatible 這兩個屬性值來查詢
* Linux 是否支援這個單板。此函式也會獲取裝置樹
* 中 chosen 節點下的 bootargs 屬性值來得到命令
* 行引數,也就是 uboot 中的 bootargs 環境變數的
* 值,獲取到的命令列引數會儲存到 command_line 中。
*/
setup_arch(&command_line);
#ifdef CONFIG_SS_PROFILING_TIME
// recode_timestamp_ext(0, "start_kernel+", t1);
recode_timestamp_init();
recode_timestamp(__LINE__, "setup_arch-");
#endif
/* 應該是和記憶體有關的初始化 */
mm_init_cpumask(&init_mm);
/* 好像是儲存命令列引數 */
setup_command_line(command_line);
/* 如果只是 SMP(多核 CPU)的話,此函式用於獲取
* CPU 核心數量,CPU 數量儲存在變數 nr_cpu_ids 中。
*/
setup_nr_cpu_ids();
/* 在 SMP 系統中有用,設定每個 CPU 的 per-cpu 資料 */
setup_per_cpu_areas();
boot_cpu_state_init();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
/* 建立系統記憶體頁區(zone)連結串列 */
build_all_zonelists(NULL, NULL);
/* 處理用於熱插拔 CPU 的頁 */
page_alloc_init();
/* 列印命令列資訊 */
pr_notice("Kernel command line: %s\n", boot_command_line);
/* 解析命令列中的 console 引數 */
parse_early_param();
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, NULL, &unknown_bootoption);
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
NULL, set_init_arg);
jump_label_init();
/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
/* 設定 log 使用的緩衝區*/
setup_log_buf(0);
/* 構建 PID 雜湊表,Linux 中每個程式都有一個 ID,
* 這個 ID 叫做 PID。透過構建雜湊表可以快速搜尋程式
* 資訊結構體。
*/
pidhash_init();
/* 預先初始化 vfs(虛擬檔案系統)的目錄項和索引節點快取*/
vfs_caches_init_early();
/* 定義核心異常列表 */
sort_main_extable();
/* 完成對系統保留中斷向量的初始化 */
trap_init();
/* 記憶體管理初始化 */
mm_init();
/* 初始化排程器,主要是初始化一些結構體 */
sched_init();
/* 關閉優先順序搶佔 */
preempt_disable();
/* 檢查中斷是否關閉,如果沒有的話就關閉中斷 */
if (WARN(!irqs_disabled(),
"Interrupts were enabled *very* early, fixing it\n"))
local_irq_disable();
/*允許及早建立工作佇列和工作項排隊/取消。工作項的
* 執行取決於 kthread,並在 workqueue_init()之後開始。
*/
idr_init_cache();
/* 初始化 RCU,RCU 全稱為 Read Copy Update(讀-複製修改) */
rcu_init();
/* 跟蹤除錯相關初始化 */
trace_init();
context_tracking_init();
/* 基數樹相關資料結構初始化 */
radix_tree_init();
/* 初始中斷相關初始化,主要是註冊 irq_desc 結構體變
* 量,因為 Linux 核心使用 irq_desc 來描述一箇中斷。
*/
early_irq_init();
/* 中斷初始化 */
init_IRQ();
/* tick 初始化 */
tick_init();
rcu_init_nohz();
/* 初始化定時器 */
init_timers();
/* 初始化高精度定時器 */
hrtimers_init();
/* 軟中斷初始化 */
softirq_init();
timekeeping_init();
/* 初始化系統時間 */
time_init();
sched_clock_postinit();
printk_nmi_init();
perf_event_init();
profile_init();
call_function_init();
WARN(!irqs_disabled(), "Interrupts were enabled early\n");
early_boot_irqs_disabled = false;
/* 使能中斷 */
local_irq_enable();
/* slab 初始化,slab 是 Linux 記憶體分配器 */
kmem_cache_init_late();
/* 初始化控制檯,之前 printk 列印的資訊都存放
* 緩衝區中,並沒有列印出來。只有呼叫此函式
* 初始化控制檯以後才能在控制檯上列印資訊。
*/
console_init();
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later,
panic_param);
/* 如果定義了宏 CONFIG_LOCKDEP,那麼此函式列印一些資訊。*/
lockdep_info();
/* 鎖自測 */
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) - disabling it.\n",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
page_ext_init();
debug_objects_mem_init();
/* kmemleak 初始化,kmemleak 用於檢查記憶體洩漏 */
kmemleak_init();
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
sched_clock_init();
/* 測定 BogoMIPS 值,可以透過 BogoMIPS 來判斷 CPU 的效能
* BogoMIPS 設定越大,說明 CPU 效能越好。
*/
calibrate_delay();
/* PID 點陣圖初始化 */
pidmap_init();
/* 生成 anon_vma slab 快取 */
anon_vma_init();
acpi_early_init();
#ifdef CONFIG_X86
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode();
#endif
#ifdef CONFIG_X86_ESPFIX64
/* Should be run before the first non-init thread is created */
init_espfix_bsp();
#endif
thread_stack_cache_init();
/* 為物件的每個用於賦予資格(憑證) */
cred_init();
/* 初始化一些結構體以使用 fork 函式 */
fork_init();
/* 給各種資源管理結構分配快取 */
proc_caches_init();
/* 初始化緩衝快取 */
buffer_init();
/* 初始化金鑰 */
key_init();
/* 安全相關初始化 */
security_init();
dbg_late_init();
/* 為 VFS 建立快取 */
vfs_caches_init();
/* 初始化訊號 */
signals_init();
/* 註冊並掛載 proc 檔案系統 */
page_writeback_init();
proc_root_init();
nsfs_init();
/* 初始化 cpuset,cpuset 是將 CPU 和記憶體資源以邏輯性
* 和層次性整合的一種機制,是 cgroup 使用的子系統之一
*/
cpuset_init();
/* 初始化 cgroup */
cgroup_init();
/* 程式狀態初始化 */
taskstats_init_early();
delayacct_init();
/* 檢查寫緩衝一致性 */
check_bugs();
acpi_subsystem_init();
sfi_init_late();
if (efi_enabled(EFI_RUNTIME_SERVICES)) {
efi_late_init();
efi_free_boot_services();
}
ftrace_init();
/* rest_init 函式 */
rest_init();
}
-
rcu_scheduler_starting
rcu_scheduler_starting 主要作用是啟動 RCU 鎖排程器 -
kernel_thread
函式 kernel_thread 建立 kernel_init 程式,也就是 init 核心程式。init 程式的 PID 為 1。init 程式一開始是核心程式(也就是執行在核心態),後面 init 程式會在根檔案系統中查詢名為“init”這個程式,這個“init”程式處於使用者態,透過執行這個“init”程式,init 程式就會實現從核心態到使用者態的轉變 -
kernel_thread
kernel_thread 建立 kthreadd 核心程式,此核心程式的 PID 為 2。kthreadd
程式負責所有核心程式的排程和管理。 -
cpu_startup_entry
cpu_startup_entry 進入 idle 程式,cpu_startup_entry 會呼叫 cpu_idle_loop,cpu_idle_loop 是個 while 迴圈,也就是 idle 程式程式碼。idle 程式的 PID 為 0,idle 程式叫做空閒程式。 -
kernel_init_freeable
kernel_init_freeable 函式用於完成 init 程式的一些其他初始化工作。 -
do_basic_setup
do_basic_setup 函式用於完成 Linux 下裝置驅動初始化工作!非常重要。do_basic_setup 會呼叫 driver_init 函式完成 Linux 下驅動模型子系統的初始化。 -
prepare_namespace
prepare_namespace 是掛載根檔案系統。根檔案系統也是由命令列引數指定的,也就是 uboot 的 bootargs 環境變數。比如“root=/dev/mmcblk1p3 rootwait rw”就表示根檔案系統在/dev/mmcblk1p3 中,也就是 EMMC 的分割槽 3 中。
參考連結
kernel目錄介紹:https://www.jianshu.com/p/c9053d396fcb
kernel 目錄 解析:https://www.cnblogs.com/yuanfang/p/1920895.html