本文件提供合作夥伴指南,說明如何改善特定 Android 裝置的啟動時間。啟動時間是系統效能的關鍵要素,因為使用者必須等待啟動程序完成,才能使用裝置。對於車輛等經常發生冷開機的裝置而言,快速開機時間至關重要 (沒有人喜歡為了輸入導航目的地而等待數十秒)。
Android 8.0 支援多項元件的多項改善功能,可縮短啟動時間。下表列出這些效能改善項目 (根據 Google Pixel 和 Pixel XL 裝置的測量結果)。
元件 | 提升幅度 |
---|---|
系統啟動載入程式 |
|
裝置核心 |
|
I/O 調整 |
|
init.*.rc |
|
啟動動畫 |
|
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.png
不存在的錯誤訊息,請執行下列操作:
- 執行下列指令:
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
- 更新
$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
- 在特定裝置的
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
這樣一來即可啟用追蹤功能 (預設為停用)。
如要進行詳細的 I/O 分析,請一併新增 block、ext4 和 f2fs。