本頁提供改善啟動時間的提示。
從模組中移除偵錯符號
與在實際裝置上從核心中移除偵錯符號的方式類似,請務必從模組中移除偵錯符號。從模組中移除偵錯符號可縮短啟動時間,因為這樣可減少下列項目:
- 從 Flash 讀取二進位檔所需的時間。
- 解壓縮 RAM 磁碟所需的時間。
- 載入模組所需的時間。
從模組中移除偵錯符號,可能會在啟動期間節省幾秒鐘的時間。
Android 平台版本預設會啟用符號去除功能,但
即可明確啟用這些物件
裝置專屬設定中的 BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES
低於裝置/vendor/device。
針對核心和 ramdisk 使用 LZ4 壓縮技術
相較於 LZ4,Gzip 可產出較小的壓縮輸出,但 LZ4 壓縮速度比 Gzip 更快就核心和模組而言,使用 Gzip 所節省的儲存空間絕對值,與 LZ4 的解壓縮時間優勢相比,並沒有那麼顯著。
Android 平台已新增 LZ4 ramdisk 壓縮功能
透過 BOARD_RAMDISK_USE_LZ4
建構。您可以在
裝置專屬設定您可以透過核心定義設定核心壓縮功能。
切換至 LZ4 後,開機時間應可加快 500 毫秒至 1000 毫秒。
避免在驅動程式中記錄過多資訊
在 ARM64 和 ARM32 中,如果函式呼叫與呼叫位置的距離超過特定距離,就需要跳躍表 (稱為程序連結表或 PLT) 才能編碼完整的跳躍位址。由於模組是以動態方式載入 這些跳轉表必須在模組載入期間修正。需要重新安置的呼叫稱為重新安置項目,其中包含 ELF 格式的明確附加項 (或簡稱 RELA) 項目。
Linux 核心會在分配 PLT 時進行一些記憶體大小最佳化 (例如快取命中最佳化)。在這個上游提交中,最佳化方案的複雜度為 O(N^2)
,其中 N
是 R_AARCH64_JUMP26
或 R_AARCH64_CALL26
類型的 RELA 數量。因此,減少 RELA 數量
都有助於縮短模組載入時間
一種常見的程式設計模式,會增加
R_AARCH64_CALL26
或 R_AARCH64_JUMP26
服務水準協議超過
驅動程式庫。對 printk()
或任何其他記錄方案的每次呼叫,通常會新增 CALL26
/JUMP26
RELA 項目。在上游提交內容中的提交文字中,請注意,即使經過最佳化,這六個模組仍需要約 250 毫秒才能載入,這是因為這六個模組是記錄量最多的前六個模組。
視情況減少記錄功能可以節省開機時間約 100 至 300 毫秒的費用。 但只會提高現有記錄的使用率。
選擇性啟用非同步探測
載入模組時,如果所支援的裝置已從 DT (裝置樹狀結構) 填入並新增至驅動程式核心,則裝置探測會在 module_init()
呼叫的背景下執行。如果在 module_init()
的背景下執行裝置探測,模組必須等到探測完成後才能完成載入。由於模組載入作業大多是序列化的,因此如果裝置需要較長的時間進行探測,就會導致開機時間變慢。
為避免啟動時間變慢,請為需要較長時間才能檢測裝置的模組啟用非同步檢測功能。為所有模組啟用非同步探測 或許不是讓執行緒分支並啟動 探測器的速度可能與探測裝置所需的時間一樣長。
透過緩慢公車連接的裝置,例如 I2C、支援 韌體載入探測功能,以及執行大量硬體的裝置 初始化作業可能會引發時間問題。因此能判斷在何時 就是收集每位司機的探測時間
如要啟用模組的非同步探測功能,僅
設定PROBE_PREFER_ASYNCHRONOUS
。
標記加入。對於模組,您也必須新增
核心指令列中的 module_name.async_probe=1
使用 google.com 載入模組時,將 async_probe=1
做為模組參數傳遞
modprobe
或insmod
。
啟用非同步探測功能後,開機時可節省約 100 至 500 毫秒。 可依硬體/驅動程式而定。
盡早檢測 CPUfreq 驅動程式
CPUfreq 驅動程式探測得越早,您就能在開機期間將 CPU 頻率調整至最高 (或某些熱力限制的最高) 值。
CPU 速度更快,啟動速度也更快這份指南也適用於「devfreq
」
控管 DRAM、記憶體和互連網路頻率的驅動程式。
使用模組時,載入順序可以依附於 initcall
層級和
驅動程式的編譯或連結順序。使用別名「MODULE_SOFTDEP()
」即可
確認 cpufreq
驅動程式是前幾個要載入的模組之一。
除了提早載入模組,您還需要確保所有用於探測 CPUfreq 驅動程式的依附元件也已探測。舉例來說,如果您在 需要時鐘或調節器控點來控制 CPU 頻率 會先探測這個物件如果 CPU 在啟動期間可能會過熱,您可能需要在 CPUfreq 驅動程式前載入熱力驅動程式。因此,請盡可能確保 CPUfreq 和相關的 devfreq 驅動程式盡早進行探測。
提早探測 CPUfreq 驅動程式所節省的資源,可能會從很少到很多,取決於您可以提早多久探測這些內容,以及啟動載入程式時 CPU 的頻率。
將模組移至第二階段初始化、供應商或 vendor_dlkm 分割區
由於第一階段 init 程序已序列化,因此畫面較少
有機會平行處理啟動程序。如果
第一個階段 init,請將模組移到第二階段 init
或 vendor_dlkm
分區中。
第一階段初始化不需要探測多個裝置,才能進入第二階段初始化。只需使用主控台和快閃儲存空間功能, 也可執行一般啟動流程
載入下列必要驅動程式:
watchdog
reset
cpufreq
針對復原和使用者空間 fastbootd
模式,第一階段初始化需要更多裝置進行探測 (例如 USB) 和顯示。請在第一階段的 RAM 磁碟區和供應商或 vendor_dlkm
分區中保留這些模組的副本。方便他們
在復原程序的第一個階段初始化或 fastbootd
啟動流程中載入。不過,請勿在正常啟動流程中,於第一階段初始化時載入復原模式模組。復原模式模組可延後至第二階段初始化,以縮短啟動時間。第一階段 init 不需要的所有其他模組
已移至該供應商或 vendor_dlkm
分區。
在提供葉子裝置清單 (例如 UFS 或序列) 的情況下,dev needs.sh
指令碼會找出依附元件或供應商 (例如時鐘、調節器或 gpio
) 所需的所有驅動程式、裝置和模組,以便進行探測。
將模組移至第二階段 init 可縮短在下列時間內的啟動時間: 方式:
- 減少 RAM 磁碟大小。
- 這樣可在系統啟動載入程式載入 ramdisk 時加快刷新速度 (序列化啟動步驟)。
- 如此一來,核心 ramdisk (序列化開機步驟)。
- 第二階段的初始化作業會以平行方式執行,因此可隱藏模組的載入時間,因為工作會在第二階段初始化時完成。
將模組移至第二階段,可將開機時節省 500 至 1000 毫秒的費用 瞭解您能移至第二階段 init 的模組數量
模組載入物流
最新的 Android 版本功能板設定可控制哪些模組複製到各個階段,以及哪些模組載入。本節著重於 以下子集:
BOARD_VENDOR_RAMDISK_KERNEL_MODULES
。這份清單會將模組複製到 RAM 磁碟。BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD
。這個清單會在第一階段初始化時載入模組。BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD
。這份清單包含 會在復原或從 ramdisk 選取fastbootd
時載入。BOARD_VENDOR_KERNEL_MODULES
。這裡列出了要複製到 或vendor_dlkm
分區。/vendor/lib/modules/
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
檔案。上述指令碼會找出並填入
選取可用的核心模組,讓其餘模組保留第二個
階段 init
如果是第二階段 init,建議您以服務形式執行模組載入作業 也不會阻擋啟動流程請使用殼層指令碼管理模組載入作業,以便在必要時回報 (或忽略) 其他物流,例如錯誤處理和緩解措施,或模組載入完成。
您可以忽略使用者建構中沒有的偵錯模組載入失敗。
如要忽略這項失敗,請將 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 。不過,請確認 系統啟動載入程式會將所有線上 CPU 的頻率設為安全高 任何支援 SoC 或產品的功能這點非常重要 模組核心,init ramdisk 解壓縮作業發生在 CPUfreq 是否已載入該驅動程式。因此,如果啟動載入程式將 CPU 頻率維持在較低的範圍,則在進行 CPU 密集工作 (解壓縮) 時,RAMDISK 解壓縮時間可能會比靜態編譯的核心時間長 (調整 RAMDISK 大小差異後),因為 CPU 頻率會非常低。上述原則同樣適用於記憶體和互連網路頻率。
在系統啟動載入程式中初始化大型 CPU 的 CPU 頻率
在載入 CPUfreq
驅動程式前,核心不知道
CPU 頻率,且未針對目前的 CPU 排程容量調整容量
頻率。如果小 CPU 的負載過高,核心可能會將執行緒遷移至大 CPU。
確保大型 CPU 的效能至少與 系統啟動載入程式離開的頻率。舉例來說,如果大 CPU 在相同頻率下效能是小 CPU 的 2 倍,但啟動載入器將小 CPU 的頻率設為 1.5 GHz,而大 CPU 的頻率設為 300 MHz,則如果核心將執行緒移至大 CPU,啟動效能就會下降。在本例中,如果以 750 MHz 的頻率啟動大 CPU 是安全的,即使您不打算明確使用大 CPU,也應這麼做。
駕駛不應在第一階段 init 載入韌體
在某些情況下,可能需要在第一階段初始化時載入韌體。但一般來說,驅動程式不應在第一階段初始化時載入任何韌體,尤其是在裝置探測情境中。如果第一階段的 RAM 磁碟區中沒有韌體,在第一階段初始化時載入韌體會導致整個啟動程序停滯。即使韌體位於第一階段的 RAM 磁碟區,仍會造成不必要的延遲。