4、目标单元依赖树分析:使用 systemctl list-dependencies 分析目标单元的依赖关系
在 systemd 初始化阶段,目标单元(target unit)是同步点而非真正的“运行级别”。一个目标单元的启动意味着其依赖树中所有单元均已成功激活。当系统卡在某个目标(如 multi-user.target)时,排查的核心手段就是分析该目标的依赖树。
4.1 依赖树的基本概念
systemd 的依赖关系分为两种主要类型:
| 依赖类型 | 指令 | 含义 | 对启动顺序的影响 |
|---|---|---|---|
| 强依赖 | Requires= |
目标单元必须成功启动,否则当前单元失败 | 阻塞启动,失败则回滚 |
| 弱依赖 | Wants= |
目标单元尽量启动,失败不影响当前单元 | 不阻塞,失败仅记录日志 |
| 排序依赖 | After= / Before= |
仅控制启动顺序,不决定是否必须成功 | 调整时序,不决定成败 |
| 逆向依赖 | RequiredBy= / WantedBy= |
被其他单元依赖时自动建立反向关系 | 影响依赖树的拓扑结构 |
排查要点: 当目标单元卡住时,优先关注
Requires= 和 After= 链上的单元。弱依赖(Wants)通常不是阻塞原因,但可能因资源竞争导致超时。
4.2 使用 systemctl list-dependencies 查看依赖树
systemctl list-dependencies 是分析目标单元依赖树的核心命令。它递归展开所有依赖,并以树状结构输出。
4.2.1 基本用法
# 查看 multi-user.target 的完整依赖树(默认递归展开所有依赖)
systemctl list-dependencies multi-user.target
# 仅查看直接依赖(不递归)
systemctl list-dependencies multi-user.target --all
# 查看逆向依赖(哪些单元依赖该目标)
systemctl list-dependencies multi-user.target --reverse
4.2.2 输出解读
典型输出片段如下:
multi-user.target
● ├─basic.target
● │ ├─sysinit.target
● │ │ ├─dev-hugepages.mount
● │ │ ├─sys-fs-fuse-connections.mount
● │ │ ├─systemd-journald.service
● │ │ ├─systemd-udevd.service
● │ │ └─…
● │ ├─sockets.target
● │ │ ├─dbus.socket
● │ │ ├─systemd-journald.socket
● │ │ └─…
● │ └─timers.target
● │ └─systemd-tmpfiles-clean.timer
● ├─crond.service
● ├─sshd.service
● ├─rsyslog.service
● └─…
树状结构中的符号含义:
●— 当前节点├─— 中间子节点└─— 最后一个子节点│— 层级延续线
常见陷阱: 默认输出仅显示
Wants= 和 Requires= 依赖,不显示 After= 排序依赖。要查看完整的排序关系,需结合 systemctl show -p After 或 systemd-analyze dot。
4.3 依赖树可视化分析
以下 SVG 图展示了 multi-user.target 的典型依赖树结构,突出强依赖与弱依赖的路径差异:
4.4 依赖树故障排查实战步骤
- 定位卡住的目标 — 使用
systemctl list-jobs查看当前等待中的 job,确定是哪个目标未完成。 - 展开依赖树 — 执行
systemctl list-dependencies <target>获取完整树状结构。 - 标记异常节点 — 结合
systemctl status <unit>检查树中每个单元的状态,重点关注failed、activating或deactivating状态的单元。 - 追踪强依赖链 — 使用
systemctl show -p Requires <target>和systemctl show -p After <target>获取精确的强依赖和排序依赖列表。 - 检查循环依赖 — 使用
systemd-analyze verify检测单元文件中是否存在循环依赖。
实战案例: 某系统卡在
multi-user.target,执行 systemctl list-dependencies multi-user.target 后发现 sysinit.target 下的 systemd-journald.service 处于 activating 状态。进一步检查 journalctl -u systemd-journald 发现磁盘 I/O 超时,最终定位为磁盘控制器驱动未正确加载。
4.5 依赖树分析的进阶技巧
| 场景 | 命令 | 用途 |
|---|---|---|
| 查看所有依赖类型 | systemctl show -p "Requires\|Wants\|After\|Before" <target> |
一次性获取所有依赖关系定义 |
| 生成依赖图(文本) | systemd-analyze dot <target> | dot -Tsvg > deps.svg |
生成可缩放矢量依赖图(需安装 graphviz) |
| 检查单元是否被屏蔽 | systemctl list-unit-files | grep masked |
屏蔽的单元会导致依赖链断裂 |
| 查看单元启动耗时 | systemd-analyze blame |
识别耗时过长的单元,辅助定位瓶颈 |
4.6 依赖树分析常见误区
- 误区一: 认为
list-dependencies输出所有依赖。实际上它默认只显示Wants和Requires,不显示After排序依赖。 - 误区二: 忽略
PartOf=和BindsTo=等特殊依赖。这些依赖在特定场景下会导致意外的启动失败。 - 误区三: 只关注目标单元本身,忽略其子目标(如
basic.target、sysinit.target)的依赖树。故障往往发生在深层子节点。 - 误区四: 认为依赖树是静态的。实际上,
systemctl daemon-reload或单元文件变更后,依赖树会重新计算。
最佳实践: 在排查前先执行
systemctl daemon-reload 确保依赖树基于最新配置。然后使用 systemd-analyze critical-chain <target> 快速定位关键路径上的最慢单元,再结合 list-dependencies 深入分析。