嵌入式 Linux 啟動時間優化實戰,2.41 秒啟動應用!
目標系統
硬件:
Beagle Bone Black (Cortex A8)
USB 攝像頭 + LCD
軟件:
Linux 5.1 + Buildroot rootfs
FFmpeg,用于采集視頻并解碼到 LCD。

當前啟動時間:
從上電到 LCD 顯示第一幀圖像:9.45 秒
1、優化編譯器
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。
2、優化應用程序
我們可以通過 ./configure 對 FFmpeg 的功能組件進行選擇。
另外,還可以用 strace 和 perf 命令調試以優化 FFmpeg 的內部d代碼。
優化后的結果:
文件系統:從 16.11 MB 縮小到 3.54 MB (-78 %)。
程序的加載和運行時間:縮短 150 ms。
整體啟動時間:縮短 350 ms。
在空間的優化很大,但是在啟動時間上的優化很小,這是因為 Linux 運行程序時只加載程序的必要部分。
3、優化 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 拼在一起之后,內核變大了許多,而內核鏡像是需要解壓,解壓的時間增多了。
4、優化內核
評估方法:
在啟動參數里添加 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。
5、優化 Bootloader
這里我們采用最好的方案:使用 Uboot Falcon mode。
Falcon mode 只執行 Uboot 的第一階段:SPL,然后跳過 Stage 2,執行加載 Kernel。
啟動時間縮短 250 ms。
總結
到此,啟動優化基本完成,最終效果如下:
[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 自上而下進行優化。
重點關注短板。




















