1. 继续上文的start_kernle()
start_kernel
已经完成了所有核心子系统(内存管理、调度器、中断、定时器等)的初始化。此时,整个内核仍然是单线程的,运行在进程0的上下文中。
第一步:rest_init()
- 从单线程到多任务的转折点
start_kernel
的最后一个调用是 rest_init()
。这个函数是整个系统的“大爆炸”奇点。
创建进程1 (kernel_init):
rest_init
(作为进程0) 调用 kernel_thread()
创建了一个新的内核线程。
- 这个线程的入口点是
kernel_init
函数。
- 此时,进程1被标记为可运行,但它首先会等待
kthreadd_done
信号。
创建进程2 (kthreadd):
rest_init
(作为进程0) 再次调用 kernel_thread()
创建了第二个内核线程。
- 这个线程的入口点是
kthreadd
函数。
- 此时,进程2也被标记为可运行。
进程0 发出信号并“退休”:
rest_init
(作为进程0) 调用 complete(&kthreadd_done)
。这个动作就像对田径比赛发令:“kthreadd
准备就绪,kernel_init
你可以跑了!”
- 然后,进程0调用
schedule()
主动放弃CPU,并进入 cpu_idle()
循环。
- 从此,进程0的角色彻底改变:它不再是引导者,而变成了空闲进程 (idle task)。它的唯一工作就是在没有其他任何进程需要运行时,让CPU休眠以节省功耗。
第二步:调度器接管,新进程开始运行
进程0进入idle后,调度器被触发。它会从就绪队列中选择一个新进程来运行。通常是进程1 (kernel_init
) 或进程2 (kthreadd
)。
进程2 (kthreadd
) 的生命周期:
- 当轮到它运行时,它会完成一些自身的初始化,然后进入一个无限循环,等待创建新内核线程的请求。它静静地待命,成为内核的“后勤总管”。
进程1 (kernel_init
) 的使命:
- 当轮到它运行时,它首先检查
kthreadd_done
信号,因为信号已发出,它不会阻塞,直接继续执行。
- 启动多核 (SMP): 调用
smp_init()
唤醒系统中其他的CPU核心。
- 执行
initcalls
: 调用 do_basic_setup()
,这会触发一个庞大的初始化调用链 (do_initcalls
),从而初始化系统中所有被编译进内核的设备驱动(如硬盘、网络、USB等)。这是内核能与硬件交互的关键。
- 挂载根文件系统: 调用
prepare_namespace()
,根据内核启动参数(如 root=/dev/sda1
),找到并挂载根文件系统到 /
。现在,内核终于可以访问磁盘上的文件了。
- 打开控制台: 打开
/dev/console
,为用户提供一个基本的输入输出设备。
第三步:init_post()
- 从内核态到用户态的终极一跃
kernel_init
完成所有准备工作后,调用 init_post()
来完成最后的交接。
- 释放初始化内存:调用
free_initmem()
,将内核在启动过程中使用过的、现在不再需要的初始化代码和数据(标记为 __init
的部分)所占用的内存全部释放,还给系统使用。
- 寻找并执行
init
程序:
init_post
会按 /sbin/init
, /etc/init
, /bin/init
, /bin/sh
的顺序,在刚刚挂载的根文件系统里寻找用户空间的第一个程序。
- 它通过
run_init_process()
-> kernel_execve()
来执行找到的程序。
execve
的魔法:
kernel_execve
是一个系统调用,它会清空进程1当前的内存空间(里面是 kernel_init
的代码),然后从磁盘加载 /sbin/init
程序到这片内存空间,设置好新的堆栈,最后把CPU的控制权交给 /sbin/init
的第一条指令。
- 这个过程完成后,PID 1 的身份彻底改变。它不再是内核线程,而是用户空间的
init
进程。
最终状态:系统完全启动
2. 对应代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| asmlinkage void __init start_kernel(void) { char *command_line;
lockdep_init(); smp_setup_processor_id(); debug_objects_early_init(); boot_init_stack_canary(); cgroup_init_early();
local_irq_disable(); early_boot_irqs_disabled = true; tick_init(); boot_cpu_init(); page_address_init(); printk(KERN_NOTICE "%s", linux_banner); setup_arch(&command_line);
mm_init_owner(&init_mm, &init_task); mm_init_cpumask(&init_mm); setup_command_line(command_line); setup_nr_cpu_ids(); setup_per_cpu_areas(); smp_prepare_boot_cpu(); build_all_zonelists(NULL); page_alloc_init();
parse_early_param(); parse_args(...); setup_log_buf(0); trap_init(); mm_init();
sched_init(); preempt_disable(); rcu_init(); radix_tree_init();
early_irq_init(); init_IRQ(); init_timers(); hrtimers_init(); softirq_init(); time_init();
local_irq_enable(); console_init();
security_init(); vfs_caches_init(totalram_pages); signals_init(); rest_init(); }
|
rest_init() -> rest_init()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| static noinline void __init_refok rest_init(void) { int pid; rcu_scheduler_starting();
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock(); kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); rcu_read_unlock();
complete(&kthreadd_done);
init_idle_bootup_task(current); schedule_preempt_disabled();
cpu_idle(); }
|
kernel_init
: Wait until kthreadd
is all set-up: wait_for_completion(&kthreadd_done);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| static int __init kernel_init(void * unused) { wait_for_completion(&kthreadd_done);
gfp_allowed_mask = __GFP_BITS_MASK;
set_mems_allowed(node_states[N_HIGH_MEMORY]); set_cpus_allowed_ptr(current, cpu_all_mask);
cad_pid = task_pid(current);
smp_prepare_cpus(setup_max_cpus);
do_pre_smp_initcalls(); lockup_detector_init();
smp_init(); sched_init_smp();
do_basic_setup();
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup(0); (void) sys_dup(0);
if (!ramdisk_execute_command) ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) { ramdisk_execute_command = NULL; prepare_namespace(); }
init_post();
return 0; }
|
kernel_init()
-> init_post()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| static noinline int init_post(void) { async_synchronize_full(); free_initmem(); mark_rodata_ro(); system_state = SYSTEM_RUNNING; numa_default_policy();
current->signal->flags |= SIGNAL_UNKILLABLE;
if (ramdisk_execute_command) { run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s\n", ramdisk_execute_command); }
if (execute_command) { run_init_process(execute_command); printk(KERN_WARNING "Failed to execute %s. Attempting defaults...\n", execute_command); }
run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel. " "See Linux Documentation/init.txt for guidance."); }
|
1 2 3 4 5 6 7 8 9
| static void run_init_process(const char *init_filename) { argv_init[0] = init_filename; kernel_execve(init_filename, argv_init, envp_init); }
|