2. 内核启动时间测量方法
精确测量内核各阶段的耗时是启动优化的第一步。嵌入式Linux内核提供了从简单到高级的多层次测量手段,包括内核编译选项、内核日志时间戳、专用调试工具以及图形化分析脚本。本章逐一介绍这些工具的原理与使用方法。
2.1 printk时间戳:最基础的测量手段
printk时间戳是内核日志自带的时间标记,通过内核配置选项 CONFIG_PRINTK_TIME 启用。启用后,每条printk消息前会附加一个以毫秒为单位的系统启动时间戳,精确度取决于硬件定时器分辨率(通常为1ms或更高)。
CONFIG_PRINTK_TIME=y,或在启动参数中添加 printk.time=1。
启用后,dmesg输出示例:
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000123] Initializing cgroup subsys cpuset
[ 0.000456] Linux version 5.10.120 ...
[ 1.234567] Mounting root filesystem
[ 2.345678] Freeing unused kernel memory: 1024K
[ 3.456789] Run /sbin/init as init process
通过对比相邻时间戳的差值,可以快速定位耗时异常的阶段。例如,从 [0.000000] 到 [1.234567] 之间的1.23秒即为内核初始化到挂载根文件系统的时间。
printk(KERN_INFO "[DBG] func_name start/end\n");,配合时间戳实现微秒级阶段测量。
2.2 initcall_debug:逐级展开内核初始化调用链
内核初始化过程由大量 initcall 组成,每个驱动或子系统通过 module_init()、arch_initcall() 等宏注册。启用 initcall_debug 后,内核会打印每个initcall的起始时间、结束时间以及耗时。
启用方法: 在bootloader的kernel命令行中添加 initcall_debug 参数。
输出示例:
[ 0.123456] initcall init_some_driver+0x0/0x100 returned 0 after 12345 usecs
[ 0.234567] initcall init_another_driver+0x0/0x200 returned 0 after 5678 usecs
通过解析这些日志,可以生成每个驱动模块的耗时列表,快速识别出哪些驱动占用了过多的初始化时间。
| 工具/方法 | 精度 | 是否需要重新编译内核 | 适用场景 |
|---|---|---|---|
| printk时间戳 | 毫秒级 | 否(可通过启动参数启用) | 快速概览各阶段耗时 |
| initcall_debug | 微秒级 | 否(仅需启动参数) | 定位单个驱动/子系统的初始化延迟 |
| bootgraph.pl | 毫秒级(图形化) | 是(需开启CONFIG_PRINTK_TIME) | 生成可视化启动时序图 |
| bootchart | 毫秒级(进程级) | 是(需开启CONFIG_BOOTCHART) | 分析用户空间进程启动顺序与CPU负载 |
initcall_debug 会增加内核日志输出量,可能轻微影响启动时间(通常小于1%),但在调试阶段可接受。生产环境建议关闭。
2.3 bootgraph.pl:将内核启动日志可视化为时序图
bootgraph.pl 是内核源码 scripts/bootgraph.pl 目录下的Perl脚本,它解析启用了 initcall_debug 和 CONFIG_PRINTK_TIME 的dmesg日志,生成SVG格式的启动时序图。图中以时间为横轴,每个initcall为一个色块,色块长度代表耗时,颜色代表调用层级。
使用方法:
- 在目标板上启动内核,添加
initcall_debug参数。 - 系统启动后,执行
dmesg > boot.log保存日志。 - 在主机上运行:
perl scripts/bootgraph.pl boot.log > bootgraph.svg
生成的SVG图可以直接在浏览器中打开,每个initcall的耗时一目了然。下图是一个典型的bootgraph输出结构示意:
2.4 bootchart:用户空间启动过程的全景分析
bootchart 是一个用户空间工具,用于分析从内核启动完成到用户空间服务就绪的整个过程。它通过内核的 CONFIG_BOOTCHART 选项或用户空间的bootchartd服务,记录每个进程的启动时间、CPU占用、I/O等待等信息,最终生成SVG格式的图表。
启用方法:
- 内核配置:启用
CONFIG_BOOTCHART=y。 - 在启动参数中添加
init=/sbin/bootchartd或使用用户空间的bootchart包。 - 系统启动后,bootchartd会自动收集数据并生成
/var/log/bootchart.tgz。 - 在主机上使用
pybootchartgui或bootchart命令解析生成SVG。
bootchart输出图包含以下关键信息:
- CPU使用率曲线: 显示整个启动过程中CPU的忙碌程度。
- 磁盘I/O活动: 显示读写请求的分布。
- 进程启动时间线: 每个进程以横条表示,从进程创建到退出,横条长度代表进程存活时间。
- 进程依赖关系: 通过箭头表示父子进程关系。
| 工具 | 数据来源 | 输出格式 | 主要分析维度 |
|---|---|---|---|
| bootgraph.pl | 内核dmesg日志(initcall_debug) | SVG | 内核initcall耗时、调用层级 |
| bootchart | /proc 文件系统 + 内核事件 | SVG / PNG | 用户空间进程启动顺序、CPU/I/O负载 |
下图展示了bootchart输出中进程时间线的典型结构:
2.5 工具选择与组合使用策略
在实际项目中,建议按照以下流程组合使用上述工具:
- 第一阶段:printk时间戳快速摸底 —— 在开发板上启用printk时间戳,记录dmesg,人工观察各阶段的大致耗时,确定是否存在明显瓶颈(例如根文件系统挂载耗时超过500ms)。
- 第二阶段:initcall_debug精确定位内核模块 —— 启用initcall_debug,使用bootgraph.pl生成SVG图,找出耗时最长的前5个initcall,锁定需要优化的驱动或子系统。
- 第三阶段:bootchart分析用户空间 —— 在用户空间启动阶段使用bootchart,分析进程启动顺序是否合理,是否存在串行依赖导致的等待,以及CPU/I/O资源是否被某个进程独占。
2.6 常见问题与避坑指南
- 时间戳精度不足: 某些低端ARM平台可能没有高精度定时器,printk时间戳精度仅为10ms级别。此时建议使用
initcall_debug的微秒级计时,或外接逻辑分析仪测量GPIO电平变化。 - 日志丢失: 启用
initcall_debug后,如果内核日志缓冲区太小,早期日志可能被覆盖。解决方法:增大log_buf_len内核参数(例如log_buf_len=4M)。 - bootchart数据不完整: 如果bootchartd启动过早,可能无法捕获init进程的完整信息。建议在启动参数中指定
init=/sbin/bootchartd确保bootchartd作为第一个用户空间进程运行。 - SVG图文字重叠: 当系统有大量initcall时,bootgraph.pl生成的SVG可能文字重叠。可通过
--output-width参数增加图表宽度(例如--output-width 2000)。