之前學習了uboot的啟動流程,現在接著學習uboot的啟動流程,關於 kernel 的啟動流程分析的大佬也是很多的,這裡還是通過流程的圖的方式進行記錄,為了像我一樣的新手,直觀的瞭解 kernel 的啟動流程。
在 kernel 啟動之前已將完成了 uboot 的啟動,看到此筆記的小夥伴應該都知道,還不瞭解的可以看我之前的筆記:UBOOT 啟動流程
在瞭解 kernel 啟動之前,先了解一下原始碼的目錄,通過下表可以初步瞭解原始碼目錄的作用,想要了解更細一點的,可以看著兩位博友的筆記:
名稱 | 描述 |
---|---|
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位元組的「頭」,這個頭資訊描述了該映象檔案的型別、載入位置、生成時間、大小等資訊。
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 函數。
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