参考./Documentation/arm64/booting.txt
Bootloader至少完成以下基本的初始化准备:
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偏移量) */
进入内核之前,必须满足以下条件:
内核启动有两种方式,压缩格式或不压缩格式,压缩模式所不同的就是其入口位于arch//boot/compressed/head.S,为与该路径下的代码主要负责执行执行前期的初始化为解压内核做准备。当完成解压内核后,就跳转到./arm/kernel/head.S开始启动内核。
本文仅分析不压缩方式启动内核,通过分析内核代码,整理出内核启动过程的部分顺序如下:
内核的启动与U-Boot一样,前面一段是汇编代码,然后跳转到C代码。汇编的入口在
./arm/kernel/head.S中,符号名为 __HEAD ,该文件包含了head-common.S。
所以从启动用户首进程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等等。
原作者:逸珺