Android 8.0 支持一系列組件的多項改進炼蛤,因而可以縮短啟動時間。下表對這些性能改進(在 Google Pixel 和 Pixel XL 設備上測得)進行了總結(jié)蝶涩。
引導加載程序
- 通過移除 UART 日志節(jié)省了 1.6 秒
- 通過從 GZIP 更改為 LZ4 節(jié)省了 0.4 秒
設備內(nèi)核
- 通過移除不使用的內(nèi)核配置和減少驅(qū)動程序大小節(jié)省了 0.3 秒
- 通過 dm-verity 預提取優(yōu)化節(jié)省了 0.3 秒
- 通過移除驅(qū)動程序中不必要的等待/測試理朋,節(jié)省了 0.15 秒
- 通過移除 CONFIG_CC_OPTIMIZE_FOR_SIZE,節(jié)省了 0.12 秒
I/O 調(diào)整
- 正常啟動時間節(jié)省了 2 秒
- 首次啟動時間節(jié)省了 25 秒
init.*.rc
- 通過并行運行 init 命令節(jié)省了 1.5 秒
- 通過及早啟動 zygote 節(jié)省了 0.25 秒
- 通過 cpuset 調(diào)整節(jié)省了 0.22 秒
啟動動畫
- 在未觸發(fā) fsck 的情況下绿聘,啟動動畫的開始時間提前了 2 秒嗽上,而觸發(fā) fsck 時啟動動畫則大得多
- 通過立即關(guān)閉啟動動畫在 Pixel XL 上節(jié)省了 5 秒
SELinux 政策 | 通過 genfscon 節(jié)省了 0.2 秒
優(yōu)化引導加載程序
要優(yōu)化引導加載程序以縮短啟動時間,請遵循以下做法:
- 對于日志記錄:
- 停止向 UART 寫入日志熄攘,因為如果日志記錄很多兽愤,則可能需要很長時間來處理。(在 Google Pixel 設備上,我們發(fā)現(xiàn)這會使引導加載程序的速度減慢 1.5 秒)烹看。
- 僅記錄錯誤情況国拇,并考慮將其他信息存儲到具有單獨檢索機制的內(nèi)存中。
- 對于內(nèi)核解壓縮惯殊,請考慮為當代硬件使用 LZ4 而非 GZIP(例如補丁程序)酱吝。請注意,不同的內(nèi)核壓縮選項具有不同的加載和解壓縮時間土思,對于特定硬件务热,某些選項可能比其他選項更適合。
- 檢查進入去抖動/特殊模式過程中是否有不必要的等待時間己儒,并最大限度地減少此類時間崎岂。
- 將在引導加載程序中花費的啟動時間以命令行的形式傳遞到內(nèi)核。
- 檢查 CPU 時鐘并考慮內(nèi)核加載和初始化 I/O 并行進行(需要多核支持)闪湾。
優(yōu)化內(nèi)核
請按照以下提示優(yōu)化內(nèi)核以縮短啟動時間冲甘。
最大限度地減少設備 defconfig
最大限度地減少內(nèi)核配置可以減小內(nèi)核大小,從而更快速地進行加載途样、解壓縮江醇、初始化并縮小受攻擊面。要優(yōu)化設備 defconfig何暇,請執(zhí)行以下操作:
識別未使用的驅(qū)動程序陶夜。查看 /dev 和 /sys 目錄,并查找?guī)в谐R?guī) SELinux 標簽的節(jié)點(這種標簽表示相應節(jié)點未配置為可由用戶空間訪問)裆站。 如果找到此類節(jié)點条辟,請將其移除。
取消設置未使用的配置
查看由內(nèi)核版本生成的 .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
移除導致每次啟動時運行不必要測試的配置。雖然此類配置(即 CONFIG_IOMMU_IO_PGTABLE_FAST_SELFTEST)在開發(fā)過程中很有用肩袍,但應從正式版內(nèi)核中移除厂僧。
最大限度地減小驅(qū)動程序大小:
如果未使用相應功能了牛,則可以移除設備內(nèi)核中的某些驅(qū)動程序颜屠,以便進一步減小內(nèi)核大小。例如鹰祸,如果 WLAN 通過 PCIe 連接甫窟,則不會用到 SDIO 支持,因此應在編譯時將其移除蛙婴。有關(guān)詳情粗井,請參閱 Google Pixel 內(nèi)核:網(wǎng)絡:無線:CNSS:添加選項以停用 SDIO 支持。
移除針對大小的編譯器優(yōu)化:
移除 CONFIG_CC_OPTIMIZE_FOR_SIZE 的內(nèi)核配置。此標記是在最初假設較小的代碼大小會產(chǎn)生熱緩存命中(因此速度更快)時引入的浇衬。然而懒构,隨著現(xiàn)代移動 SoC 變得更加強大,這一假設不再成立耘擂。
此外胆剧,移除此標記可以使編譯器針對未初始化的變量發(fā)出警告,當存在 CONFIG_CC_OPTIMIZE_FOR_SIZE 標記時醉冤,這一功能在 Linux 內(nèi)核中是停用的(僅這一項更改就已幫助我們在某些 Android 設備驅(qū)動程序中發(fā)現(xiàn)了很多有意義的錯誤)秩霍。
延遲初始化
很多進程都在設備啟動期間啟動,但只有關(guān)鍵路徑 (bootloader > kernel > init > file system mount > zygote > system server) 中的組件才會直接影響啟動時間蚁阳。使用早期內(nèi)核日志來識別對啟動 init 進程不重要的外設/組件铃绒,然后將這些外設/組件延遲到啟動過程的后期來啟動。
優(yōu)化 I/O 效率
提高 I/O 效率對縮短啟動時間來說至關(guān)重要螺捐,對任何不必要內(nèi)容的讀取都應推遲到啟動之后再進行(在 Google Pixel 上颠悬,啟動時大約要讀取 1.2GB 的數(shù)據(jù))。
調(diào)整文件系統(tǒng)
當從頭開始讀取某個文件或依序讀取塊時定血,預讀的 Linux 內(nèi)核便會啟動椿疗,這就需要調(diào)整專門用于啟動的 I/O 調(diào)度程序參數(shù)(與普通應用的工作負載特性不同)。
支持無縫 (A/B) 更新的設備在首次啟動時會極大地受益于文件系統(tǒng)調(diào)整(例如糠悼,Google Pixel 的啟動時間縮短了 20 秒)。例如浅乔,為 Google Pixel 調(diào)整了以下參數(shù):
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
其他
- 使用內(nèi)核配置 DM_VERITY_HASH_PREFETCH_MIN_SIZE(默認大小為 128)來啟用 dm-verity 哈希預提取大小倔喂。
- 為了提升文件系統(tǒng)穩(wěn)定性及取消每次啟動時的強制檢查,請在 BoardConfig.mk 中設置 TARGET_USES_MKE2FS靖苇,以使用新的 ext4 生成工具席噩。
分析 I/O
要了解啟動過程中的 I/O 活動,請使用內(nèi)核 ftrace 數(shù)據(jù)(systrace 也使用該數(shù)據(jù)):
trace_event=block,ext4 in BOARD_KERNEL_CMDLINE
要針對每個文件細分文件訪問權(quán)限贤壁,請對內(nèi)核進行以下更改(僅限開發(fā)版內(nèi)核悼枢;請勿在正式版內(nèi)核中應用這些更改):
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`:提供系統(tǒng)級細分。
優(yōu)化 init.*.rc
Init 是從內(nèi)核到框架建立之前的銜接過程名船,設備通常會在不同的 init 階段花費幾秒鐘時間绰上。
并行運行任務
雖然當前的 Android init 差不多算是一種單線程進程,但您仍然可以并行執(zhí)行一些任務渠驼。
在 Shell 腳本服務中執(zhí)行緩慢命令蜈块,然后通過等待特定屬性,在稍后加入。Android 8.0 通過新的wait_for_property命令支持此用例百揭。
識別 init 中的緩慢操作爽哎。系統(tǒng)會記錄 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
查看此日志可能會發(fā)現(xiàn)可以改進的機會课锌。
啟動服務并及早啟用關(guān)鍵路徑中的外圍設備。例如盹舞,有些 SOC 需要先啟動安全相關(guān)服務产镐,然后再啟動 SurfaceFlinger。在 ServiceManager 返回“wait for service”(等待服務)時查看系統(tǒng)日志 - 這通常表明必須先啟動依賴服務踢步。
移除 init.*.rc 中所有未使用的服務和命令癣亚。只要是早期階段的 init 中沒有使用的服務和命令,都應推遲到啟動完成后再使用获印。
使用調(diào)度程序調(diào)整
使用調(diào)度程序調(diào)整述雾,以便及早啟動設備。以下是取自 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
部分服務在啟動過程中可能需要進行優(yōu)先級提升兼丰。例如:
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 觸發(fā)器的早期階段啟動 zygote(默認情況下玻孟,zygote 會在 main 類中啟動,比 zygote-start 晚得多)鳍征。這樣做時黍翎,請確保允許 zygote 在所有 CPU 中運行(因為錯誤的 cpuset 設置可能會強制 zygote 在特定 CPU 中運行)。
優(yōu)化啟動動畫
請按照以下提示來優(yōu)化啟動動畫艳丛。
配置為及早啟動
Android 8.0 支持在裝載用戶數(shù)據(jù)分區(qū)之前匣掸,及早啟動動畫。然而氮双,即使 Android 8.0 中使用了新的 ext4 工具鏈碰酝,系統(tǒng)也會出于安全原因定期觸發(fā) fsck,導致啟動 bootanimation 服務時出現(xiàn)延遲戴差。
為了使 bootanimation 及早啟動送爸,請將 fstab 裝載分為以下兩個階段:
在早期階段,僅裝載不需要運行檢查的分區(qū)(例如 system/ 和 vendor/)暖释,然后啟動 bootanimation 服務及其依賴服務(例如 servicemanager 和 surfaceflinger)袭厂。
在第二個階段,裝載需要運行檢查的分區(qū)(例如 data/)球匕。
啟動動畫將會更快速地啟動(且啟動時間恒定)嵌器,不受 fsck 影響。
干凈利落地結(jié)束
在收到退出信號后谐丢,bootanimation 會播放最后一部分爽航,而這一部分的長度會延長啟動時間蚓让。快速啟動的系統(tǒng)不需要很長的動畫讥珍,如果啟動動畫很長历极,在很大程度上就體現(xiàn)不出所做的任何改進。我們建議縮短循環(huán)播放和結(jié)尾的時間衷佃。
優(yōu)化 SELinux
請按照以下提示優(yōu)化 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 會成為匹配項
將標簽移動到(https://selinuxproject.org/page/FileStatements#genfscon)克婶。這一現(xiàn)有的 SELinux 功能會將文件匹配前綴傳遞到 SELinux 二進制文件的內(nèi)核中筒严,而內(nèi)核會將這些前綴應用于內(nèi)核生成的文件系統(tǒng)。這也有助于修復錯誤標記的內(nèi)核創(chuàng)建的文件情萤,從而防止用戶空間進程之間可能出現(xiàn)的爭用情況(試圖在重新標記之前訪問這些文件)鸭蛙。
工具和方法
請使用以下工具來幫助您收集用于優(yōu)化目標的數(shù)據(jù)。
bootchart
bootchart 可為整個系統(tǒng)提供所有進程的 CPU 和 I/O 負載細分筋岛。該工具不需要重建系統(tǒng)映像娶视,可以用作進入 systrace 之前的快速健全性檢查。
要啟用 bootchart睁宰,請運行以下命令:
adb shell 'touch /data/bootchart/enabled'
adb reboot
在設備啟動后肪获,獲取啟動圖表:
ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh
完成后,請刪除 /data/bootchart/enabled 以防止每次都收集日期數(shù)據(jù)勋陪。
systrace
systrace 允許在啟動期間收集內(nèi)核和 Android 跟蹤記錄。 systrace 的可視化可以幫助分析啟動過程中的具體問題硫兰。(不過诅愚,要查看整個啟動過程中的平均數(shù)量或累計數(shù)量,直接查看內(nèi)核跟蹤記錄更為方便)劫映。
要在啟動過程中啟用 systrace违孝,請執(zhí)行以下操作:
write /sys/kernel/debug/tracing/tracing_on 0
更改為:
write /sys/kernel/debug/tracing/tracing_on 0
這將啟用跟蹤功能(默認處于停用狀態(tài))。
在 device.mk 文件中泳赋,添加下面一行內(nèi)容:
PRODUCT_PROPERTY_OVERRIDES += debug.atrace.tags.enableflags=802922
在設備 BoardConfig.mk 文件中雌桑,添加以下內(nèi)容:
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