暗色模式

Linux 开机启动分析:从 systemd-analyze blame 到 critical-chain 定位慢服务

技术教程
2026-09-18
7
0
本文要点
  • systemd-analyze time 说这台机器开机用了 31.995s:内核 3.895s,用户态 28.100s,默认 target graphical.target 在 26.166s 达成——两个数字口径不同,别当成矛盾
  • ⚠️ 最大的坑:blame 排行榜前两名(2min 196ms + 1min 10.108s)加起来就已经是开机总时长的 6 倍。因为 blame 统计的是单元自己从开始激活到结束的墙钟时间(含排队等依赖),不是「它让开机多等了几秒」
  • 判断谁真的拖慢开机要看 critical-chain:沿关键路径从 target 往下读,@ 是单元被激活的时刻,+ 是它自身耗时;不在路径上的单元再慢也不影响开机
  • ⚠️ 第二个坑:关键路径里赫然出现 @16h 48min 43.609s——一个比开机时刻晚 16 小时的「启动时间」。这说明那根本不是开机那次运行,必须用 journalctlsystemctl show 交叉验证
  • 本机实证:wait-online 开机那次只用了 14 秒,blame 里的 2 分钟是次日 apt 自动升级 systemd(249.11-0ubuntu3.1 → 249.11-0ubuntu3.22)重启 networkd 时被顺带重跑、并超时失败的那一次

开机那 32 秒,到底花在哪

服务器重启之后变慢了,第一件事是搞清楚时间花在哪一段。systemd 自带的分析器 systemd-analyze 就是干这个的,一条命令给出总账:

systemd-analyze time
systemd-analyze blame | head -6
Startup finished in 3.895s (kernel) + 28.100s (userspace) = 31.995s 
graphical.target reached after 26.166s in userspace
  2min 196ms systemd-networkd-wait-online.service
1min 10.108s apt-daily-upgrade.service
     31.402s apt-daily.service
      3.509s snapd.service
      2.405s pollinate.service
      2.113s motd-news.service

systemd-analyze time 给出总账,blame 给出按耗时排序的单元清单

第一段是时间构成:内核启动到 systemd 接管用了 3.895s,从 systemd 接管到宣告开机完成用了 28.100s,合计 31.995s。第二行说默认 target 在 26.166s 达成。

这两个数字看着矛盾(28.100s vs 26.166s),其实是两个事件:「Startup finished in …」是 systemd 宣告开机完成、初始事务收尾的时刻;graphical.target reached 是那个 target 单元被激活的时刻。差了不到 2 秒,日常排查盯后者所在的链路就够了。这台机器的默认 target 可以用 systemctl get-default 确认,输出就是 graphical.target

另外,systemd-analyze time 在物理机上还会多出 firmwareloader 两段(固件自检、引导器加载内核)。这台小机没有这两行,因为它是 BIOS 启动的 KVM 虚拟机,/sys/firmware/efi 目录根本不存在——systemd 拿不到 EFI 固件的时间戳,只能报内核和用户态。判断一台机器是物理机还是虚拟机,Linux 硬件信息采集 里有更系统的办法。

blame 排行榜:前三名加起来比开机还长

systemd-analyze blame 会把系统里所有单元按「耗时」从大到小排出来。这台机器一共 94 个单元,榜首是 systemd-networkd-wait-online.service,两分多钟。

但先别急着去关它。把前三名加起来:

2min 196ms + 1min 10.108s + 31.402s ≈ 3min 42s

整台机器开机才 31.995s。慢的服务加起来比开机本身长 6 倍,这只有一个解释:blame 里的数字不是「它让开机多等了几秒」。

blame 到底在统计什么

它统计的是单元从「开始激活」到「结束」的墙钟时间,也就是 systemd 里 InactiveExitTimestampInactiveEnterTimestamp 的差值——这里面包含了它在队列里等依赖、等前序单元的时间,不只是自己干活的时间。用 systemctl show 把这三个单元的原始时间戳摊开看就清楚了:

systemctl show systemd-networkd-wait-online.service -p InactiveExitTimestamp -p InactiveEnterTimestamp -p ExecMainStartTimestamp -p ExecMainExitTimestamp
InactiveExitTimestamp=Thu 2026-09-10 06:45:33 UTC
InactiveEnterTimestamp=Thu 2026-09-10 06:47:33 UTC
ExecMainStartTimestamp=Thu 2026-09-10 06:45:33 UTC
ExecMainExitTimestamp=Thu 2026-09-10 06:47:33 UTC

06:45:33 → 06:47:33 正好 120 秒,对上 blame 里的 2min 196ms。另外两个也一样:apt-daily-upgrade06:24:48 → 06:25:59(71 秒,对上 1min 10.108s,其中主进程 06:25:19 才真正启动,前 31 秒都在排队);apt-daily19:59:34 → 20:00:06(32 秒,对上 31.402s,而它的主进程其实只跑了 2 秒)。

注意这些时间戳的日期:2026-09-10 和 2026-09-17,而这台机器是 2026-09-09 13:56 开机的。榜上这几位,没有一个是在开机那一刻跑的。

critical-chain:谁真的在关键路径上

blame 回答「哪些单元耗时长」,critical-chain 回答「开机到底在等谁」。它沿着依赖关系,把从 target 一路到底层的那条最长路径打出来:

systemd-analyze critical-chain
The time when unit became active or started is printed after the "@" character.
The time the unit took to start is printed after the "+" character.

graphical.target @26.166s
└─multi-user.target @26.166s
  └─networkd-dispatcher.service @19.677s +643ms
    └─basic.target @19.632s
      └─paths.target @19.617s
        └─ua-license-check.path @19.606s
          └─sysinit.target @19.605s
            └─cloud-init.service @17.596s +2.006s
              └─systemd-networkd-wait-online.service @16h 48min 43.609s +2min 196ms
                └─systemd-journald.socket @310ms
                  └─system.slice @278ms
                    └─-.slice @278ms

critical-chain 输出的关键路径:从 graphical.target 一路读到根

读法很短:每一行是一个单元,@ 后面是它被激活的时刻+ 后面是它自己启动花了多久。箭头方向是「上面的在等下面的」。所以从下往上读就是开机的等待链条:

journald.socket(0.310s 就绪)→ systemd-networkd-wait-onlinecloud-init.service(17.596s 激活,自身耗时 2.006s)→ sysinit.target(19.605s)→ … → multi-user.target / graphical.target(26.166s)。

关键路径上的服务才是真正影响开机的。反过来,一个耗时很长但不在链上的单元,对开机时间毫无影响——这正是 blame 榜首那位的情况。

那行 16 小时的时间戳

链里最扎眼的是这一行:

└─systemd-networkd-wait-online.service @16h 48min 43.609s +2min 196ms

@16h 48min 43.609s 的意思是「这个单元在开机后 16 小时 48 分才被激活」。一台开机只要 32 秒的机器,关键路径上却挂着一个 16 小时后才发生的「启动」——这一眼就说明:systemd-analyze 读的是该单元最近一次运行的状态,而不是开机那一次blamecritical-chain 都基于同一份运行时状态,所以会被同一个问题污染。

交叉验证:那 2 分钟发生在开机后 16 小时

systemd-analyze 只反映「最近一次」,要还原真相得去翻日志。这台机器开机于 2026-09-09 13:56:46,把这个单元本次开机以来的全部记录打出来:

journalctl -u systemd-networkd-wait-online.service -b --no-pager -o short-iso
2026-09-09T13:56:53+0000 ubuntu systemd[1]: Starting Wait for Network to be Configured...
2026-09-09T13:57:07+0000 ubuntu systemd-networkd-wait-online[547]: managing: eth0
2026-09-09T13:57:07+0000 ubuntu systemd[1]: Finished Wait for Network to be Configured.
2026-09-10T06:45:33+0000 MFY001832765701 systemd[1]: systemd-networkd-wait-online.service: Deactivated successfully.
2026-09-10T06:45:33+0000 MFY001832765701 systemd[1]: Stopped Wait for Network to be Configured.
2026-09-10T06:45:33+0000 MFY001832765701 systemd[1]: Stopping Wait for Network to be Configured...
2026-09-10T06:45:33+0000 MFY001832765701 systemd[1]: Starting Wait for Network to be Configured...
2026-09-10T06:47:33+0000 MFY001832765701 systemd-networkd-wait-online[24240]: Timeout occurred while waiting for network connectivity.
2026-09-10T06:47:33+0000 MFY001832765701 systemd[1]: systemd-networkd-wait-online.service: Main process exited, code=exited, status=1/FAILURE
2026-09-10T06:47:33+0000 MFY001832765701 systemd[1]: systemd-networkd-wait-online.service: Failed with result 'exit-code'.
2026-09-10T06:47:33+0000 MFY001832765701 systemd[1]: Failed to start Wait for Network to be Configured.

journalctl 摊开两次运行:开机那次 14 秒成功,次日凌晨那次 120 秒超时失败

真相一目了然,这里有两次运行:

  • 开机那次13:56:53 启动,13:57:07 完成,14 秒,成功。开机确实在这里等了十几秒——但远不是 2 分钟。
  • 次日凌晨那次2026-09-10 06:45:33 启动,06:47:33 因为 Timeout occurred while waiting for network connectivity 失败,整整 120 秒。blame 记的就是这一次。

那它为什么会在凌晨 06:45 又跑一遍?看 dpkg 日志:

grep "2026-09-10 06:45" /var/log/dpkg.log | grep -iE "upgrade (systemd|libsystemd)"
2026-09-10 06:45:32 upgrade systemd-timesyncd:amd64 249.11-0ubuntu3.1 249.11-0ubuntu3.22
2026-09-10 06:45:32 upgrade systemd-sysv:amd64 249.11-0ubuntu3.1 249.11-0ubuntu3.22
2026-09-10 06:45:32 upgrade systemd:amd64 249.11-0ubuntu3.1 249.11-0ubuntu3.22
2026-09-10 06:45:32 upgrade libsystemd0:amd64 249.11-0ubuntu3.1 249.11-0ubuntu3.22

06:45:32 系统自动升级了 systemd 本体,下一秒 06:45:33 networkd 就被重启,而 systemd-networkd-wait-online.service 里写着 Requires=systemd-networkd.serviceAfter=systemd-networkd.service——老大重启,它自然被带着重跑一遍。重跑这次没能等到 eth0 完成配置,撞上程序自带的 120 秒超时(systemctl showTimeoutStartUSec=infinity,所以这个 120 秒是 systemd-networkd-wait-online 自己的默认值,不是 systemd 掐的),留下一条失败记录,顺便霸占了 blame 榜首。

顺带解释了这个单元的另一个常见疑问:它明明在开机 19 秒前后就完事了,为什么 critical-chain 还把它画在链上?因为链是按依赖关系画的,不是按时间画的——systemd-networkd-wait-online.service 里写着 Before=cloud-init.servicecloud-init.serviceAfter=systemd-networkd-wait-online.service,只要这层关系在,它永远出现在链上,哪怕它当时只花了 14 秒。

这套方法怎么用

把上面的过程收成三步:

  1. systemd-analyze time 看总量,确认慢的是内核段还是用户态段;
  2. systemd-analyze critical-chain 找关键路径,路径上的单元才是候选,路径外的再慢也不用管;
  3. 对每个候选,用 journalctl -u <单元> -bsystemctl show <单元> -p ExecMainStartTimestamp -p ExecMainExitTimestamp 核实「这次运行是什么时候」——时间戳不在本次开机附近的,直接排除。

第 3 步是这套方法里最容易被跳过、也最容易翻车的一步。blame 榜单只适合当线索清单,不能当结论。

想看得更直观,可以让它输出一张时间轴图:

systemd-analyze plot > /tmp/boot.svg

这台机器生成的 SVG 是 106 KB,横轴是时间、纵轴是单元,哪些单元拖出长长的色条一眼可见——适合贴进故障报告。浏览器直接打开即可,不需要额外装工具。

真要动手优化时,对象应该是关键路径上的单元。两个本机案例:

wait-online 加个更短的超时。它超时是程序自己的 120 秒默认值,用 drop-in 覆盖掉(注意 ExecStart= 空赋值那一行是必须的,systemd 对同名指令是追加语义,不先清空会报错):

# systemctl edit systemd-networkd-wait-online.service
[Service]
ExecStart=
ExecStart=/lib/systemd/systemd-networkd-wait-online --timeout=15

apt-daily / apt-daily-upgrade 这类由 timer 触发的每日任务,要改的是 timer 而不是 service。这台机器上 systemctl cat apt-daily.timer 显示 OnCalendar=*-*-* 6,18:00RandomizedDelaySec=12h,再配 Persistent=true(错过的时间点开机后补跑)。想让它们离开机远一点,调 timer 的 OnCalendar / RandomizedDelaySec 才是正解——反正它们的耗时再长也不在关键路径上。

systemd-analyze 还有几个顺手的小工具:verify 检查单元文件语法(改完 drop-in 先跑一次)、security 给单元的沙箱化程度打分、dot 输出依赖图、unit-files 列出单元的配置来源、calendartimespan 校验时间表达式。它们的共同点是:都属于「看一眼系统状态」的只读命令,排障时不会有副作用。

小结

systemd-analyze 的两个子命令容易被当成一回事,其实分工完全不同:blame 排的是单元自身的耗时榜,critical-chain 画的才是开机真正等待的那条路径——只有后者能回答「开机慢在谁身上」。

更要记住的是它俩共享的那个前提:数据来自最近一次运行状态。这台机器上,一个 32 秒开机的系统,榜单第一名却是开机 16 小时后才发生的 120 秒超时。看到「耗时比开机总时长还长」的数字,别怀疑机器,去 journalctlsystemctl show 里核对时间戳——那才是真相所在。

发表评论

暂无评论,快来抢沙发吧!