優化啟動時間

本文檔為合作夥伴提供了改善特定 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 秒
輸入/輸出調整
  • 正常啟動節省 2 秒
  • 首次啟動節省 25 秒
初始化.*.rc
  • 透過並行 init 指令節省了 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(範例patch )。請記住,不同的核心壓縮選項可能具有不同的載入和解壓縮時間,並且某些選項可能比其他選項更適合您的特定硬體。
  • 檢查去抖動/特殊模式進入的不必要的等待時間並將其最小化。
  • 將引導程式中花費的引導時間作為命令列傳遞給核心。
  • 檢查 CPU 時脈並考慮核心載入和初始化 I/O 的並行化(需要多核心支援)。

優化 I/O 效率

提高 I/O 效率對於縮短啟動時間至關重要,讀取任何不必要的內容應推遲到啟動後(在 Google Pixel 上,啟動時讀取約 1.2GB 的資料)。

調整檔案系統

當從頭開始讀取檔案或按順序讀取區塊時,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 開啟 dm-verity 哈希預取大小(預設大小為 128)。
  • 為了獲得更好的檔案系統穩定性並減少每次啟動時發生的強制檢查,請透過在 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

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

並行運行任務

雖然目前的 Android init 或多或少是一個單執行緒進程,但您仍然可以並行執行一些任務。

  • 在 shell 腳本服務中執行慢速命令,然後透過等待特定屬性來加入該命令。 Android 8.0 透過新的wait_for_property指令支援此用例。
  • 識別 init 中的緩慢操作。系統會記錄 init 指令 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 中所有未使用的服務和指令。早期初始化中未使用的任何內容都應推遲到引導完成。

注意:屬性服務是 init 進程的一部分,因此如果 init 忙於內建命令,則在引導期間呼叫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-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 以縮短啟動時間。

  • 使用乾淨的正規表示式(regex) 。當符合file_contextssys/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 提供整個系統所有進程的 CPU 和 I/O 負載細分。它不需要重建系統映像,並且可以在深入 systrace 之前用作快速健全性檢查。

若要啟用引導圖:

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 允許在啟動期間收集核心和 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 分析,還需要添加區塊以及 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