

沉淀、分享、成长,让自己和他人都能有所收获!😄
📢启动速度是嵌入式产品一个重要的性能指标,更快的启动速度会让客户有更好的使用体验,在某些方面还会节省能耗,因为可以直接关机而不需要休眠。
启动速度优化可提升产品的竞争力。对于某些系统来说,启动速度是硬性要求。
优化之前,需要充分了解当前系统的启动时序情况。
打开 kernel 配置,使能如下选项
kernel hacking --->
[*] Show timing information on printks
将会在内核的 log 前加入时间戳。
注:此方法主要用来测量内核启动过程中各个阶段的耗时。
在 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 的耗时。
在内核源码中自带了一个工具 (scripts/bootgraph.pl) 可用于分析启动时间。
kernelCONFIG_PRINTK_TIME 选项kernel cmdline 加上"initcall_debug=1"dmesg | perl $(Kernel_DIR)/scripts/bootgraph.pl > output.svg"SVG 浏览器(比如 Inkscape,Gimp,Firefox 等)来查看输出文件 output.svg注:此方法主要用来测量内核启动过程中各个阶段的耗时。
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,方便分析。注:此方法主要用来测量挂载文件系统到主应用程序启动过程中的耗时。
在适当的地方加入操作 gpio 的代码,通过示波器抓取波形得到各阶段耗时。
注:此方法可用来测量整个启动中各阶段的耗时。
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"
通常来说,内核启动耗时较多,需要更深入的优化。
比较不同压缩方式的启动时间和 flash 占用情况,选择一种符合实际情况的。
内核镜像可以由 kernel 自解压,也有 uboot 进行解压的情况。
对于 kernel 自解压的情况,如果压缩过的 kernel 与解压后的 kernel 地址冲突,则会先把自己复制到安全的地方,然后再解压,防止自我覆盖。这就需要耗费复制的时间。
比如对于运行地址为 0x80008000 的内核来说,bootloader 可以将其加载到 0x81008000,当然其他位置也可以。
裁剪内核,带来的加速是两个方面的。一是体积变小,加载解压耗时减少;二是内核启动时初始化内容变少。
裁剪要根据产品的实际情况来,将不需要的功能及模块都去掉。
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 进行预设。
在 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
内核 initcall 有很多级别,其中启动中最耗时的就是各 module 的 initcall,针对多核方案,可以考虑将 module initcall 并行执行来节省时间。
目前内核 do_initcalls 是一个一个按照顺序来执行,可以修改成新建内核线程来执行。
加入 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
需要考虑启动速度的界定,对于内核 module 的优化主要有两点:
比如某个应用,会开启主界面联网,启动速度以出现主界面为准,那么可以考虑将 disp 编入内核,wifi 编译成模块,后续需要时再动态加载。