|
[导读] 前面的文章有提到linux启动的第一个进程为init,那么该进程究竟是如何从内核启动入口一步一步运行起来的,而该进程又有些什么作用呢?做嵌入式Linux开发,有必要对这些概念了解清楚。本文基于ARM体系的内核启动做出解析。 跳转内核前基本准备 参考./Documentation/arm64/booting.txt Bootloader至少完成以下基本的初始化准备:
进入内核之前,必须满足以下条件:
内核启动有两种方式,压缩格式或不压缩格式,压缩模式所不同的就是其入口位于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阶段 当内核调度器运行后,就会执行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等等。 |
微信公众号
手机版