kernel 啟動流程

澆築菜鳥發表於2022-12-12

一、概述

之前學習了uboot的啟動流程,現在接著學習uboot的啟動流程,關於 kernel 的啟動流程分析的大佬也是很多的,這裡還是透過流程的圖的方式進行記錄,為了像我一樣的新手,直觀的瞭解 kernel 的啟動流程。

在 kernel 啟動之前已將完成了 uboot 的啟動,看到此筆記的小夥伴應該都知道,還不瞭解的可以看我之前的筆記:UBOOT 啟動流程

二、kernel 檔案目錄介紹

在瞭解 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 檔案,這裡透過對不它們的區別,便可瞭解每個檔案的作用

  1. vmlinux
    vmlinux 是編譯出來的最原始的核心檔案,沒有經過壓縮,所以檔案比較大。

  2. Image
    Image 是 Linux 核心映象檔案,僅包含可執行的二進位制資料。是使用 objcopy 取消掉 vmlinux 中的一些其他資訊,比如符號表等,雖然也沒有壓縮,但是檔案比vmlinux小了很多。

  3. zImage
    zImage 是經過 gzip 壓縮後的 Image,一般燒寫的都是 zImage 映象檔案

  4. uImage
    uImage 是老版本 uboot 專用的映象檔案,uImag 是在 zImage 前面加了一個長度為 64位元組的“頭”,這個頭資訊描述了該映象檔案的型別、載入位置、生成時間、大小等資訊。

四、kernel 彙編啟動階段

  1. vmlinux.lds
    vmlinux.lds 是連結指令碼,透過分析 kernel 頂層 Makefile 檔案可知,映象檔案的打包是從 vmlinux.lds 連結指令碼開始的,vmlinux.lds 檔案位置在 arch/arm/kernel 目錄下,在檔案中使用了ENTRY(stext) 指定了入口 為 stext。

  2. stext
    stext 在檔案 arch/arm/kernel/head.S 中,主要完成了 kernel 的彙編啟動階段。

  3. safe_svcmode_maskall
    safe_svcmode_maskall 在檔案arch/arm/include/asm/assembler.h 中,主要作用,確保cpu處於SVC模式,並且關閉所有終端

  4. __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 中

  5. __lookup_machine_type
    __lookup_machine_type檢測是否支援當前單板。

  6. __vet_atags
    __vet_atags 在檔案 arch/arm/kernel/head-common.S 中,主要作用是驗證 atags 或裝置樹(dtb)的合法性。

  7. __fixup_smp
    __fixup_smp 在當前檔案中,主要作用是處理多核,透過宏CONFIG_SMP_ON_UP 開啟。

  8. __create_page_tables
    __create_page_tables 在檔案檔案 arch/arm/kernel/head.S中,主要作用是建立頁表。

  9. __mmap_switched
    __mmap_switched 在檔案 arch/arm/kernel/head-common.S 中,主要作用是將函式__mmap_switched的地址儲存到 r13 暫存器中,最終會呼叫start_kernel 函式

  10. __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();
}
  1. rcu_scheduler_starting
    rcu_scheduler_starting 主要作用是啟動 RCU 鎖排程器

  2. kernel_thread
    函式 kernel_thread 建立 kernel_init 程式,也就是 init 核心程式。init 程式的 PID 為 1。init 程式一開始是核心程式(也就是執行在核心態),後面 init 程式會在根檔案系統中查詢名為“init”這個程式,這個“init”程式處於使用者態,透過執行這個“init”程式,init 程式就會實現從核心態到使用者態的轉變

  3. kernel_thread
    kernel_thread 建立 kthreadd 核心程式,此核心程式的 PID 為 2。kthreadd
    程式負責所有核心程式的排程和管理。

  4. cpu_startup_entry
    cpu_startup_entry 進入 idle 程式,cpu_startup_entry 會呼叫 cpu_idle_loop,cpu_idle_loop 是個 while 迴圈,也就是 idle 程式程式碼。idle 程式的 PID 為 0,idle 程式叫做空閒程式。

  5. kernel_init_freeable
    kernel_init_freeable 函式用於完成 init 程式的一些其他初始化工作。

  6. do_basic_setup
    do_basic_setup 函式用於完成 Linux 下裝置驅動初始化工作!非常重要。do_basic_setup 會呼叫 driver_init 函式完成 Linux 下驅動模型子系統的初始化。

  7. 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

相關文章