NAS 产品的用户体验有一个关键指标:从按下电源到可以访问共享文件夹,需要多长时间?我们的目标是 30 秒内完成,而原始方案需要 75 秒。这篇文章记录了从 75 秒优化到 30 秒的完整过程。
一、为什么需要定制 Linux?
1.1 NAS 的特殊需求
与通用服务器不同,NAS 设备需要:
- 快速启动:家用设备,用户不接受服务器那样的启动时间
- 资源受限:ARM 处理器 + 1GB 内存
- 稳定性要求高:24×7 运行,减少不必要的服务
- 硬件强绑定:只需支持特定的 SoC 和外设
通用发行版(如 Ubuntu Server)包含大量我们不需要的功能,启动时间长、内存占用高。
1.2 定制方案选择
| 方案 | 优点 | 缺点 |
|---|
| Yocto | 完全可控、最小化 | 学习曲线陡峭 |
| Buildroot | 简单轻量 | 包管理弱 |
| 裁剪 Ubuntu | 上手快 | 难以彻底精简 |
我们选择了 Yocto,因为需要长期维护和精细控制。
二、启动时间分析
2.1 基线测量
使用 systemd-analyze 工具:
1
2
3
4
5
| # 查看总启动时间
systemd-analyze
# 输出
Startup finished in 8.5s (kernel) + 66.2s (userspace) = 74.7s
|
74.7 秒,太慢了!
2.2 瓶颈定位
1
2
| # 查看各服务启动耗时(blame = 谁该背锅)
systemd-analyze blame | head -20
|
输出:
1
2
3
4
5
6
7
| 32.1s network-online.target
18.5s cloud-init.service
8.2s snapd.service
5.3s systemd-journal-flush.service
4.1s docker.service
3.8s accounts-daemon.service
...
|
发现问题:
network-online.target 等待网络就绪 32 秒cloud-init 是云服务器用的,我们不需要snapd 也不需要- 很多服务是串行启动的
2.3 依赖关系可视化
1
2
| # 生成启动时序图
systemd-analyze plot > boot-timeline.svg
|

从图中可以看到:
- 很多服务在等待
network-online.target - 但我们的核心服务(NAS 文件共享)其实只需要网卡初始化,不需要完全"在线"
三、优化策略
3.1 禁用不需要的服务
1
2
3
4
5
6
7
8
9
10
11
12
| # 禁用 cloud-init(云服务器专用)
systemctl disable cloud-init.service
systemctl disable cloud-config.service
systemctl disable cloud-final.service
# 禁用 snapd
systemctl disable snapd.service
systemctl disable snapd.seeded.service
# 禁用不需要的账户服务
systemctl disable accounts-daemon.service
systemctl disable whoopsie.service
|
效果:减少 30 秒!
3.2 优化网络等待
问题:network-online.target 默认等待 DHCP 获取 IP,可能需要 30+ 秒。
方案一:减少 DHCP 超时
1
2
3
4
5
6
7
8
9
10
11
12
| # /etc/systemd/network/eth0.network
[Match]
Name=eth0
[Network]
DHCP=yes
[DHCP]
RouteMetric=100
UseDNS=true
UseMTU=true
Timeout=10 # 原来是 60 秒
|
方案二:让核心服务不依赖 network-online
1
2
3
4
5
6
| # /lib/systemd/system/nas-agent.service
[Unit]
Description=NAS Agent
# 改为只依赖 network.target(网卡初始化),而非 network-online.target(获取到 IP)
After=network.target
Wants=network.target
|
效果:又减少 20 秒!
3.3 服务并行化
检查关键路径:
1
| systemd-analyze critical-chain nas-agent.service
|
输出:
1
2
3
4
5
6
| nas-agent.service @35.2s
└─multi-user.target @35.1s
└─docker.service @30.5s +4.5s
└─containerd.service @25.3s +5.1s
└─local-fs.target @25.2s
└─...
|
发现 nas-agent 被 docker 阻塞了,但其实两者没有依赖关系!
修复:
1
2
3
4
5
| # /lib/systemd/system/nas-agent.service
[Unit]
Description=NAS Agent
After=network.target
# 移除不必要的顺序依赖
|
效果:docker 和 nas-agent 现在并行启动,减少 5 秒。
3.4 内核优化
3.4.1 禁用不需要的内核模块
1
2
| # 检查已加载的模块
lsmod | wc -l # 150+ 个!
|
创建模块黑名单:
1
2
3
4
5
6
7
| # /etc/modprobe.d/blacklist-nas.conf
blacklist bluetooth
blacklist btusb
blacklist snd_hda_intel
blacklist nouveau
blacklist i2c_piix4
# ...
|
效果:内核启动阶段减少 2 秒。
3.4.2 调整 initramfs
1
2
| # 查看 initramfs 内容
lsinitramfs /boot/initrd.img-$(uname -r) | wc -l # 300+ 个文件
|
精简 initramfs(只保留必要的驱动):
1
2
| # /etc/initramfs-tools/initramfs.conf
MODULES=dep # 只包含依赖的模块,而非全部
|
重新生成:
效果:initramfs 从 50MB 减少到 15MB,加载时间减少 3 秒。
四、Systemd 调优技巧
4.1 Type=simple vs Type=oneshot
1
2
3
4
5
6
7
8
9
10
| # 快速启动的服务用 simple(默认)
[Service]
Type=simple
ExecStart=/usr/bin/daemon
# 需要等待完成的用 oneshot
[Service]
Type=oneshot
ExecStart=/usr/bin/init-script
RemainAfterExit=yes
|
4.2 懒加载:socket activation
不是所有服务都需要立即启动。使用 socket activation,在有请求时才启动服务:
1
2
3
4
5
6
7
| # /etc/systemd/system/nas-api.socket
[Socket]
ListenStream=8080
Accept=false
[Install]
WantedBy=sockets.target
|
1
2
3
4
| # /etc/systemd/system/nas-api.service
[Service]
ExecStart=/usr/bin/nas-api
# 当有连接到 8080 端口时才启动
|
效果:启动时少启动 3 个服务,减少 2 秒。
4.3 ReadWritePaths 加速
1
2
3
4
5
| # 限制服务的文件系统访问范围,加快启动
[Service]
ReadWritePaths=/var/lib/nas-agent
ProtectSystem=strict
ProtectHome=true
|
Systemd 不需要遍历整个文件系统,启动更快。
五、最终成果
5.1 优化前后对比
| 阶段 | 优化前 | 优化后 | 减少 |
|---|
| 内核启动 | 8.5s | 5.5s | -3s |
| initramfs | 12.0s | 6.0s | -6s |
| Systemd (network) | 32.0s | 8.0s | -24s |
| Systemd (其他服务) | 22.0s | 10.0s | -12s |
| 总计 | 74.7s | 29.5s | -60% |
5.2 关键优化点总结
| 优化措施 | 节省时间 |
|---|
| 禁用 cloud-init/snapd | 30s |
| 减少网络等待 | 20s |
| 服务并行化 | 5s |
| 内核模块精简 | 2s |
| initramfs 精简 | 6s |
| socket activation | 2s |
六、持续监控
6.1 CI 中集成启动时间测试
1
2
3
4
5
6
7
8
9
10
11
12
13
| #!/bin/bash
# boot-time-check.sh
MAX_BOOT_TIME=35
BOOT_TIME=$(systemd-analyze | grep "userspace" | awk '{print $4}' | sed 's/s//')
if (( $(echo "$BOOT_TIME > $MAX_BOOT_TIME" | bc -l) )); then
echo "启动时间回归!当前: ${BOOT_TIME}s, 阈值: ${MAX_BOOT_TIME}s"
exit 1
fi
echo "启动时间正常: ${BOOT_TIME}s"
|
6.2 新增服务检查清单
每次添加新服务时,必须检查:
- 是否可以 socket activation?
- 是否真的需要 network-online?
- 是否可以并行启动?
- 是否设置了合理的资源限制?
七、总结
| 技术点 | 核心命令/配置 |
|---|
| 启动分析 | systemd-analyze blame/plot |
| 依赖链分析 | systemd-analyze critical-chain |
| 禁用服务 | systemctl disable |
| 网络优化 | DHCP Timeout + network.target |
| 并行化 | 检查并移除不必要的 After= |
| 内核精简 | modprobe blacklist |
| initramfs | MODULES=dep |
核心原则:不要假设,要测量。每一步优化都用数据说话。
相关文章