对于任何操作系统来讲,开机时间的优化都是一个很关键的工作。如果用户每次启动设备都需要等待很长的时间,那么其用户体验是很差的。本文从Android12出发,分以下三部分阐述Android系统的开机优化:
- Android开机过程
- 分析开机时间
- 开机速度优化实践
备注
1. 文中所有的代码都省略了无关部分,并且省略了省略号;
2. 由于作者能力有限,难免会有不正确或者不完善的地方。欢迎大家指正。
1. Android开机过程
要想进行开机速度的优化,首先需要了解开机的详细流程。开机过程从CPU上电开始,到锁屏界面显示出来结束。下图为比较简洁的开机流程图。
再来看一张比较详细的开机流程图
总的来说,开机过程分为以下六主要子过程
1.1 Boot ROM
Boot ROM是硬编码在CPU内部固定地址的一段ROM(在一些较老的系统上也可能使用外挂Boot ROM(相对CPU来说)),这块代码是由CPU制造商提供。当用户按下电源键或者系统重启之后,触发CPU上电动作,此时其它硬件还未初始化,然而这块ROM就已经可读了。CPU首先执行PBL(Primary Boot Loader,主引导加载程序,固化在ROM上)代码。在必要的硬件初始化之后,Boot ROM开始加载Bootloader到RAM中,然后PC指针跳过去执行bootloader。在加载 Bootloader之前,PBL也可以进行验证。如果验证无法通过,则不会加载运行Bootloader,从而开机失败。
A. CPU刚上电时,CPU 处于未初始化状态,还没有设定内部时钟,此时只有内部 RAM 可用。当电源稳定后会开始执行 Boot ROM 代码。Boot ROM通过系统寄存器映射到 ASIC (Application Specific Integrated Circuit, 即专用集成电路,是指应特定用户要求和特定电子系统的需要而设计、制造的集成电路)中的物理区域来找到boot media,进而可以找到Bootloader
B. boot media序列确定之后,Boot ROM 加载 Bootloader到内部 RAM 中,之后Boot ROM代码会跳到Bootloader
1.2Bootloader
Bootloader是一个特殊的独立于内核的程序,是CPU复位后进入操作系统之前执行的一段代码。Bootloader完成由硬件启动到操作系统启动的过渡,从而为操作系统提供基本的运行环境,如初始化CPU、时钟、堆栈、存储器系统等。Bootloader功能类似于PC机的BIOS程序,其代码与CPU芯片的内核结构、具体型号、应用系统的配置及使用的操作系统等因素有关,因此不可能有通用的bootloader,开发时需要用户根据具体情况进行移植。嵌入式Linux系统中常用的Bootloader有armboot、redboot、blob、U-Boot、Bios-lt、Bootldr等,其中U-Boot是当前比较流行,功能比较强大的Bootloader,可以支持多种体系结构,但相对也比较复杂。硬件初始化完成之后,Bootloader将boot.img(kernel + ramdisk(ramdisk.img中主要是存放android启动后第一个用户进程init可执行文件和init.*.rc等相关启动脚本以及sbin目录下的adbd工具))从flash上copy到RAM里面,然后CPU执行转向kernel。
A. Bootloader第一阶段首先会检测和设置外部RAM
B. 外部 RAM可用之后,将Bootloader代码加载到外部RAM中
C. Bootloader第二阶段包含了设置文件系统,内存,网络等等。
D. Bootloader查找Linux内核并将其从boot media (或者其他地方,这取决于系统配置) 加载到 RAM 中,并且会配置一些内核启动时需要的启动参数
E. Bootloader执行完之后会跳转到 Linux 内核执行
一般也可将Bootloader程序的执行分为两个阶段,如下图所示
执行Bootloader程序过程中,如果镜像验证失败、BootLinux (&Info) 函数启动失败或者接收到启动至 fastboot 的命令(比如使用 adb reboot bootloader进行重启、在启动时按下了电源键+下音量键组合)时,会进入到Fastboot模式(Fastboot 是一种电脑通过USB数据线对手机固件进行刷写、擦除/格式化、调试、传输各种指令的固件通信协议, 俗称线刷模式或快速引导模式)。
1.3Kernel
Android kernel基于上游 Linux LTS (Linux Long Term Supported,长期支持) 内核。在 Google,LTS 内核会与 Android 专用补丁结合,形成所谓的“Android 通用内核 (ACK,Android Common Kernel)”。较新的 ACK(版本 5.4 及更高版本)也称为 GKI (Generic Kernel Image,通用内核镜像 )内核。 GKI项目通过统一核心内核并将 SoC 和板级支持从核心内核移至可加载模块中,解决了内核碎片化问题。GKI 内核为内核模块提供了稳定的内核模块接口 (KMI),因此模块和内核可以独立进行更新。GKI 具有以下特点:
- 基于 ACK 来源构建而成。
- 是每个架构和每个 LTS 版本的单内核二进制文件以及关联的可加载模块(目前只有适用于 android11-5.4 和 android12-5.4 的 arm64)。
- 已经过关联 ACK 支持的所有 Android 平台版本的测试。在 GKI 内核版本的生命周期内不会发生功能弃用。
- 为给定 LTS 中的驱动程序提供了稳定版 KMI。
- 不包含 SoC 专用代码或板卡专用代码。下图显示了 GKI 内核和供应商模块架构:
由于Android的kernel实际上就是Linux kernel,只是针对移动设备做了一些优化,所以与其它Linux kernel的启动方式大同小异,都是对start_kernel函数的调用和执行。Kernel主要工作内容为设置缓存、被保护存储器、计划列表,加载驱动,启动kernel守护,挂载根目录,初始化输入输出,开启中断,初始化进程表等。当内核完成这些系统设置后,接下来在系统文件中寻找”init”文件,然后启动root进程或者系统的第一个进程。
Kernel启动过程分为两个阶段:
1)内核引导阶段。通常使用汇编语言编写,主要检查内核与当前硬件是否匹配。这部分也与硬件体系结构相关。
2)内核启动阶段。引导阶段结束前,将调用start_kernel()进入内核启动阶段。内核启动阶段相关的代码主要位于kernel/init/main.c。
A. 内存管理单元和高速缓存初始化完成之后,系统便可以使用虚拟内存和启动用户空间进程
B. 内核在根目录寻找初始化程序(/system/core/init),执行该程序以启动init进程
Kernel启动的核心函数是start_kernel函数,它完成了内核的大部分初始化工作。这个函数在最后调用了reset_init函数进行后续的初始化。reset_init函数最主要的任务就是启动内核线程kernel_init。kernel_init函数将完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程。到init_post函数为止,内核的初始化已经基本完成。
1.4 init进程
用户空间的第一个进程便是init进程,进程号为1。
当系统启动完成之后,init进程会作为守护进程监视其它进程。在Linux中所有的进程都是由init进程直接或间接fork出来的。在init进程启动的过程中,会相继启动servicemanager(binder服务管理者)、Zygote进程。而Zygote又会创建system_server进程以及app进程。
对于init进程的功能分为4部分:
- 解析并运行所有的init.rc相关文件
- 根据rc文件,生成相应的设备驱动节点
- 处理子进程的终止(signal方式)
- 提供属性服务的功能
init进程涉及的主要代码文件有
system/core/init/
-main.cpp
-init.cpp
-parser.cpp
/system/core/rootdir/
-init.rc
init进程的入口为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
// 创建设备节点、权限设定等
if(!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if(argc > 1){
// 初始化日志系统
if(!strcmp(argv[1], "subcontext")){
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap function_map;
return SubcontextMain(argc, argv, &function_map);
}
// 2.创建安全增强型Linux(SELinux)
if(!strcmp(argv[1], "selinux_setup")){
return SetupSelinux(argv);
}
// 3.解析init.rc文件、提供服务、创建epoll与处理子进程的终止等
if (!strcmp(argv[1], "second_stage")){
return SecondStageMain(argc, argv);
}
}
// 1.挂载相关文件系统
return FirstStageMain(argc, argv);
}
主要执行了三步
- FirstStageMain
- SetupSelinux
- SecondStageMain
FirstStageMain
init进程启动的第一步,主要是挂载相关的文件系统
// 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);
// 创建于挂载相关文件系统
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
// 原始命令不可暴露给没有特权的进程
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已经挂载在/dev下,并且已生成/dev/kmsg,故可以与外界通信
// 初始化日志系统
InitKernelLogging(argv);
// 进入下一步
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));
// 只有在错误发生的情况下execv()函数才会返回
PLOG(FATAL) << "execv(\"" << path << "\") failed";
return 1;
}
主要通过mount挂载对应的文件系统,mkdir创建对应的文件目录,并配置相应的访问权限。
需要注意的是,这些文件只是在应用运行的时候存在,一旦应用运行结束就会随着应用一起消失。
挂载的文件系统主要有四类:
- tmpfs: 一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中。由于tmpfs是驻留在RAM的,因此它的内容是不持久的。断电后,tmpfs 的内容就消失了,这也是被称作tmpfs的根本原因。
- devpts: 为伪终端提供了一个标准接口,它的标准挂接点是/dev/pts。只要pty(pseudo-tty, 虚拟终端)的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。
- proc: 也是一个虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。
- sysfs: 与proc文件系统类似,也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在/sys目录下。
在FirstStageMain还会通过InitKernelLogging(argv)来初始化log日志系统。此时Android还没有自己的系统日志,采用kernel的log系统,打开的设备节点/dev/kmsg, 那么可通过cat /dev/kmsg来获取内核log。
最后会通过execv方法传递对应的path与下一阶段的参数selinux_setup。
SetupSelinux
// system/core/init/selinux.cpp
int SetupSelinux(char** argv) {
//初始化本阶段内核日志
InitKernelLogging(argv);
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
// 初始化 SELinux,加载 SELinux 策略
SelinuxSetupKernelLogging();
SelinuxInitialize();
// 再次调用 main 函数,并传入 second_stage 进入第二阶段
// 而且此次启动就已经在 SELinux 上下文中运行
if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
}
// 进入下一步
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 是安全加强型 Linux,能够很好的对全部进程强制执行访问控制,从而让 Android 更好的保护和限制系统服务、控制对应用数据和系统日志的访问,提高系统安全性。
接下来调用execv进入到最后阶段SecondStageMain。
SecondStageMain
// system/core/init/init.cpp
int SecondStageMain(int argc, char** argv) {
SetStdioToDevNull(argv);
// 初始化本阶段内核日志
InitKernelLogging(argv);
// 系统属性初始化
property_init();
// 建立 Epoll
Epoll epoll;
// 注册信号处理
InstallSignalFdHandler(&epoll);
// 加载默认的系统属性
property_load_boot_defaults(load_debug_prop);
// 启动属性服务
StartPropertyService(&epoll);
subcontexts = InitializeSubcontexts();
//加载系统启动脚本"/init.rc"
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
LoadBootScripts(am, sm);
// 触发early-init,,init,late-init流程
am.QueueEventTrigger("early-init");
am.QueueEventTrigger("init");
am.QueueBuiltinAction(InitBinder, "InitBinder");
am.QueueEventTrigger("late-init");
//解析启动脚本
while (true) {
// 执行 Action
am.ExecuteOneCommand();
// 还有就是重启死掉的子进程
auto next_process_action_time = HandleProcessActions();
}
}
SecondStageMain的主要工作总结
- 使用epoll对init子进程的信号进行监听
- 初始化系统属性,使用mmap共享内存
- 开启属性服务,并注册到epoll中
- 加载系统启动脚本"init.rc"
- 解析启动脚本,启动相关服务
重点介绍下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的名称都是唯一的,对于重复的命名视为无效。init.rc中的Action、Service语句都有相应的类来解析,即ActionParser、ServiceParser。 以下为init.rc配置文件的部分内容。
// 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进程。
1.5 Zygote
Zygote进程是Android中所有Java进程的父进程。Zygote进程在Init进程启动过程中被以service服务的形式启动。Zygote进程相关的.rc配置文件为init.zygote64.rc或者init.zygote32.rc。以init.zygote64.rc为例,其内容如下
// 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进程解析init.zygote64.rc配置文件之后,会调用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);
}
}
执行到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);
// 调用ZygoteInit类的main()方法
env.CallStaticVoidMethod(startClass, startMeth, strArray)
}
AndroidRuntime.cpp的start方法主要做了以下工作:
- 加载libart.so
- 启动虚拟机
- 加载注册JNI方法
- 启动Zygote
执行ZygoteInit.java的main()方法
//frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
@UnsupportedAppUsage
public static void main(String argv[]) {
//zygote进程会fork出system_server进程
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
// zygote进程中,r == null;zygote子进程(如system_server进程)中, r != null
if (r != null) {
r.run();
return;
}
}
Log.i(TAG, "Accepting command socket connections");
// zygote进程会在select loop死循环;而非zygote进程中之前已return。
// loops forever in the zygote.
caller = zygoteServer.runSelectLoop(abiList);
if (caller != null) {
caller.run();
}
}
Zygote进程主要做了以下工作
- 加载虚拟机,并为JVM注册JNI方法
- 提前加载类PreloadClasses
- 提前加载资源PreLoadResouces
- fork system_server
- 提前加载类PreloadClasses, 调用runSelectLoop方法,等待进程孵化请求
zygote进程在fork子进程的时候可以共享虚拟机和资源,从而加快进程的启动速度,节省内存。
1.6 SystemServer
SystemServer进程由Zygote进程fork而来,是Zygote孵化出的第一个进程。SystemServer和Zygote进程是Android Framework层的两大重要进程。SystemServer负责启动和管理整个Java frameWork。SystemServer进程在开启的时候,会初始化AMS、WMS、PMS等关键服务。同时会加载本地系统的服务库,调用createSystemContext()创建系统上下文,创建ActivityThread及开启各种服务等等。
SystemServer的启动相关代码如下
// frameworks/base/services/java/com/android/server/SystemServer.java
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
try {
Looper.prepareMainLooper();
// 初始化本地服务
System.loadLibrary("android_servers");
// 初始化系统上下文
createSystemContext();
// 创建SystemServiceManager
mSystemServiceManager = new SystemServiceManager(mSystemContext);
mSystemServiceManager.setStartInfo(mRuntimeRestart,
mRuntimeStartElapsedTime, mRuntimeStartUptime);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
// 构建线程池,以便并行执行一些初始化任务
SystemServerInitThreadPool.get();
} finally {
traceEnd(); // InitBeforeStartServices
}
// 开启服务
try {
traceBeginAndSlog("StartServices");
startBootstrapServices();// 启动引导服务
startCoreServices();// 启动核心服务
startOtherServices();// 启动其他服务
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最重要的工作就是通过执行三个方法来启动所有服务
- startBootstrapServices();
- startCoreServices();
- startOtherServices();
分别对应引导服务、核心服务和其他服务:
- 引导服务(Bootstrap services):这类服务包括 Installer,ActivityManagerService
PowerManagerService, DisplayManagerService, PackageManagerService, UserManagerService等 - 核心服务(Core services ): 这类服务包括 LightsService, BatteryService, UsageStatsServtce,
WebViewUpdateService等 - 其他服务:所有其它服务
在startOtherServices()方法中会启动SystemUI,之后SystemServer通知AMS系统已准备好,此时AMS启动桌面并且发送BOOT_COMPLETED广播。至此,系统层面启动流程结束。
通过下图再回顾下整个开机流程
2. 分析开机时间
要想进行开机速度的优化,我们需要分析开机时间的分布,从而找出异常耗时的地方,从而进行实际的优化工作。下面介绍如何分析开机时间。
2.1 分析开机日志
Android的log系统是独立于Linux内核log系统的. Android系统把Log分为了四类,不同的类别记录不同的Log信息,默认通过logcat抓取的是main信息:
- main - 主要的Log信息,大部分应用级别的Log信息都在这里
- events - 系统事件相关的Log信息
- radio - 无线/电话相关的Log信息
- system - 低级别的系统调试Log信息
通过查看events.txt中搜索"boot_progress"关键字或者通过以下命令过滤日志输出adb logcat -d -v time -b "events" | grep "boot_progress"
行末数字即为此刻距开机时刻的时间间隔。每行代表开机的各个关键阶段。
阶段 | 描述 |
---|---|
boot_progress_start | 系统进入用户空间,标志着kernel启动完成 |
boot_progress_preload_start | Zygote启动 |
boot_progress_preload_end | Zygote结束 |
boot_progress_system_run | SystemServer ready,开始启动Android系统服务 |
boot_progress_pms_start | PMS开始扫描安装的应用 |
boot_progress_pms_system_scan_start | PMS先行扫描/system目录下的安装包 |
boot_progress_pms_data_scan_start | PMS扫描/data目录下的安装包 |
boot_progress_pms_scan_end | PMS扫描结束 |
boot_progress_pms_ready | PMS就绪 |
boot_progress_ams_ready | AMS就绪 |
boot_progress_enable_screen | AMS启动完成后开始激活屏幕,从此以后屏幕才能响应用户的触摸,它在WindowManagerService发出退出开机动画的时间节点之前 |
sf_stop_bootanim | SF设置service.bootanim.exit属性值为1,标志系统要结束开机动画了 |
wm_boot_animation_done | 开机动画结束,这一步用户能直观感受到开机结束 |
各行log对应的打印代码为
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");
}
可以将测试机与对比机抓取的此log数据制作成表格,制作成折线图,可以更加直观的观察到耗时异常的流程。
通过"boot_progress_"关键字分析日志,粒度较大,只能定位出大概的耗时流程,之后还需分析流程内部具体的耗时情况。开机各流程内部也有相应的日志,可以进行更加细致的分析。例如在SystemServiceManager.java类中启动服务时,会打印启动某项服务的日志。通过查看某个服务A与下一个服务的日志时间,可以计算出启动服务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工具可以进行更加直观的分析。
2.2 使用bootchart工具
bootchart是一个能对GNU/Linux boot过程进行性能分析并把结果直观化的开源工具,在系统启动过程中自动收集 CPU 占用率、磁盘吞吐率、进程等信息,并以图形方式显示分析结果,可用作指导优化系统启动过程。BootChart包含数据收集工具和图像产生工具,数据收集工具在原始的BootChart中是独立的shell程序,但在Android中,数据收集工具被集成到了init程序中。
以下涉及到的命令,请自行应该参数的path。
- 抓取bootchart数据
bootchart开始生成数据的源码
// system/core/init/bootchart.cpp
static int do_bootchart_start() {
// 只要存在/data/bootchart/enabled文件,即抓取bootchart数据
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
在设备启动后,提取启动图表:
adb pull /data/bootchart
获取到bootchart数据之后进行打包
tar -czf bootchart.tgz *
- 分析数据
下载Boot Chart包并解压,使用bootchart.jar解析生成的文件输出图片
java -jar bootchart.jar bootchart.tgz
从生成的图片可以更加直观详细的看到开机耗时以及硬件使用情况。个人认为,bootchart的分析应该是以PIXEL或者开机速度正常机子的bootchart为参考来对照分析。使用完之后,记得删除enabled文件以防每次开机都收集启动数据。
2.3 抓取boottrace
抓取开机阶段的trace,也就是boottrace,是一种重要的分析开机的手段。抓取方式如下:
- 将手机中的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到手机里面
adb push atrace.rc /system/etc/init/
- 打开抓取boottrace的属性开关
adb shell setprop persist.debug.atrace.boottrace 1
- 重启手机
- 手机启动完成之后等待几秒,关闭boottrace属性开关
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. 开机速度优化实践
在Android S升级Android T的过程中,遇到一个开机速度的问题。同一个项目的手机,在升级Android T之后,比之前的开机时间大概要多18秒左右。
首先在手机开机之后,查看boot_progress相关日志如下
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
发现,在boot_progress_pms_ready到boot_progress_ams_ready之间,耗时近20秒,严重超时。
确定了大体的耗时异常点之后,我们抓取boottrace再进行进一步的分析。
由于PMS和AMS服务军运行再SystemServer进程当中,所以我们重点关注SystemServer进程的运行情况。
查看trace文件发现,主要是因为在启动AudioService的时候耗时较长。startService相关的日志也表明了这一点。
可见,启动AudioService耗时15.5秒左右。通过在AudioService相关的代码里面添加log,最终定位到为AudioService驱动在Android T上的问题。转交Audio模块处理之后,开机时间正常。
参考
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系统启动流程(二)init进程
Android Zygote Startup
安卓10开机时间优化分析
Android开机各个阶段(Android R)
优化启动时间