linux内核启动流程分析2

  • 简单分析启动过程中初始化等操作流程

1. 继续上文的start_kernle()

start_kernel 已经完成了所有核心子系统(内存管理、调度器、中断、定时器等)的初始化。此时,整个内核仍然是单线程的,运行在进程0的上下文中。

第一步:rest_init() - 从单线程到多任务的转折点

start_kernel 的最后一个调用是 rest_init()。这个函数是整个系统的“大爆炸”奇点。

  1. 创建进程1 (kernel_init)

    • rest_init (作为进程0) 调用 kernel_thread() 创建了一个新的内核线程。
    • 这个线程的入口点是 kernel_init 函数。
    • 此时,进程1被标记为可运行,但它首先会等待 kthreadd_done 信号。
  2. 创建进程2 (kthreadd)

    • rest_init (作为进程0) 再次调用 kernel_thread() 创建了第二个内核线程。
    • 这个线程的入口点是 kthreadd 函数。
    • 此时,进程2也被标记为可运行。
  3. 进程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)。

  1. 进程2 (kthreadd) 的生命周期

    • 当轮到它运行时,它会完成一些自身的初始化,然后进入一个无限循环,等待创建新内核线程的请求。它静静地待命,成为内核的“后勤总管”。
  2. 进程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() 来完成最后的交接。

  1. 释放初始化内存:调用 free_initmem(),将内核在启动过程中使用过的、现在不再需要的初始化代码和数据(标记为 __init 的部分)所占用的内存全部释放,还给系统使用。
  2. 寻找并执行 init 程序
    • init_post 会按 /sbin/init, /etc/init, /bin/init, /bin/sh 的顺序,在刚刚挂载的根文件系统里寻找用户空间的第一个程序。
    • 它通过 run_init_process() -> kernel_execve() 来执行找到的程序。
  3. 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;

/* 最早期初始化(调试、安全、CPU信息) */
lockdep_init(); // 锁依赖检测
smp_setup_processor_id(); // 设置当前 CPU ID
debug_objects_early_init(); // 调试对象初始化
boot_init_stack_canary(); // 栈溢出保护
cgroup_init_early(); // 控制组(早期阶段)

/* 禁用中断,初始化时钟 & 架构 */
local_irq_disable();
early_boot_irqs_disabled = true;
tick_init(); // 初始化 tick timer
boot_cpu_init(); // 标记 boot CPU
page_address_init(); // 页地址映射表初始化
printk(KERN_NOTICE "%s", linux_banner); // 打印内核 banner
setup_arch(&command_line); // **架构相关初始化**(内存、MMU、DTB)

/* 初始化内存管理 */
mm_init_owner(&init_mm, &init_task);
mm_init_cpumask(&init_mm);
setup_command_line(command_line); // 解析 boot 参数
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); // 准备 CPU 数据结构
build_all_zonelists(NULL); // 建立内存分配区域
page_alloc_init(); // 页分配器初始化

/* 参数解析 + 基础子系统初始化 */
parse_early_param(); // 解析 early 参数
parse_args(...); // 解析 boot args
setup_log_buf(0); // 日志缓冲区
trap_init(); // 异常处理
mm_init(); // 完整内存初始化

/* 调度器 & 同步机制 */
sched_init(); // 调度器初始化
preempt_disable(); // 禁止抢占
rcu_init(); // RCU
radix_tree_init(); // radix 树

/* 中断和定时器 */
early_irq_init();
init_IRQ(); // 完整中断
init_timers(); // 普通 timer
hrtimers_init(); // 高精度 timer
softirq_init(); // 软件中断
time_init(); // 时间子系统

/* 启用中断 + 控制台 */
local_irq_enable();
console_init();

/* 剩余初始化 + 创建 init 进程 */
security_init();
vfs_caches_init(totalram_pages); // 文件系统缓存
signals_init();
rest_init(); // 创建 init/kthreadd
}

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(); // **RCU(读-复制-更新)调度器初始化**
/*
* 创建 init 进程(PID 1)
* 调用 kernel_thread() → do_fork(),目标函数是 kernel_init()
*/
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy(); // **设置 NUMA 默认策略**
/*
* 创建 kthreadd 线程(内核线程管理器,负责所有内核线程)
* 它的 PID 通常是 2
*/
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
/*
* 通过 PID 查找 kthreadd 的 task_struct,并保存到全局变量
*/
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
/*
* 通知 kernel_init()(init 进程)可以继续执行
*/
complete(&kthreadd_done);
/*
* 当前任务是 idle 任务(PID 0)
* 执行 schedule() 一次,启动调度
*/
init_idle_bootup_task(current);
schedule_preempt_disabled();
/*
* 进入 idle 循环(CPU 空闲状态)
* 此后 init 和 kthreadd 运行在用户态,PID 0 进入低功耗空转
*/
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); // 等待 kthreadd 创建完成(PID 2)

gfp_allowed_mask = __GFP_BITS_MASK; // 允许内存分配(调度器已准备好,可以阻塞分配)

set_mems_allowed(node_states[N_HIGH_MEMORY]); // init 进程允许在任意内存节点分配
set_cpus_allowed_ptr(current, cpu_all_mask); // init 进程可以在所有 CPU 上运行

cad_pid = task_pid(current); // 保存 init 进程 PID

smp_prepare_cpus(setup_max_cpus); // 准备多核 CPU 初始化

do_pre_smp_initcalls(); // 调用 SMP 初始化前的 initcall
lockup_detector_init(); // 启动死锁检测机制

smp_init(); // 启动其他 CPU(多核)
sched_init_smp(); // 初始化 SMP 调度器

do_basic_setup(); // 驱动子系统、文件系统等核心初始化

// 打开 /dev/console 作为标准输入输出
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); // 复制文件描述符,stdin → stdout
(void) sys_dup(0); // 复制文件描述符,stdin → stderr

// 检查是否有 early userspace init,如果没有,则默认 /init
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";

// 如果 /init 不存在,准备挂载根文件系统
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace(); // 挂载根文件系统
}

// 进入用户空间:释放 __init 段,执行 init 程序
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(); // 等待所有异步 __init 任务完成
free_initmem(); // 释放 __init 段内存(只执行一次)
mark_rodata_ro(); // 将只读数据段标记为只读,防止修改
system_state = SYSTEM_RUNNING; // 内核状态切换为运行态
numa_default_policy(); // 设置默认 NUMA 策略

current->signal->flags |= SIGNAL_UNKILLABLE; // init 进程不可被 kill

// 如果 ramdisk_execute_command 存在(initrd/initramfs 提供的 init)
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command); // 尝试执行 /init
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}

// 如果uboot中内核参数 init=xxx 指定了 init 程序
if (execute_command) {
run_init_process(execute_command); // 执行指定 init
printk(KERN_WARNING "Failed to execute %s. Attempting defaults...\n",
execute_command);
}

// 按优先级尝试启动用户空间的 init 进程
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh"); // 最后 fallback 到 shell

// 如果所有都失败,内核 panic
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; // 设置 argv[0] 为要执行的 init 程序路径
kernel_execve(init_filename, argv_init, envp_init);
// 调用 kernel_execve(),在内核态执行 execve 系统调用,加载用户态程序
// init_filename: 要执行的程序路径(/sbin/init 等)
// argv_init: 参数数组(至少包含 init_filename)
// envp_init: 环境变量(初始化时的默认环境变量)
}
作者

GoKo Mell

发布于

2024-10-12

更新于

2025-09-13

许可协议

# 相关文章
  1.linux内核启动流程分析1
评论

:D 一言句子获取中...