当前位置:首页>Linux>Linux 嵌入式产品启动速度优化

Linux 嵌入式产品启动速度优化

  • 2026-10-08 14:38:21
Linux 嵌入式产品启动速度优化

Linux驱动企业级一对一辅导【就业为止~】

感兴趣Linux底层驱动可以私信我:

文章目录

  •  一、观测方法
    • 1.1、printk time
    • 1.2、initcall_debug
    • 1.3、bootgraph
    • 1.4、bootchart
    • 1.5、gpio + 示波器
    • 1.6、grabserial
  • 二、内核优化方法
    • 2.1、kernel 压缩方式
    • 2.2、加载位置
    • 2.3、内核裁剪
    • 2.4、预设置 lpj 数值
    • 2.5、initcall 优化
    • 2.6、内核 initcall module 并行
    • 2.7、减少 pty/tty 个数
    • 2.8、内核 module

沉淀、分享、成长,让自己和他人都能有所收获!😄

📢启动速度是嵌入式产品一个重要的性能指标,更快的启动速度会让客户有更好的使用体验,在某些方面还会节省能耗,因为可以直接关机而不需要休眠。
启动速度优化可提升产品的竞争力。对于某些系统来说,启动速度是硬性要求。

一、观测方法


优化之前,需要充分了解当前系统的启动时序情况。

1.1、printk time


打开 kernel 配置,使能如下选项

kernel hacking --->
[*] Show timing information on printks

将会在内核的 log 前加入时间戳。

注:此方法主要用来测量内核启动过程中各个阶段的耗时。

1.2、initcall_debug


在 kernel 的 cmdline 中加入参数,修改文件:/<board>/configs/env-<kernelversion>.cfg

initcall_debug=1
setargs_nand=setenv bootargs console=${console} earlyprintk=${earlyprintk} root=${nand_root} initcall_debug=${initcall_debug} init=${init}

开启之后,启动中会打印每个 initcall 函数调用及其耗时。

注:此方法主要用来测量内核 initcall 的耗时。

1.3、bootgraph


在内核源码中自带了一个工具 (scripts/bootgraph.pl) 可用于分析启动时间。

  • kernel
     编译时需要包含 CONFIG_PRINTK_TIME 选项
  • 在 kernel cmdline 加上"initcall_debug=1"
  • 在系统启动完毕后执行"dmesg | perl $(Kernel_DIR)/scripts/bootgraph.pl > output.svg"
  • 使用 SVG 浏览器(比如 Inkscape,Gimp,Firefox 等)来查看输出文件 output.svg

注:此方法主要用来测量内核启动过程中各个阶段的耗时。

1.4、bootchart


bootchart 是一个用于 linux 启动过程性能分析的开源软件工具,在系统启动过程自动收集 CPU 占用率、进程等信息,并以图形方式显示分析结果,可用作指导优化系统启动过程。

  • 修改 kernel cmdline。修改文件 <board>/configs/env-<kernel-version>.cfg,将其中的 init 修改为"init=/sbin/bootchartd"。
  • 收集信息。bootchartd 会从/proc/stat,/proc/diskstat,/proc/[pid]/stat 中采集信息,经过处理后保存为 bootchart.tgz 文件。
  • 转换图片。在 PC 上通过 pybootchartgui.py 工具将 bootchart.tgz 转换为 bootchart.png,方便分析。

注:此方法主要用来测量挂载文件系统到主应用程序启动过程中的耗时。

1.5、gpio + 示波器


在适当的地方加入操作 gpio 的代码,通过示波器抓取波形得到各阶段耗时。

注:此方法可用来测量整个启动中各阶段的耗时。

1.6、grabserial


Grabserial 是 Tim Bird 用 python 写的一个抓取串口的工具,这个工具能够为收到的每一行信息添加上时间戳。可从如下路径下载使用:https://github.com/tbird20d/grabserial

介绍文档:http://elinux.org/Grabserial

常见的用法:

sudo grabserial -v -S -d /dev/ttyUSB0 -e 30-t

如果要在某个字符串重置时间戳,可以使用 -m 参数

sudo grabserial -v -S -d /dev/ttyUSB0 -e 30-t -m "Starting kernel"
  • -v 显示参数等信息
  • -s 跳过对串口的检查
  • -d 指定串口,如上述为指定 /dev/ttyUSB0 为操作的串口
  • -e 参数指定时间,如上述命令表示抓取 30s 的串口记录
  • -t 表示加上时间戳
  • -m 匹配到指定字符串就重置时间戳的时间,也就是从 0 开始

二、内核优化方法


  • 优化没有止境。需要根据目标来选择优化方法,综合考虑优化效果与优化难度。
  • 优化需要具有针对性。由于各方案 CPU 个数及频率、flash 类型及大小、kernel/rootfs 压缩类型与尺寸、所需功能、主应用等的不同,需要针对性的进行优化。

通常来说,内核启动耗时较多,需要更深入的优化。

2.1、kernel 压缩方式


比较不同压缩方式的启动时间和 flash 占用情况,选择一种符合实际情况的。

2.2、加载位置


内核镜像可以由 kernel 自解压,也有 uboot 进行解压的情况。
对于 kernel 自解压的情况,如果压缩过的 kernel 与解压后的 kernel 地址冲突,则会先把自己复制到安全的地方,然后再解压,防止自我覆盖。这就需要耗费复制的时间。
比如对于运行地址为 0x80008000 的内核来说,bootloader 可以将其加载到 0x81008000,当然其他位置也可以。

2.3、内核裁剪


裁剪内核,带来的加速是两个方面的。一是体积变小,加载解压耗时减少;二是内核启动时初始化内容变少。
裁剪要根据产品的实际情况来,将不需要的功能及模块都去掉。

2.4、预设置 lpj 数值


LPJ 也就是 loops_per_jiffy,每次启动都会计算一次,但如果没有做修改的话,这个值每次启动算出来都是一样的,可以直接提供数值跳过计算。

如下 log 所示,有 skipped,lpj 由 timer 计算得来,不需要再校准 calibrate 了。

[0.019918] Calibrating delay loop(skipped), value calculated using timer frequency..48.00BogoMIPS(lpj=240000)

如果没有 skipped,则可以在 cmdline 中添加 lpj=XXX 进行预设。

2.5、initcall 优化


在 cmdline 中设置 initcall_debug=1,即可打印跟踪所有内核初始化过程中调用 initcall 的顺序以及耗时。
具体修改tina/target/allwinner//configs/env-.cfg 文件,新增一行"initcall_debug=1",然后在"setargs_*" 后加入" initcall_debug=${initcall_debug}",如下所示。

setargs_nand=setenv bootargs console=${console} console=tty0 root=${nand_root} init=${init} loglevel=${loglevel} partitions=${partitions}
initcall_debug=${initcall_debug}

加入后,内核启动时就会有类似如下的打印,对于耗时较多的 initcall,可进行深入优化。

[0.021772] initcall sunxi_pinctrl_init+0x0/0x44 returned 0 after 9765 usecs
[0.067694] initcall param_sysfs_init+0x0/0x198 returned 0 after 29296 usecs
[0.070240] initcall genhd_device_init+0x0/0x88 returned 0 after 9765 usecs
[0.080405] initcall init_scsi+0x0/0x90 returned 0 after 9765 usecs
[0.090384] initcall mmc_init+0x0/0x84 returned 0 after 9765 usecs

2.6、内核 initcall module 并行


内核 initcall 有很多级别,其中启动中最耗时的就是各 module 的 initcall,针对多核方案,可以考虑将 module initcall 并行执行来节省时间。
目前内核 do_initcalls 是一个一个按照顺序来执行,可以修改成新建内核线程来执行。

2.7、减少 pty/tty 个数


加入 initcall 打印之后,发现 pty/tty init 耗时很多,可减少个数来缩短 init 时间。

initcall pty_init+0x0/0x3c4 returned 0 after 239627 usecs
initcall chr_dev_init+0x0/0xdc returned 0 after 36581 usecs

2.8、内核 module


需要考虑启动速度的界定,对于内核 module 的优化主要有两点:

  • 对于必须要加载的模块,直接编译进内核
  • 对于不急需的功能,可以编译成模块。

比如某个应用,会开启主界面联网,启动速度以出现主界面为准,那么可以考虑将 disp 编入内核,wifi 编译成模块,后续需要时再动态加载。

最新文章

随机文章