如何利用dmesg优化启动速度
小樊
35
2025-12-09 23:30:39
用 dmesg 定位瓶颈并落地优化
一、快速定位耗时阶段
- 启用内核时间信息:在内核配置中打开 CONFIG_PRINTK_TIME=y,或在启动命令行加入 printk.time=1,让每条内核日志前带有时间戳,便于计算阶段耗时。
- 打开 initcall 级别耗时:在命令行加入 initcall_debug=1,启动时会打印每个 initcall 的开始与结束时间、耗时。
- 生成火焰图:启动完成后执行
dmesg | perl $(KERNEL_DIR)/scripts/bootgraph.pl > boot.svg
用浏览器打开 boot.svg,一眼看出哪些 initcall/阶段最耗时。
- 串口时间戳辅助:若需对齐 U-Boot 与内核时序,可用 grabserial 抓取串口日志并自动加时间戳,例如
grabserial -d /dev/ttyUSB0 -t -m “Starting kernel”
以上步骤能快速把“时间黑洞”定位到具体驱动或初始化阶段,为后续优化提供明确靶心。
二、常见高耗时根因与 dmesg 特征
- 驱动初始化过慢或同步等待:initcall 火焰图中长条块;dmesg 中可见某驱动 probe 前后长时间无日志。
- 压缩/解压内核耗时:启动早期日志间隔较大;可通过更换 内核压缩方式(如 LZO/GZIP)并实测对比启动时间。
- loops_per_jiffy 校准:若未预设,会看到 “Calibrating delay loop” 计算过程;可在 cmdline 加入 lpj=240000(示例值)跳过校准。
- PCI/USB 枚举与 BIOS 交接问题:dmesg 出现 “EHCI/XHCI BIOS handoff failed” 等字样,往往伴随数百毫秒甚至秒级等待。
- 控制台输出过多:大量 printk 会拖慢控制台刷新;可结合 quiet 降低控制台输出,同时保留时间戳用于分析。
- initramfs/initrd 体积大、初始化脚本串行:dmesg 中 initramfs 阶段时间长;可减少不必要的 ko、优化 init 脚本并行度。
以上现象在 dmesg 与火焰图中均有明显特征,按图索骥即可落地优化。
三、从 dmesg 到优化的闭环动作
- 裁剪与按需编译:关闭产品不需要的功能与驱动(设为 module 或移除),既减小镜像又减少初始化项;必要时做内核裁剪。
- 调整内核压缩与加载方式:对比 LZO/GZIP 等压缩比与解压耗时;若由 U-Boot 解压,可调整加载地址避免自解压时的内存搬移。
- 预设 lpj:在 dmesg 确认 “Calibrating delay loop(skipped)” 或拿到 lpj 数值后,写入 cmdline(如 lpj=240000)以省去校准。
- 并行化与延后加载:对非关键驱动改为 module 并在用户态按需加载;将耗时驱动从内核内置改为模块,必要时延后到系统服务启动后再加载。
- 优化 initramfs/initrd:精简不必要的 ko 与脚本,减少 init 阶段的串行等待;必要时调整 initramfs 生成策略(如 modules=dep)。
- 降低控制台输出:在保留 printk.time=1 的前提下使用 quiet 减少控制台刷屏时间,避免影响实测。
- U-Boot 配合:关闭不必要的功能与输出、缩短或去除 bootdelay,减少引导阶段等待。
这些动作均源自 dmesg/initcall_debug 的指认结果,按“影响最大、代价最小”的顺序实施,收益通常立竿见影。
四、实操命令清单与判读要点
- 采集与可视化
- dmesg -T > boot.log(人类可读时间戳)
- dmesg | perl scripts/bootgraph.pl > boot.svg(火焰图)
- 串口对齐:grabserial -d /dev/ttyUSB0 -t -m “Starting kernel”
- 快速筛选
- dmesg | grep -i “error|fail|warn”
- dmesg | grep -E “usb|pci|ehci|xhci|mmc|sd”
- 判读要点
- 关注火焰图中“长条”initcall;
- 查找 “Calibrating delay loop” 是否可 lpj 预设;
- 留意 “BIOS handoff failed” 等 PCI/USB 交接提示;
- 对比开启/关闭 quiet 时的控制台输出量变化。
以上命令覆盖从采集、筛选到判读的关键环节,配合火焰图即可高效闭环优化。