目录

定制 Linux 发行版:从 Yocto 构建到 Init 启动优化 60%

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
          ...

发现问题

  1. network-online.target 等待网络就绪 32 秒
  2. cloud-init 是云服务器用的,我们不需要
  3. snapd 也不需要
  4. 很多服务是串行启动的

2.3 依赖关系可视化

1
2
# 生成启动时序图
systemd-analyze plot > boot-timeline.svg

../images/230615-boot-timeline.png

从图中可以看到:

  • 很多服务在等待 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  # 只包含依赖的模块,而非全部

重新生成:

1
update-initramfs -u

效果: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.5s5.5s-3s
initramfs12.0s6.0s-6s
Systemd (network)32.0s8.0s-24s
Systemd (其他服务)22.0s10.0s-12s
总计74.7s29.5s-60%

5.2 关键优化点总结

优化措施节省时间
禁用 cloud-init/snapd30s
减少网络等待20s
服务并行化5s
内核模块精简2s
initramfs 精简6s
socket activation2s

六、持续监控

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
initramfsMODULES=dep

核心原则:不要假设,要测量。每一步优化都用数据说话。


相关文章