本頁面提供改善啟動時間的提示。
從模組中移除偵錯符號
與在實際工作裝置上從核心中移除偵錯符號的方式類似,請務必從模組中移除偵錯符號。從模組中移除偵錯符號可縮短啟動時間,因為這樣可減少下列項目:
- 從 Flash 讀取二進位檔所需的時間。
- 解壓縮 RAM 磁碟所需的時間。
- 載入模組所需的時間。
從模組中移除偵錯符號,可能會在啟動期間節省幾秒鐘的時間。
根據預設,Android 平台版本會啟用符號去除功能,但如要明確啟用這項功能,請在 device/vendor/device 底下的裝置專屬設定中設定 BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES
。
為核心和 RAM 磁碟使用 LZ4 壓縮
與 LZ4 相比,Gzip 產生的壓縮輸出內容較小,但 LZ4 的解壓縮速度比 Gzip 快。就核心和模組而言,使用 Gzip 所節省的儲存空間絕對值,與 LZ4 的解壓縮時間優勢相比,並沒有太大差異。
透過 BOARD_RAMDISK_USE_LZ4
為 Android 平台版本新增 LZ4 主記憶體磁碟壓縮支援功能。您可以在裝置專屬設定中設定這個選項。您可以透過核心 defconfig 設定核心壓縮功能。
切換至 LZ4 後,開機時間應可縮短 500 毫秒至 1000 毫秒。
避免在驅動程式中記錄過多資訊
在 ARM64 和 ARM32 中,如果函式呼叫與呼叫位置的距離超過特定距離,就需要跳躍表 (稱為程序連結表或 PLT) 才能編碼完整的跳躍位址。由於模組是動態載入,因此這些跳躍表必須在模組載入期間修正。需要重新安置的呼叫稱為重新安置項目,其中包含 ELF 格式的明確附加項 (或簡稱 RELA) 項目。
在分配 PLT 時,Linux 核心會進行一些記憶體大小最佳化作業 (例如快取命中最佳化)。在這個上游提交中,最佳化方案具有 O(N^2)
複雜度,其中 N
是 R_AARCH64_JUMP26
或 R_AARCH64_CALL26
類型的 RELA 數量。因此,減少這類 RELA 的數量有助於縮短模組載入時間。
驅動程式中過度記錄會導致 R_AARCH64_CALL26
或 R_AARCH64_JUMP26
RELA 數量增加,對 printk()
或任何其他記錄配置的每次呼叫,通常會新增 CALL26
/JUMP26
RELA 項目。在上游提交內容中的提交文字中,請注意,即使經過最佳化,這六個模組仍需要約 250 毫秒才能載入,這是因為這六個模組是記錄量最多的前六個模組。
減少記錄作業可節省約 100 到 300 毫秒的啟動時間,實際時間取決於現有記錄作業的過度程度。
有選擇地啟用非同步探測
載入模組時,如果所支援的裝置已從 DT (裝置樹狀結構) 填入並新增至驅動程式核心,則裝置探測會在 module_init()
呼叫的背景下執行。在 module_init()
的背景下完成裝置探測後,模組必須等到探測完成才能完成載入。由於模組載入作業大多是序列化的,因此如果裝置需要較長的時間進行探測,就會導致開機時間變慢。
為避免啟動時間變慢,請為需要較長時間才能檢查裝置的模組啟用非同步檢測功能。為所有模組啟用非同步探測功能可能沒有幫助,因為分支執行緒和啟動探測所需的時間,可能會和探測裝置所需的時間一樣長。
透過 I2C 等速度較慢的匯流排連線的裝置、在探測功能中執行韌體載入作業的裝置,以及執行大量硬體初始化的裝置,都可能導致時機問題。如要找出發生此問題的時間,最佳做法是收集每個驅動程式的探測時間,並加以排序。
如要為模組啟用非同步探測功能,不只在驅動程式程式碼中設定 PROBE_PREFER_ASYNCHRONOUS
標記。針對模組,您還需要在核心指令列中新增 module_name.async_probe=1
,或是在使用 modprobe
或 insmod
載入模組時,將 async_probe=1
傳遞為模組參數。
啟用非同步探測功能後,開機時間可節省約 100 到 500 毫秒,具體時間視硬體/驅動程式而定。
盡早檢測 CPUfreq 驅動程式
CPUfreq 驅動程式探測得越早,您就能在開機期間將 CPU 頻率調整至最高 (或某些熱力限制的最高) 頻率。CPU 速度越快,開機速度就越快。這項指南也適用於控制 DRAM、記憶體和互連頻率的 devfreq
驅動程式。
在模組中,載入順序可能會依 initcall
層級和驅動程式的編譯或連結順序而定。使用別名 MODULE_SOFTDEP()
,確保 cpufreq
驅動程式是載入的頭幾個模組之一。
除了提早載入模組,您還需要確保所有用於探測 CPUfreq 驅動程式的依附元件也已探測。舉例來說,如果您需要時脈或調節器句柄來控制 CPU 的頻率,請務必先對這些項目進行探測。如果 CPU 在啟動期間可能會過熱,您可能需要在 CPUfreq 驅動程式前載入熱力驅動程式。因此,請盡可能確保 CPUfreq 和相關的 devfreq 驅動程式盡早進行探測。
提早探測 CPUfreq 驅動程式所節省的資源,可能會從很少到很多,取決於您可以提早多久探測這些內容,以及啟動載入程式讓 CPU 進入的頻率。
將模組移至第二階段初始化、供應商或 vendor_dlkm 分割區
由於第一階段的初始化程序是序列化的,因此沒有太多機會可以並行執行啟動程序。如果模組不需要在第一階段初始化完成後執行,請將模組移至供應商或 vendor_dlkm
分區,以便進行第二階段初始化。
第一階段初始化不需要探測多個裝置,才能進入第二階段初始化。正常的啟動流程只需要主控台和快閃儲存空間功能。
載入下列必要驅動程式:
watchdog
reset
cpufreq
針對復原和使用者空間 fastbootd
模式,第一階段初始化需要更多裝置進行探測 (例如 USB) 和顯示。請在第一階段的 RAM 磁碟區和供應商或 vendor_dlkm
分區中保留這些模組的副本。這樣一來,系統就能在第一階段初始化時載入這些檔案,以便進行復原或 fastbootd
開機流程。不過,請勿在正常啟動流程中,於第一階段初始化時載入復原模式模組。復原模式模組可延後至第二階段初始化,以縮短啟動時間。所有其他在第一階段初始化時不需要的模組,都應移至供應商或 vendor_dlkm
分區。
在提供葉子裝置清單 (例如 UFS 或序列) 的情況下,dev needs.sh
指令碼會找出依附元件或供應商 (例如時鐘、調節器或 gpio
) 所需的所有驅動程式、裝置和模組,以便進行探測。
將模組移至第二階段初始化,可透過以下方式縮短啟動時間:
- 縮減 RAM 磁碟大小。
- 這樣一來,當 Bootloader 載入 RAMDISK (序列化啟動步驟) 時,就能加快讀取 Flash 的速度。
- 這樣一來,當核心解壓縮 ramdisk (序列化開機步驟) 時,解壓縮速度就會加快。
- 第二階段的初始化作業會以平行方式執行,因此可隱藏模組的載入時間,因為工作會在第二階段初始化時完成。
將模組移至第二階段可節省 500 到 1000 毫秒的啟動時間,具體取決於您能將多少模組移至第二階段初始化。
模組載入物流
最新的 Android 版本功能板設定可控制哪些模組複製到各個階段,以及哪些模組載入。本節將著重於以下子集:
BOARD_VENDOR_RAMDISK_KERNEL_MODULES
。這份清單會將模組複製到 RAM 磁碟。BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD
。這個清單會在第一階段初始化時載入模組。BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD
。在從 RAM 磁碟區選取 recovery 或fastbootd
時,要載入的模組清單。BOARD_VENDOR_KERNEL_MODULES
:這個模組清單會複製到/vendor/lib/modules/
目錄中的供應商或vendor_dlkm
分區。BOARD_VENDOR_KERNEL_MODULES_LOAD
。這個模組清單會在第二階段初始化時載入。
您也必須將 RAMDISK 中的啟動和復原模組複製到 /vendor/lib/modules
中的供應商或 vendor_dlkm
分割區。將這些模組複製到供應商分割區,可確保模組不會在第二階段初始化期間隱藏,這對於偵錯和收集 modinfo
以製作錯誤報告非常有用。
只要啟動模組集已縮減至最小,複製作業就應會在供應商或 vendor_dlkm
分區中占用最少的空間。請確認供應商的 modules.list
檔案在 /vendor/lib/modules
中包含篩選過的模組清單。篩選清單可確保啟動時間不會受到模組重新載入 (這項程序耗時) 的影響。
請確認復原模式模組是以群組方式載入。您可以在復原模式下載復原模式模組,也可以在每個啟動流程的第二階段初始階段執行。
您可以使用裝置 Board.Config.mk
檔案執行這些動作,如以下範例所示:
# All kernel modules
KERNEL_MODULES := $(wildcard $(KERNEL_MODULE_DIR)/*.ko)
KERNEL_MODULES_LOAD := $(strip $(shell cat $(KERNEL_MODULE_DIR)/modules.load)
# First stage ramdisk modules
BOOT_KERNEL_MODULES_FILTER := $(foreach m,$(BOOT_KERNEL_MODULES),%/$(m))
# Recovery ramdisk modules
RECOVERY_KERNEL_MODULES_FILTER := $(foreach m,$(RECOVERY_KERNEL_MODULES),%/$(m))
BOARD_VENDOR_RAMDISK_KERNEL_MODULES += \
$(filter $(BOOT_KERNEL_MODULES_FILTER) \
$(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES))
# ALL modules land in /vendor/lib/modules so they could be rmmod/insmod'd,
# and modules.list actually limits us to the ones we intend to load.
BOARD_VENDOR_KERNEL_MODULES := $(KERNEL_MODULES)
# To limit /vendor/lib/modules to just the ones loaded, use:
# BOARD_VENDOR_KERNEL_MODULES := $(filter-out \
# $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES))
# Group set of /vendor/lib/modules loading order to recovery modules first,
# then remainder, subtracting both recovery and boot modules which are loaded
# already.
BOARD_VENDOR_KERNEL_MODULES_LOAD := \
$(filter-out $(BOOT_KERNEL_MODULES_FILTER), \
$(filter $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD)))
BOARD_VENDOR_KERNEL_MODULES_LOAD += \
$(filter-out $(BOOT_KERNEL_MODULES_FILTER) \
$(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))
# NB: Load order governed by modules.load and not by $(BOOT_KERNEL_MODULES)
BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD := \
$(filter $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))
# Group set of /vendor/lib/modules loading order to boot modules first,
# then the remainder of recovery modules.
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD := \
$(filter $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD += \
$(filter-out $(BOOT_KERNEL_MODULES_FILTER), \
$(filter $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD)))
本範例展示了 BOOT_KERNEL_MODULES
和 RECOVERY_KERNEL_MODULES
的較易管理子集,可在電路板設定檔中本地指定。上述指令碼會從所選可用核心模組中找出並填入每個子集模組,並將剩餘的模組留給第二階段初始化。
針對第二階段初始化,建議您以服務形式執行模組載入作業,以免阻斷啟動流程。請使用殼層指令碼管理模組載入作業,以便在必要時回報 (或忽略) 其他物流,例如錯誤處理和緩解措施,或模組載入完成。
您可以忽略使用者版本中未出現的偵錯模組載入失敗。如要忽略這項失敗,請將 vendor.device.modules.ready
屬性設為觸發 init rc
指令碼啟動流程的後續階段,以便繼續前往啟動畫面。如果您在 /vendor/etc/init.insmod.sh
中使用下列程式碼,請參考以下指令碼範例:
#!/vendor/bin/sh
. . .
if [ $# -eq 1 ]; then
cfg_file=$1
else
# Set property even if there is no insmod config
# to unblock early-boot trigger
setprop vendor.common.modules.ready
setprop vendor.device.modules.ready
exit 1
fi
if [ -f $cfg_file ]; then
while IFS="|" read -r action arg
do
case $action in
"insmod") insmod $arg ;;
"setprop") setprop $arg 1 ;;
"enable") echo 1 > $arg ;;
"modprobe") modprobe -a -d /vendor/lib/modules $arg ;;
. . .
esac
done < $cfg_file
fi
在硬體 rc 檔案中,您可以使用以下方式指定 one shot
服務:
service insmod-sh /vendor/etc/init.insmod.sh /vendor/etc/init.insmod.<hw>.cfg
class main
user root
group root system
Disabled
oneshot
模組從第一階段移至第二階段後,您可以進行其他最佳化作業。您可以使用 modprobe 封鎖清單功能,將第二階段的啟動流程分割,以便納入非必要模組的延遲模組載入作業。您可以延後載入特定 HAL 專用的模組,只在啟動 HAL 時載入模組。
為縮短實際開機時間,您可以在模組載入服務中特別選擇較適合在啟動畫面後載入的模組。舉例來說,您可以在初始啟動流程 (例如 sys.boot_complete
Android 屬性信號) 完成後,明確延遲載入影片解碼器或 Wi-Fi 的模組。請確認在沒有核心驅動程式時,延遲載入模組的 HAL 會阻斷足夠長的時間。
或者,您也可以在啟動流程 rc 指令碼中使用 init 的 wait<file>[<timeout>]
指令,等待所選 sysfs
項目顯示驅動程式模組已完成探測作業。舉例來說,您可以等待顯示器驅動程式在復原或 fastbootd
的背景中完成載入作業,再顯示選單圖形。
在系統啟動載入程式中,將 CPU 頻率初始化為合理的值
由於啟動循環測試期間的熱或電力問題,並非所有 SoC/產品都能以最高頻率啟動 CPU。不過,請務必確保 Bootloader 將所有線上 CPU 的頻率設為 SoC 或產品可安全使用的最高頻率。這點非常重要,因為在使用完全模組化核心時,系統會先執行初始化 RAM 磁碟解壓縮作業,才能載入 CPUfreq 驅動程式。因此,如果 Bootloader 將 CPU 頻率維持在較低的範圍,則在進行 CPU 密集型工作 (解壓縮) 時,RAMDISK 解壓縮作業的時間可能會比靜態編譯的核心更長 (調整 RAMDISK 大小差異後),因為 CPU 頻率會非常低。記憶體和互連頻率也是如此。
在系統啟動載入程式中初始化大型 CPU 的 CPU 頻率
在載入 CPUfreq
驅動程式之前,核心不會偵測 CPU 頻率,也不會根據目前的頻率調整 CPU 排程容量。如果小 CPU 的負載過高,核心可能會將執行緒遷移至大 CPU。
請確保大 CPU 的效能至少與小 CPU 相同,以便啟動載入程式時,可將其保留在內。舉例來說,如果大 CPU 在相同頻率下效能是小 CPU 的 2 倍,但啟動載入器將小 CPU 的頻率設為 1.5 GHz,而大 CPU 的頻率設為 300 MHz,則如果核心將執行緒移至大 CPU,啟動效能就會下降。在本例中,如果以 750 MHz 的頻率啟動大 CPU 是安全的,即使您不打算明確使用大 CPU,也應這麼做。
驅動程式不應在第一階段初始化時載入韌體
在某些情況下,可能需要在第一階段初始化時載入韌體。但一般來說,驅動程式不應在第一階段初始化時載入任何韌體,尤其是在裝置探測情境中。如果第一階段的 RAM 磁碟區中沒有韌體,在第一階段初始化時載入韌體會導致整個啟動程序停滯。即使韌體位於第一階段的 RAM 磁碟區,仍會造成不必要的延遲。