By Toradex 胡珊逢
ARM平台嵌入式Linux下有些應用對系統啟動時間有著特殊的要求。在很多場合下,這些系統並不需要針對所有任務立即就位,但是針對某些關鍵任務(例如接收以太網命令或者顯示用戶界面)則必須能夠應對。該博文將提供一些方法和簡單的步驟,基於Toradex Colibri i.MX6 ARM系統模塊上優化啟動時間。
提示: 文中涉及到的部分方法需要重新編譯 U-boot、內核以及文件系統。請參考文末所附Toradex開發者中心網站上的相關文章。
在我們開始動手優化之前, 我們需要一個合適的方法來測量啟動時間。如果想要十分精准地測量啟動時間,這甚至需要牽涉到硬件(例如 GPIO 和示波器)。在絕大多數場合下,通過監控系統串口控制台輸出已經是相當准確了。Tim Bird 的 grabserial 是一個廣泛使用的工具,可以用於查看串口控制台輸出的時間信息。這個工具能夠為收到的每一行信息添加上時間戳,如下面所示:
--------------------------------------
ban@LinuxDev:~/software/grabserial-1.8.1$ sudo ./grabserial -d /dev/ttyUSB0 -t
[0.000003 0.000003]
[0.267171 0.267168]
[0.267267 0.000096] U-Boot 2015.04 (Dec 15 2015 - 16:07:42)
[0.271167 0.003900]
[0.271261 0.000094] CPU: Freescale i.MX6DL rev1.1 at 792 MHz
[0.275565 0.004304] Reset cause: POR
[0.277501 0.001936] I2C: ready
[0.278540 0.001039] DRAM: 512 MiB
……
--------------------------------------
第一列數字代表時間戳(從收到第一個字符算起),第二行代表的是收到當前一行和上一行信息之間的時間間隔。
Linux 系統的啟動,主要可以分為以下 3 個階段,文章將逐一討論。
- Boot loader
- Linux kernel
- User space (init system)
Boot loader實際上在 boot loader 啟動之前,還有兩個步驟:硬件初始化和 boot ROM。硬件初始化需要滿足電源上電順序以及總線和處理器芯片復位時序要求。這個階段的耗時一般是固定的,在 10 ~ 200 ms。
圖 1 boot ROM 啟動時間
圖1 中,黃色信號是核心板 VCC,藍色信號是串口輸出 TX。從核心板上電到串口輸出第一個字符之間的時間大約為 102ms。這個就是 Colibri I.MX6D 的 硬件初始化和 boot ROM 運行的時間。由於這部分代碼是固化在SoC 上面,所以用戶一般無法對其進行優化。
ARM 處理器從位於內部的 ROM 上啟動固件。該固件從啟動介質上加載 boot loader。該階段的時間一般很短,取決於 boot loader 的大小。除了減小 boot loader 體積,很難做其他的優化。實際上能夠做的優化和調整還是在 boot loader (U-Boot)。
目前發布的 V2.5 Beta 3 版本,從第一個字符輸出到內核啟動的時間約為 1.7 秒。主要涉及以下過程:
- U-Boot 初始化(約 370 ms,從接收到的第一個字符算起)
- Autoboot delay(1s)
- 讀取 device tree 和內核加載(127 ms)
- 加載device tree 和內核加載(約 100 ms)
- 最後跳轉至內核起始地址
Uboot 啟動到內核啟動時間:~1700ms
最顯著的優化就是降低 Autoboot delay 。這個值可以使用下面的命令設置為 0:
--------------------------------------
setenv bootdelay 0
saveenv
--------------------------------------
這個也可以使用 CONFIG_BOOTDELAY 將其配置為默認值。在目前的發布版本中,如果將 bootdelay 設置為0,那麼將會沒有辦法直接進入 boot loader 的命令行模式。U-Boot 提供一個選項CONFIG_ZERO_BOOTDELAY_CHECK,在 bootdelay 為 0 的情況下,用於檢測一個字符。我們已經將其添加到下一個發布版本的默認配置中。
Uboot 啟動到內核啟動時間:~700ms
移除一些功能有助於減少分配時間和初始化這些功能的時間。例如移除 USB Client 和 DFU 功能,相關Patch請從這裡下載。
這可以將 U-Boot 尺寸減小到 289 KB,縮短 U-Boot 讀取時間 。用同樣的方法,可以結合項目實際的外設需求,裁剪不需要的外設支持和功能。顯然內核大小和加載時間具有線性關系,因此,優化內核尺寸將可以進一步提高啟動時間。
Kernel為了只測量內核啟動的時間,可以使用 grabserial 的匹配功能重置 boot loader 輸出信息中的時間。
--------------------------------------
sudo ./grabserial -d /dev/ttyUSB0 -t -m "^Starting kernel*" -q "^\[ *[]0-9.]* Freeing unused kernel memory.*"
--------------------------------------
啟動結束的時間多少有點難以確定,因為內核將會繼續初始化硬件,即使文件系統已經掛載和第一個用戶空間進程(init)開始運行(延時初始化)。 “Freeing unused kernel memory” 是 init 進程啟動前發出的最後消息,因此將其標記為內核線性任務的結束(請查看 kernel_init in init/main.c)。我將會使用這個信息的時間戳信息來比較啟動時間。我們模塊上默認內核的壓縮尺寸為 4.8MB,啟動時間為 4.6 秒。
Kernel 啟動到 init 啟動時間:4.6秒。
同 U-Boot 一樣,Linux 內核也是同步地將信息發送到串口。具體的方法取決於所使用的串口,其會同步等待直到字符在串口上發送完畢。這個的優點在於,當內核崩潰的時候,那個時候的所有信息都是可見的。假如信息是異步輸出,最後輸出的信息將不 會指示內核所崩潰的地方。
內核中有一個參數,可以最大限度減少輸出的信息:“Quiet”。然而,這也將屏蔽我們測試啟動時間的字符信息(“Freeing unused kernel memory”)。最簡單的方法輸出這些信息是利用日志級別輸出特定的信息。在'mm/page_alloc.c' 中搜索 “Freeing %s memory”。我將使用 ‘pr_alert’ 輸出信息。這個方法起高了 3秒。
--------------------------------------
[0.925608 0.000178] Starting kernel ...
[0.002166 0.002166]
……
[1.432211 0.207636] [ 1.203216] Freeing unused kernel memoryban@LinuxDev:~/software/grabserial-1.8.1$
--------------------------------------
Kernel 啟動到 init 啟動時間提高到 1.4秒。
進一步提高啟動時間的另一個簡單的方法是移除功能。Yocto 項目提供了一個方便的工具 ksize.py,這個需要在內核編譯目錄中運行。這個工具能夠顯示內核各個部分的大小。第一個表格顯示了大致的概況(為了獲得准確的概況, 編譯之前使用 make clean)。
--------------------------------------
toradex@tdx-lab-linux:~/Toradex/webinar/linux-toradex$ ./ksize.py
Linux Kernel total | text data bss
--------------------------------------------------------------------------------
vmlinux 9321389 | 8589061 311188 421140
--------------------------------------------------------------------------------
drivers/built-in.o 2973586 | 2810983 121247 41356
……
usr/built-in.o 138 | 138 0 0
--------------------------------------------------------------------------------
sum 8174774 | 7475382 283956 415436
delta 1146615 | 1113679 27232 5704
--------------------------------------
可以被安全移除的一般是應用相關的功能。浏覽各個第一層目錄,有助於快速確定最可能移除的對象。
另外一個可以幫助我們進一步分析,內核啟動時各個功能所消耗的時間是使用 Linux 源碼中scripts/bootgraph.pl 腳本,將啟動日志進行圖形化分析。為此,需要在 U-boot 中配置
--------------------------------------
setenv defargs 'enable_wait_mode=off galcore.contiguousSize=50331648 initcall_debug printk.time=1 quiet'
--------------------------------------
在 Linux 導出日志
--------------------------------------
root@colibri-imx6:~# dmesg > boot.log
--------------------------------------
在電腦上運行以下命令
--------------------------------------
cat ./boot.log | ./scripts/bootgraph.pl > boot.svg
--------------------------------------
圖 2 Kernel boot
根據上面工具的分析,以及實際項目外設和功能的需求,將不必要的驅動從內核中移除。例如我們將移除無線網絡驅動、CAAM(i.MX6硬件加密加速模塊)、文件系統(ext4, nfs, cifs 等)、SPI等功能。重新編譯後,新的內核縮減到 3.6 MB。
--------------------------------------
[0.818066 0.000252] Starting kernel ...
……
[0.909013 0.434209] [ 0.723273] Freeing unused kernel memoryban@LinuxDev:~/software/grabserial-1.8.1$
--------------------------------------
Kernel 啟動到 init 啟動時間提高到 0.9秒。
User SpaceSystemd在 Linux 用戶空間,初始化工作由 init 系統完成。 Toradex BSP 鏡像使用 Ångströ標准啟動 init 系統,其稱為Systemd。Systemd 目前已成成為桌面 Linux的標准 init 系統,具有豐富的功能,特別是為動態系統所設計。Systemd 同樣會影響啟動時間。多個守護進程可以用同時啟動(利用現在的多核系統); 支持在稍後的一個時間延時加載服務時激活 socket 以及支持按需啟動的設備激活。並且,集成的日志守護進程 journald 由於使用二進制日志文件和完善的日志文件管理可以節省空間。
根據實際應用,一個嵌入式系統可能是相當靜態的。因此,並不需要 Systemd 的動態功能。不幸的是,Systemd 並不是一個很模塊化的系統,各個模塊之間由相互依賴關系。這使得精簡 Systemd 變得困難。這一節將分文兩部分,第一部分使用 Systemd 啟動優化技術,第二部分會使用 System V。
在這兩部分中,我們使用 “Freeing unused kernel memory” 作為測量基准時間。
sudo ./grabserial -d /dev/ttyUSB0 -t -m "^\[ *[]0-9.]* Freeing unused kernel memory.*"
在默認的 BSP 中
User space 啟動到Login 的時間為 ~5.3 秒。
--------------------------------------
[0.447125 0.447125] [FAILED] Failed to start Load Kernel Modules.
[0.463050 0.015925] See "systemctl status systemd-modules-load.service" for details.
……
[5.302394 0.016012] colibri-imx6 login:
--------------------------------------
由於采用了自己編譯的內核,因此在加載模塊驅動的時候提示錯誤。只需要將內核模塊重新編譯,放到目標板的 /lib/modules/ 目錄中即可。當然,如果應用中需要相應的驅動,完全可以不加載這些模塊驅動。
--------------------------------------
root@colibri-imx6:~# rm /etc/modules-load.d/libcomposite.conf
--------------------------------------
systemctl 工具可以查看所有的啟動項目
--------------------------------------
root@colibri-imx6:~# systemctl status
--------------------------------------
Systemd 提供了 systemd-analyze 工具,當使用 “blame” 時,能夠打印出各個服務以及其啟動的時間。這個可以發現最消耗啟動時間的服務。但是,其中的值可能具有迷惑性,因為測量的時間是實際流逝的時間。服務有可 能處於睡眠狀態,這時的 CPU 其實在處理其他任務。所以在列表頂部的服務不一定是最耗時的,特別是在單核系統上。默認的 BSP 中並沒有包含 systemd-analyze,可以通過下面的命令,在線安裝。
--------------------------------------
root@colibri-imx6:~# opkg update
root@colibri-imx6:~# opkg install systemd-analyze
root@colibri-imx6:~# cat systemd.blame
3.188s dev-mmcblk0p2.device
1.080s systemd-journal-flush.service
……
178ms systemd-fsck-root.service
--------------------------------------
systemd-analyze plot 也可以用圖形的形式輸出啟動項的情況。
--------------------------------------
root@colibri-imx6:~# systemd-analyze plot > systemd.svg
--------------------------------------
圖 3 Systemd boot
啟動服務可以使用 disable 命令來關閉。有些服務(特別是 Systemd 自身提供的)可能需要掩碼才能關閉它們。另外有一些可能是系統運行所需的。因此,在關閉服務時需要特別小心,而且一次只能處理一個。
--------------------------------------
root@colibri-imx6:~# systemctl disable usbg.service
--------------------------------------
Toradex 的 BSP 中,采用 ConnMan 作為網絡管理工具,可以很靈活的管理無線網絡連接。 但 ConnMan 的啟動會消耗較多的時間。對於不使用網絡或僅用有線網絡的情況,/etc/systemd/network/wired.network 配置可以有效降低啟動時間。
Toradex 的 BSP 中,采用 ConnMan 作為網絡管理工具,可以很靈活的管理無線網絡連接。 但 ConnMan 的啟動會消耗較多的時間。對於不使用網絡或僅用有線網絡的情況,/etc/systemd/network/wired.network 配置可以有效降低啟動時間。
--------------------------------------
[Match]
Name=eth0
[Network]
DHCP=ipv4
--------------------------------------
在不需要記錄系統日志的應用中,將日志存儲功能禁用後,也可以在一定程度上縮減啟動時間。
--------------------------------------
/etc/systemd/journald.conf
[Journal]
Storage=none
#Compress=yes
--------------------------------------
內核參數中的 quiet,同樣也適用於 Systemd。這個有助於 Systemd 的啟動時間。
按照上述的優化,User space 啟動到Login 的時間為 ~3.8 秒。
--------------------------------------
[1.748022 0.493043] [ 0.783547] Freeing unused kernel memory: 280K (805f6000 - 8063c000)
[0.609885 0.609885] [ 1.390393] systemd-fsck[117]: rootfs: clean, 21509/876392 files, 446716/3543040 blocks
……
[3.799609 0.016926] colibri-imx6 login:
--------------------------------------
System V init在很長一段時間內,Linux 也使用 SysV 作為標准的 init 系統。由於基於腳本的系統,這是模塊化的,並且可以相對容易地精簡系統。特別是對相對靜態的系統,並不需要 Systemd 的設備激活和 socket 激活。此時,SysV可以是很好的選擇。
Toradex 的 Linux BSP 基於 OpenEmbedded 框架發布,用戶可以很方便的配置 SysV。所需的patch文件請從這裡下載。
重新編譯使用 SysV 的鏡像
--------------------------------------
toradex@tdx-lab-linux:~/Toradex/oe-core/v2.5/oe-core/build$ bitbake console-trdx-image
--------------------------------------
在使用上面的鏡像更新目標板後,在 Linux 執行
--------------------------------------
root@colibri-imx6:~# /usr/sbin/resize.sh
--------------------------------------
如果使用的是 Colibri I.MX6D 512MB , 在 U-boot 中執行
--------------------------------------
Colibri iMX6 # patch_ddr_size
--------------------------------------
在使用 SysV 的系統上,同樣也可以將一些不需要的啟動項刪除。
--------------------------------------
root@colibri-imx6:~#update-rc.d -f bootlogd remove
root@colibri-imx6:~#update-rc.d -f mountall.sh remove
root@colibri-imx6:~#update-rc.d -f networking remove
--------------------------------------
按照上述的優化,User space 啟動到Login 的時間為 ~1.1 秒。
--------------------------------------
[1.594064 0.371247] [ 0.661430] Freeing unused kernel memory: 280K (805f6000 - 8063c000)
[0.057864 0.057864] INIT: version 2.88 booting
……
[1.098062 0.000060] colibri-imx6 login:
--------------------------------------
在 /etc/init.d 建立啟動腳本加載應用
--------------------------------------
root@colibri-imx6:vi /etc/init.d/drawing.sh
### BEGIN INIT INFO
# Provides: toradex
# Default-Start: 3 5
# Default-Stop: 0 1 2 6
# Description: draw rectangles on fb0
### END INIT INFO
/home/root/rectangles &
root@colibri-imx6:chmod a+x drawing.sh
root@colibri-imx6:update-rc.d drawing.sh defaults
--------------------------------------
到此,使用上述方法,應用程序能在 2.55 秒時間內啟動。
--------------------------------------
ban@LinuxDev:~/software/grabserial-1.8.1$ sudo ./grabserial -d /dev/ttyUSB0 -t
[0.000002 0.000002]
[0.162820 0.162818]
[0.162917 0.000097] U-Boot 2015.04 (Apr 14 2016 - 10:09:25)
……
[0.690563 0.001000] Starting kernel ...
……
[2.633291 0.000076] colibri-imx6 login:
--------------------------------------
參考:
Flashing Embedded Linux to iMX6 Modules
http://developer.toradex.com/knowledge-base/flashing-linux-on-imx6-modules
Build U-Boot and Linux Kernel from Source Code
http://developer.toradex.com/knowledge-base/build-u-boot-and-linux-kernel-from-source-code
OpenEmbedded (core)
http://developer.toradex.com/knowledge-base/board-support-package/openembedded-%28core%29