對于任何操作系統(tǒng)來講谭梗,開機(jī)時間的優(yōu)化都是一個很關(guān)鍵的工作忘晤。如果用戶每次啟動設(shè)備都需要等待很長的時間,那么其用戶體驗(yàn)是很差的默辨。本文從Android12出發(fā)德频,分以下三部分闡述Android系統(tǒng)的開機(jī)優(yōu)化:
- Android開機(jī)過程
- 分析開機(jī)時間
- 開機(jī)速度優(yōu)化實(shí)踐
備注
1. 文中所有的代碼都省略了無關(guān)部分,并且省略了省略號缩幸;
2. 由于作者能力有限壹置,難免會有不正確或者不完善的地方。歡迎大家指正表谊。
1. Android開機(jī)過程
要想進(jìn)行開機(jī)速度的優(yōu)化钞护,首先需要了解開機(jī)的詳細(xì)流程。開機(jī)過程從CPU上電開始爆办,到鎖屏界面顯示出來結(jié)束难咕。下圖為比較簡潔的開機(jī)流程圖。
再來看一張比較詳細(xì)的開機(jī)流程圖
總的來說距辆,開機(jī)過程分為以下六主要子過程
1.1 Boot ROM
Boot ROM是硬編碼在CPU內(nèi)部固定地址的一段ROM(在一些較老的系統(tǒng)上也可能使用外掛Boot ROM(相對CPU來說))余佃,這塊代碼是由CPU制造商提供。當(dāng)用戶按下電源鍵或者系統(tǒng)重啟之后跨算,觸發(fā)CPU上電動作爆土,此時其它硬件還未初始化,然而這塊ROM就已經(jīng)可讀了诸蚕。CPU首先執(zhí)行PBL(Primary Boot Loader步势,主引導(dǎo)加載程序,固化在ROM上)代碼背犯。在必要的硬件初始化之后坏瘩,Boot ROM開始加載Bootloader到RAM中,然后PC指針跳過去執(zhí)行bootloader漠魏。在加載 Bootloader之前倔矾,PBL也可以進(jìn)行驗(yàn)證。如果驗(yàn)證無法通過柱锹,則不會加載運(yùn)行Bootloader破讨,從而開機(jī)失敗。
A. CPU剛上電時奕纫,CPU 處于未初始化狀態(tài),還沒有設(shè)定內(nèi)部時鐘烫沙,此時只有內(nèi)部 RAM 可用匹层。當(dāng)電源穩(wěn)定后會開始執(zhí)行 Boot ROM 代碼。Boot ROM通過系統(tǒng)寄存器映射到 ASIC (Application Specific Integrated Circuit, 即專用集成電路,是指應(yīng)特定用戶要求和特定電子系統(tǒng)的需要而設(shè)計(jì)升筏、制造的集成電路)中的物理區(qū)域來找到boot media撑柔,進(jìn)而可以找到Bootloader
B. boot media序列確定之后,Boot ROM 加載 Bootloader到內(nèi)部 RAM 中您访,之后Boot ROM代碼會跳到Bootloader
1.2Bootloader
Bootloader是一個特殊的獨(dú)立于內(nèi)核的程序铅忿,是CPU復(fù)位后進(jìn)入操作系統(tǒng)之前執(zhí)行的一段代碼。Bootloader完成由硬件啟動到操作系統(tǒng)啟動的過渡灵汪,從而為操作系統(tǒng)提供基本的運(yùn)行環(huán)境檀训,如初始化CPU、時鐘享言、堆棧峻凫、存儲器系統(tǒng)等。Bootloader功能類似于PC機(jī)的BIOS程序,其代碼與CPU芯片的內(nèi)核結(jié)構(gòu)览露、具體型號荧琼、應(yīng)用系統(tǒng)的配置及使用的操作系統(tǒng)等因素有關(guān),因此不可能有通用的bootloader,開發(fā)時需要用戶根據(jù)具體情況進(jìn)行移植差牛。嵌入式Linux系統(tǒng)中常用的Bootloader有armboot命锄、redboot、blob偏化、U-Boot脐恩、Bios-lt、Bootldr等夹孔,其中U-Boot是當(dāng)前比較流行被盈,功能比較強(qiáng)大的Bootloader,可以支持多種體系結(jié)構(gòu)搭伤,但相對也比較復(fù)雜只怎。硬件初始化完成之后,Bootloader將boot.img(kernel + ramdisk(ramdisk.img中主要是存放android啟動后第一個用戶進(jìn)程init可執(zhí)行文件和init.*.rc等相關(guān)啟動腳本以及sbin目錄下的adbd工具))從flash上copy到RAM里面怜俐,然后CPU執(zhí)行轉(zhuǎn)向kernel身堡。
A. Bootloader第一階段首先會檢測和設(shè)置外部RAM
B. 外部 RAM可用之后,將Bootloader代碼加載到外部RAM中
C. Bootloader第二階段包含了設(shè)置文件系統(tǒng)拍鲤,內(nèi)存贴谎,網(wǎng)絡(luò)等等。
D. Bootloader查找Linux內(nèi)核并將其從boot media (或者其他地方季稳,這取決于系統(tǒng)配置) 加載到 RAM 中擅这,并且會配置一些內(nèi)核啟動時需要的啟動參數(shù)
E. Bootloader執(zhí)行完之后會跳轉(zhuǎn)到 Linux 內(nèi)核執(zhí)行
一般也可將Bootloader程序的執(zhí)行分為兩個階段,如下圖所示
執(zhí)行Bootloader程序過程中景鼠,如果鏡像驗(yàn)證失敗仲翎、BootLinux (&Info) 函數(shù)啟動失敗或者接收到啟動至 fastboot 的命令(比如使用 adb reboot bootloader進(jìn)行重啟、在啟動時按下了電源鍵+下音量鍵組合)時,會進(jìn)入到Fastboot模式(Fastboot 是一種電腦通過USB數(shù)據(jù)線對手機(jī)固件進(jìn)行刷寫溯香、擦除/格式化鲫构、調(diào)試、傳輸各種指令的固件通信協(xié)議, 俗稱線刷模式或快速引導(dǎo)模式)玫坛。
1.3Kernel
Android kernel基于上游 Linux LTS (Linux Long Term Supported结笨,長期支持) 內(nèi)核。在 Google湿镀,LTS 內(nèi)核會與 Android 專用補(bǔ)丁結(jié)合炕吸,形成所謂的“Android 通用內(nèi)核 (ACK,Android Common Kernel)”肠骆。較新的 ACK(版本 5.4 及更高版本)也稱為 GKI (Generic Kernel Image算途,通用內(nèi)核鏡像 )內(nèi)核。 GKI項(xiàng)目通過統(tǒng)一核心內(nèi)核并將 SoC 和板級支持從核心內(nèi)核移至可加載模塊中蚀腿,解決了內(nèi)核碎片化問題嘴瓤。GKI 內(nèi)核為內(nèi)核模塊提供了穩(wěn)定的內(nèi)核模塊接口 (KMI),因此模塊和內(nèi)核可以獨(dú)立進(jìn)行更新莉钙。GKI 具有以下特點(diǎn):
- 基于 ACK 來源構(gòu)建而成廓脆。
- 是每個架構(gòu)和每個 LTS 版本的單內(nèi)核二進(jìn)制文件以及關(guān)聯(lián)的可加載模塊(目前只有適用于 android11-5.4 和 android12-5.4 的 arm64)。
- 已經(jīng)過關(guān)聯(lián) ACK 支持的所有 Android 平臺版本的測試磁玉。在 GKI 內(nèi)核版本的生命周期內(nèi)不會發(fā)生功能棄用停忿。
- 為給定 LTS 中的驅(qū)動程序提供了穩(wěn)定版 KMI。
- 不包含 SoC 專用代碼或板卡專用代碼蚊伞。下圖顯示了 GKI 內(nèi)核和供應(yīng)商模塊架構(gòu):
由于Android的kernel實(shí)際上就是Linux kernel席赂,只是針對移動設(shè)備做了一些優(yōu)化,所以與其它Linux kernel的啟動方式大同小異时迫,都是對start_kernel函數(shù)的調(diào)用和執(zhí)行颅停。Kernel主要工作內(nèi)容為設(shè)置緩存、被保護(hù)存儲器掠拳、計(jì)劃列表癞揉,加載驅(qū)動,啟動kernel守護(hù)溺欧,掛載根目錄喊熟,初始化輸入輸出,開啟中斷姐刁,初始化進(jìn)程表等芥牌。當(dāng)內(nèi)核完成這些系統(tǒng)設(shè)置后,接下來在系統(tǒng)文件中尋找”init”文件聂使,然后啟動root進(jìn)程或者系統(tǒng)的第一個進(jìn)程壁拉。
Kernel啟動過程分為兩個階段:
1)內(nèi)核引導(dǎo)階段拐叉。通常使用匯編語言編寫,主要檢查內(nèi)核與當(dāng)前硬件是否匹配扇商。這部分也與硬件體系結(jié)構(gòu)相關(guān)。
2)內(nèi)核啟動階段宿礁。引導(dǎo)階段結(jié)束前案铺,將調(diào)用start_kernel()進(jìn)入內(nèi)核啟動階段。內(nèi)核啟動階段相關(guān)的代碼主要位于kernel/init/main.c梆靖。
A. 內(nèi)存管理單元和高速緩存初始化完成之后控汉,系統(tǒng)便可以使用虛擬內(nèi)存和啟動用戶空間進(jìn)程
B. 內(nèi)核在根目錄尋找初始化程序(/system/core/init),執(zhí)行該程序以啟動init進(jìn)程
Kernel啟動的核心函數(shù)是start_kernel函數(shù)返吻,它完成了內(nèi)核的大部分初始化工作姑子。這個函數(shù)在最后調(diào)用了reset_init函數(shù)進(jìn)行后續(xù)的初始化。reset_init函數(shù)最主要的任務(wù)就是啟動內(nèi)核線程kernel_init测僵。kernel_init函數(shù)將完成設(shè)備驅(qū)動程序的初始化街佑,并調(diào)用init_post函數(shù)啟動用戶空間的init進(jìn)程。到init_post函數(shù)為止捍靠,內(nèi)核的初始化已經(jīng)基本完成沐旨。
1.4 init進(jìn)程
用戶空間的第一個進(jìn)程便是init進(jìn)程,進(jìn)程號為1榨婆。
當(dāng)系統(tǒng)啟動完成之后磁携,init進(jìn)程會作為守護(hù)進(jìn)程監(jiān)視其它進(jìn)程。在Linux中所有的進(jìn)程都是由init進(jìn)程直接或間接fork出來的良风。在init進(jìn)程啟動的過程中谊迄,會相繼啟動servicemanager(binder服務(wù)管理者)、Zygote進(jìn)程烟央。而Zygote又會創(chuàng)建system_server進(jìn)程以及app進(jìn)程统诺。
對于init進(jìn)程的功能分為4部分:
- 解析并運(yùn)行所有的init.rc相關(guān)文件
- 根據(jù)rc文件,生成相應(yīng)的設(shè)備驅(qū)動節(jié)點(diǎn)
- 處理子進(jìn)程的終止(signal方式)
- 提供屬性服務(wù)的功能
init進(jìn)程涉及的主要代碼文件有
system/core/init/
-main.cpp
-init.cpp
-parser.cpp
/system/core/rootdir/
-init.rc
init進(jìn)程的入口為main.cpp類的main方法吊档。
// system/core/init/main.cpp
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
__asan_set_error_report_callback(AsanReportCallback);
#endif
// 創(chuàng)建設(shè)備節(jié)點(diǎn)篙议、權(quán)限設(shè)定等
if(!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if(argc > 1){
// 初始化日志系統(tǒng)
if(!strcmp(argv[1], "subcontext")){
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap function_map;
return SubcontextMain(argc, argv, &function_map);
}
// 2.創(chuàng)建安全增強(qiáng)型Linux(SELinux)
if(!strcmp(argv[1], "selinux_setup")){
return SetupSelinux(argv);
}
// 3.解析init.rc文件、提供服務(wù)怠硼、創(chuàng)建epoll與處理子進(jìn)程的終止等
if (!strcmp(argv[1], "second_stage")){
return SecondStageMain(argc, argv);
}
}
// 1.掛載相關(guān)文件系統(tǒng)
return FirstStageMain(argc, argv);
}
主要執(zhí)行了三步
- FirstStageMain
- SetupSelinux
- SecondStageMain
FirstStageMain
init進(jìn)程啟動的第一步鬼贱,主要是掛載相關(guān)的文件系統(tǒng)
// system/core/init/first_stage_init.cpp
int FirstStageMain(int argc, char** argv){
if(REBOOT_BOOTLOADER_ON_PANIC){
InstallRebootSignalHandlers();
}
boot_clock::time_point start_time = boot_clock::now();
std::vector<std::pair<std::string, int>> errors;
#define CHECKCALL(x) \
if (x != 0) errors.emplace_back(#x " failed", errno);
umask(0);
// 創(chuàng)建于掛載相關(guān)文件系統(tǒng)
CHECKCALL(clearenv());
CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
// Get the basic filesystem setup we need put together in the initramdisk
// on / and then we'll let the rc file figure out the rest.
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
CHECKCALL(mkdir("/dev/pts", 0755));
CHECKCALL(mkdir("/dev/socket", 0755));
CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
#define MAKE_STR(x) __STRING(x)
CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
// 原始命令不可暴露給沒有特權(quán)的進(jìn)程
CHECKCALL(chmod("/proc/cmdline", 0440));
gid_t groups[] = {AID_READPROC};
CHECKCALL(setgroups(arraysize(groups), groups));
CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
// tmpfs已經(jīng)掛載在/dev下,并且已生成/dev/kmsg香璃,故可以與外界通信
// 初始化日志系統(tǒng)
InitKernelLogging(argv);
// 進(jìn)入下一步
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));
// 只有在錯誤發(fā)生的情況下execv()函數(shù)才會返回
PLOG(FATAL) << "execv(\"" << path << "\") failed";
return 1;
}
主要通過mount掛載對應(yīng)的文件系統(tǒng)这难,mkdir創(chuàng)建對應(yīng)的文件目錄,并配置相應(yīng)的訪問權(quán)限葡秒。
需要注意的是姻乓,這些文件只是在應(yīng)用運(yùn)行的時候存在嵌溢,一旦應(yīng)用運(yùn)行結(jié)束就會隨著應(yīng)用一起消失。
掛載的文件系統(tǒng)主要有四類:
- tmpfs: 一種虛擬內(nèi)存文件系統(tǒng)蹋岩,它會將所有的文件存儲在虛擬內(nèi)存中赖草。由于tmpfs是駐留在RAM的,因此它的內(nèi)容是不持久的剪个。斷電后秧骑,tmpfs 的內(nèi)容就消失了,這也是被稱作tmpfs的根本原因扣囊。
- devpts: 為偽終端提供了一個標(biāo)準(zhǔn)接口乎折,它的標(biāo)準(zhǔn)掛接點(diǎn)是/dev/pts。只要pty(pseudo-tty, 虛擬終端)的主復(fù)合設(shè)備/dev/ptmx被打開侵歇,就會在/dev/pts下動態(tài)的創(chuàng)建一個新的pty設(shè)備文件骂澄。
- proc: 也是一個虛擬文件系統(tǒng),它可以看作是內(nèi)核內(nèi)部數(shù)據(jù)結(jié)構(gòu)的接口惕虑,通過它我們可以獲得系統(tǒng)的信息坟冲,同時也能夠在運(yùn)行時修改特定的內(nèi)核參數(shù)。
- sysfs: 與proc文件系統(tǒng)類似枷遂,也是一個不占有任何磁盤空間的虛擬文件系統(tǒng)樱衷。它通常被掛接在/sys目錄下。
在FirstStageMain還會通過InitKernelLogging(argv)來初始化log日志系統(tǒng)酒唉。此時Android還沒有自己的系統(tǒng)日志矩桂,采用kernel的log系統(tǒng),打開的設(shè)備節(jié)點(diǎn)/dev/kmsg痪伦, 那么可通過cat /dev/kmsg來獲取內(nèi)核log侄榴。
最后會通過execv方法傳遞對應(yīng)的path與下一階段的參數(shù)selinux_setup。
SetupSelinux
// system/core/init/selinux.cpp
int SetupSelinux(char** argv) {
//初始化本階段內(nèi)核日志
InitKernelLogging(argv);
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
// 初始化 SELinux网沾,加載 SELinux 策略
SelinuxSetupKernelLogging();
SelinuxInitialize();
// 再次調(diào)用 main 函數(shù)癞蚕,并傳入 second_stage 進(jìn)入第二階段
// 而且此次啟動就已經(jīng)在 SELinux 上下文中運(yùn)行
if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
}
// 進(jìn)入下一步
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
// execv() only returns if an error happened, in which case we
// panic and never return from this function.
PLOG(FATAL) << "execv(\"" << path << "\") failed";
return 1;
}
這階段主要是初始化 SELinux。SELinux 是安全加強(qiáng)型 Linux辉哥,能夠很好的對全部進(jìn)程強(qiáng)制執(zhí)行訪問控制桦山,從而讓 Android 更好的保護(hù)和限制系統(tǒng)服務(wù)、控制對應(yīng)用數(shù)據(jù)和系統(tǒng)日志的訪問醋旦,提高系統(tǒng)安全性恒水。
接下來調(diào)用execv進(jìn)入到最后階段SecondStageMain。
SecondStageMain
// system/core/init/init.cpp
int SecondStageMain(int argc, char** argv) {
SetStdioToDevNull(argv);
// 初始化本階段內(nèi)核日志
InitKernelLogging(argv);
// 系統(tǒng)屬性初始化
property_init();
// 建立 Epoll
Epoll epoll;
// 注冊信號處理
InstallSignalFdHandler(&epoll);
// 加載默認(rèn)的系統(tǒng)屬性
property_load_boot_defaults(load_debug_prop);
// 啟動屬性服務(wù)
StartPropertyService(&epoll);
subcontexts = InitializeSubcontexts();
//加載系統(tǒng)啟動腳本"/init.rc"
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
LoadBootScripts(am, sm);
// 觸發(fā)early-init饲齐,钉凌,init,late-init流程
am.QueueEventTrigger("early-init");
am.QueueEventTrigger("init");
am.QueueBuiltinAction(InitBinder, "InitBinder");
am.QueueEventTrigger("late-init");
//解析啟動腳本
while (true) {
// 執(zhí)行 Action
am.ExecuteOneCommand();
// 還有就是重啟死掉的子進(jìn)程
auto next_process_action_time = HandleProcessActions();
}
}
SecondStageMain的主要工作總結(jié)
- 使用epoll對init子進(jìn)程的信號進(jìn)行監(jiān)聽
- 初始化系統(tǒng)屬性捂人,使用mmap共享內(nèi)存
- 開啟屬性服務(wù)御雕,并注冊到epoll中
- 加載系統(tǒng)啟動腳本"init.rc"
- 解析啟動腳本矢沿,啟動相關(guān)服務(wù)
重點(diǎn)介紹下init.rc文件的解析
//system/core/init/init.cpp
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
Parser parser = CreateParser(action_manager, service_list);
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/init.rc");
if (!parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
if (!parser.ParseConfig("/product/etc/init")) {
late_import_paths.emplace_back("/product/etc/init");
}
if (!parser.ParseConfig("/product_services/etc/init")) {
late_import_paths.emplace_back("/product_services/etc/init");
}
if (!parser.ParseConfig("/odm/etc/init")) {
late_import_paths.emplace_back("/odm/etc/init");
}
if (!parser.ParseConfig("/vendor/etc/init")) {
late_import_paths.emplace_back("/vendor/etc/init");
}
} else {
parser.ParseConfig(bootscript);
}
}
通過ParseConfig來解析init.rc配置文件。.rc文件以行為單位酸纲,以空格為間隔捣鲸,以#開始代表注釋行。.rc文件主要包含Action闽坡、Service摄狱、Command、Options无午、Import,其中對于Action和Service的名稱都是唯一的祝谚,對于重復(fù)的命名視為無效宪迟。init.rc中的Action、Service語句都有相應(yīng)的類來解析交惯,即ActionParser剑肯、ServiceParser穴吹。 以下為init.rc配置文件的部分內(nèi)容。
// system/core/rootdir/init.rc
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.${ro.zygote}.rc
import /init.trace.rc
on early-init
start ueventd
mkdir /mnt 0775 root system
on init
mount tmpfs none /sys/fs/cgroup mode=0750,uid=0,gid=1000
mkdir /sys/fs/cgroup/memory 0750 root system
mount cgroup none /sys/fs/cgroup/memory memory
on property:sys.boot_from_charger_mode=1
class_stop charger
trigger late-init
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
service logd /system/bin/logd
class core
socket logd stream 0666 logd logd
socket logdr seqpacket 0666 logd logd
socket logdw dgram 0222 logd logd
seclabel u:r:logd:s0
service console /system/bin/sh
class core
console
disabled
user shell
seclabel u:r:shell:s0
service adbd /sbin/adbd --root_seclabel=u:r:su:s0
class core
socket adbd stream 660 system system
disabled
seclabel u:r:adbd:s0
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart healthd
onrestart restart zygote
onrestart restart media
onrestart restart surfaceflinger
onrestart restart drm
on late-init
trigger early-fs
trigger fs
trigger post-fs
trigger late-fs
trigger post-fs-data
trigger load_persist_props_action
// 這里啟動zygote-start
trigger zygote-start
trigger firmware_mounts_complete
trigger early-boot
trigger boot
可以看到,在解析init.rc的配置中琉雳,在late-init階段啟動了Zygote進(jìn)程。
1.5 Zygote
Zygote進(jìn)程是Android中所有Java進(jìn)程的父進(jìn)程康辑。Zygote進(jìn)程在Init進(jìn)程啟動過程中被以service服務(wù)的形式啟動樱溉。Zygote進(jìn)程相關(guān)的.rc配置文件為init.zygote64.rc或者init.zygote32.rc。以init.zygote64.rc為例齐饮,其內(nèi)容如下
// system/core/rootdir/init.zygote64.rc
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc reserved_disk
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
onrestart exec_background - system system -- /ssystem/bin/vdc volume abort_fuse
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cmeraserver
onrestart restart media
onrestart restart netd
onrestart setprop sys.android.reboot 1
writepid /dev/cpuset/foreground/tasks
critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
init進(jìn)程解析init.zygote64.rc配置文件之后捐寥,會調(diào)用app_process
// frameworks/base/cmds/app_process/app_main.cpp
int main(int argc, char* const argv[])
{
if(zygote){
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
}
}
執(zhí)行到frameworks/base/core/jni/AndroidRuntime.cpp的start()方法
// frameworks/base/core/jni/AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
ALOGD(">>>>>> START %s uid %d <<<<<<\n",class Name!= NULL祖驱? class Name: "(unknown)", getuid() );
// 打印LOG_BOOT_PROGRESS_START日志
LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
if(startVm(&mJavaVM, &env, zygote, primaryZygote) != 0){
return;
}
onVmCreated(env);
// 調(diào)用ZygoteInit類的main()方法
env.CallStaticVoidMethod(startClass, startMeth, strArray)
}
AndroidRuntime.cpp的start方法主要做了以下工作:
- 加載libart.so
- 啟動虛擬機(jī)
- 加載注冊JNI方法
- 啟動Zygote
執(zhí)行ZygoteInit.java的main()方法
//frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
@UnsupportedAppUsage
public static void main(String argv[]) {
//zygote進(jìn)程會fork出system_server進(jìn)程
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
// zygote進(jìn)程中握恳,r == null;zygote子進(jìn)程(如system_server進(jìn)程)中捺僻, r != null
if (r != null) {
r.run();
return;
}
}
Log.i(TAG, "Accepting command socket connections");
// zygote進(jìn)程會在select loop死循環(huán)乡洼;而非zygote進(jìn)程中之前已return。
// loops forever in the zygote.
caller = zygoteServer.runSelectLoop(abiList);
if (caller != null) {
caller.run();
}
}
Zygote進(jìn)程主要做了以下工作
- 加載虛擬機(jī)匕坯,并為JVM注冊JNI方法
- 提前加載類PreloadClasses
- 提前加載資源PreLoadResouces
- fork system_server
- 提前加載類PreloadClasses, 調(diào)用runSelectLoop方法束昵,等待進(jìn)程孵化請求
zygote進(jìn)程在fork子進(jìn)程的時候可以共享虛擬機(jī)和資源,從而加快進(jìn)程的啟動速度醒颖,節(jié)省內(nèi)存妻怎。
1.6 SystemServer
SystemServer進(jìn)程由Zygote進(jìn)程fork而來,是Zygote孵化出的第一個進(jìn)程泞歉。SystemServer和Zygote進(jìn)程是Android Framework層的兩大重要進(jìn)程逼侦。SystemServer負(fù)責(zé)啟動和管理整個Java frameWork匿辩。SystemServer進(jìn)程在開啟的時候,會初始化AMS榛丢、WMS铲球、PMS等關(guān)鍵服務(wù)。同時會加載本地系統(tǒng)的服務(wù)庫晰赞,調(diào)用createSystemContext()創(chuàng)建系統(tǒng)上下文稼病,創(chuàng)建ActivityThread及開啟各種服務(wù)等等。
SystemServer的啟動相關(guān)代碼如下
// frameworks/base/services/java/com/android/server/SystemServer.java
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
try {
Looper.prepareMainLooper();
// 初始化本地服務(wù)
System.loadLibrary("android_servers");
// 初始化系統(tǒng)上下文
createSystemContext();
// 創(chuàng)建SystemServiceManager
mSystemServiceManager = new SystemServiceManager(mSystemContext);
mSystemServiceManager.setStartInfo(mRuntimeRestart,
mRuntimeStartElapsedTime, mRuntimeStartUptime);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
// 構(gòu)建線程池掖鱼,以便并行執(zhí)行一些初始化任務(wù)
SystemServerInitThreadPool.get();
} finally {
traceEnd(); // InitBeforeStartServices
}
// 開啟服務(wù)
try {
traceBeginAndSlog("StartServices");
startBootstrapServices();// 啟動引導(dǎo)服務(wù)
startCoreServices();// 啟動核心服務(wù)
startOtherServices();// 啟動其他服務(wù)
SystemServerInitThreadPool.shutdown();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
} finally {
traceEnd();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
可以看到然走,SystemServer最重要的工作就是通過執(zhí)行三個方法來啟動所有服務(wù)
- startBootstrapServices();
- startCoreServices();
- startOtherServices();
分別對應(yīng)引導(dǎo)服務(wù)、核心服務(wù)和其他服務(wù):
- 引導(dǎo)服務(wù)(Bootstrap services):這類服務(wù)包括 Installer戏挡,ActivityManagerService
PowerManagerService, DisplayManagerService, PackageManagerService, UserManagerService等 - 核心服務(wù)(Core services ): 這類服務(wù)包括 LightsService, BatteryService, UsageStatsServtce,
WebViewUpdateService等 - 其他服務(wù):所有其它服務(wù)
在startOtherServices()方法中會啟動SystemUI芍瑞,之后SystemServer通知AMS系統(tǒng)已準(zhǔn)備好,此時AMS啟動桌面并且發(fā)送BOOT_COMPLETED廣播褐墅。至此拆檬,系統(tǒng)層面啟動流程結(jié)束。
通過下圖再回顧下整個開機(jī)流程
2. 分析開機(jī)時間
要想進(jìn)行開機(jī)速度的優(yōu)化妥凳,我們需要分析開機(jī)時間的分布竟贯,從而找出異常耗時的地方,從而進(jìn)行實(shí)際的優(yōu)化工作逝钥。下面介紹如何分析開機(jī)時間屑那。
2.1 分析開機(jī)日志
Android的log系統(tǒng)是獨(dú)立于Linux內(nèi)核log系統(tǒng)的. Android系統(tǒng)把Log分為了四類,不同的類別記錄不同的Log信息艘款,默認(rèn)通過logcat抓取的是main信息:
- main - 主要的Log信息齐莲,大部分應(yīng)用級別的Log信息都在這里
- events - 系統(tǒng)事件相關(guān)的Log信息
- radio - 無線/電話相關(guān)的Log信息
- system - 低級別的系統(tǒng)調(diào)試Log信息
通過查看events.txt中搜索"boot_progress"關(guān)鍵字或者通過以下命令過濾日志輸出adb logcat -d -v time -b "events" | grep "boot_progress"
行末數(shù)字即為此刻距開機(jī)時刻的時間間隔。每行代表開機(jī)的各個關(guān)鍵階段磷箕。
階段 | 描述 |
---|---|
boot_progress_start | 系統(tǒng)進(jìn)入用戶空間选酗,標(biāo)志著kernel啟動完成 |
boot_progress_preload_start | Zygote啟動 |
boot_progress_preload_end | Zygote結(jié)束 |
boot_progress_system_run | SystemServer ready,開始啟動Android系統(tǒng)服務(wù) |
boot_progress_pms_start | PMS開始掃描安裝的應(yīng)用 |
boot_progress_pms_system_scan_start | PMS先行掃描/system目錄下的安裝包 |
boot_progress_pms_data_scan_start | PMS掃描/data目錄下的安裝包 |
boot_progress_pms_scan_end | PMS掃描結(jié)束 |
boot_progress_pms_ready | PMS就緒 |
boot_progress_ams_ready | AMS就緒 |
boot_progress_enable_screen | AMS啟動完成后開始激活屏幕,從此以后屏幕才能響應(yīng)用戶的觸摸岳枷,它在WindowManagerService發(fā)出退出開機(jī)動畫的時間節(jié)點(diǎn)之前 |
sf_stop_bootanim | SF設(shè)置service.bootanim.exit屬性值為1芒填,標(biāo)志系統(tǒng)要結(jié)束開機(jī)動畫了 |
wm_boot_animation_done | 開機(jī)動畫結(jié)束,這一步用戶能直觀感受到開機(jī)結(jié)束 |
各行l(wèi)og對應(yīng)的打印代碼為
boot_progress_start
// frameworks/base/core/jni/AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
for (size_t i = 0; i < options.size(); ++i) {
if (options[i] == startSystemServer) {
primary_zygote = true;
/* track our progress through the boot sequence */
const int LOG_BOOT_PROGRESS_START = 3000;
LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
}
}
}
boot_progress_preload_start
// frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
public static void main(String[] argv){
if(!enableLazyPreload){
bootTimingsTraceLog.traceBegin("ZygotePreload");
EventLog.writeEvent(BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis());
preload(bootTimingsTraceLog);
EventLog.writeEvent(BOOT_PROGRESS_PRELOAD_END, SystemClock.uptimeMillis());
bootTimingsTraceLog.traceEnd();
}
}
boot_progress_system_run
// frameworks/base/services/java/com/android/server/SystemServer.java
private void run() {
Slog.i(TAG, "Entered the Android system server!");
final long uptimeMillis = SystemClock.elapsedRealtime();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis);
if (!mRuntimeRestart){
FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_INIT_START,uptimeMillis);
}
}
boot_progress_pms_start
// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
LockGuard.installLock(mLock, LockGuard.INDEX_PACKAGES);
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,SystemClock.uptimeMillis()
);
}
boot_progress_pms_system_scan_start
// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
long startTime = SystemClock.uptimeMillis();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START, startTime);
}
boot_progress_pms_data_scan_start
// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
final long systemScanTime = SystemClock.uptimeMillis() - startTime;
final int systemPackagesCount = mPackages.size();
Slog.i(TAG, "Finished scanning system apps. Time: " + systemScanTime
+ " ms, packageCount: " + systemPackagesCount
+ " , timePerPackage: "
+ (systemPackagesCount == 0 ? 0 : systemScanTime / systemPackagesCount)
+ " , cached: " + cachedSystemApps);
if (mIsUpgrade && systemPackagesCount > 0) {
FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME,
systemScanTime / systemPackagesCount);
}
if (!mOnlyCore) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0,
packageParser, executorService);
}
}
boot_progress_pms_scan_end
// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
mPackageUsage.read(mSettings.mPackages);
mCompilerStats.read();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
SystemClock.uptimeMillis());
Slog.i(TAG, "Time to scan packages: "
+ ((SystemClock.uptimeMillis()-startTime)/1000f)
+ " seconds");
}
boot_progress_pms_ready
// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
t.traceBegin("write settings");
mSettings.writeLPr();
t.traceEnd();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY, SystemClock.uptimeMillis());
}
boot_progress_ams_ready
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public void systemReady(final Runnable goingCallback, @NonNull TimingsTraceAndSlog t) {
t.traceEnd();
EventLog.writeBootProgressAmsReady(SystemClock.uptimeMillis());
}
boot_progress_enable_screen
// frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@Override
public void enableScreenAfterBoot(boolean booted) {
writeBootProgressEnableScreen(SystemClock.uptimeMillis());
mWindowManager.enableScreenAfterBoot();
synchronized (mGlobalLock) {
updateEventDispatchingLocked(booted);
}
}
sf_stop_bootanim
// frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::bootFinished()
{
property_set("service.bootanim.exit", "1");
LOG_EVENT_LONG(LOGTAG_SF_STOP_BOOTANIM, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
}
wm_boot_animation_done
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private void performEnableScreen() {
EventLogTags.writeWmBootAnimationDone(SystemClock.uptimeMillis());
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim");
}
可以將測試機(jī)與對比機(jī)抓取的此log數(shù)據(jù)制作成表格空繁,制作成折線圖殿衰,可以更加直觀的觀察到耗時異常的流程。
通過"boot_progress_"關(guān)鍵字分析日志盛泡,粒度較大闷祥,只能定位出大概的耗時流程,之后還需分析流程內(nèi)部具體的耗時情況傲诵。開機(jī)各流程內(nèi)部也有相應(yīng)的日志凯砍,可以進(jìn)行更加細(xì)致的分析箱硕。例如在SystemServiceManager.java類中啟動服務(wù)時,會打印啟動某項(xiàng)服務(wù)的日志悟衩。通過查看某個服務(wù)A與下一個服務(wù)的日志時間剧罩,可以計(jì)算出啟動服務(wù)A的耗時。
// frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
public <T extends SystemService> T startService(Class<T> serviceClass)
{
try {
final String name = serviceClass.getName();
Slog.i(TAG, "Starting " + name);
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartService " + name);
try {
Constructor<T> constructor = serviceClass . getConstructor (Context.class);
service = constructor.newInstance(mContext);
} catch ...
startService(service);
return service;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
使用bootchart工具可以進(jìn)行更加直觀的分析座泳。
2.2 使用bootchart工具
bootchart是一個能對GNU/Linux boot過程進(jìn)行性能分析并把結(jié)果直觀化的開源工具惠昔,在系統(tǒng)啟動過程中自動收集 CPU 占用率、磁盤吞吐率挑势、進(jìn)程等信息镇防,并以圖形方式顯示分析結(jié)果,可用作指導(dǎo)優(yōu)化系統(tǒng)啟動過程潮饱。BootChart包含數(shù)據(jù)收集工具和圖像產(chǎn)生工具营罢,數(shù)據(jù)收集工具在原始的BootChart中是獨(dú)立的shell程序,但在Android中饼齿,數(shù)據(jù)收集工具被集成到了init程序中。
以下涉及到的命令蝙搔,請自行應(yīng)該參數(shù)的path缕溉。
- 抓取bootchart數(shù)據(jù)
bootchart開始生成數(shù)據(jù)的源碼
// system/core/init/bootchart.cpp
static int do_bootchart_start() {
// 只要存在/data/bootchart/enabled文件,即抓取bootchart數(shù)據(jù)
std::string start;
if (!android::base::ReadFileToString("/data/bootchart/enabled", &start)) {
LOG(VERBOSE) << "Not bootcharting";
return 0;
}
g_bootcharting_thread = new std::thread(bootchart_thread_main);
return 0;
}
所以只要生成/data/bootchart/enabled文件即可
adb shell touch /data/bootchart/enabled
adb reboot
在設(shè)備啟動后吃型,提取啟動圖表:
adb pull /data/bootchart
獲取到bootchart數(shù)據(jù)之后進(jìn)行打包
tar -czf bootchart.tgz *
- 分析數(shù)據(jù)
下載Boot Chart包并解壓证鸥,使用bootchart.jar解析生成的文件輸出圖片
java -jar bootchart.jar bootchart.tgz
從生成的圖片可以更加直觀詳細(xì)的看到開機(jī)耗時以及硬件使用情況。個人認(rèn)為勤晚,bootchart的分析應(yīng)該是以PIXEL或者開機(jī)速度正常機(jī)子的bootchart為參考來對照分析枉层。使用完之后,記得刪除enabled文件以防每次開機(jī)都收集啟動數(shù)據(jù)赐写。
2.3 抓取boottrace
抓取開機(jī)階段的trace鸟蜡,也就是boottrace,是一種重要的分析開機(jī)的手段挺邀。抓取方式如下:
- 將手機(jī)中的atrace.rc拉取下來揉忘,并備份;
adb pull /system/etc/init/atrace.rc
- 在文件atrace.rc末尾添加
on property:persist.debug.atrace.boottrace=1
start boottrace
service boottrace /system/bin/atrace --async_start -b 30720 gfx input view webview wm am sm audio video binder_lock binder_driver camera hal res dalvik rs bionic power pm ss database network adb vibrator aidl sched
disabled
oneshot
- 將修改后的atrace.rc文件push到手機(jī)里面
adb push atrace.rc /system/etc/init/
- 打開抓取boottrace的屬性開關(guān)
adb shell setprop persist.debug.atrace.boottrace 1
- 重啟手機(jī)
- 手機(jī)啟動完成之后等待幾秒端铛,關(guān)閉boottrace屬性開關(guān)
adb shell setprop persist.debug.atrace.boottrace 0
- 生成boottrace文件
adb shell atrace --async_stop -z -c -o /data/local/tmp/boot_trace
- 拉取boottrace日志文件
adb pull /data/local/tmp/boot_trace
之后就可以通過分析boot_trace文件來分析了泣矛。
3. 開機(jī)速度優(yōu)化實(shí)踐
在Android S升級Android T的過程中,遇到一個開機(jī)速度的問題禾蚕。同一個項(xiàng)目的手機(jī)您朽,在升級Android T之后,比之前的開機(jī)時間大概要多18秒左右换淆。
首先在手機(jī)開機(jī)之后哗总,查看boot_progress相關(guān)日志如下
07-15 04:13:35.244 I/boot_progress_start( 1059): 4040
07-15 04:13:35.934 I/boot_progress_preload_start( 1059): 4730
07-15 04:13:37.255 I/boot_progress_preload_end( 1059): 6051
07-15 04:13:37.636 I/boot_progress_system_run( 2221): 6432
07-15 04:13:38.260 I/boot_progress_pms_start( 2221): 7056
07-15 04:13:38.473 I/boot_progress_pms_system_scan_start( 2221): 7269
07-15 04:13:38.797 I/boot_progress_pms_data_scan_start( 2221): 7593
07-15 04:13:38.803 I/boot_progress_pms_scan_end( 2221): 7599
07-15 04:13:38.894 I/boot_progress_pms_ready( 2221): 7690
07-15 04:13:58.006 I/boot_progress_ams_ready( 2221): 26802
07-15 04:13:59.164 I/boot_progress_enable_screen( 2221): 27960
發(fā)現(xiàn)几颜,在boot_progress_pms_ready到boot_progress_ams_ready之間,耗時近20秒魂奥,嚴(yán)重超時菠剩。
確定了大體的耗時異常點(diǎn)之后,我們抓取boottrace再進(jìn)行進(jìn)一步的分析耻煤。
由于PMS和AMS服務(wù)軍運(yùn)行再SystemServer進(jìn)程當(dāng)中具壮,所以我們重點(diǎn)關(guān)注SystemServer進(jìn)程的運(yùn)行情況。
查看trace文件發(fā)現(xiàn)哈蝇,主要是因?yàn)樵趩覣udioService的時候耗時較長棺妓。startService相關(guān)的日志也表明了這一點(diǎn)。
可見炮赦,啟動AudioService耗時15.5秒左右怜跑。通過在AudioService相關(guān)的代碼里面添加log,最終定位到為AudioService驅(qū)動在Android T上的問題吠勘。轉(zhuǎn)交Audio模塊處理之后性芬,開機(jī)時間正常。
參考
Boot ROM
Booting process of Android devices
Android Boot Process
Android白話啟動篇(Android booting process)
Booting process of Android devices
Android Boot Sequence
Android init 啟動
深入研究源碼:Android10.0系統(tǒng)啟動流程(二)init進(jìn)程
Android Zygote Startup
安卓10開機(jī)時間優(yōu)化分析
Android開機(jī)各個階段(Android R)
優(yōu)化啟動時間