系统启动时间优化
1. 概述
1.1 功能简介
这篇教程讲的是 shimetapi RK3588 开发板(C-3588LQ V1.0 核心板 + SHIMETAPI-M5-R2A V1.0 底板)在 Linux 6.1 系统下如何把开机时间压下来。优化的范围覆盖从 Boot ROM 到用户空间应用就绪的整条链路,用到的手段包括 U-Boot 裁剪(U-Boot 是开机后最先运行的引导程序,负责把内核从 eMMC 读进内存并启动它)、内核配置精简、rootfs 缩减(rootfs 就是根文件系统,挂载在 / 上的那套目录)、systemd 服务管理(systemd 是 Linux 的 init 进程兼服务管理器,开机后由它拉起各项服务),以及应用预加载。照着做完,启动时间可以从默认的 10-15 秒缩短到 5 秒以内。
RK3588 采用 RockChip 八核处理器(4×Cortex-A76 + 4×Cortex-A55),主频最高 2.4GHz,板载 eMMC 5.1 存储(64GB 标配),通过 sdhci@fe2e0000 控制器连接。
1.2 应用场景
不同产品对「多快算开机完成」的定义不一样,优化重点也随之不同:
| 场景 | 启动时间要求 | 优化重点 |
|---|---|---|
| 智慧零售终端 | < 5 秒到 GUI 就绪 | 内核裁剪 + systemd 服务精简 + 应用预加载 |
| 工业控制 HMI | < 3 秒到应用界面 | 最小化 rootfs + 预编译应用 + readahead |
| 安防监控设备 | < 8 秒到视频流就绪 | 内核模块延迟加载 + NPU 预初始化 |
| 数字标牌 | < 10 秒到内容展示 | 服务裁剪 + 文件系统压缩优化 |
| 车载信息娱乐 | < 5 秒到仪表盘显示 | 全链路优化 + 并行初始化 |
1.3 工作原理
动手前先弄清开机要经过哪几步,再决定砍哪一段。RK3588 的完整启动流程如下:
Boot ROM (~0.1s) → SPL (~0.5s) → U-Boot (~1-2s) → Kernel (~2-3s) → Rootfs (~1s) → App (~1s)各阶段的位置和耗时分布:
| 阶段 | 存储位置 | 默认耗时 | 优化潜力 |
|---|---|---|---|
| Boot ROM | 芯片内部 | ~0.1s | 不可优化 |
| SPL | SRAM → eMMC | ~0.5s | 轻度优化 |
| U-Boot | eMMC | ~1-2s | 显著优化 |
| Kernel | eMMC | ~2-3s | 显著优化 |
| Rootfs | eMMC | ~0.5-1s | 中度优化 |
| 用户空间 | eMMC | ~3-5s | 显著优化 |
从这张表看,Boot ROM 和 SPL 基本动不了,值得花时间的是 U-Boot、Kernel 和用户空间三段。存储方面,eMMC 5.1 通过 sdhci@fe2e0000 控制器连接 RK3588,理论带宽可达 400MB/s(HS400 模式),实际顺序读取约 250-300MB/s,只要各阶段配置得当,读文件通常不是瓶颈。
2. 硬件准备
2.1 硬件清单
| 组件 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|
| 主控板 | C-3588LQ V1.0 核心板 + SHIMETAPI-M5-R2A V1.0 底板 | 1 | 已烧录 Linux 6.1 系统 |
| 调试串口线 | USB 转 TTL(3.3V) | 1 | 波特率 1500000 |
| 电源适配器 | 4V/5A DC(误差 ±5%) | 1 | 必须使用标配电源 |
| 电脑 | Windows/Linux/Mac | 1 | 安装串口终端和交叉编译工具链 |
2.2 硬件确认
开工前先确认板子上的 eMMC 分区、内存和系统版本都在预期状态。逐条执行下面的命令,对照注释里的预期结果:
# 确认 eMMC 设备及容量
lsblk
sudo fdisk -l /dev/mmcblk0
# 预期分区:mmcblk0p1(loader), mmcblk0p2(parameter), mmcblk0p3(boot), mmcblk0p4(rootfs), mmcblk0p5(userdata)
# 确认 DDR 内存和 CPU 信息
free -h
cat /proc/cpuinfo | grep -E "model name|processor" | head -8
# 确认内核版本
uname -a
# 预期:Linux 6.1.x aarch64 GNU/Linux再记一个救命操作:BOOT_KEY 按键连接至 SARADC_VIN1_KEY/RECOVERY(底板原理图第 5 页),按住它上电,板子会进入 Maskrom 模式(Rockchip 的底层强制烧录模式),万一系统改坏了也能从这里救回来。
3. 软件准备
3.1 内核配置项
要分析启动慢在哪,内核得先把启动过程记录下来。编译内核前打开下面这些配置项:前四项用于启动时间测量,其中 ftrace 是内核自带的函数级跟踪工具,能记录每个函数何时被调用;后三项是可选的性能分析开关。
# 启动时间测量(必选)
CONFIG_PRINTK_TIME=y
CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_BOOT_TIME_TRACER=y
# 性能分析(可选)
CONFIG_KPROBES=y
CONFIG_KPROBE_EVENTS=y
CONFIG_SCHEDSTATS=y3.2 工具安装
板子上装好测量工具,主机上准备好交叉编译环境(在 x86 电脑上编译 ARM64 内核用):
sudo apt-get update
sudo apt-get install -y systemd-analyze bootchart libcap2-bin build-essential bc bison flex libssl-dev libelf-dev dwarves python3
# 验证 systemd 版本(需 249+)
systemctl --version
# 交叉编译工具链(主机端)
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64装完后看一下 systemctl --version 的输出,版本号在 249 以上即可。
4. 操作步骤
4.1 启动时间测量
动手改之前先量基线,不然改完也不知道有没有效果。systemd-analyze 是 systemd 自带的启动耗时统计工具,先用它把整体数据和各服务排名拿到手:
# 查看整体启动时间
systemd-analyze
# 输出(本机):
# Startup finished in 3.876s (kernel) + 2.656s (userspace) = 6.532s
# graphical.target reached after 2.634s in userspace.
# 各服务耗时排名
systemd-analyze blame | head -8
# 输出:
# 1.312s apt-daily-upgrade.service
# 1.019s user-runtime-dir@1000.service
# 977ms blueman-mechanism.service ← 蓝牙管理,关键路径第一名
# 554ms rknn3.service
# 478ms wifibt-init.service
# 431ms dev-mmcblk0p6.device
# 378ms user@1000.service
# 325ms udisks2.service
# 关键路径(依赖链)
systemd-analyze critical-chain
# 输出(节选):
# graphical.target @2.634s
# └─multi-user.target @2.633s
# └─blueman-mechanism.service @1.654s +977ms ← 关键路径上的大头
# └─basic.target @1.647s ...
# 内核启动时间线(ftrace;注意 debugfs 需 root,普通用户读 tracing 目录报权限错误)
sudo su -c 'cd /sys/kernel/debug/tracing && echo function_graph > current_tracer && echo 1 > tracing_on'
# 更方便的方式是用 trace-cmd(可用):
sudo trace-cmd record -p function_graph -d 1 ls /
sudo trace-cmd report | head -20上面的输出取自本机。从 blame 排名看,apt-daily-upgrade.service 和 blueman-mechanism.service 最耗时;再看 critical-chain 的依赖链,后者正好卡在通往 graphical.target 的关键路径上,优先处理它收益最大。想深入内核内部,就用 ftrace 的 function_graph 模式,trace-cmd 是它的封装前端,比手动操作 debugfs 省事。
4.2 U-Boot 优化
U-Boot 阶段的开销主要有两块:bootdelay(开机后等待按键中断的倒计时秒数)和一堆用不到的驱动。在 defconfig(configs/rk3588_shimetapi_defconfig)里做三处修改:
# 在 defconfig 中修改(configs/rk3588_shimetapi_defconfig)
# 1. 减少自动启动延迟
CONFIG_BOOTDELAY=1
# 2. 禁用不需要的功能
# CONFIG_CMD_USB is not set
# CONFIG_CMD_NET is not set
# CONFIG_CMD_NFS is not set
# CONFIG_CMD_PING is not set
# CONFIG_USB is not set
# CONFIG_NET is not set
# 3. 启用 HS400 增强模式(DTS 中配置)
# &sdhci {
# max-frequency = <200000000>;
# uhs200-speed;
# };改完重新编译并烧录 U-Boot。确认方法:重启后串口里看到自动启动倒计时变成 1 秒,说明第一步已生效。
4.3 内核优化
内核阶段的思路是少做无用功:不加载用不到的文件系统、网络协议和调试功能。但有一条底线,eMMC 启动链路的驱动必须保留(代码里标了「必须保留的模块」的那几项),砍错了系统就起不来了。
# 1. 裁剪内核配置(make menuconfig)
# 禁用不需要的文件系统
# CONFIG_BTRFS_FS is not set
# CONFIG_XFS_FS is not set
# CONFIG_F2FS_FS is not set
# 禁用不需要的网络协议
# CONFIG_NETFILTER is not set
# CONFIG_BRIDGE is not set
# 禁用调试功能(生产环境)
# CONFIG_DEBUG_INFO is not set
# CONFIG_KGDB is not set
# CONFIG_MAGIC_SYSRQ is not set
# 必须保留的模块
# CONFIG_MMC_SDHCI_OF_DWCMSHC=y # eMMC 控制器驱动(sdhci@fe2e0000)
# CONFIG_MMC=y # MMC 核心支持
# CONFIG_MMC_BLOCK=y # MMC 块设备驱动
# CONFIG_DEVTMPFS=y # 设备文件系统
# CONFIG_DEVTMPFS_MOUNT=y # 自动挂载 devtmpfs
# 2. 使用 LZ4 压缩(解压速度比 gzip 快 3 倍)
sed -i 's/CONFIG_KERNEL_GZIP=y/# CONFIG_KERNEL_GZIP is not set/' .config
sed -i 's/# CONFIG_KERNEL_LZ4 is not set/CONFIG_KERNEL_LZ4=y/' .config
# 重新编译
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image
# 3. 精简设备树(rk3588-shimetapi.dts)
# 禁用不需要的外设节点:
//&hdmi0 { status = "disabled"; };
//&hdmi1 { status = "disabled"; };
//&usb_host0_ehci { status = "disabled"; };
//&sata0 { status = "disabled"; };
//&pcie2x1 { status = "disabled"; };两点说明。压缩方式从 gzip 换成 LZ4 后,内核自解压速度更快,启动自然提前;设备树里注释掉的 HDMI、USB、SATA、PCIe 节点,用不到的可以逐个禁用,每禁一个就少初始化一个外设,但先确认你的应用真的不用它。
4.4 rootfs 优化
rootfs 越大越碎,冷启动时读文件就越慢。squashfs 是一种只读压缩文件系统,把整个 rootfs 压成一个镜像、读时按需解压,常用于追求启动速度的嵌入式设备:
# 1. 使用 squashfs(只读压缩文件系统)
sudo mkfs.squashfs /path/to/rootfs rootfs.squashfs -comp lz4 -b 1M -no-xattrs -all-root
# 修改 U-Boot 启动参数
setenv bootargs "root=/dev/mmcblk0p4 rootfstype=squashfs rootwait ro console=ttyS2,1500000n8"
# 2. systemd-readahead 已废弃(systemd v216 起移除,本机 systemd 252
# Unit systemd-readahead-replay.service could not be found)
# 现代替代:内核默认 page cache 预读 + ureadahead(Ubuntu 系)或自行 preload
# 3. 文件系统挂载优化(/etc/fstab)
# /dev/mmcblk0p6 / ext4 defaults,noatime,nodiratime,commit=600 0 1
sudo mount -o remount,noatime /
# (remount 前后 findmnt 对比):
# rw,relatime → rw,noatime ← 立即生效
# 注意:本机根分区是 mmcblk0p6(不是 p4)两处提醒。其一,systemd-readahead 已经废弃(systemd v216 起移除,本机也确认服务不存在),预读交给内核默认的 page cache 预读,或者用 ureadahead。其二,挂载选项加 noatime 是为了让读文件不再顺手更新访问时间戳,省掉一批小写入;remount 之后用 findmnt 对比,看到 rw,relatime 变成 rw,noatime 就是生效了。另外分区别搞混:本机根分区是 mmcblk0p6,不是 p4。
4.5 服务裁剪
系统起来后 systemd 会按依赖拉起一批服务,服务越多启动越慢。裁剪时逐条确认再禁用,别直接照抄别人的清单,本机就发现清单里大部分服务根本不存在:
# 禁用不需要的服务(⚠ 本机:清单里大部分服务不存在!逐条 is-enabled 验证:
# bluetooth=enabled(唯一真在的);
# NetworkManager-wait-online=masked、man-db/apt-daily/apt-daily-upgrade=static(由 timer 触发);
# Plymouth-quit-wait/avahi/cups/ModemManager/accounts-daemon/whoopsie/unattended-upgrades/fwupd 均未安装)
sudo systemctl disable bluetooth.service # 存在且 enabled(无蓝牙需求时)
# apt 系定时任务按需屏蔽 timer:
sudo systemctl mask apt-daily.timer apt-daily-upgrade.timer
# 查看优化效果
systemctl list-unit-files --state=enabled --type=service | grep -c "\.service"
# 本机已启用服务数: 36(出厂已较精简)本机的情况说明出厂固件已经比较精简(36 个已启用服务),真正能动的就是 bluetooth 和两个 apt 定时任务。确认优化效果的方法:再跑一遍上面的统计命令,数字比之前小即生效。
4.6 应用预加载
预加载的思路是趁系统刚起来、负载还低的时候,把主程序和它依赖的共享库提前读进内核的 page cache(文件缓存),等真正的应用服务启动时就不必再等 eMMC。做法是建一个 systemd 服务,开机后自动执行预读脚本:
# 创建预加载服务
sudo tee /etc/systemd/system/app-preload.service << 'EOF'
[Unit]
Description=Application Preload
After=multi-user.target
DefaultDependencies=no
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/app-preload.sh
ExecStartPost=/usr/bin/systemctl start my-application.service
[Install]
WantedBy=multi-user.target
EOF
# 创建预加载脚本
sudo tee /usr/local/bin/app-preload.sh << 'SCRIPT'
#!/bin/bash
# 预加载主程序和共享库到内存缓存
cat /usr/local/bin/my-application > /dev/null 2>&1
LIBS=$(ldd /usr/local/bin/my-application 2>/dev/null | grep -o '/[^ ]*\.so[^ ]*' | sort -u)
for lib in $LIBS; do cat "$lib" > /dev/null 2>&1; done
cat /etc/my-app/config.json > /dev/null 2>&1
SCRIPT
sudo chmod +x /usr/local/bin/app-preload.sh
sudo systemctl enable app-preload.serviceenable 之后重启一次,用 systemctl status app-preload.service 确认它处于 active 状态即配置成功。
5. 代码详解
5.1 boot 流程分析
把启动过程拆开看,每一段各司其职。Boot ROM 固化在芯片内部,不可优化:它检测启动模式引脚(SARADC),初始化 SRAM,从 eMMC 加载 SPL 到 SRAM,然后跳转执行。SPL 是 U-Boot 的第一阶段小程序,在 SRAM 中运行,负责初始化 DDR(LPDDR4/4X)、基本时钟树和 eMMC 控制器(sdhci@fe2e0000),再把 U-Boot 本体加载到 DDR。U-Boot 接手后完整初始化外设(串口、存储设备),加载内核(Image)和设备树(DTB),设置 bootargs,最后跳转内核;这一步的优化重点是减少 bootdelay 和禁用不必要的驱动。进入内核阶段后,RK3588 的 8 核 CPU 可以利用并行初始化,配合 CONFIG_PRINTK_TIME=y,dmesg 里每条消息都带时间戳,照着时间戳就能定位耗时最长的初始化函数:
# 内核启动时间线示例(dmesg)
[ 0.000000] Booting Linux on physical CPU 0x0000000000
[ 0.089342] Calibrating delay loop...
[ 0.108901] Memory: 7864320K/8388608K available
[ 1.234567] VFS: Mounted root filesystem on device 179:4.
[ 2.345678] Run /sbin/init as init process5.2 各阶段优化汇总
第 4 节的各项做法汇总如下,方便对照预估收益和风险。风险标「高」的做法(比如 Falcon 模式,即跳过完整 U-Boot 直接引导内核)不建议新手尝试:
| 阶段 | 优化方法 | 预计节省 | 风险 |
|---|---|---|---|
| U-Boot | bootdelay 3→1 秒 | ~2s | 低 |
| U-Boot | 禁用 USB/网络初始化 | ~0.5-1s | 中 |
| U-Boot | Falcon 模式(跳过完整 U-Boot) | ~1-2s | 高 |
| Kernel | LZ4 压缩替代 gzip | ~0.3s | 低 |
| Kernel | 裁剪不需要的驱动 | ~0.5-1s | 中 |
| Rootfs | squashfs 压缩 | ~0.5-1s | 中 |
| Rootfs | readahead 预读 | ~0.3-0.5s | 低 |
| 服务 | 禁用非关键 systemd 服务 | ~2-3s | 低 |
| 应用 | 预加载关键库 | ~0.5-1s | 低 |
6. 常见问题
问题1:优化后系统启动失败,停留在 U-Boot 命令行
现象是修改 U-Boot 配置后系统无法启动,停在 => 提示符。原因是禁用了必要的存储控制器驱动。先手动指定启动设备把系统拉起来,再检查配置,确保下面三项没有被禁用:
# 手动指定启动设备
setenv bootcmd "mmc dev 0; mmc read 0x0 0x40000 0x8000; go 0x0"
boot
# 确保以下配置未被禁用
# CONFIG_MMC=y
# CONFIG_MMC_DWC=y
# CONFIG_MMC_SDHCI=y问题2:systemd-analyze 显示启动时间无明显改善
禁用服务后启动时间变化不大,多半因为被禁的服务不在关键路径上,或者瓶颈在别处。重新分析一遍:
systemd-analyze critical-chain --no-pager # 重新分析关键路径
systemd-analyze blame | head -5 # 查看实际阻塞的服务
# 对比第一次启动(冷启动)和第二次启动(预读启动)的时间差异问题3:使用 squashfs 后报错 "read-only filesystem"
切到 squashfs 后系统提示文件系统只读,这是预期行为:squashfs 本身只读,需要配合 overlayfs(把只读层和可写层叠加成一个视图的文件系统)实现可写层:
# 使用可写 userdata 分区作为 overlayfs upper 层
setenv bootargs "... init=/sbin/init overlay_root=/dev/mmcblk0p5"
# 或在 /etc/fstab 中配置
# overlay / overlay overlay lowerdir=/squashfs-root,upperdir=/upper,workdir=/work 0 0问题4:eMMC 读写速度不达预期
优化后启动速度仍不达标,常见原因是 eMMC 没工作在 HS400 模式。先查工作模式,再读写速度:
# 检查 eMMC 工作模式
dmesg | grep -i "mmc"
# 应看到:mmc0: new HS200/HS400 MMC card
# 测试读写速度
sudo dd if=/dev/zero of=/tmp/test bs=1M count=100 oflag=direct
sudo dd if=/tmp/test of=/dev/null bs=1M count=100 iflag=direct
# 预期:读 200-300MB/s,写 100-200MB/s问题5:ftrace 报错 "function_graph not supported"
用 ftrace 追踪启动过程时提示不支持,说明内核没启用 CONFIG_FUNCTION_TRACER。先检查当前配置,没开就重新编译:
# 检查当前内核配置
zcat /proc/config.gz | grep FUNCTION_TRACER
# 若未启用,重新编译内核
make ARCH=arm64 menuconfig
# Kernel hacking → Tracing → 启用所有子选项
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
sudo cp arch/arm64/boot/Image /boot/Image问题6:优化后 GPU 驱动加载失败
禁用设备树节点后 Mali-G610 MP4 无法初始化,多半是裁剪时误伤了 GPU 相关节点或时钟资源。按顺序检查节点状态和驱动日志:
# 检查 GPU 设备树节点状态
cat /proc/device-tree/gpu/status
# 应输出 "okay"
# 检查驱动加载
dmesg | grep -i "mali"
# 确保 GPU 节点未被禁用:
//&gpu {
// status = "okay";
// mali@fb000000 { status = "okay"; };
//};7. 相关文档
| 文档 | 路径 | 说明 |
|---|---|---|
| 系统启动与基础操作 | ../01quick-start-guide/01系统启动与基础操作.md | 基础启动流程和串口登录 |
| 网络配置与 SSH | ../01quick-start-guide/02网络配置与SSH.md | 网络服务配置 |
| 文件系统与存储 | ../04application-development/06文件系统与存储.md | 存储分区和文件系统管理 |
