4、systemd第一阶段:systemd-stub与EFI stub的内核参数处理
在UEFI启动环境下,Linux内核的加载并非直接由bootloader完成,而是经过EFI stub和systemd-stub两个关键阶段。这两个组件负责从EFI固件接收控制权,解析并传递内核参数,最终将系统引导至initramfs。理解这一阶段的内核参数处理机制,对于调试启动失败、参数传递错误等问题至关重要。
核心概念:EFI stub是内嵌于Linux内核镜像中的小型EFI应用程序,负责从UEFI固件接管硬件;systemd-stub则是systemd项目提供的UEFI启动存根,用于统一管理内核命令行、设备树和凭据。
4.1 EFI stub:内核自带的启动存根
EFI stub是编译进Linux内核(CONFIG_EFI_STUB)的一段代码,它使得内核镜像本身成为一个合法的EFI可执行文件。当UEFI固件直接加载内核镜像(如vmlinuz.efi)时,EFI stub首先获得执行权。
4.1.1 EFI stub的主要职责
- 初始化EFI运行时服务:获取EFI系统表、内存映射、启动服务等。
- 解析EFI加载选项:从UEFI固件的
LoadOptions变量中读取内核命令行参数。 - 设置图形模式:初始化EFI GOP(Graphics Output Protocol),为早期启动提供显示支持。
- 退出EFI启动服务:在跳转到内核主函数前,调用
ExitBootServices(),将控制权完全交给内核。 - 处理EFI内存映射:将UEFI内存映射转换为内核可识别的
e820表。
4.1.2 内核参数传递流程
EFI stub从UEFI固件获取内核参数的路径如下:
- UEFI固件将内核命令行字符串存储在
EFI_LOADED_IMAGE_PROTOCOL.LoadOptions中。 - EFI stub调用
efi_get_loaded_image()获取该协议接口。 - 读取
LoadOptions指向的Unicode字符串,转换为ASCII格式。 - 将转换后的字符串传递给内核的
boot_params.hdr.cmd_line_ptr。
调试要点:如果内核参数未生效,首先检查UEFI固件是否正确传递了
LoadOptions。可以在UEFI Shell中使用dmpstore -all查看当前启动选项变量。
4.2 systemd-stub:统一启动存根
systemd-stub(systemd-stub或sd-stub)是systemd项目提供的UEFI启动存根,通常与systemd-boot配合使用。它被设计为EFI stub的替代或补充,提供更丰富的功能,特别是对统一内核镜像(UKI)的支持。
4.2.1 systemd-stub与EFI stub的对比
| 特性 | EFI stub | systemd-stub |
|---|---|---|
| 来源 | 内嵌于Linux内核 | systemd项目独立编译 |
| UKI支持 | 有限 | 原生支持,可嵌入initramfs、内核、cmdline、DTB |
| 凭据管理 | 不支持 | 支持EFI变量和TPM2密封凭据 |
| 设备树处理 | 需额外配置 | 自动从UKI中提取并加载DTB |
| 安全启动兼容性 | 基础支持 | 深度集成,支持PE签名和测量启动 |
| 内核参数来源 | 仅LoadOptions |
LoadOptions + UKI内嵌cmdline + 凭据中的参数 |
4.2.2 systemd-stub的内核参数合并策略
systemd-stub在启动时会从多个来源收集内核参数,并按照优先级合并:
- UKI内嵌命令行:编译时嵌入到
.cmdlinePE节区,优先级最低。 - EFI变量:读取
LoaderEntryKernelOptions等EFI变量。 - 凭据(Credentials):从TPM2密封或EFI变量中解封的凭据参数。
- UEFI交互式输入:如果用户在启动菜单中编辑了参数,则覆盖上述所有来源。
注意:systemd-stub在合并参数时,如果同一参数出现多次,最后一次出现的值生效。这意味着UEFI交互式输入具有最高优先级。
4.3 内核参数处理流程总览
下图展示了从UEFI固件到内核主函数的内核参数完整处理流程:
4.4 关键内核参数与调试方法
4.4.1 影响stub行为的关键参数
| 参数 | 作用阶段 | 说明 |
|---|---|---|
efi=debug |
EFI stub | 启用EFI stub的调试输出,显示内存映射、加载选项等 |
noefi |
EFI stub | 禁用EFI运行时服务,回退到传统BIOS模式 |
stub=debug |
systemd-stub | 启用systemd-stub的详细日志,显示参数合并过程 |
systemd.efi-options |
systemd-stub | 从EFI变量中读取额外选项的键名 |
initrd= |
两者 | 指定initramfs路径,EFI stub从EFI分区加载,systemd-stub从UKI中提取 |
console= |
两者 | 设置控制台设备,影响stub阶段的日志输出 |
4.4.2 调试技巧
- 启用stub调试输出:在UEFI启动菜单中编辑内核命令行,添加
efi=debug(EFI stub)或stub=debug(systemd-stub),观察屏幕输出的调试信息。 - 检查EFI变量:在Linux启动后,使用
efibootmgr -v查看当前启动项的内核参数设置。 - 模拟UKI启动:使用
systemd-boot的bootctl set-oneshot命令临时指定内核参数,测试参数传递是否正确。 - 捕获stub日志:如果系统无法完全启动,可以在UEFI Shell中手动加载内核并添加
debug参数,通过串口或帧缓冲捕获日志。
实战案例:某系统启动时内核参数
root=始终不生效。通过添加efi=debug发现EFI stub读取的LoadOptions包含乱码,原因是UEFI固件将Unicode字符串错误地截断。解决方法是在UEFI设置中重新配置启动项,确保参数长度不超过固件限制。
4.5 安全启动与参数完整性
在启用安全启动(Secure Boot)的环境下,内核参数的篡改可能导致系统拒绝启动。systemd-stub通过以下机制保障参数完整性:
- PE签名验证:UKI镜像的
.cmdline节区被包含在PE签名范围内,任何修改都会导致签名失效。 - TPM2测量:systemd-stub在启动时将内核参数哈希值扩展到TPM2 PCR(平台配置寄存器),用于远程证明。
- 凭据密封:敏感参数(如磁盘加密密钥)可以通过TPM2密封,仅在PCR状态匹配时才能解封。
安全提示:在安全启动环境下,不要依赖UEFI交互式输入传递敏感参数,因为交互式输入不会被签名保护。应使用UKI内嵌或TPM2密封的凭据。
4.6 本章小结
EFI stub和systemd-stub是UEFI启动路径上的两个关键组件,负责从固件接收并处理内核参数。EFI stub作为内核内置的轻量级方案,仅支持从LoadOptions获取参数;而systemd-stub提供了更强大的多源合并、UKI支持和安全启动集成能力。掌握这两个阶段的内核参数处理机制,能够帮助开发者快速定位启动参数传递失败、参数被覆盖或安全启动校验失败等问题。