啟動時間最佳化

本頁提供改善啟動時間的提示。

從模組中移除偵錯符號

與在實際裝置上從核心中移除偵錯符號的方式類似,請務必從模組中移除偵錯符號。從模組中移除偵錯符號可縮短啟動時間,因為這樣可減少下列項目:

  • 從 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),其中 NR_AARCH64_JUMP26R_AARCH64_CALL26 類型的 RELA 數量。因此,減少 RELA 數量 都有助於縮短模組載入時間

一種常見的程式設計模式,會增加 R_AARCH64_CALL26R_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 做為模組參數傳遞 modprobeinsmod

啟用非同步探測功能後,開機時可節省約 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/modulesvendor_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 磁碟區,仍會造成不必要的延遲。