4. 启动流程第三阶段:start_kernel函数解析、早期控制台初始化与架构特定设置
当解压缩后的内核映像被引导加载程序(如GRUB或U-Boot)跳转到入口点后,执行流会迅速进入与体系结构无关的C语言世界。这一阶段的起点是 start_kernel 函数,它位于 init/main.c 中,是整个内核初始化的“总调度器”。本章将深入解析该函数的核心执行路径,重点分析早期控制台的初始化机制以及架构特定设置的衔接方式。
start_kernel 并非内核启动的“第一行代码”,但它标志着从汇编语言到C语言、从架构相关到架构无关的转折点。理解它的执行顺序,是分析启动性能瓶颈的基础。
4.1 start_kernel 函数执行全景
start_kernel 函数负责初始化内核的核心子系统,其执行顺序严格遵循依赖关系。以下SVG图展示了其主要调用链和阶段划分。
从SVG图中可以看出,start_kernel 的执行可以划分为四个主要阶段:
- 早期架构与陷阱设置:包括
setup_arch()、trap_init()和mm_init(),负责解析内核命令行、初始化页表、设置中断向量表。 - 控制台与调度器:
early_console_init()启用早期打印能力,sched_init()初始化进程调度器。 - 核心子系统:VFS、信号、工作队列等通用子系统的初始化。
- 驱动与最终启动:通过
do_initcalls()执行各级驱动初始化,最终由rest_init()创建init进程并进入空闲循环。
4.2 早期控制台初始化机制
早期控制台(Early Console)是内核在标准控制台驱动完全初始化之前,用于输出日志和调试信息的机制。它在启动性能分析中至关重要,因为启动早期的许多关键事件(如内存初始化、调度器设置)都依赖它来记录时间戳。
4.2.1 早期控制台与标准控制台的对比
| 特性 | 早期控制台 (early console) | 标准控制台 (console) |
|---|---|---|
| 初始化时机 | start_kernel 早期,sched_init() 之前 |
console_init() 调用,通常在 do_initcalls() 阶段 |
| 驱动依赖 | 通常使用架构特定的简单驱动(如 8250 UART 的轮询模式) | 依赖完整的设备驱动模型、中断和 DMA |
| 输出能力 | 仅支持同步轮询输出,无缓冲,速度较慢 | 支持缓冲、中断驱动、多路复用 |
| 性能影响 | 低(但频繁输出会显著拖慢启动) | 中等(通常只在启动后期启用) |
| 配置方式 | 内核命令行参数 earlycon |
内核命令行参数 console |
earlycon 被设置为非常慢的串口(如 115200 bps),并且内核启用了大量的 pr_early() 或 early_printk() 调用,启动时间可能会增加数百毫秒甚至数秒。在生产环境的性能分析中,建议禁用早期控制台或将其输出重定向到快速虚拟设备(如 QEMU 的 virtio-console)。
4.2.2 早期控制台的初始化流程
早期控制台的初始化通常通过 early_console_init() 函数完成,其内部逻辑如下:
// 简化后的 early_console_init 逻辑
void __init early_console_init(void)
{
struct earlycon_device *dev;
const char *p;
// 1. 解析内核命令行中的 "earlycon" 参数
p = strstr(boot_command_line, "earlycon");
if (!p)
return; // 未指定 earlycon,跳过
// 2. 根据参数匹配注册的 earlycon 驱动
dev = earlycon_match(p);
if (!dev) {
early_printk("No matching earlycon driver found\n");
return;
}
// 3. 调用驱动的 setup 函数,初始化硬件
if (dev->setup(dev)) {
early_printk("Failed to setup earlycon\n");
return;
}
// 4. 注册 early_console 到内核打印系统
register_early_console(dev);
early_printk("Early console initialized on %s\n", dev->name);
}
在 QEMU 环境中,常用的早期控制台配置包括:
- 串口 (serial):
earlycon=uart8250,io,0x3f8或earlycon=uart,mmio,0x3f8 - virtio 控制台:
earlycon=virtio,mmio,0x10000000(需要 QEMU 设备支持) - QEMU 调试控制台:
earlycon=qemu,io,0x3f8(QEMU 特有的快速输出通道)
4.3 架构特定设置:setup_arch 深度解析
setup_arch() 是 start_kernel 中第一个被调用的架构相关函数。以 x86 架构为例,它负责以下关键任务:
4.3.1 关键数据结构:boot_params 与 e820 表
在 x86 架构中,引导加载程序通过 boot_params 结构体将硬件信息传递给内核。该结构体包含:
- e820_map:物理内存布局表,描述哪些区域是可用 RAM、保留区域或 ACPI 数据。
- screen_info:显示模式信息,用于早期 framebuffer 控制台。
- hdr:引导协议头部,包含内核入口点、加载地址等。
setup_arch() 会解析 e820 表,并调用 sanitize_e820_map() 进行校验和合并,最终计算出 max_pfn(最大物理页框号)和 max_low_pfn(低端内存最大页框号),这些值将直接影响后续的内存管理初始化。
4.3.2 架构特定设置的性能影响
| 架构 | 关键耗时操作 | QEMU 调优建议 |
|---|---|---|
| x86 | ACPI 表解析、SMP 启动 AP 处理器、MTRR 设置 | 使用 -machine q35,accel=kvm 加速 ACPI;限制 CPU 数量以减少 AP 启动开销 |
| ARM64 | 设备树 (DTB) 解析、PCIe 域枚举、GIC 初始化 | 使用预编译的 DTB 文件(-dtb 参数);避免不必要的 PCIe 设备 |
| RISC-V | 设备树解析、CLINT/PLIC 初始化 | 使用 -machine virt 并精简设备树节点 |
setup_arch() 中,ACPI 表解析和 SMP AP 处理器启动是 x86 架构下最常见的启动延迟来源。可以通过内核启动参数 acpi=off 或 maxcpus=1 来快速验证这些阶段对启动时间的影响。在 QEMU 中,使用 -smp cores=1,threads=1 可以强制单核启动,从而消除 AP 启动开销。
4.4 从 start_kernel 到 rest_init 的过渡
当 start_kernel 完成所有核心子系统的初始化后,它会调用 rest_init()。这个函数标志着内核从“单线程初始化”阶段进入“多进程并发”阶段:
// rest_init 的核心逻辑
static noinline void __init rest_init(void)
{
struct task_struct *tsk;
// 1. 创建内核线程 kernel_init (PID=1)
tsk = kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
if (IS_ERR(tsk))
panic("Failed to create init process");
// 2. 创建内核线程 kthreadd (PID=2)
tsk = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
if (IS_ERR(tsk))
panic("Failed to create kthreadd");
// 3. 调度器准备就绪,允许抢占
preempt_enable_no_resched();
// 4. 调用 cpu_startup_entry() 进入 idle 循环
cpu_startup_entry(CPUHP_ONLINE);
}
此时,kernel_init 线程将负责执行剩余的驱动初始化(通过 do_initcalls())、挂载根文件系统,并最终执行用户空间的 /sbin/init 程序。而 cpu_startup_entry() 则使当前 CPU 进入空闲循环,等待调度器分配任务。
start_kernel 入口到 rest_init 调用之间的时间差,通常被称为“内核核心初始化时间”。在 QEMU 中,通过 -trace events=kvm_exit 或 ftrace 可以精确测量这一阶段的耗时。对于性能敏感的虚拟化场景,这一时间应控制在 50ms 以内。
4.5 本章小结
本章深入剖析了 start_kernel 函数的执行流程,重点强调了早期控制台初始化与架构特定设置 setup_arch() 的作用。理解这些阶段对于定位启动性能瓶颈至关重要:
- 早期控制台 是启动早期唯一的输出通道,但其轮询模式可能成为性能瓶颈,在 QEMU 中应优先使用快速虚拟设备。
- setup_arch() 的执行效率高度依赖架构和硬件配置,ACPI 解析和 SMP 启动是常见的延迟来源。
- rest_init() 标志着初始化阶段的结束和用户空间启动的开始,是测量内核核心初始化时间的自然分界点。
在下一阶段,我们将进入驱动初始化与根文件系统挂载的详细分析,进一步探索用户空间启动前的最后准备步骤。