加速 Linux 启动时间:分析与优化 Systemd 单元依赖

使用 systemd-analyze、critical-chain 和单元依赖清理来查找并修复 Linux 启动缓慢的路径。

加速 Linux 启动时间:分析与优化 Systemd 单元依赖

Linux 启动时间优化始于一个问题:究竟是什么阻碍了你的机器变得可用?在大多数现代发行版上,systemd 会并行启动服务,但一个缓慢的单元或不必要的排序规则仍可能拖慢启动路径。

通过检查哪些单元耗时以及哪些单元位于关键链上,你可以决定是调整、延迟还是禁用它们。下面的示例聚焦于 systemd-analyze 和保持系统可预测的小型依赖更改。

理解 Systemd 启动过程

Systemd 通过尽可能并行执行服务来管理启动过程。然而,一个服务只有在满足其所有显式和隐式依赖后才能启动。如果单元 A 要求单元 B 完全激活后才能继续,那么单元 A 就被单元 B 阻塞。识别这些阻塞依赖是加速的第一步。

关键 Systemd 分析工具

Systemd 提供了几个强大的命令行工具来诊断启动性能。以下工具对于定位瓶颈至关重要:

1. systemd-analyze(总体视图)

此命令提供内核、用户空间初始化以及加载可用目标所花费总时间的高级概览。

systemd-analyze

示例输出解读:

组件 耗时
内核 1.234s
Initrd 0.500s
用户空间 5.789s
总计 7.523s

这能快速显示瓶颈是在内核阶段(固件/驱动加载)还是用户空间阶段(服务启动)。

2. systemd-analyze blame(识别慢速单元)

此命令按激活耗时排序列出单元,最长的排在最前面。

systemd-analyze blame

关注点: 查看前 10 个条目。这些是启动期间主动消耗时间的服务。注意,较长的初始化时间可能仅仅意味着该服务确实做了很多工作;目标是看这些工作是否需要在启动时完成。

3. systemd-analyze critical-chain(依赖分析)

此命令显示通向启动目标(通常是 graphical.targetmulti-user.target)的依赖链。它突出显示在系统被认为完全启动之前必须完成的单元序列。

systemd-analyze critical-chain

关键链中列出的单元是优化的主要目标,因为延迟它们会延迟整个系统启动。

4. systemd-analyze plot(可视化启动序列)

为了以图形方式展示并行性和阻塞情况,可以使用 plot 命令生成 SVG 文件:

systemd-analyze plot > boot_analysis.svg
# 在浏览器中打开 boot_analysis.svg

此图表直观地展示了哪些服务在并行运行,哪些在等待其他服务,使依赖问题一目了然。

优化技术:修改单元文件

一旦使用上述工具识别出慢速或阻塞单元,优化涉及要么加速单元本身,要么改变其何时运行。

1. 处理 blame 识别的慢速单元

如果 blame 输出中排名靠前的服务(例如,slow-database.service 耗时 10 秒)对于基本系统操作(如登录或基本网络)不是立即必需的,考虑延迟它。

操作: 更改其启动依赖级别。

  • 如果它当前在 multi-user.target 启动,检查它是否可以从定时器、套接字、路径单元或手动命令启动。
  • 如果服务是可选的,禁用它通常比更改核心依赖行为更安全。仅在你理解 systemd 通常为该单元类型添加的默认排序时,才使用 DefaultDependencies=no

2. 使用 WantsRequiresAfter 优化依赖

单元文件使用依赖指令控制执行顺序。这里的错误配置是导致不必要顺序执行的常见原因。

依赖类型:

  • Requires=:强依赖。如果所需单元失败,此单元也会失败。
  • Wants=:弱依赖。如果所需单元可用,此单元会启动,但如果所需单元失败,它仍会尝试启动。
  • After=:排序指令。此单元仅在指定单元完成启动后才会启动(无论成功与否)。
  • Before=:排序指令。此单元必须在指定单元之前启动。

最佳实践提示:对于可选关系,优先使用 Wants 而非 Requires Wants= 改变的是失败行为,而非排序本身。除非你同时添加了诸如 After= 的排序规则,否则所需单元仍可以并行启动。

移除不必要的 After= 约束

加速启动时间最有效的方法是消除不必要的排序约束。如果单元 A 在功能上不依赖于单元 B 在单元 A 开始之前启动,则从单元 A 的定义中移除 After=unit-b.service 行。

示例修改(概念性):

假设你的自定义应用程序单元 app.service 不必要地等待网络配置服务:

# /etc/systemd/system/app.service
[Unit]
Description=My Application
Requires=network.target
After=network.target  <-- 可能不必要的等待!

[Service]
ExecStart=/usr/bin/myapp

如果你的应用程序只需要本地回环接口或只需要建立本地文件锁,等待完整的网络栈(network.target)可能会浪费几秒钟。如果你确认应用程序并不真正需要外部网络,移除 After=network.target 行。Systemd 随后会尝试在并行设置网络的同时尽快启动 app.service

3. 屏蔽不需要的服务

如果 systemd-analyze blame 显示某个正在运行的服务你完全不需要(例如,服务器上不必要的蓝牙支持,或特定的硬件监视器),禁用或屏蔽它会完全阻止其启动。

  • 禁用: systemctl disable <unit>(阻止它在未来启动时启动)。
  • 屏蔽(更强): systemctl mask <unit>(将单元链接到 /dev/null,也阻止手动启动尝试)。
# 示例:如果没有蜂窝调制解调器,屏蔽 ModemManager
sudo systemctl mask ModemManager.service

重新加载并验证更改

修改任何单元文件后(特别是放在 /etc/systemd/system/ 中的文件),你必须通知 systemd 重新加载其配置守护进程,然后才能重启测试:

sudo systemctl daemon-reload

# 然后,在重启前检查依赖或状态
systemctl list-dependencies myapp.service

最后,始终重启系统以衡量优化对启动序列的真实影响。

sudo reboot

重启后,立即再次运行 systemd-analyze 以量化通过优化实现的时间节省。

要点

将启动调优视为一个小更改循环:使用 systemd-analyze 测量,找到关键路径上的单元,仅移除你能证明合理的排序规则,然后重启并再次测量。最安全的成果通常来自禁用不需要的服务、将工作转换为定时器或套接字激活,以及移除你自己单元中不必要的 After= 行。