參考./Documentation/arm64/booting.txt
Bootloader至少完成以下基本的初始化準備:
設置並初始化RAM(必須),引導加載程序應找到並初始化內核將用於系統中易失性數據存儲的所有RAM。它以機器相關的方式執行此操作。(它可以使用內部算法來自動定位和調整所有RAM的大小,或者可以使用機器中RAM的知識或引導加載程序設計者認為合適的任何其他方法。)
設置設備樹dtb(必須) , 設備樹blob(dtb)必須8位元組對齊,並且大小不能超過2兆字節。由於dtb將使用最大2 MB的塊進行映射以可緩存,因此它不能放置在必須使用任何特定屬性進行映射的任何2M區域內。注意:v4.2之前的版本還要求將DTB放置在512 MB區域內,從內核映像下方的text_offset字節開始計算。
解壓縮內核映像(可選),AArch64內核當前不提供解壓縮器,因此如果使用壓縮的Image目標(例如Image.gz),則需要由引導加載程序執行解壓縮(gzip等)。對於未實現此要求的引導加載程序,可以使用未經壓縮內核編譯。
調用內核映像(必須)。壓縮內核頭部如下:
u32 code0; /* 可執行code */
u32 code1; /* 可執行code */
u64 text_offset; /* 加載偏移,小端 */
u64 image_size; /* 有效映象尺寸,小端 */
u64 flags; /* 內核標誌, 小端 */
u64 res2 = 0; /* 保留 */
u64 res3 = 0; /* 保留 */
u64 res4 = 0; /* 保留 */
u32 magic = 0x644d5241; /* 幻數,小端, "ARM\x64" */
u32 res5; /* 保留(用於PE COFF偏移量) */
進入內核之前,必須滿足以下條件:
禁止所有具有DMA功能的設備,以免內存被虛假錯誤的網絡數據包或磁碟數據損壞。
主CPU通用寄存器設置:
CPU模式
Caches, MMUs
MMU必須關閉。
指令緩存可以打開或關閉。
與加載的內核映像相對應的地址範圍必須清除到PoC。如果存在系統緩存或啟用了緩存的其他相關主伺服器,則通常需要通過VA而不是通過設置/方式操作來維護緩存。
遵循VA對架構化緩存維護的系統緩存。必須配置並啟用操作。
不遵循VA對架構化混存維護的系統緩存,必須配置和禁用操作(不推薦)。
架構定時器
連貫性
系統寄存器
主CPU必須直接跳轉到內核映像的第一條指令。此CPU傳遞的設備樹Blob必須為每個cpu節點包含一個「啟用方法」屬性。支持的啟用方法如下所述。引導加載程序將生成這些設備樹屬性,並將其插入內核入口之前的blob中。
具有「旋轉表」啟用方法的CPU在其cpu節點中必須具有「 cpu-release-addr」屬性。此屬性標識自然對齊的64位零初始化內存位置。
具有「 psci」啟用方法的CPU應該保留在內核之外(即,在內存節點中描述給內核的內存區域之外,或者在內核中通過/ memreserve /描述給內核描述的內存保留區域之外)。設備樹)。內核將按照ARM文檔編號ARM DEN 0022A(「 ARM處理器上的電源狀態協調接口系統軟體」)中的說明發出CPU_ON調用,以將CPU帶入內核。設備樹應包含一個「 psci」節點,參考/bindings/arm/psci.txt.
第二CPU通用寄存器設置的x0/x1/x2/x3都為0,保留。
內核啟動有兩種方式,壓縮格式或不壓縮格式,壓縮模式所不同的就是其入口位於arch/ /boot/compressed/head.S,為與該路徑下的代碼主要負責執行執行前期的初始化為解壓內核做準備。當完成解壓內核後,就跳轉到./arm/kernel/head.S開始啟動內核。
本文僅分析不壓縮方式啟動內核,通過分析內核代碼,整理出內核啟動過程的部分順序如下:
內核的啟動與U-Boot一樣,前面一段是彙編代碼,然後跳轉到C代碼。彙編的入口在
./arm/kernel/head.S中,符號名為__HEAD,該文件包含了head-common.S。
所以從啟動用戶首進程init而言,我將其分成大致分為四大步:
head.S ,初始化通用部分環境,與晶片無關
start_kernel, head.S完成後,調準到start_kernel,進入C函數執行,該函數為於./init/main.c中
rest_init,創建init進程,以及kthredd進程,其中Init進程號為1,kthredd為內核進程。
啟動調度器,執行kernel_init,該函數將調用根文件系統中的init執行文件,至此用戶空間的init進程就啟動起來了。
剖析彙編代碼比較枯燥,這裡就不進行描述了。僅就其作用進行總結:
該函數主要完成以下以下工作:
lockdep 死鎖檢測模塊初始化,
RCU機制初始化:RCU(Read-Copy Update),顧名思義就是讀-拷貝修改,它是基於其原理命名的。對於被RCU保護的共享數據結構,讀者不需要獲得任何鎖就可以訪問它,但寫者在訪問它時首先拷貝一個副本,然後對副本進行修改,最後使用一個回調(callback)機制在適當的時機把指向原來數據的指針重新指向新的被修改的數據。這個時機就是所有引用該數據的CPU都退出對共享數據的操作。
SMP初始化,對稱多處理"(Symmetrical Multi-Processing)簡稱SMP,完成CPU ID的創建。
debug_objects_early_init,負責調試對象初始化,以便於內核調試
lockdep死鎖檢測模塊初始化,lockdep的工作方式是在內核中的鎖定調用包起來。每次採用或釋放特定類型的鎖時,都會記錄該事實以及輔助詳細信息,例如處理器當時是否正在處理中斷。Lockdep還記錄了使用新鎖時還持有哪些其他鎖;這是lockdep能夠執行的許多檢查的關鍵。
調用setup_arch(&command_line),該函數位於arch/
/kernel/setup.c,用於解析從bootloader傳入的引導命令行。初始化控制臺,以列印啟動日誌。
初始化其他各子系統,如VFS,trace,內存管理子系統,FORK子系統,cgroup,acpi,proc文件系統,內核服務,緩存等等。
……
調用rest_init,以創建init進程以及內核進程,並啟動內核調度器。
代碼如下,其注釋如下,主要作用就是先創建init進程使其進程號為1,這是第一個用戶空間進程,該進程執行後在衍生出一系列的應用進程。具體取決於啟動腳本或者Init的具體實現。然後創建內核進程kthreadd,該進程用於管理內核進程。該進程進程號為2。所有內核進程都是kthreadd的後代, kthreadd枚舉其他內核線程;它提供了接口例程,內核服務可以在運行時動態生成其他內核進程。通過kthread_create_list維護其他內核進程。可以使用ps -ef命令從命令行查看內核線程-它們顯示在[方括號]中:
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting();
smpboot_thread_init();
/*創建init進程,第一個用戶空間進程我們
*需要首先生成init,以便它獲得pid 1,但是
*init任務最終將要創建kthread,如果在創建
*kthreadd之前對其進行調度,則OOPS。*/
kernel_thread(kernel_init, NULL, CLONE_FS);
numa_default_policy();
/*創建kthreadd用於管理內核線程*/
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
/*RCU 鎖*/
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
/*讓內核進程kthreadd處於就緒態TASK_NORMAL*/
complete(&kthreadd_done);
/* 啟動調度器 */
init_idle_bootup_task(current);
schedule_preempt_disabled();
/* 禁用搶佔的情況下調用cpu_idle */
cpu_startup_entry(CPUHP_ONLINE);
}
當內核調度器運行後,就會執行kernel_init函數:
static int __ref kernel_init(void *unused)
{
int ret;
kernel_init_freeable();
/* 同步完成所有初始化操作 */
async_synchronize_full();
#ifndef CONFIG_INITCALLS_THREAD
free_initmem();
#endif
mark_readonly();
system_state = SYSTEM_RUNNING;
numa_default_policy();
flush_delayed_fput();
/*如果使能了ramdisk執行命令啟動init*/
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/* 如果execute_command使能,則按命令啟動init*/
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
/*如果前面兩項都沒有使能,則依次在根文件系統下尋找並啟動Init*/
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
從而init用戶進程就啟動起來了,至於最終執行的是哪一個Init可執行文件,取決於系統移植的配置,如前文描述,常見的有busybox init,systemV init,systemD init等等。