要分析 Linux 啟動流程,同樣需要先編譯一下 Linux 源碼,因為有很多文件是需要編譯才 會生成的。首先分析 Linux 內核的連接腳本文件 arch/arm/kernel/vmlinux.lds,通過鏈接腳本可以 找到 Linux 內核的第一行程序是從哪里執行的。vmlinux.lds 中有如下代碼:
492 OUTPUT_ARCH(arm)
493 ENTRY(stext)
494 jiffies = jiffies_64;
495 SECTIONS
496 {
497 /*
498 * XXX: The linker does not define how output sections are
499 * assigned to input sections when there are multiple statements
500 * matching the same input section name. There is no documented
501 * order of matching.
502 *
503 * unwind exit sections must be discarded before the rest of the
504 * unwind sections get included.
505 */
506 /DISCARD/ : {
507 *(.ARM.exidx.exit.text)
508 *(.ARM.extab.exit.text)
509
......
645 }
ENTRY 指明了了 Linux 內核入口,入口為 stext,stext 定義在文件 arch/arm/kernel/head.S 中 , 因 此 要 分 析 Linux 內 核 的 啟 動 流 程 , 就 得 先 從 文 件 arch/arm/kernel/head.S 的 stext 處開始分析。
Linux 內核入口 stext。
stext 是 Linux 內核的入口地址,在文件 arch/arm/kernel/head.S 中有如下所示提示內容:
/* * Kernel startup entry point. * --------------------------- * * This is normally called from the decompressor code. The requirements * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0, * r1 = machine nr, r2 = atags or dtb pointer. .....
*/
①、關閉 MMU。 ②、關閉 D-cache。 ③、I-Cache 無所謂。 ④、r0=0。 ⑤、r1=machine nr(也就是機器 ID)。 ⑥、r2=atags 或者設備樹(dtb)首地址。
Linux 內核的入口點 stext 其實相當于內核的入口函數,stext 函數內容如下:
80 ENTRY(stext)
......
91 @ ensure svc mode and all interrupts masked
92 safe_svcmode_maskall r9
93
94 mrc p15, 0, r9, c0, c0 @ get processor id
95 bl __lookup_processor_type @ r5=procinfo r9=cpuid
96 movs r10, r5 @ invalid processor (r5=0)?
97 THUMB( it eq ) @ force fixup-able long branch encoding
98 beq __error_p @ yes, error 'p'
99
......
107
108 #ifndef CONFIG_XIP_KERNEL
......
113 #else
114 ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case
115 #endif
116
117 /*
118 * r1 = machine no, r2 = atags or dtb,
119 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
120 */
121 bl __vet_atags
......
128 bl __create_page_tables
129
130 /*
131 * The following calls CPU specific code in a position independent
132 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of
133 * xxx_proc_info structure selected by __lookup_processor_type
134 * above. On return, the CPU will be ready for the MMU to be
135 * turned on, and r0 will hold the CPU control register value.
136 */
137 ldr r13, =__mmap_switched @ address to jump to after
138 @ mmu has been enabled
139 adr lr, BSYM(1f) @ return (PIC) address
140 mov r8, r4 @ set TTBR1 to swapper_pg_dir
141 ldr r12, [r10, #PROCINFO_INITFUNC]
142 add r12, r12, r10
143 ret r12
144 1: b __enable_mmu
145 ENDPROC(stext)
第 92 行,調用函數 safe_svcmode_maskall 確保 CPU 處于 SVC 模式,并且關閉了所有的中 斷。safe_svcmode_maskall 定義在文件 arch/arm/include/asm/assembler.h 中。
第 94 行,讀處理器 ID,ID 值保存在 r9 寄存器中。
第 95 行,調用函數__lookup_processor_type 檢查當前系統是否支持此 CPU,如果支持就獲 取 procinfo 信 息 。 procinfo 是 proc_info_list 類 型 的 結 構 體 , proc_info_list 在 文 件 arch/arm/include/asm/procinfo.h 中的定義如下:
struct proc_info_list { unsigned int cpu_val; unsigned int cpu_mask; unsigned long __cpu_mm_mmu_flags; /* used by head.S */ unsigned long __cpu_io_mmu_flags; /* used by head.S */ unsigned long __cpu_flush; /* used by head.S */ const char *arch_name; const char *elf_name; unsigned int elf_hwcap; const char *cpu_name; struct processor *proc; struct cpu_tlb_fns *tlb; struct cpu_user_fns *user; struct cpu_cache_fns *cache;
};
Linux 內核將每種處理器都抽象為一個 proc_info_list 結構體,每種處理器都對應一個 procinfo。因此可以通過處理器 ID 來找到對應的 procinfo 結構,__lookup_processor_type 函數找 到對應處理器的 procinfo 以后會將其保存到 r5 寄存器中。 繼續回到示例代碼 36.2.1.2 中,第 121 行,調用函數__vet_atags 驗證 atags 或設備樹(dtb)的 合法性。函數__vet_atags 定義在文件 arch/arm/kernel/head-common.S 中。 第 128 行,調用函數__create_page_tables 創建頁表。 第 137 行,將函數__mmap_switched 的地址保存到 r13 寄存器中。__mmap_switched 定義在 文件 arch/arm/kernel/head-common.S,__mmap_switched 最終會調用 start_kernel 函數。 第 144 行 , 調 用 __enable_mmu 函 數 使 能 MMU , __enable_mmu 定 義 在 文 件 arch/arm/kernel/head.S 中。__enable_mmu 最終會通過調用__turn_mmu_on 來打開 MMU, __turn_mmu_on 最后會執行 r13 里面保存的__mmap_switched 函數。
這里重點說一下內核為什么要關閉MMU,也就是內存映射,后面又要打開?這個過程是為什么?
在內核啟動的早期階段,系統處于物理地址模式下(即 MMU 處于關閉狀態),這樣可以直接訪問物理內存,方便初始化關鍵數據結構和加載必要的代碼。具體原因包括:
初始引導環境簡化操作:上電后,處理器默認運行在關閉 MMU 的狀態,此時所有地址都是物理地址,避免了虛擬地址轉換的復雜性。這有助于啟動代碼(bootloader 和內核早期代碼)順利加載并執行。
頁表構建:內核在啟動過程中需要構建頁表,以建立虛擬地址到物理地址的映射。此時必須依賴物理地址操作來確保數據和代碼能被正確訪問。構建好頁表之前,開啟 MMU 會導致地址轉換錯誤或不可預知的行為。
切換到虛擬內存管理:一旦內核完成了頁表的設置(例如通過調用 __create_page_tables),它就可以啟用 MMU,這時所有內存訪問都將通過頁表進行地址轉換。這樣做能夠提供虛擬內存、內存保護、緩存管理等高級功能,對后續內核和應用程序的穩定性與安全性至關重要。
系統安全與隔離:開啟 MMU 后,內核可以實現內核空間和用戶空間的隔離,以及各種內存保護機制,這些都是現代操作系統必備的特性。
__mmap_switched 函數定義在文件 arch/arm/kernel/head-common.S 中
81 __mmap_switched:
82 adr r3, __mmap_switched_data
83
84 ldmia r3!, {r4, r5, r6, r7}
85 cmp r4, r5 @ Copy data segment if needed
86 1: cmpne r5, r6
87 ldrne fp, [r4], #4
88 strne fp, [r5], #4
89 bne 1b
90
91 mov fp, #0 @ Clear BSS (and zero fp)
92 1: cmp r6, r7
93 strcc fp, [r6],#4
94 bcc 1b
95
96 ARM( ldmia r3, {r4, r5, r6, r7, sp})
97 THUMB( ldmia r3, {r4, r5, r6, r7} )
98 THUMB( ldr sp, [r3, #16] )
99 str r9, [r4] @ Save processor ID
100 str r1, [r5] @ Save machine type
101 str r2, [r6] @ Save atags pointer
102 cmp r7, #0
103 strne r0, [r7] @ Save control register values
104 b start_kernel
105 ENDPROC(__mmap_switched)
最終調用 start_kernel 來啟動 Linux 內核,start_kernel 函數定義在文件 init/main.c 中。
start_kernel 通過調用眾多的子函數來完成 Linux 啟動之前的一些初始化工作,由于 start_kernel 函數里面調用的子函數太多,而這些子函數又很復雜,因此我們簡單的來看一下一些重要的子函數。精簡并添加注釋后的 start_kernel 函數內容如下:
asmlinkage __visible void __init start_kernel(void)
{ char *command_line; char *after_dashes; lockdep_init(); /* lockdep 是死鎖檢測模塊,此函數會初始化
* 兩個 hash 表。此函數要求盡可能早的執行! */
set_task_stack_end_magic(&init_task);/* 設置任務棧結束魔術數,
*用于棧溢出檢測
*/ smp_setup_processor_id(); /* 跟 SMP 有關(多核處理器),設置處理器 ID。 * 有很多資料說 ARM 架構下此函數為空函數,那是因 * 為他們用的老版本 Linux,而那時候 ARM 還沒有多 * 核處理器。
*/ debug_objects_early_init(); /* 做一些和 debug 有關的初始化 */ boot_init_stack_canary(); /* 棧溢出檢測初始化 */ cgroup_init_early(); /* cgroup 初始化,cgroup 用于控制 Linux 系統資源*/ local_irq_disable(); /* 關閉當前 CPU 中斷 */ early_boot_irqs_disabled = true; /* * 中斷關閉期間做一些重要的操作,然后打開中斷 */ boot_cpu_init(); /* 跟 CPU 有關的初始化 */ page_address_init(); /* 頁地址相關的初始化 */ pr_notice("%s", linux_banner);/* 打印 Linux 版本號、編譯時間等信息 */ setup_arch(&command_line); /* 架構相關的初始化,此函數會解析傳遞進來的 * ATAGS 或者設備樹(DTB)文件。會根據設備樹里面 * 的 model 和 compatible 這兩個屬性值來查找 * Linux 是否支持這個單板。此函數也會獲取設備樹 * 中 chosen 節點下的 bootargs 屬性值來得到命令 * 行參數,也就是 uboot 中的 bootargs 環境變量的
* 值,獲取到的命令行參數會保存到
*command_line 中。 */ mm_init_cpumask(&init_mm); /* 看名字,應該是和內存有關的初始化 */ setup_command_line(command_line); /* 好像是存儲命令行參數 */ setup_nr_cpu_ids(); /* 如果只是 SMP(多核 CPU)的話,此函數用于獲取 * CPU 核心數量,CPU 數量保存在變量 * nr_cpu_ids 中。
*/ setup_per_cpu_areas(); /* 在 SMP 系統中有用,設置每個 CPU 的 per-cpu 數據 */ smp_prepare_boot_cpu(); build_all_zonelists(NULL, NULL); /* 建立系統內存頁區(zone)鏈表 */ page_alloc_init(); /* 處理用于熱插拔 CPU 的頁 */
/* 打印命令行信息 */
pr_notice("Kernel command line: %s\n", boot_command_line); parse_early_param(); /* 解析命令行中的 console 參數 */ after_dashes = parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, -1, -1, &unknown_bootoption); if (!IS_ERR_OR_NULL(after_dashes)) parse_args("Setting init args", after_dashes, NULL, 0, -1, -1, set_init_arg); jump_label_init(); setup_log_buf(0); /* 設置 log 使用的緩沖區*/ pidhash_init(); /* 構建 PID 哈希表,Linux 中每個進程都有一個 ID, * 這個 ID 叫做 PID。通過構建哈希表可以快速搜索進程 * 信息結構體。 */
vfs_caches_init_early(); /* 預先初始化 vfs(虛擬文件系統)的目錄項和
* 索引節點緩存
*/ 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(); idr_init_cache(); /* IDR 初始化,IDR 是 Linux 內核的整數管理機 * 制,也就是將一個整數 ID 與一個指針關聯起來。 */ rcu_init(); /* 初始化 RCU,RCU 全稱為 Read Copy Update(讀-拷貝修改) */ trace_init(); /* 跟蹤調試相關初始化 */ context_tracking_init(); radix_tree_init(); /* 基數樹相關數據結構初始化 */ early_irq_init(); /* 初始中斷相關初始化,主要是注冊 irq_desc 結構體變 * 量,因為 Linux 內核使用 irq_desc 來描述一個中斷。 */ init_IRQ(); /* 中斷初始化 */ tick_init(); /* tick 初始化 */
rcu_init_nohz(); init_timers(); /* 初始化定時器 */ hrtimers_init(); /* 初始化高精度定時器 */ softirq_init(); /* 軟中斷初始化 */ timekeeping_init(); time_init(); /* 初始化系統時間 */ sched_clock_postinit(); perf_event_init(); profile_init(); call_function_init(); WARN(!irqs_disabled(), "Interrupts were enabled early\n"); early_boot_irqs_disabled = false; local_irq_enable(); /* 使能中斷 */ kmem_cache_init_late(); /* slab 初始化,slab 是 Linux 內存分配器 */ console_init(); /* 初始化控制臺,之前 printk 打印的信息都存放 * 緩沖區中,并沒有打印出來。只有調用此函數 * 初始化控制臺以后才能在控制臺上打印信息。 */ if (panic_later) panic("Too many boot %s vars at `%s'", panic_later, panic_param); lockdep_info();/* 如果定義了宏 CONFIG_LOCKDEP,那么此函數打印一些信息。*/ locking_selftest() /* 鎖自測 */ ...... page_ext_init(); debug_objects_mem_init(); kmemleak_init(); /* kmemleak 初始化,kmemleak 用于檢查內存泄漏 */ setup_per_cpu_pageset(); numa_policy_init(); if (late_time_init) late_time_init(); sched_clock_init(); calibrate_delay(); /* 測定 BogoMIPS 值,可以通過 BogoMIPS 來判斷 CPU 的性能 * BogoMIPS 設置越大,說明 CPU 性能越好。 */ pidmap_init(); /* PID 位圖初始化 */ anon_vma_init(); /* 生成 anon_vma slab 緩存 */ acpi_early_init(); ...... thread_info_cache_init(); cred_init(); /* 為對象的每個用于賦予資格(憑證) */ fork_init(); /* 初始化一些結構體以使用 fork 函數 */ proc_caches_init(); /* 給各種資源管理結構分配緩存 */ buffer_init(); /* 初始化緩沖緩存 */ key_init(); /* 初始化密鑰 */ security_init(); /* 安全相關初始化 */ dbg_late_init(); vfs_caches_init(totalram_pages); /* 為 VFS 創建緩存 */ signals_init(); /* 初始化信號 */ page_writeback_init(); /* 頁回寫初始化 */ proc_root_init(); /* 注冊并掛載 proc 文件系統 */ nsfs_init(); cpuset_init(); /* 初始化 cpuset,cpuset 是將 CPU 和內存資源以邏輯性 * 和層次性集成的一種機制,是 cgroup 使用的子系統之一 */ cgroup_init(); /* 初始化 cgroup */ 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 函數 */
}
start_kernel 里面調用了大量的函數,每一個函數都是一個龐大的知識點,如果想要學習 Linux 內核,那么這些函數就需要去詳細的研究。本教程注重于嵌入式 Linux 入門,因此不會去 講太多關于 Linux 內核的知識。start_kernel 函數最后調用了 rest_init,接下來簡單看一下 rest_init 函數。
rest_init 函數定義在文件 init/main.c 中,函數內容如下:
383 static noinline void __init_refok rest_init(void)
384 {
385 int pid;
386
387 rcu_scheduler_starting();
388 smpboot_thread_init();
389 /*
390 * We need to spawn init first so that it obtains pid 1, however
391 * the init task will end up wanting to create kthreads, which,
392 * if we schedule it before we create kthreadd, will OOPS.
393 */
394 kernel_thread(kernel_init, NULL, CLONE_FS);
395 numa_default_policy();
396 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
397 rcu_read_lock();
398 kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
399 rcu_read_unlock();
400 complete(&kthreadd_done);
401
402 /*
403 * The boot idle thread must execute schedule()
404 * at least once to get things moving:
405 */
406 init_idle_bootup_task(current);
407 schedule_preempt_disabled();
408 /* Call into cpu_idle with preempt disabled */
409 cpu_startup_entry(CPUHP_ONLINE);
410 }
第 387 行,調用函數 rcu_scheduler_starting,啟動 RCU 鎖調度器
第 394 行,調用函數 kernel_thread 創建 kernel_init 進程,也就是大名鼎鼎的 init 內核進程。 init 進程的 PID 為 1。init 進程一開始是內核進程(也就是運行在內核態),后面 init 進程會在根 文件系統中查找名為“init”這個程序,這個“init”程序處于用戶態,通過運行這個“init”程 序,init 進程就會實現從內核態到用戶態的轉變。
第 396 行,調用函數 kernel_thread 創建 kthreadd 內核進程,此內核進程的 PID 為 2。kthreadd 進程負責所有內核進程的調度和管理。
第 409 行,最后調用函數 cpu_startup_entry 來進入 idle 進程,cpu_startup_entry 會調用 cpu_idle_loop,cpu_idle_loop 是個 while 循環,也就是 idle 進程代碼。idle 進程的 PID 為 0,idle 進程叫做空閑進程,如果學過 FreeRTOS 或者 UCOS 的話應該聽說過空閑任務。idle 空閑進程 就和空閑任務一樣,當 CPU 沒有事情做的時候就在 idle 空閑進程里面“瞎逛游”,反正就是給 CPU 找點事做。當其他進程要工作的時候就會搶占 idle 進程,從而奪取 CPU 使用權。其實大 家應該可以看到 idle 進程并沒有使用 kernel_thread 或者 fork 函數來創建,因為它是有主進程演 變而來的。 ????????在 Linux 終端中輸入“ps -A”就可以打印出當前系統中的所有進程,其中就能看到 init 進程和 kthreadd 進程。
init 進程的 PID 為 1,kthreadd 進程的 PID 為 2。之所以圖 36.2.4.1 中沒有顯示 PID 為 0 的 idle 進程,那是因為 idle 進程是內核進程。init 進程,kernel_init 就是 init 進程的進程函數。
kernel_init 函數就是 init 進程具體做的工作,定義在文件 init/main.c 中。
928 static int __ref kernel_init(void *unused)
929 {
930 int ret;
931
932 kernel_init_freeable(); /* init 進程的一些其他初始化工作 */
933 /* need to finish all async __init code before freeing the
memory */
934 async_synchronize_full(); /* 等待所有的異步調用執行完成 */
935 free_initmem(); /* 釋放 init 段內存 */
936 mark_rodata_ro();
937 system_state = SYSTEM_RUNNING; /* 標記系統正在運行 */
938 numa_default_policy();
939
940 flush_delayed_fput();
941
942 if (ramdisk_execute_command) {
943 ret = run_init_process(ramdisk_execute_command);
944 if (!ret)
945 return 0;
946 pr_err("Failed to execute %s (error %d)\n",
947 ramdisk_execute_command, ret);
948 }
949
950 /*
951 * We try each of these until one succeeds.
952 *
953 * The Bourne shell can be used instead of init if we are
954 * trying to recover a really broken machine.
955 */
956 if (execute_command) {
957 ret = run_init_process(execute_command);
958 if (!ret)
959 return 0;
960 panic("Requested init %s failed (error %d).",
961 execute_command, ret);
962 }
963 if (!try_to_run_init_process("/sbin/init") ||
964 !try_to_run_init_process("/etc/init") ||
965 !try_to_run_init_process("/bin/init") ||
966 !try_to_run_init_process("/bin/sh"))
967 return 0;
968
969 panic("No working init found. Try passing init= option to
kernel. "
970 "See Linux Documentation/init.txt for guidance.");
971 } 第 932 行,kernel_init_freeable 函數用于完成 init 進程的一些其他初始化工作,稍后再來具
體看一下此函數。
第 940 行,ramdisk_execute_command 是一個全局的 char 指針變量,此變量值為“/init”,
也就是根目錄下的 init 程序。ramdisk_execute_command 也可以通過 uboot 傳遞,在 bootargs 中
使用“rdinit=xxx”即可,xxx 為具體的 init 程序名字。 第 943 行,如果存在“/init”程序的話就通過函數 run_init_process 來運行此程序。 第 956 行,如果 ramdisk_execute_command 為空的話就看 execute_command 是否為空,反
正不管如何一定要在根文件系統中找到一個可運行的 init 程序。execute_command 的值是通過
uboot 傳遞,在 bootargs 中使用“init=xxxx”就可以了,比如“init=/linuxrc”表示根文件系統中
的 linuxrc 就是要執行的用戶空間 init 程序。 第 963~966 行,如果 ramdisk_execute_command 和 execute_command 都為空,那么就依次
查找“/sbin/init”、“/etc/init”、“/bin/init”和“/bin/sh”,這四個相當于備用 init 程序,如果這四
個也不存在,那么 Linux 啟動失敗! 第 969 行,如果以上步驟都沒有找到用戶空間的 init 程序,那么就提示錯誤發生! 最后來簡單看一下 kernel_init_freeable 函數,前面說了,kernel_init 會調用此函數來做一些
init 進程初始化工作。kernel_init_freeable 定義在文件 init/main.c 中,縮減后的函數內容如下:
示例代碼 36.2.5.2 kernel_init_freeable 函數
973 static noinline void __init kernel_init_freeable(void)
974 {
975 /*
976 * Wait until kthreadd is all set-up.
977 */
978 wait_for_completion(&kthreadd_done);/* 等待 kthreadd 進程準備就緒 */
......
998
999 smp_init(); /* SMP 初始化 */
1000 sched_init_smp(); /* 多核(SMP)調度初始化 */
1001
1002 do_basic_setup(); /* 設備初始化都在此函數中完成 */
1003
1004 /* Open the /dev/console on the rootfs, this should never fail */
1005 if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) <
0)
1006 pr_err("Warning: unable to open an initial console.\n");
1007
1008 (void) sys_dup(0);
1009 (void) sys_dup(0);
1010 /*
1011 * check if there is an early userspace init. If yes, let it do
1012 * all the work
1013 */
1014
1015 if (!ramdisk_execute_command)
1016 ramdisk_execute_command = "/init";
1017
1018 if (sys_access((const char __user *) ramdisk_execute_command,
0) != 0) {
1019 ramdisk_execute_command = NULL;
1020 prepare_namespace();
1021 }
1022
1023 /*
1024 * Ok, we have completed the initial bootup, and
1025 * we're essentially up and running. Get rid of the
1026 * initmem segments and start the user-mode stuff..
1027 *
1028 * rootfs is available now, try loading the public keys
1029 * and default modules
1030 */
1031
1032 integrity_load_keys();
1033 load_default_modules();
1034 }
現在總結一下流程吧。
內核入口與解壓內核
內核入口代碼通常位于各架構目錄下的匯編文件(如 x86 的 arch/x86/boot
或 ARM 的 arch/arm/kernel/head.S
)。在這一步:
內核首先解壓自身(如果內核是壓縮的)。
完成最基本的 CPU 和內存環境設置。
設置處理器模式與寄存器
啟動時系統一般在關閉 MMU 的物理地址模式下運行,便于直接訪問物理內存。早期代碼還會根據處理器 ID 找到對應的 procinfo
結構,并做一些必要的寄存器初始化(例如將對應的結構地址保存到特定寄存器中)。
建立頁表與啟用 MMU
內核在這一階段構建頁表,將物理地址映射到虛擬地址空間。這包括:
調用函數如 __create_page_tables
來創建頁表。
通過調用 __enable_mmu
(最終由 __turn_mmu_on
實現)來開啟 MMU,實現從物理地址模式到虛擬地址模式的切換。
切換后,所有內存訪問均通過虛擬地址進行,便于實現內存保護、緩存管理等功能。
啟動第一個用戶進程:init
內核初始化完成后,啟動第一個用戶空間進程(通常為 init 進程,PID 為 1),它負責加載和啟動后續用戶空間的各項服務。
init 進程可能采用 System V init、systemd 或其它初始化系統,進一步啟動登錄服務、網絡服務、圖形界面等。
系統進入正常運行狀態
隨著 init 進程和相關服務的啟動,整個系統逐步進入穩定的多任務運行狀態,用戶可以開始使用系統。