Linux系統移植篇(十一)Linux 內核啟動流程

要分析 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 進程和相關服務的啟動,整個系統逐步進入穩定的多任務運行狀態,用戶可以開始使用系統。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/72636.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/72636.shtml
英文地址,請注明出處:http://en.pswp.cn/web/72636.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Docker入門】構建推送第一個Docker映像

【Docker入門】構建推送第一個Docker映像 Build and Push the First Docker Image By JacksonML Docker的容器(Container)映像是輕量級的可執行獨立包&#xff0c;包含代碼、運行時、庫、環境變量以及配置文件&#xff0c;它對于運行軟件至關重要。注冊表可在團隊間分享映像。…

【eNSP實戰】(續)一個AC多個VAP的實現—將隧道轉發改成直接轉發

在 一個AC多個VAP的實現—CAPWAP隧道轉發 此篇文章配置的基礎上&#xff0c;將隧道轉發改成直接轉發 一、改成直接轉發需要改動的配置 &#xff08;一&#xff09;將連接AP的接口改成trunk口&#xff0c;并允許vlan100、101、102通過 [AC1]interface GigabitEthernet 0/0/8 …

SPI 總線協議

1、協議介紹 SPI&#xff0c;是英語 Serial Peripheral interface 的縮寫&#xff0c;顧名思義就是串行外圍設備接口。是 Motorola 首先在其 MC68HCXX 系列處理器上定義的。 SPI&#xff0c;是一種高速的&#xff0c;全雙工&#xff0c;同步的通信總線。主節點或子節點的數據在…

我愛學算法之——滑動窗口攻克子數組和子串難題(上)

現在來學習"滑動窗口"這一算法思想。 至于什么是"滑動窗口"呢&#xff1f;簡單來說就是同向雙指針&#xff1b;現在來通過題目來了解什么是"滑動窗口" 一、長度最小的子數組 題目鏈接&#xff1a;長度最小的子數組 題目解析 先來看題目&#…

ora-600 ktugct: corruption detected---惜分飛

接手一個oracle 21c的庫恢復請求,通過Oracle數據庫異常恢復檢查腳本(Oracle Database Recovery Check)腳本檢測之后,發現undo文件offline之后,做了resetlogs操作,導致該文件目前處于WRONG RESETLOGS狀態 嘗試恢復數據庫ORA-16433錯誤 SQL> recover datafile 1; ORA-00283:…

20. Excel 自動化:Excel 對象模型

一 Excel 對象模型是什么 Excel對象模型是Excel圖形用戶界面的層次結構表示&#xff0c;它允許開發者通過編程來操作Excel的各種組件&#xff0c;如工作簿、工作表、單元格等。 xlwings 是一個Python庫&#xff0c;它允許Python腳本與Excel進行交互。與一些其他Python庫&#x…

IIS 服務器日志和性能監控

Internet Information Services &#xff08;IIS&#xff09; 是 Microsoft 提供的一款功能強大、靈活且可擴展的 Web 服務器&#xff0c;用于托管網站、服務和應用程序。IIS 支持 HTTP、HTTPS、FTP、SMTP 和更多用于提供網頁的協議&#xff0c;因此廣泛用于企業環境。 IIS 的…

jenkins pipline 自動化測試

以下是一個典型的 Jenkins Pipeline 示例&#xff0c;用于執行自動化測試流程&#xff08;支持單元測試、集成測試、代碼質量掃描&#xff09;&#xff0c;包含多階段執行和測試結果處理&#xff1a; pipeline {agent anyenvironment {// 定義環境變量PROJECT_NAME "my-…

APP測試

一、APP測試范圍 功能測試性能測試&#xff1a;CPU、內存占用、啟動速度、流量、電量消耗、流暢度、穩定性專項測試&#xff1a;安裝卸載升級、push消息推送 、交叉事件測試 、用戶體驗測試 、兼容性測試 二、APP包發布方式及策略 分類&#xff1a; 內部發布渠道。如&#x…

12 File文件對象:創建、獲取基本信息、遍歷文件夾、查找文件;字符集的編解碼 (黑馬Java視頻筆記)

文章目錄 File >> 存儲數據的方案1. 認識File2. File操作2.1 創建File對象2.2 File操作1&#xff09;對文件對象的信息的操作2&#xff09;文件/文件夾的創建/刪除3&#xff09;??對文件夾的遍歷 3. 方法遞歸3.1 認識遞歸3.2 遞歸算法及其執行流程1) 案例&#xff1a;2…

oracle 基礎知識之 多表查詢

多表查詢定義&#xff1a;當查詢的數據并不是來源一個表時&#xff0c;需要使用多表連接操作完成查詢。多表連接查詢通過表之間的關聯字段&#xff0c;一次查詢出多個表的數據。多表查詢包括了等值連接、左連接、右連接、完全連接。 1.等值連接 等值連接也稱為簡單連接&#xf…

服務器防火墻根據什么特征來過濾數據包?

防火墻是服務器安全防護的第一道屏障&#xff0c;它的主要作用是監控、過濾和控制進出服務器的數據流量&#xff0c;防止惡意攻擊、非法訪問和數據泄露。防火墻通過分析數據包的特定特征來決定是否允許、拒絕或限制數據的傳輸。 服務器防火墻的基本工作原理&#xff1a; 防火墻…

Prims region.Views 為null

原因&#xff1a; 導航未完成或異步問題 解決方式&#xff1a;使用回調確認導航完成后再操作視圖 _regionManager.RequestNavigate("MonitorRegion", "MonitorView", nps, navigationResult > {if (navigationResult.Result true){var region _regio…

reconstruct_3d_object_model_for_matching例子

文章目錄 1.獲取om3文件2.準備可視化3.準備3D可視化4.讀取3D模型5.顯示成對注冊結果16.顯示成對注冊結果27.聯合注冊模型8.處理圖像8.1子采樣8.2 圖像計算與平滑8.3 三角測量 9.基于表面做3D匹配10.評估模型準確度10.1 在場景中找到模型10.2 計算模型和場景之間的距離 11.立體系…

軟件安全性測試的重要性和常用工具介紹,軟件測試服務公司推薦

在當今數字化快速發展的時代&#xff0c;軟件已經成為各行各業不可或缺的一部分。然而&#xff0c;隨著軟件系統的復雜性增加&#xff0c;安全性問題也愈發突出&#xff0c;因此軟件產品生產周期中安全測試必不可少。軟件安全性測試是指對軟件系統進行評估&#xff0c;以發現潛…

Redis項目:短信驗證碼登錄

這是黑馬的黑馬點評項目&#xff0c;短信驗證碼的業務。一開始是使用session做的&#xff0c;后來重構&#xff0c;使用redis緩存來完成。 第一層攔截器&#xff1a; public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stri…

Docker下載,包含Win、Mac

介紹 Docker 是一種開源的容器化平臺&#xff0c;通過操作系統級虛擬化技術實現應用的快速開發、部署和運行。以下從多個維度對 Docker 進行詳細介紹&#xff1a; 一、Docker 的核心概念與功能 容器化技術 Docker 利用 Linux 內核的容器隔離技術&#xff08;如 Cgroups 和 Nam…

使用 ESP8266 和 Android 應用程序實現基于 IOT 的語音控制家庭自動化

使用 ESP8266 實現基于 IOT 的語音控制家庭自動化 歡迎來到另一個令人興奮的項目,我們將使用 Wi-Fi 模塊構建一個語音控制ESP8266家庭自動化系統,您可以在其中通過語音通過 Android 應用程序從世界任何地方控制您的家用電器。是的,您只需使用語音命令即可打開或關閉負載(L…

【HarmonyOS Next】鴻蒙中自定義彈框OpenCustomDialog、CustomDialog與DialogHub的區別詳解

【HarmonyOS Next】鴻蒙中自定義彈框OpenCustomDialog、CustomDialog與DialogHub的區別詳解 一、三者的區別與關系 1. 官方迭代過程為&#xff1a; CustomDialog 》 OpenCustomDialog 》 DialogHub 迭代過程表明&#xff0c;彈框的調用越來越便捷&#xff0c;與UI解耦&…

【C++】stack和queue的使用及模擬實現(含deque的簡單介紹)

文章目錄 前言一、deque的簡單介紹1.引入deque的初衷2.deque的結構3.為什么選擇deque作為stack和queue的底層默認容器 二、stack1.stack的介紹2.stack的使用3.stack的模擬實現 三、queue1.queue的介紹2.queue的使用3.queue的模擬實現 前言 一、deque的簡單介紹&#xff08;引入…