如何快速启动嵌入式Linux应用

嵌入式技术

1330人已加入

描述

本文的目标是尝试一些比较简单有效的方法,并不会覆盖所有的优化技巧。

1 目标系统

硬件:

Beagle Bone Black (Cortex A8)

USB 摄像头 + LCD

软件:

Linux 5.1 + Buildroot rootfs

FFmpeg,用于采集视频并解码到 LCD。

当前启动时间: 从上电到 LCD 显示第一帧图像:9.45 秒。 2 优化编译器 ARM vs Thumb2 比较基于 ARM 或者 Thumb2 指令集编译出来的系统和应用。 ARM:rootfs 为 3.79 MB,ffmpeg 为 227 KB。 Thumb2:3.10 MB (-18 %),183 KB (-19 %)。 性能方面:Thumb2 的性能明显略有提升
(约小于 5 %)。 虽然性能有所提升,但是我个人还是会选择 ARM 指令集。 musl vs uClibc Buildroot 里有 3 种 C库可以选择:glibc、musl、uClibc,这里我们只比较后面 2 种比较小巧的库。 musl:680 KB (统计 /lib 目录)。 uClibc:570 KB (-16 %)。 uClibc 节省了 110 KB,我们选择 uClibc。

3 优化应用程序

我们可以通过 ./configure 对 FFmpeg 的功能组件进行选择。 另外,还可以用 strace 和 perf 命令调试以优化 FFmpeg 的内部d代码。 优化后的结果: 文件系统:从 16.11 MB 缩小到 3.54 MB (-78 %)。 程序的加载和运行时间:缩短 150 ms。 整体启动时间:缩短 350 ms。 在空间的优化很大,但是在启动时间上的优化很小,这是因为 Linux 运行程序时只加载程序的必要部分。

4 优化 Init 和根文件系统

思路: 使用 bootchartd 分析系统启动并裁剪不必要的服务。 将 /etc/init.d/ 下的启动脚本合并为一个。 不挂载 /proc 和 /sys。 裁剪 BusyBox,文件系统越小,内核挂载可能会越快。 将 Init 程序替换成我们的应用程序。 静态编译应用程序。 裁剪掉不常用的文件,找出长时间不访问的文件:

 


$ find / -atime -1000 -type f
  优化后的结果: 文件系统:裁剪 Busybox 后,从 3.54 MB 缩小到 2.33 MB (-34 %)。 启动时间:基本没改变,大概是因为文件系统本身就足够小了。 使用 initramfs 作为 rootfs: 一般情况下,Linux 系统会先挂载 initramfs,init ramfs 很小且位于内存中,再由 initramfs 负责负载根文件系统。 当我们将 Buildroot rootfs 裁剪得很小时,就可以考虑直接将其作为 initramfs 使用。 这样有什么好处呢? initramfs 可以和 Kernel 拼接在一起,Bootloader 负责将 Kernel+initramfs 加载到内存中,内核不再需要访问磁盘。 内核不再需要 block/storage 和 filesystem 相关的功能,体积会变得更小,加载时间和初始化时间都会变小。 注意,需要关闭initramfs 的压缩 (CONFIG_INITRAMFS_COMPRESSION_NONE)。 优化后的结果: 即便禁用了 CONFIG_BLOCK 和 CONFIG_MMC 后,总启动时间仍多了 20ms。这可能是因为 Kernel + initramfs 拼在一起之后,内核变大了许多,而内核镜像是需要解压,解压的时间增多了。

 

5 优化内核

评估方法: 在启动参数里添加 initcall_debug,能得到更多内核 log:

 


[ 3.750000] calling ov2640_i2c_driver_init+0x0/0x10 @ 1
[ 3.760000] initcall ov2640_i2c_driver_init+0x0/0x10 returned 0 after 544 usecs
[ 3.760000] calling at91sam9x5_video_init+0x0/0x14 @ 1
[ 3.760000] at91sam9x5-video f0030340.lcdheo1: video device registered @ 0xe0d3e340, irq = 24
[ 3.770000] initcall at91sam9x5_video_init+0x0/0x14 returned 0 after 10388 usecs
[ 3.770000] calling gspca_init+0x0/0x18 @ 1
[ 3.770000] gspca_main: v2.14.0 registered
[ 3.770000] initcall gspca_init+0x0/0x18 returned 0 after 3966 usecs
...
  另外,可以用 scripts/bootgraph.pl 将 dmesg 的信息转换成图片:

$ scripts/bootgraph.pl boot.log > boot.svg
 

 

嵌入式

接下来,找出消耗时间最多的环节,进行优化。 裁掉 tracing 在 Kernel hacking 里关闭 Tracers 相关的功能。 启动时间:缩短 550ms。 内核大小:缩小 217KB。 裁掉一些用不上的硬件功能


omap8250_platform_driver_init() // (660 ms)
cpsw_driver_init()  // (112 ms)
am335x_child_init() // (82 ms)
...
  预设 loops per jiffy 在每次启动时,内核都会校准 delay loop 的值,用于 udelay() 函数。 这会测量 loops per jiffy (lpj) 的值。我们只需要启动一次内核,在log 查找 lpj 值:

Calibrating delay loop... 996.14 BogoMIPS (lpj=4980736)
  然后将 lpj=4980736 填写到启动参数中,即可:

 
Calibrating delay loop (skipped) preset value.. 996.14 BogoMIPS (lpj=4980736)

大约缩短了 82 ms。 禁用 CONFIG_SMP SMP 的初始化很慢。它通常在默认配置中是启用的,即使是一个单核 CPU。 如果我们的平台是单核的,可以禁用 SMP。 关闭后,内核缩小:-188 KB (-4.6 %),启动时间缩短 126ms. 禁用 log 启动参数里添加 quiet,启动时间缩短 577 ms。 禁用 CONFIG_PRINTK 和 CONFIG_BUG 后,内核缩小 118 KB (-5.8 %)。 禁用 CONFIG_KALLSYMS 后,内核缩小 107 KB (-5.7 %)。 合计,启动时间缩短 767 ms。 开启 CONFIG_EMBEDDED 和 CONFIG_EXPERT 这会让系统调用变得更精简,内核会变得没那么通用,但是能保持你的应用程序能运行就足够了。 内核缩小 51 KB。 启动时间缩短 34 ms。 选择 SLAB memory allocators 一般是 SLAB、SLOB、SLUB 三选一。 SLAB:默认选择,最通用、最传统、最可靠。 SLOB:更简洁,代码量更少,更节省空间,适合嵌入式系统,使能后,内核缩小 5 KB,但是启动时间增加 1.43 S! SLUB:更合适大型系统,使能后,启动时间增加 2 ms。 因此,我们仍使用 SLAB。 内核压缩方式 不同压缩方式的特点如下:

 

嵌入式

实测效果:

嵌入式

看起来,gzip 和 lzo 表现更好。测试的效果应该是和 CPU/磁盘 的性能相关的。 内核编译参数 使能 CONFIG_CC_OPTIMIZE_FOR_SIZE,该选项可能是用 gcc -Os 代替 gcc -O2。

嵌入式

注意,这只是在 BeagleBone Black + Linux 5.1 上的测试结果,不同平台之间有差异。 禁用 /proc 等伪文件系统 要考虑应用的兼容性。 ffmpeg 依赖 /proc ,所以只能关闭一些 proc 相关的选项:CONFIG_PROC_SYSCTL、CONFIG_PROC_PAGE_MONITOR CONFIG_CONFIGFS_FS,启动时间没有变化。 关闭 sysfs, 启动时间缩短 35 ms。 拼接 DTB 启用 CONFIG_ARM_APPENDED_DTB:


 
$ cat arch/arm/boot/zImage arch/arm/boot/dts/am335x-boneblack-lcd4.dtb > zImage


$ setenv bootcmd 'fatload mmc 0:1 81000000 zImage; bootz 81000000'

启动时间缩短 26 ms。

 

 

 

6 优化 Bootloader

这里我们采用最好的方案:使用 Uboot Falcon mode。 Falcon mode 只执行 Uboot 的第一阶段:SPL,然后跳过 Stage 2,执行加载 Kernel。 启动时间缩短 250 ms。

7 总结

到此,启动优化基本完成,最终效果如下:

 

 


[0.000000 0.000000]
[0.000785 0.000785] U-Boot SPL 2019.01 (Oct 27 2019 - 08:04:06 +0100)
[0.057822 0.057822] Trying to boot from MMC1
[0.378878 0.321056] fdt_root: FDT_ERR_BADMAGIC
[0.775306 0.396428] Waiting for /dev/video0 to be ready...
[1.966367 1.191061] Starting ffmpeg
...
[2.412284 0.004277] First frame decoded
  从上电到 LCD 显示第一帧图像,总时间为 2.41 秒。 最有效果的步骤如下:

 

嵌入式

仍值得优化的空间: 系统花了 1.2 秒等待 USB 摄像头的枚举,这里是否有办法加速? 是否可以关闭 tty 和终端登录? 最后,关于优化启动时间,有一些原则可以遵循:

请不要过早地进行优化。

从一些影响面最小的点开始优化。

从 rootfs 、kernel、bootloader 自上而下进行优化。

重点关注短板。

编辑:黄飞

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分