縮短啟動時間

本文件提供合作夥伴指南,說明如何改善特定 Android 裝置的啟動時間。啟動時間是系統效能的關鍵要素,因為使用者必須等待啟動程序完成,才能使用裝置。對於車輛等經常發生冷開機的裝置而言,快速開機時間至關重要 (沒有人喜歡為了輸入導航目的地而等待數十秒)。

Android 8.0 支援多項元件的多項改善功能,可縮短啟動時間。下表列出這些效能改善項目 (根據 Google Pixel 和 Pixel XL 裝置的測量結果)。

元件 提升幅度
系統啟動載入程式
  • 移除 UART 記錄後,可節省 1.6 秒
  • 從 GZIP 改為 LZ4,節省 0.4 秒
裝置核心
  • 移除未使用的核心設定並縮減驅動程式大小,節省 0.3 秒
  • 透過 dm-verity 預先讀取最佳化功能節省 0.3 秒
  • 移除不必要的等待/測試,節省 0.15 秒
  • 移除 CONFIG_CC_OPTIMIZE_FOR_SIZE 可節省 0.12 秒
I/O 調整
  • 在正常啟動時節省 2 秒
  • 首次啟動時節省 25 秒
init.*.rc
  • 並行執行初始化指令,節省 1.5 秒
  • 提早啟動 Zygote 可節省 0.25 秒
  • 透過 cpuset 調整,節省 0.22 秒
啟動動畫
  • 在未觸發 fsck 的情況下,啟動時間提早 2 秒,在觸發 fsck 的情況下,啟動時間會大幅增加
  • 在 Pixel XL 上啟用立即關閉啟動動畫功能,可節省 5 秒
SELinux 政策 使用 genfscon 可節省 0.2 秒

最佳化系統啟動載入程式

如要改善啟動時間,請最佳化啟動載入器:

  • 如要記錄:
    • 請停用寫入 UART 的記錄功能,因為大量記錄可能會耗費很長的時間。(在 Google Pixel 裝置上,我們發現這會讓引導程式速度變慢 1.5 秒)。
    • 請只記錄錯誤情況,並考慮使用其他機制將其他資訊儲存在記憶體中,以便擷取。
  • 針對核心解壓縮作業,請考慮使用 LZ4 來支援當代硬體,而非 GZIP (例如修補程式)。請注意,不同的核心壓縮選項可能有不同的載入和解壓縮時間,且某些選項可能比其他選項更適合您的特定硬體。
  • 檢查去抖動/特殊模式輸入的不必要等待時間,並將其降至最低。
  • 將啟動載入程式中耗費的啟動時間,以 cmdline 的形式傳遞至核心。
  • 檢查 CPU 時鐘,並考慮使用平行處理 (需要多核心支援) 來載入核心並初始化 I/O。

提升 I/O 效率

改善 I/O 效率對於縮短開機時間至關重要,因此請在開機後再讀取任何非必要的資料 (在 Google Pixel 上,開機時會讀取約 1.2 GB 的資料)。

調整檔案系統

當檔案從開頭讀取,或區塊依序讀取時,Linux 核心會啟動預讀功能,因此必須特別調整啟動作業的 I/O 調度器參數 (與一般應用程式的工作負載特徵不同)。

支援無縫 (A/B) 更新的裝置,在首次啟動時 (例如 Google Pixel 的 20 秒) 進行檔案系統調整,可帶來極大助益。舉例來說,我們為 Google Pixel 調整了下列參數:

on late-fs
  # boot time fs tune
    # boot time fs tune
    write /sys/block/sda/queue/iostats 0
    write /sys/block/sda/queue/scheduler cfq
    write /sys/block/sda/queue/iosched/slice_idle 0
    write /sys/block/sda/queue/read_ahead_kb 2048
    write /sys/block/sda/queue/nr_requests 256
    write /sys/block/dm-0/queue/read_ahead_kb 2048
    write /sys/block/dm-1/queue/read_ahead_kb 2048

on property:sys.boot_completed=1
    # end boot time fs tune
    write /sys/block/sda/queue/read_ahead_kb 512
    ...

其他

  • 使用核心設定 DM_VERITY_HASH_PREFETCH_MIN_SIZE (預設大小為 128) 開啟 dm-verity 雜湊預先擷取大小。
  • 為提升檔案系統穩定性,並避免每次開機時發生的強制檢查作業,請在 BoardConfig.mk 中設定 TARGET_USES_MKE2FS,以便使用新的 ext4 產生工具。

分析 I/O

如要瞭解啟動期間的 I/O 活動,請使用核心 ftrace 資料 (systrace 也會使用):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

如要細分每個檔案的檔案存取權,請對核心進行下列變更 (僅限開發核心,請勿在正式版核心中使用):

diff --git a/fs/open.c b/fs/open.c
index 1651f35..a808093 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -981,6 +981,25 @@
 }
 EXPORT_SYMBOL(file_open_root);
 
+static void _trace_do_sys_open(struct file *filp, int flags, int mode, long fd)
+{
+       char *buf;
+       char *fname;
+
+       buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       if (!buf)
+               return;
+       fname = d_path(&filp-<f_path, buf, PAGE_SIZE);
+
+       if (IS_ERR(fname))
+               goto out;
+
+       trace_printk("%s: open(\"%s\", %d, %d) fd = %ld, inode = %ld\n",
+                     current-<comm, fname, flags, mode, fd, filp-<f_inode-<i_ino);
+out:
+       kfree(buf);
+}
+
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
 {
 	struct open_flags op;
@@ -1003,6 +1022,7 @@
 		} else {
 			fsnotify_open(f);
 			fd_install(fd, f);
+			_trace_do_sys_open(f, flags, mode, fd);

請使用下列指令碼,協助分析啟動效能。

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py 透過細分啟動程序中的重點步驟,測量啟動時間。
  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace 提供每個檔案的存取權資訊。
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace 提供系統層級的細目資料。

最佳化 init.*.rc

初始化是從核心到架構建立的橋樑,裝置通常會在不同的初始化階段花費幾秒鐘的時間。

並行執行工作

雖然目前的 Android 初始化程序或多或少是單執行緒程序,但您還是可以並行執行某些工作。

  • 在殼層指令碼服務中執行緩慢的指令,並稍後等待特定屬性加入。Android 8.0 使用新的 wait_for_property 指令支援此用途。
  • 找出初始化作業中速度緩慢的作業。系統會記錄初始化指令 exec/wait_for_prop 或任何耗時的動作 (在 Android 8.0 中,任何指令耗時超過 50 毫秒)。例如:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    查看這份記錄檔,或許能發現可改善之處。

  • 提早在關鍵路徑中啟動服務並啟用周邊裝置。舉例來說,某些 SOC 需要先啟動安全性相關服務,才能啟動 SurfaceFlinger。當 ServiceManager 傳回「等待服務」時,請查看系統記錄,這通常表示必須先啟動依附的服務。
  • 移除 init.*.rc 中所有未使用的服務和指令。在初始階段初始化中未使用的任何項目,都應延後至啟動完成時再執行。

注意:屬性服務是初始化程序的一部分,因此如果初始化程序在內建指令中忙碌,在啟動期間呼叫 setproperty 可能會導致長時間延遲。

使用排程器調整

使用排程器調整功能進行早期啟動。以 Google Pixel 為例:

on init
    # boottime stune
    write /dev/stune/schedtune.prefer_idle 1
    write /dev/stune/schedtune.boost 100
    on property:sys.boot_completed=1
    # reset stune
    write /dev/stune/schedtune.prefer_idle 0
    write /dev/stune/schedtune.boost 0

    # or just disable EAS during boot
    on init
    write /sys/kernel/debug/sched_features NO_ENERGY_AWARE
    on property:sys.boot_completed=1
    write /sys/kernel/debug/sched_features ENERGY_AWARE

某些服務可能需要在啟動期間提高優先順序。範例如下:

init.zygote64.rc:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
...

提早啟動 Zygote

採用檔案式加密的裝置可以在 zygote-start 觸發事件中提早啟動 zygote (預設情況下,zygote 會在類別 main 中啟動,這比 zygote-start 晚得多)。執行此操作時,請務必允許 zygote 在所有 CPU 上執行 (因為錯誤的 cpuset 設定可能會強制 zygote 在特定 CPU 上執行)。

停用省電模式

在裝置啟動期間,可停用 UFS 和/或 CPU 節流器等元件的省電設定。

注意:為提高效率,請在充電器模式下啟用省電功能。

on init
    # Disable UFS powersaving
    write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 0
    write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 0
    write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 0
    write /sys/module/lpm_levels/parameters/sleep_disabled Y
on property:sys.boot_completed=1
    # Enable UFS powersaving
    write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 1
    write /sys/module/lpm_levels/parameters/sleep_disabled N
on charger
    # Enable UFS powersaving
    write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 1
    write /sys/class/typec/port0/port_type sink
    write /sys/module/lpm_levels/parameters/sleep_disabled N

延後非關鍵初始化

非關鍵初始化作業 (例如 ZRAM) 可延後至 boot_complete

on property:sys.boot_completed=1
   # Enable ZRAM on boot_complete
   swapon_all /vendor/etc/fstab.${ro.hardware}

最佳化啟動動畫

請參考下列提示,改善開機動畫。

設定提早開始

Android 8.0 可在掛載使用者資料區隔之前,提早啟動啟動動畫。不過,即使在 Android 8.0 中使用新的 ext4 工具鍊,基於安全考量,fsck 仍會定期觸發,導致啟動 bootanimation 服務的時間延遲。

如要提早啟動 bootanimation,請將 fstab 掛載分為兩個階段:

  • 在早期階段,請只掛載不需要執行檢查的區段 (例如 system/vendor/),然後啟動啟動動畫服務及其依附元件 (例如 servicemanager 和 surfaceflinger)。
  • 在第二階段,掛載需要執行檢查的分區 (例如 data/)。

無論是否使用 fsck,啟動動畫都會以更快的速度 (且在固定時間內) 開始。

完成清潔

收到退出信號後,開機動畫會播放最後一部分,這部分的長度可能會延長開機時間。快速啟動的系統不需要長時間的動畫,因為這可能會有效隱藏任何改善項目。建議您縮短重複迴圈和結尾。

最佳化 SELinux

請參考下列提示,將 SELinux 最佳化,以縮短啟動時間。

  • 使用簡潔的規則運算式。在 file_contexts 中比對 sys/devices 的 SELinux 政策時,不當格式的規則運算式可能會造成大量額外負擔。舉例來說,正規表示式 /sys/devices/.*abc.*(/.*)? 會誤將所有包含「abc」的 /sys/devices 子目錄強制掃描,因此會同時比對 /sys/devices/abc/sys/devices/xyz/abc。將這個規則運算式改為 /sys/devices/[^/]*abc[^/]*(/.*)? 後,系統只會比對 /sys/devices/abc
  • 將標籤移至 genfscon。這項現有的 SELinux 功能會將檔案比對前置字串傳遞至 SELinux 二進位檔中的核心,核心會將這些字串套用至核心產生的檔案系統。這也能協助修正標示錯誤的核心建立檔案,避免在重新標示發生之前,使用者空間程序嘗試存取這些檔案時發生競爭條件。

工具和方法

使用下列工具,協助您收集最佳化目標的資料。

Bootchart

Bootchart 會提供整個系統所有程序的 CPU 和 I/O 負載細目。這項工具不需要重建系統映像檔,而且可用於快速進行健全性檢查,再深入瞭解 systrace。

如要啟用 bootchart:

adb shell 'touch /data/bootchart/enabled'
adb reboot

開機後,擷取啟動圖表:

$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh

完成後,請刪除 /data/bootchart/enabled,以免每次都收集資料。

如果 bootchart 無法運作,且您收到 bootchart.png 不存在的錯誤訊息,請執行下列操作:
  1. 執行下列指令:
          sudo apt install python-is-python3
          cd ~/Documents
          git clone https://github.com/xrmx/bootchart.git
          cd bootchart/pybootchartgui
          mv main.py.in main.py
        
  2. 更新 $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh,指向 pybootchartgui 的本機副本 (位於 ~/Documents/bootchart/pybootchartgui.py)

Systrace

Systrace 可在開機期間收集核心和 Android 追蹤記錄。透過 systrace 視覺化資訊,您可以分析啟動期間的特定問題。(不過,如要檢查整個啟動期間的平均數量或累積數量,直接查看核心追蹤記錄會比較容易)。

如何在啟動期間啟用 systrace:

  • frameworks/native/cmds/atrace/atrace.rc 中變更:
      write /sys/kernel/debug/tracing/tracing_on 0
      write /sys/kernel/tracing/tracing_on 0

    到:

      #    write /sys/kernel/debug/tracing/tracing_on 0
      #    write /sys/kernel/tracing/tracing_on 0
  • 這樣一來即可啟用追蹤功能 (預設為停用)。

  • device.mk 檔案中,新增下列行:
    PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
    PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  • 在裝置 BoardConfig.mk 檔案中,新增下列內容:
    BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
  • 如要進行詳細的 I/O 分析,請一併新增 block、ext4 和 f2fs。

  • 在特定裝置的 init.rc 檔案中,新增下列內容:
    on property:sys.boot_completed=1          // This stops tracing on boot complete
    write /d/tracing/tracing_on 0
    write /d/tracing/events/ext4/enable 0
    write /d/tracing/events/f2fs/enable 0
    write /d/tracing/events/block/enable 0
  • 開機後,擷取追蹤記錄:

    adb root && adb shell atrace --async_stop -z -c -o /data/local/tmp/boot_trace
    adb pull /data/local/tmp/boot_trace
    $ANDROID_BUILD_TOP/external/chromium-trace/systrace.py --from-file=boot_trace