Android 8.0 開機時間優化


Android 8.0 開機時間優化

背景

在嵌入式行業中,有些搭載 了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 通過並行運行 init 命令節省了 1.5 秒
通過及早啟動 zygote 節省了 0.25 秒
通過 cpuset 調整節省了 0.22 秒
啟動動畫 在未觸發 fsck 的情況下,啟動動畫的開始時間提前了 2 秒,而觸發 fsck 時啟動動畫則大得多
通過立即關閉啟動動畫在 Pixel XL 上節省了 5 秒
SELinux 政策 通過 genfscon 節省了 0.2 秒

優化引導加載程序

要優化引導加載程序以縮短啟動時間,請遵循以下做法:

1、對於日志記錄:

  • 停止向 UART 寫入日志,因為如果日志記錄很多,則可能需要很長時間來處理。(在 Google Pixel 設備上,我們發現這會使引導加載程序的速度減慢 1.5 秒)量產軟件關掉UART日志。
  • 僅記錄錯誤情況,並考慮將其他信息存儲到具有單獨檢索機制的內存中。

2、對於內核解壓縮,請考慮為當代硬件使用 LZ4 而非 GZIP(例如補丁程序)。

請注意,不同的內核壓縮選項具有不同的加載和解壓縮時間,對於特定硬件,某些選項可能比其他選項更適合。

3、檢查進入去抖動/特殊模式過程中是否有不必要的等待時間,並最大限度地減少此類時間。

4、將在引導加載程序中花費的啟動時間以命令行的形式傳遞到內核。

5、檢查 CPU 時鍾並考慮內核加載和初始化 I/O 並行進行(需要多核支持)。

優化內核

請按照以下提示優化內核以縮短啟動時間。

最大限度地減少設備 defconfig

最大限度地減少內核配置可以減小內核大小,從而更快速地進行加載、解壓縮、初始化並縮小受攻擊面。

要優化設備 defconfig,請執行以下操作:

1、識別未使用的驅動程序。查看 /dev/sys 目錄,並查找帶有常規 SELinux 標簽的節點(這種標簽表示相應節點未配置為可由用戶空間訪問)。如果找到此類節點,請將其移除。

2、取消設置未使用的配置。查看由內核版本生成的 .config 文件,以明確取消設置所有已默認啟用但並未使用的配置。

例如,我們從 Google Pixel 中移除了以下未使用的配置:

CONFIG_ANDROID_LOGGER=y
CONFIG_IMX134=y
CONFIG_IMX132=y
CONFIG_OV9724=y
CONFIG_OV5648=y
CONFIG_GC0339=y
CONFIG_OV8825=y
CONFIG_OV8865=y
CONFIG_s5k4e1=y
CONFIG_OV12830=y
CONFIG_USB_EHCI_HCD=y
CONFIG_IOMMU_IO_PGTABLE_FAST_SELFTEST=y
CONFIG_IKCONFIG=y
CONFIG_RD_BZIP2=y
CONFIG_RD_LZMA=y
CONFIG_TI_DRV2667=y
CONFIG_CHR_DEV_SCH=y
CONFIG_MMC=y
CONFIG_MMC_PERF_PROFILING=y
CONFIG_MMC_CLKGATE=y
CONFIG_MMC_PARANOID_SD_INIT=y
CONFIG_MMC_BLOCK_MINORS=32
CONFIG_MMC_TEST=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_PLTFM=y
CONFIG_MMC_SDHCI_MSM=y
CONFIG_MMC_SDHCI_MSM_ICE=y
CONFIG_MMC_CQ_HCI=y
CONFIG_MSDOS_FS=y
# CONFIG_SYSFS_SYSCALL is not set
CONFIG_EEPROM_AT24=y
# CONFIG_INPUT_MOUSEDEV_PSAUX is not set
CONFIG_INPUT_HBTP_INPUT=y
# CONFIG_VGA_ARB is not set
CONFIG_USB_MON=y
CONFIG_USB_STORAGE_DATAFAB=y
CONFIG_USB_STORAGE_FREECOM=y
CONFIG_USB_STORAGE_ISD200=y
CONFIG_USB_STORAGE_USBAT=y
CONFIG_USB_STORAGE_SDDR09=y
CONFIG_USB_STORAGE_SDDR55=y
CONFIG_USB_STORAGE_JUMPSHOT=y
CONFIG_USB_STORAGE_ALAUDA=y
CONFIG_USB_STORAGE_KARMA=y
CONFIG_USB_STORAGE_CYPRESS_ATACB=y
CONFIG_SW_SYNC_USER=y
CONFIG_SEEMP_CORE=y
CONFIG_MSM_SMEM_LOGGING=y
CONFIG_IOMMU_DEBUG=y
CONFIG_IOMMU_DEBUG_TRACKING=y
CONFIG_IOMMU_TESTS=y
CONFIG_MOBICORE_DRIVER=y
# CONFIG_DEBUG_PREEMPT is not set

3、移除導致每次啟動時運行不必要測試的配置。雖然此類配置(即 CONFIG_IOMMU_IO_PGTABLE_FAST_SELFTEST)在開發過程中很有用,但應從正式版內核中移除。

最大限度地減小驅動程序大小

如果未使用相應功能,則可以移除設備內核中的某些驅動程序,以便進一步減小內核大小。

例如,如果 WLAN通過 PCIe 連接,則不會用到 SDIO 支持,因此應在編譯時將其移除。

有關詳情,請參閱 Google Pixel 內核:網絡:無線:CNSS:添加選項以停用 SDIO 支持。

移除針對大小的編譯器優化

移除 CONFIG_CC_OPTIMIZE_FOR_SIZE 的內核配置。此標記是在最初假設較小的代碼大小會產生熱緩存命中(因此速度更快)時引入的。然而,隨着現代移動 SoC 變得更加強大,這一假設不再成立。

此外,移除此標記可以使編譯器針對未初始化的變量發出警告,當存在 CONFIG_CC_OPTIMIZE_FOR_SIZE 標記時,這一功能在 Linux 內核中是停用的(僅這一項更改就已幫助我們在某些 Android 設備驅動程序中發現了很多有意義的錯誤)。

延遲初始化

很多進程都在設備啟動期間啟動,但只有關鍵路徑 (bootloader > kernel > init > file system mount > zygote > system server) 中的組件才會直接影響啟動時間。在內核啟動期間執行 initcall 來識別啟動速度緩慢且對啟動 init 進程不重要的外設/組件,然后通過將這些外設/組件移入可加載的內核模塊將其延遲到啟動過程的后期來啟動。移入異步設備/驅動程序探測還有助於並行啟動內核 > init 重要路徑中啟動速度緩慢的組件。

BoardConfig - common . mk :
    BOARD_KERNEL_CMDLINE += initcall_debug ignore_loglevel

driver :
    . probe_type = PROBE_PREFER_ASYNCHRONOUS ,

注意:必須添加 EPROBEDEFER 支持來妥善解決驅動程序依賴問題。

優化 I/O 效率

提高 I/O 效率對縮短啟動時間來說至關重要,對任何不必要內容的讀取都應推遲到啟動之后再進行(在 Google Pixel 上,啟動時大約要讀取 1.2GB 的數據)。

調整文件系統

當從頭開始讀取某個文件或依序讀取塊時,預讀的 Linux 內核便會啟動,這就需要調整專門用於啟動的 I/O 調度程序參數(與普通應用的工作負載特性不同)。

支持無縫 (A/B) 更新的設備在首次啟動時會極大地受益於文件系統調整(例如,Google Pixel 的啟動時間縮短了 20 秒)。

例如,我們為 Google Pixel 調整了以下參數:

# 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 );

使用以下腳本來幫助分析啟動性能。

  • packages/services/Car/tools/bootanalyze/bootanalyze.py:負責衡量啟動時間,並詳細分析啟動過程中的重要步驟。
  • packages/services/Car/tools/io_analysis/check_file_read.py boot_trace:提供每個文件的訪問信息。
  • packages/services/Car/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 返回“wait for service”(等待服務)時查看系統日志 - 這通常表明必須先啟動依賴服務。

  • 移除 init.*.rc 中所有未使用的服務和命令。只要是早期階段的 init 中沒有使用的服務和命令,都應推遲到啟動完成后再使用。

注意:“屬性”服務是 init 進程的一部分,因此,在啟動期間調用 setproperty 可能會導致較長時間的延遲(如果 init 忙於執行內置命令)。

使用調度程序調整

使用調度程序調整,以便及早啟動設備。以下是取自 Google Pixel 的示例:

on init
  # update cpusets now that processors are up
  write /dev/cpuset/top-app/cpus 0-3
  write /dev/cpuset/foreground/cpus 0-3
  write /dev/cpuset/foreground/boost/cpus 0-3
  write /dev/cpuset/background/cpus 0-3
  write /dev/cpuset/system-background/cpus 0-3
  # set default schedTune value for foreground/top-app (only affects EAS)
  write /dev/stune/foreground/schedtune.prefer_idle 1
  write /dev/stune/top-app/schedtune.boost 10
  write /dev/stune/top-app/schedtune.prefer_idle 1

部分服務在啟動過程中可能需要進行優先級提升。例如:

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 影響。

干凈利落地結束

在收到退出信號后,bootanimation 會播放最后一部分,而這一部分的長度會延長啟動時間。快速啟動的系統不需要很長的動畫,如果啟動動畫很長,在很大程度上就體現不出所做的任何改進。我們建議縮短循環播放和結尾的時間。

優化 SELinux

請按照以下提示優化 SELinux 以縮短啟動時間。

  • 使用簡潔的正則表達式 (regex)。在為 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 以防止每次都收集日期數據。

systrace

systrace 允許在啟動期間收集內核和 Android 跟蹤記錄。 systrace 的可視化可以幫助分析啟動過程中的具體問題。(不過,要查看整個啟動過程中的平均數量或累計數量,直接查看內核跟蹤記錄更為方便)。

要在啟動過程中啟用 systrace(這將啟用跟蹤功能,默認處於停用狀態),請在 frameworks/native/atrace/atrace.rc 中,將

write /sys/kernel/debug/tracing/tracing_on 0

更改為:

#write /sys/kernel/debug/tracing/tracing_on 0

device.mk 文件中,添加下面這行內容:

PRODUCT_PROPERTY_OVERRIDES +=    debug . atrace . tags . enableflags = 802922

在設備 BoardConfig.mk 文件中,添加以下內容:

BOARD_KERNEL_CMDLINE := ... trace_buf_size = 64M trace_event = sched_wakeup , sched_switch , sched_blocked_reason , sched_cpu_hotplug

要獲得詳細的 I/O 分析,還需要添加塊和 ext4;在設備專用的 init.rc 文件中,進行以下更改:

  • on property:sys.boot_completed=1(這會在啟動完成后停止跟蹤)
  • write /d/tracing/tracing_on 0
  • write /d/tracing/events/ext4/enable 0
  • write /d/tracing/events/block/enable 0

在設備啟動后,獲取跟蹤記錄:

adb root && adb shell "cat /d/tracing/trace" < boot_trace ./external/chromium-trace/catapult/tracing/bin/trace2html boot_trace --output boot_trace.html 

注意:Chrome 無法處理過大的文件。請考慮使用 tailheadgrep 分割 boot_trace 文件,以獲得必要的部分。由於事件過多,I/O 分析通常需要直接分析獲取的 boot_trace


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM