操作系统内功 · 第 33 篇|系统启动链路:从按下电源到 systemd 就绪的全过程

全景图:五段旅程,三种失败模式

从按下电源到登录提示符,x86-64 服务器的旅程分五段:

flowchart LR
    P[上电] --> FW["固件 UEFI<br>POST 自检、设备枚举"]
    FW --> BL["引导加载 GRUB<br>选内核、传 cmdline"]
    BL --> K["内核初始化<br>解压、建页表、初始化子系统"]
    K --> IR["initramfs<br>加载驱动、挂真实根"]
    IR --> SD["systemd (PID 1)<br>按依赖图并行拉服务"]
    SD --> UP[服务就绪]

三段各有一种典型失败:固件段——硬件变更/外设故障导致 POST 卡死(内存报警、RAID 卡故障);内核段——新内核缺驱动(initramfs 不匹配)导致 panic;服务段——某个单元 hang 住拖住依赖图(最常见:NFS/网络文件系统挂载超时把后半程拖住)。重启排障的第一步就是判断「卡在哪一段」——三段的观测入口完全不同。

固件与引导:UEFI、GRUB 与 cmdline

UEFI 固件完成 POST(加电自检:CPU/内存/基础外设)、设备枚举(PCI 扫描,大服务器上内存与 RAID 卡初始化可能占几十秒到分钟级)、启动项选择。云虚拟机的固件段被大幅简化(虚拟设备枚举快),裸金属服务器的这一段反而最慢——「重启后 3 分钟没动静」在裸金属上常常只是 RAID 卡在做一致性检查。

GRUB(或 systemd-boot)加载 /boot 的内核镜像(vmlinuz)与 initramfs,把启动参数(cmdline)交给内核。cmdline 是内核行为的第一配置面:console=(串口调试)、transparent_hugepage=never(第 19 篇的巨页禁用)、isolcpus(CPU 隔离)、root=(根设备)。排障时 cat /proc/cmdline 确认本次启动用的参数——改了内核参数但没生效,一半是没进 GRUB 配置,一半是进错了启动项。

内核初始化与 initramfs:鸡生蛋问题的解

内核镜像解压后开始初始化:建内核页表、初始化内存管理(memblock → 伙伴系统,第 3 篇)、初始化各子系统(调度器、中断、VFS)、启动内核线程。dmesg 的时间戳([ 0.000000] 开始)就是这段的逐行记录。

initramfs 解决的是鸡生蛋问题:根文件系统在磁盘上(ext4/XFS),但内核需要 ext4 驱动才能挂它——驱动放内核里会让内核镜像无限膨胀(NVMe、RAID、加密、网络文件系统……),放模块里又需要先挂上根才能读。解法:把启动必需的驱动与工具打包进一个内存文件系统(initramfs),内核先把它挂为根,在里面加载真正的磁盘驱动、找到真实根设备(UUID 定位)、switch_root 切换过去。

initramfs 的工程含义:换内核、换存储驱动、改挂载配置时,initramfs 必须重新生成(dracut -f 或 update-initramfs -u)。经典的启动 panic「VFS: Unable to mount root fs」几乎都是 initramfs 里缺对应驱动或根设备 UUID 变了(扩容/迁移后)。云服务器的 virtio 驱动必须编进 initramfs——镜像制作时漏了它,实例重启直接起不来。

dmesg | head -50        # 内核初始化逐行记录(时间戳定位慢点)
lsinitrd /boot/initramfs-$(uname -r).img | grep -i virtio   # 检查驱动在不在
cat /proc/cmdline       # 本次启动的内核参数
systemd-analyze         # 启动耗时分解:firmware+loader+kernel+initrd+userspace

systemd:按依赖图并行拉服务

内核完成初始化后启动 PID 1——现代发行版是 systemd。它的革命性在启动模型:SysV init 的串行脚本(一个一个跑 /etc/rcN.d)被替换为单元(unit)依赖图上的并行启动——没有依赖关系的服务同时拉起,总时长取决于最长依赖链而非服务总数。

单元的四种依赖关系决定图的形状:Requires(强依赖,挂了连坐)、Wants(弱依赖,挂了不影响)、After(顺序,我只保证在你之后启动)、Before(反向顺序)。「服务起来了但依赖的数据库还没 ready」类故障的根源:After 只保证启动顺序,不保证就绪——真正的就绪协同要靠应用层重试、健康检查或 socket activation(systemd 先代持 socket,应用启动即有连接可收,第 16 篇 fd 传递的又一个应用)。

systemd 的目标(target)替代运行级别:multi-user.target(多用户文本)、graphical.target(图形)、rescue.target(单用户救援)。服务单元文件的位置与优先级(/usr/lib/systemd/system 发行版默认 → /etc/systemd/system 管理员覆盖)——改配置改 /etc 下的,systemctl daemon-reload 生效,这个优先级规则是「改了配置不生效」的第二大来源。

systemd-analyze blame | head     # 哪些单元最耗时
systemd-analyze critical-chain   # 关键路径:启动慢的依赖链
systemctl list-dependencies sshd # 某服务的依赖图
journalctl -b -p err             # 本次启动的错误级日志

云服务器的特有环节:云初始化链

云镜像在标准启动链之外多一段 cloud-init:探测云平台元数据(169.254.169.254)、应用首次配置(主机名、SSH key、网络)、执行 user-data 脚本。它的两个工程影响:重启耗时比裸镜像长(cloud-init 各模块串行执行,systemd-analyze blame 里 cloud-init 常年上榜);user-data 默认只在首启执行(scripts-user 的 once 语义)——「改了 user-data 重启没生效」是云上最常见的误会,需要手动执行或改 cloud-config 的执行策略。

云上还有一层基础设施级的启动依赖:云盘挂载(云厂商的块设备在固件段之后才可见)、弹性网卡的 DHCP、安全组放行——「服务没起来」的排查要区分「OS 层没起来」与「云层没给资源」。journalctl -b 从内核时间戳开始看,能把这两层的时间线拼在一起。

重启恢复的工程纪律

把启动链路的知识落到运维纪律:

  1. 服务配置 enable 而非手动拉起:systemctl enable 保证进依赖图;手动 start 的进程重启即失忆。容器化时代同样成立:restart policy 是容器的 enable。
  2. 挂载依赖写全:NFS/对象存储挂载的服务,单元里加 RequiresMountsFor=,避免「服务起来了挂载还没好」。
  3. 关键服务的启动超时与重试:TimeoutStartSec 调大(慢启动的 JVM/大实例数据库),应用层做依赖重试(After 不保证就绪)。
  4. 定期验证重启:systemd-analyze 与演练重启是唯一可信的灾难恢复测试——「配置都 enable 了」不等于「重启后能恢复」,只有真重启过才算数。
  5. 内核升级后 initramfs 同步:升级脚本链(yum/dnf 的 kernel 包钩子)一般自动处理,手工编译内核的自建环境要补课。

kexec 与快速重启:跳过固件的第二通道

标准重启(reboot)要走完整链路:关机信号、固件重新 POST、GRUB、内核解压——裸金属服务器上固件段动辄几十秒到几分钟。kexec 提供第二通道:在运行中的内核里直接加载新内核镜像并跳转过去,固件与引导加载被完全绕过,重启时间从分钟级压到秒级。它的两个生产用法方向相反:其一,快速内核升级——新内核经 kexec 加载,跳过硬件自检(RAID 卡一致性检查、内存训练这些最耗时的固件环节),大型机群的内核滚动升级窗口成倍缩短;其二,崩溃转储(kdump)——系统 panic 时 kexec 快速切换到预加载的捕获内核(capture kernel),在干净的小内核里把崩溃现场内存完整 dump 到磁盘,比传统的内存镜像到交换分区可靠得多,这是所有生产内核调试基础设施的地基。代价与边界:kexec 跳过了固件重新初始化,硬件状态继承自上一个内核——绝大多数情况没问题,但固件 bug 修复、RAID 卡固件升级、硬件配置变更仍需冷重启(ipmitool power cycle 是服务器管理的真重启);虚拟化环境里 kexec 的收益缩小(固件段本来就快),云厂商的 Live Patch 与热迁移是更常用的免重启手段。运维决策树:内核安全补丁急着上且补丁小 → livepatch;补丁大或跨版本 → kexec 快速重启;固件或硬件变更 → 冷重启无捷径。知道 kexec 的存在,把「重启就要十分钟」从宿命变成选项之一。

启动时间的持续治理:从 3 分钟到 40 秒的案例

用一个启动优化的完整案例把本篇的工具串起来。背景:某 80 节点集群单机重启到服务就绪 3 分 10 秒,滚动升级与故障恢复被拖慢。第一步量化:systemd-analyze 显示 firmware 45s(BIOS 自检加 RAID 卡,裸金属常态)+ kernel 1.2s + initrd 3s + userspace 2m20s——优化空间集中在用户态。第二步拆解:systemd-analyze blame 前三名是某存储服务 45s(等待挂载超时后才降级启动)、监控 agent 20s(串行拉取全量配置)、数据库本地缓存预热 30s。第三步治理。存储服务:After 加网络-online.target 改为 Wants 加应用层重试,启动不再等超时,省 40s;它真正需要的是「挂载好了再干活」而非「挂载好了才启动」,socket activation 模式让服务先起、数据通路后通。监控 agent:配置改本地缓存加增量拉取,20s 变 2s。缓存预热:改成后台异步预热(服务先就绪、流量进来时边服务边预热),30s 移出关键路径。第四步验证与固化:systemd-analyze critical-chain 确认关键路径缩短到 55s,重启演练纳入季度例行。最终单机 1 分 20 秒(firmware 的 45s 是硬成本,kexec 可绕过但集群没上)。案例的通用方法论:启动优化 = 量化分解(analyze)+ 找假依赖(等待改重试)+ 异步化(移出关键路径),三步的性价比远高于「把服务写得更快启动」。

启动链路的安全视角:Secure Boot 与度量启动

启动链路同时是安全链条的起点,两套机制值得后端工程师认识其存在与代价。Secure Boot:UEFI 固件验证引导加载的签名,GRUB 验证内核签名,内核可继续验证模块——「信任链逐级传递」,防的是 bootkit 类攻击(改引导区常驻内存的恶意程序)。云镜像与主流发行版默认支持,但两个工程摩擦点常见:自编译内核模块(某些监控 agent、GPU 驱动旧版)需要签名(自签证书注册进 MOK 数据库),没签名就 load 失败——「升级后模块加载不了」的排查清单里加一条「Secure Boot 开着吗」。度量启动(measured boot):比签名验证更进一步,把启动链的每一段哈希记入 TPM 芯片的 PCR 寄存器,远程方( attestation 服务)比对度量值判断机器是否处于可信状态——零信任架构的机器准入(只允许「启动链没被篡改」的机器进内网)靠它实现。代价是启动流程的工程约束:内核、initramfs、内核参数的任何变更都会改变度量值,需要 attestation 侧同步白名单——变更管理从「改配置」升级为「改配置加更新信任基线」。这两套机制的成本收益判断:互联网业务的常规集群通常只开 Secure Boot(一次性的模块签名配置成本),金融与强合规场景才上度量启动(持续运营成本)——知道链条在哪,出了「模块加载失败」「机器准入被拒」这类事故才有排查方向。

小结

启动链路的五段旅程:UEFI 自检与设备枚举(裸金属最慢的一段)→ GRUB 选内核传参(cmdline 是内核行为的第一配置面)→ 内核初始化(dmesg 时间戳可逐行定位)→ initramfs 解决「驱动在根上、根要驱动挂」的鸡生蛋 → systemd 按依赖图并行拉服务(After 管顺序不管就绪)。三类失败各有观测入口,cloud-init 与云基础设施给云服务器加了两段特有依赖。运维纪律五条的核心是「enable + 演练」:没真重启过的服务,恢复能力等于零。下一篇用工具把这一切照出来:strace、perf 与 BCC 的内核机制与三层进阶。

← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?