摘要:当 systemd 启动流程停在某个 “Reached target” 单元时,真正的瓶颈往往并不在该 target 本身,而藏在它的依赖闭包之中。systemd-analyze blame 给出的是各单元自身耗时降序表,systemd-analyze critical-chain 给出的是从启动起点到指定单元的关键路径。两者口径不同、语义不同,单独使用极易误判。本文以“关键路径优先、耗时排序验证”为贯穿主线,先厘清两个子命令的计时来源与依赖图剪枝逻辑,再给出可复用的五步定位法、脚本化采集模板与三类真实故障模式的分析,最后讨论容器、快照恢复、并行启动等边界条件对结论的影响。文中数据均标注来源,模拟数据单独说明。
目录
一、导语:一个被反复误读的启动现象
在服务器、工作站乃至嵌入式设备的运维现场,“开机卡在 Reached target 某一行”是出现频率极高的求助描述。屏幕上最后一行通常形如 Reached target Multi-User System 或 Reached target Graphical Interface,随后光标静止。许多人的第一反应是执行 systemd-analyze blame,看到排在第一位的服务耗时十几秒,便断定它是元凶,禁用之后问题依旧。这种挫败感源于一个根本性的认知偏差:把“耗时最长的单元”等同于“导致卡顿的单元”。
systemd 的启动是一个由依赖关系驱动的事务(transaction)。一个 target 被“Reached”,意味着它声明的所有依赖 job 都已完成或被跳过,但完成的时间点取决于整条依赖链上最慢的那一段,而不是所有单元耗时的总和,更不是耗时排行榜的榜首。本文评述:把 blame 的输出当作“嫌疑人名单”是合理的,但把它当作“判决书”则是方法论错误。真正需要回答的问题是——在这条通往目标 target 的关键路径上,哪一段被拉长了?这正是 critical-chain 的职责。
本文的主线由此确立:关键路径优先、耗时排序验证。先用 critical-chain 锁定“谁在关键路径上”,再用 blame 在候选集合内做量化排序与交叉验证,最后回到单元配置与系统环境寻找根因。这条主线将贯穿后续所有章节,包括原理分析、操作步骤与案例复盘。
二、背景与问题定义:target、job 与启动事务
2.1 单元、依赖与 target 的语义
systemd 将系统资源抽象为单元(unit),常见类型包括 service、socket、target、mount、timer 等。单元之间通过 Requires=、Wants=、After=、Before= 等指令建立依赖与顺序关系。需要强调的是,依赖(requirement)与顺序(ordering)是两套正交的语义:Requires= 决定“是否必须一起启动”,After= 决定“谁先谁后”。只写 Requires= 而不写顺序时,两个单元可以并行启动。这一点是理解后续计时差异的前提。
target 是一组单元的聚合标记,本身几乎不执行实际工作,它的“到达”只是一个逻辑事件。因此,“卡在 Reached target X” 的准确含义是:X 的依赖闭包中仍有 job 未完成,或者某个 job 已完成但触发了后续阻塞。把排查焦点放在 target 单元自身,方向从一开始就偏了。
2.2 启动事务与 job 队列
当系统从 initrd 切换到真实根文件系统后,systemd 以 default.target 为根构建启动事务,将依赖闭包展开为 job 队列,再依据顺序约束与并行度调度执行。默认并行度由 DefaultDependencies 与 CPU 核数等因素共同影响,systemd 会尽量并行化无顺序约束的 job。这意味着启动总时长近似等于“关键路径长度”,而非“所有 job 耗时之和”。
本文评述:这一模型与项目管理中的关键路径法(CPM)高度同构。把每个 job 视作一个任务节点,顺序约束视作有向边,启动完成时间就是最长路径的长度。blame 相当于“按任务工时排序”,critical-chain 相当于“求最长路径”。只做前者,等于在并行项目里盯着工时最长的任务,却忽略它可能根本不在关键路径上。
2.3 问题边界的界定
本文讨论的范围限定为:系统已进入 systemd 管理阶段、能够登录或进入救援模式、且卡顿表现为“某个 target 之后长时间无进展”的情形。不覆盖内核早期解压、initramfs 内 udev 冷插拔风暴、硬件固件自检等更早阶段的问题——这些阶段 systemd-analyze 的常规输出无法覆盖,需借助 systemd-analyze plot 的 firmware/loader 段或内核日志另行分析。明确边界,是为了避免把工具能力外推导致的误诊。
三、原理剖析:blame 与 critical-chain 的计时口径差异
3.1 blame 的计时来源
systemd-analyze blame 读取的是 systemd 在启动过程中为每个单元记录的“激活耗时”,即从该单元的 job 开始执行到其进入 active 状态的时间差。它按耗时降序输出,默认只显示前若干条,可用 --no-pager 与管道查看全部。关键点在于:这个耗时是单元自身的墙钟时间,不扣除其等待依赖的时间。一个服务若在 ExecStart 之前等待网络就绪,其“激活耗时”会包含这段等待,从而在排行榜上虚高。
此外,blame 的输出受启动模式影响。若系统从休眠恢复或使用 kexec,部分单元的计时起点会不同;在容器环境中,由于 PID 1 并非完整 systemd 或启动事务被裁剪,blame 的数值可能缺失或失真。这些边界条件在后文“局限”一节还会展开。
3.2 critical-chain 的路径语义
systemd-analyze critical-chain 默认以 default.target 为终点,回溯出一条“最慢”的依赖链,输出形如:
multi-user.target @8.412s
└─network-online.target @8.401s
└─NetworkManager-wait-online.service @2.105s +6.290s
└─NetworkManager.service @1.980s +118ms
└─dbus.service @1.640s +332ms
└─basic.target @1.602s
└─...
每一行末尾的 +X 表示该单元自身的激活耗时,行首的 @Y 表示其激活完成的时刻。整条链的末端到顶端的时间差,就是关键路径长度。本文评述:critical-chain 的价值不在于给出一个数字,而在于给出一个集合——恰好位于关键路径上的单元集合。这个集合通常只有个位数到十几个成员,把排查范围从数百个单元压缩到十几个,是效率提升的核心来源。
3.3 两种口径的对照表
| 维度 | systemd-analyze blame | systemd-analyze critical-chain |
|---|---|---|
| 输出形态 | 耗时降序列表 | 单条(或多条)依赖链 |
| 计时对象 | 单元自身激活墙钟时间 | 路径上各单元激活时刻与耗时 |
| 是否考虑并行 | 否,彼此独立 | 是,隐含并行结构 |
| 回答的问题 | 谁最慢? | 谁拖住了终点? |
| 典型误用 | 直接禁用榜首服务 | 忽略链上“等待型”耗时 |
表中的“等待型耗时”指单元在自身激活窗口内等待外部条件(网络、设备、锁)所消耗的时间。它会计入 blame 与 critical-chain 的 +X,但根因在外部。识别这类耗时,是协同分析的关键技巧之一。
四、协同定位主线:关键路径优先、耗时排序验证
4.1 主线的形式化表述
设启动事务的依赖图记为有向无环图 G=(V,E),边表示顺序约束,节点权重为单元激活耗时。目标 target 的完成时刻 T 等于从起点到该节点的最长路径长度。定位问题可形式化为:寻找使 T 显著偏大的路径段集合 S。critical-chain 给出 S 的一个近似解(systemd 内部按最慢路径回溯),blame 则在 S 及其邻域内提供精确的权重排序。两者结合,等价于“先求最长路径,再在路径上做敏感度分析”。
本文评述:这一形式化并非文字游戏,它直接导出了操作顺序——先跑 critical-chain 而不是 blame。若先跑 blame,人容易被榜首吸引,形成先入为主的假设,后续验证会不自觉地寻找支持该假设的证据,这是典型的确认偏误。把 critical-chain 放在第一步,是用结构约束对抗认知偏差。
4.2 三类耗时信号的区分
在关键路径上,耗时信号可分为三类,处理策略截然不同:
- 自身计算型:单元进程真正在消耗 CPU 或 IO,如数据库恢复、镜像解压。特征是
+X大且进程 CPU 时间与之匹配。处理方向是优化单元本身或调整其触发时机。 - 外部等待型:单元在等待网络、设备节点、锁或另一服务。特征是
+X大但进程处于睡眠态。处理方向是消除等待源或改用异步/超时策略。 - 串行放大型:单个单元耗时不大,但因顺序约束被迫串行,累积拉长路径。特征是链上节点多、单点
+X小。处理方向是审视After=是否过严,能否并行化。
区分这三类信号,需要把 critical-chain 的输出与 blame 的数值、以及 systemctl show 的状态时间戳交叉比对。下一节的五步法将这一过程固化为可执行步骤。
4.3 为什么不能只看 blame 榜首
设想一个常见场景:某虚拟化平台上,blame 榜首是一个耗时 20 秒的存储多路径服务,但 critical-chain 显示它位于一条与目标 target 并行的分支上,其完成时刻早于关键路径的中段。此时禁用或优化它,对启动总时长毫无影响。反之,关键路径上一个仅耗时 3 秒的服务,若恰好位于串行链的咽喉位置,其优化收益可能立竿见影。
笔者认为,这种“排行榜幻觉”在容器化与云主机环境中尤为普遍,因为虚拟化层引入的 IO 抖动会随机抬高某些单元的耗时,使排行榜本身就不稳定。用结构(关键路径)约束数值(耗时排序),才能得到可复现的结论。
五、工程实践:五步操作路径与脚本化采集
5.1 第一步:确定卡住的目标单元
在故障机上,先确认最后到达的 target。可通过串口/虚拟控制台观察最后一行,或在能登录的情况下执行:
systemctl list-jobs --no-pager
systemctl list-units --state=activating --no-pager
systemctl list-units --state=failed --no-pager
list-jobs 会显示仍在排队或运行的 job,这是最直接的“卡在哪”证据。若系统已完全卡死无法登录,应通过内核命令行追加 systemd.log_level=debug 或使用救援模式复现。确定目标单元后,记下其名称,作为 critical-chain 的终点参数。
5.2 第二步:以目标单元为终点跑关键路径
systemd-analyze critical-chain multi-user.target
systemd-analyze critical-chain graphical.target
# 也可指定具体服务
systemd-analyze critical-chain NetworkManager-wait-online.service
观察输出中 +X 最大的若干节点,以及链的深度。若链非常深而单点耗时都小,说明是串行放大型问题;若某节点 +X 显著突出,进入第三步。
5.3 第三步:在候选集合内用 blame 排序
systemd-analyze blame --no-pager | head -n 30
# 只看关键路径上的单元(示例:过滤出链上出现的名字)
systemd-analyze blame --no-pager | grep -E 'NetworkManager|dbus|systemd-udev'
注意:blame 的数值与 critical-chain 的 +X 应基本一致(同一计时来源),若出现明显差异,通常是因为两次采集之间系统状态变化,或单元被重新激活。此时应固定采集窗口,必要时重启后立即采集。
5.4 第四步:区分等待型与计算型耗时
对候选单元,用 systemctl show 查看其状态时间戳与主进程信息:
systemctl show -p ActiveEnterTimestamp -p InactiveExitTimestamp \
-p ExecMainStartTimestamp -p ExecMainPID NetworkManager-wait-online.service
# 观察主进程是否在睡眠等待
ps -o pid,stat,wchan:32,etime,cmd -p <PID>
若 wchan 显示 poll_schedule_timeout、do_select 等,且 stat 为 S,基本可判定为等待型。此时应转向其等待对象:网络是否未就绪、设备节点是否未出现、上游服务是否未监听。可用 strace -p 短时采样确认系统调用,但生产环境慎用,避免干扰。
5.5 第五步:验证与回归
任何调整(禁用单元、放宽 After=、增加超时、改为 socket 激活)之后,必须做前后对比。建议采集三次冷启动数据取中位数,避免单次抖动误导。对比脚本模板如下:
#!/usr/bin/env bash
# startup-snapshot.sh —— 采集启动分析快照(模拟脚本,供参考)
OUT="/var/log/startup-snap-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$OUT"
systemd-analyze > "$OUT/summary.txt" 2>&1
systemd-analyze blame --no-pager > "$OUT/blame.txt" 2>&1
systemd-analyze critical-chain --no-pager > "$OUT/chain.txt" 2>&1
systemd-analyze plot > "$OUT/plot.svg" 2>&1
journalctl -b -o short-monotonic > "$OUT/journal.txt" 2>&1
echo "saved to $OUT"
该脚本为笔者在实验环境中使用的简化模板,非任何官方发布物,读者可自行扩展。plot 生成的 SVG 是“图文并茂”分析的重要素材:横轴为时间,每个单元一条色块,能直观看出并行度与长条单元,配合 critical-chain 的文字链,形成“图看全局、链看路径、表看数值”的三层证据。
拓展阅读:systemd 官方手册页对 analyze 子命令的说明见 freedesktop.org 的 systemd-analyze 手册;启动流程与依赖语义可参考 systemd.target 手册;社区排障讨论可检索 Unix & Linux Stack Exchange 的 systemd 标签。
六、案例与数据:三类典型故障模式
6.1 模式一:网络等待型(wait-online 类)
这是最广为人知的模式。NetworkManager-wait-online.service 或 systemd-networkd-wait-online.service 会在关键路径上占据数秒到数十秒,具体取决于 DHCP 响应、链路聚合协商、DNS 可达性判断等。其 +X 几乎全部是等待时间。critical-chain 中它常紧邻 network-online.target,而后者又被多用户 target 依赖。
处理策略有三:其一,确认业务是否真的需要 network-online 语义,许多服务只需本地回环或 socket 激活,可移除对 network-online.target 的强依赖;其二,调整 wait-online 的超时与判定条件,例如 systemd-networkd 的 --any 参数可改为“任一接口就绪即返回”;其三,将服务改为 socket 激活或延迟启动,把网络等待移出关键路径。本文评述:第三种方案收益最大但改动也最大,需评估服务的启动顺序假设是否被破坏。
6.2 模式二:设备/存储探测型(udev 与 mount)
在物理机与带外挂存储的环境里,systemd-udev-settle.service(已废弃但仍有残留)或某个 .mount 单元可能成为关键路径上的长条。典型表现是 critical-chain 显示某 mount 单元 +X 很大,journal 中出现设备未就绪、multipath 探测超时、或 NFS 挂载重试的记录。这类问题的根因常在存储侧或固件侧,systemd 只是“如实记录了等待”。
可验证的数据点包括:/proc/diskstats 在启动窗口内的 IO 完成数、journalctl -b -u systemd-udevd 的事件密度、以及 mount 单元的 TimeoutSec 是否被触发。若确认是存储探测慢,应优先在存储层解决,而非简单给 mount 加 noauto 掩盖问题。
6.3 模式三:串行放大与顺序约束过严
这类问题最隐蔽:blame 榜首可能只有 2 到 3 秒,critical-chain 却显示链深达十几层。常见诱因是自定义单元里滥写 After=,把本可并行的服务串成一条线;或 DefaultDependencies=no 使用不当,导致单元被插入到意外的位置。诊断方法是导出依赖图:
systemd-analyze dot multi-user.target | dot -Tsvg > deps.svg
# 或查看某单元的依赖与反向依赖
systemctl list-dependencies --reverse myapp.service
在图上寻找“单入口单出口”的长串结构,评估每一段 After= 是否必要。笔者在若干自研服务上观察到,开发者习惯性地写 After=network.target 与 After=postgresql.service,即使服务本身已具备重连逻辑,这会把启动时间无谓地串起来。移除冗余顺序约束、改用重试与健康检查,往往能显著缩短关键路径。
6.4 数据说明
本文不虚构具体机型的实测秒数作为普遍结论。上述模式描述基于 systemd 官方文档的语义与公开社区案例的归纳;若读者需要量化基线,应在自己的硬件与镜像上采集,并注明采集条件(冷启动/热启动、是否启用快照、磁盘类型)。任何跨环境的秒数对比,若未说明这些条件,都不具备可迁移性。这一点在学术与工程上都应坚持。
七、对比与局限:工具边界与常见误判
7.1 工具的固有局限
第一,critical-chain 默认只回溯一条最慢路径,当存在多条长度接近的并行路径时,它可能只展示其中一条,遗漏另一条上的问题。可用 --fuzz 参数放宽时间容差,让更多接近的路径被纳入。第二,blame 的计时包含等待,前文已述。第三,两者都依赖 systemd 自身的时间戳,若系统时钟在启动早期发生跳变(如 NTP 校时),时间戳可能出现负值或异常,需结合 journalctl -o short-monotonic 的单调时钟交叉验证。
第四,容器环境通常不运行完整 systemd,或启动事务被裁剪,analyze 的输出可能为空或误导。第五,从休眠/快照恢复时,部分单元的激活时间被“压缩”,blame 数值不能代表真实冷启动成本。本文评述:把这些局限写进排查清单,比事后解释“为什么工具没查出来”更有价值。
7.2 与替代方法的对比
| 方法 | 优势 | 局限 |
|---|---|---|
| critical-chain + blame | 零依赖、语义清晰、可脚本化 | 单路径回溯、等待型耗时需人工区分 |
| systemd-analyze plot | 全局并行视图、直观 | 不直接给出因果、大图阅读成本高 |
| journal 单调时钟分析 | 可定位具体阻塞点与错误 | 信息量大、需过滤 |
| eBPF/性能剖析 | 可深入内核态与 IO 栈 | 部署门槛高、早期启动阶段难附加 |
笔者认为,这四类方法不是替代关系而是层次关系:analyze 两件套负责“定位到单元”,journal 负责“定位到事件”,eBPF 负责“定位到内核路径”。从单元到事件再到内核,逐层下钻,是控制排查成本的有效策略。
7.3 常见误判清单
- 把 blame 榜首直接当作根因,忽略其是否在关键路径上。
- 看到
Reached target就以为是 target 本身的问题,未检查list-jobs。 - 把等待型耗时当作计算型,去优化一个其实在睡觉的进程。
- 在容器或快照恢复环境中套用冷启动结论。
- 单次采集即下结论,未做多次冷启动的中位数对比。
- 为缩短启动而滥用
mask,破坏服务依赖的完整性,埋下运行期故障。
八、结论与展望
回到最初的问题:卡在 Reached target 时,blame 与 critical-chain 如何配合?答案是明确的——以 critical-chain 确定结构、以 blame 量化权重、以 journal 与进程状态区分耗时性质、以多次冷启动验证结论。这条“关键路径优先、耗时排序验证”的主线,把两个看似独立的子命令组织成一个有先后、有反馈的分析闭环,避免了排行榜幻觉与确认偏误。
从工程视角看,启动优化不应止于“把某个服务禁掉”。更稳健的做法是把启动依赖当作架构的一部分来治理:区分“必须同步就绪”与“可以异步就绪”的能力,用 socket 激活、健康检查、重试退避替代粗暴的顺序约束,让关键路径尽可能短而稳定。本文评述:启动时间是一个系统性指标,它反映的是依赖设计的质量,而非单个服务的性能。
展望未来,随着 systemd 在边缘设备与不可变镜像中的普及,启动分析会更多与镜像构建、快照恢复、可信启动等流程交织。工具层面,plot 的可视化与机器可读输出的结合、以及在受限环境中对启动事务的轻量记录,都是值得关注的方向。对从业者而言,掌握本文这条分析主线,比记住任何单条命令都更有长期价值。
主要参考文献
- systemd 项目文档. systemd-analyze(1) 手册页[EB/OL]. freedesktop.org, 访问于 2025.
- systemd 项目文档. systemd.target(5) 与 systemd.service(5) 手册页[EB/OL]. freedesktop.org, 访问于 2025.
- systemd 项目文档. systemd.special(7):启动目标与顺序语义[EB/OL]. freedesktop.org, 访问于 2025.
- P. Systemd 启动流程与依赖解析机制分析[J]. Linux 运维实践, 2023(社区技术汇编,非同行评议).
- Unix & Linux Stack Exchange. systemd 标签下关于 critical-chain 与 blame 差异的讨论帖[EB/OL]. 访问于 2025.
- Arch Wiki. Improve boot performance[EB/OL]. wiki.archlinux.org, 访问于 2025.
- Red Hat 文档. Managing systemd 与启动性能分析[EB/OL]. docs.redhat.com, 访问于 2025.
- Linux 内核文档. 单调时钟与启动时间戳相关说明[EB/OL]. kernel.org, 访问于 2025.
- 社区案例汇编. 网络等待与 udev 探测导致启动延迟的公开排障记录[EB/OL]. 2023–2025.
说明:上述条目为本文写作时参考的公开资料类型汇总,具体版本与页码请以原始文献为准;文中未引用任何虚构的作者团队实验数据,涉及脚本与数值均为方法示例或模拟说明。
文章声明:本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。
内容仅供学习参考。如需引用,请以原始文献为准。 全文约 6200 字 | 参考文献 60 篇(主要 9 篇)

