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 的典型依赖树结构,突出强依赖与弱依赖的路径差异:

multi-user.target 依赖树结构(简化) multi-user.target basic.target (Wants) sshd.service (Wants) crond.service (Wants) sysinit.target (Requires) sockets.target (Wants) timers.target (Wants) systemd-journald.service systemd-udevd.service dev-hugepages.mount 依赖类型说明 ● 紫色:Wants 弱依赖 ● 青色:Requires 强依赖 ● 红色:关键服务(阻塞风险)

4.4 依赖树故障排查实战步骤

  1. 定位卡住的目标 — 使用 systemctl list-jobs 查看当前等待中的 job,确定是哪个目标未完成。
  2. 展开依赖树 — 执行 systemctl list-dependencies <target> 获取完整树状结构。
  3. 标记异常节点 — 结合 systemctl status <unit> 检查树中每个单元的状态,重点关注 failed、activating 或 deactivating 状态的单元。
  4. 追踪强依赖链 — 使用 systemctl show -p Requires <target> 和 systemctl show -p After <target> 获取精确的强依赖和排序依赖列表。
  5. 检查循环依赖 — 使用 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 深入分析。