对于任何操作系统来讲,开机时间的优化都是一个很关键的工作。如果用户每次启动设备都需要等待很长的时间,那么其用户体验是很差的。本文从Android12出发,分以下三部分阐述Android系统的开机优化:
Android开机过程
分析开机时间
开机速度优化实践
要想进行开机速度的优化,首先需要了解开机的详细流程。开机过程从CPU上电开始,到锁屏界面显示出来结束。下图为比较简洁的开机流程图。
再来看一张比较详细的开机流程图
总的来说,开机过程分为以下六主要子过程
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
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。
Android_boot_2_bootloader.png
A. Bootloader第一阶段首先会检测和设置外部RAM
B. 外部 RAM可用之后,将Bootloader代码加载到外部RAM中
C. Bootloader第二阶段包含了设置文件系统,内存,网络等等。
D. Bootloader查找Linux内核并将其从boot media (或者其他地方,这取决于系统配置) 加载到 RAM 中,并且会配置一些内核启动时需要的启动参数
E. Bootloader执行完之后会跳转到 Linux 内核执行
一般也可将Bootloader程序的执行分为两个阶段,如下图所示
bootloader_1.png
执行Bootloader程序过程中,如果镜像验证失败、BootLinux (&Info) 函数启动失败或者接收到启动至 fastboot 的命令(比如使用 adb reboot bootloader进行重启、在启动时按下了电源键+下音量键组合)时,会进入到Fastboot模式(Fastboot 是一种电脑通过USB数据线对手机固件进行刷写、擦除/格式化、调试、传输各种指令的固件通信协议, 俗称线刷模式或快速引导模式)。
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 内核和供应商模块架构:
generic-kernel-image-architecture.png
由于Android的kernel实际上就是Linux kernel,只是针对移动设备做了一些优化,所以与其它Linux kernel的启动方式大同小异,都是对start_kernel函数的调用和执行。Kernel主要工作内容为设置缓存、被保护存储器、计划列表,加载驱动,启动kernel守护,挂载根目录,初始化输入输出,开启中断,初始化进程表等。当内核完成这些系统设置后,接下来在系统文件中寻找”init”文件,然后启动root进程或者系统的第一个进程。
Kernel启动过程分为两个阶段:
1)内核引导阶段。通常使用汇编语言编写,主要检查内核与当前硬件是否匹配。这部分也与硬件体系结构相关。
2)内核启动阶段。引导阶段结束前,将调用start_kernel()进入内核启动阶段。内核启动阶段相关的代码主要位于kernel/init/main.c。
Android_boot_3_kernel.png
A. 内存管理单元和高速缓存初始化完成之后,系统便可以使用虚拟内存和启动用户空间进程
B. 内核在根目录寻找初始化程序(/system/core/init),执行该程序以启动init进程
Kernel启动的核心函数是start_kernel函数,它完成了内核的大部分初始化工作。这个函数在最后调用了reset_init函数进行后续的初始化。reset_init函数最主要的任务就是启动内核线程kernel_init。kernel_init函数将完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程。到init_post函数为止,内核的初始化已经基本完成。
boot_kernel.png
用户空间的第一个进程便是init进程,进程号为1。
init_process.png
当系统启动完成之后,init进程会作为守护进程监视其它进程。在Linux中所有的进程都是由init进程直接或间接fork出来的。在init进程启动的过程中,会相继启动servicemanager(binder服务管理者)、Zygote进程。而Zygote又会创建system_server进程以及app进程。
Android_boot_4_init.png
对于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.cppint 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.cppint 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.cppint 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.cppint SecondStageMain(int argc, char** argv) {SetStdioToDevNull(argv);// 初始化本阶段内核日志InitKernelLogging(argv);// 系统属性初始化property_init();// 建立 EpollEpoll 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) {// 执行 Actionam.ExecuteOneCommand();// 还有就是重启死掉的子进程auto next_process_action_time = HandleProcessActions();}}
SecondStageMain的主要工作总结
使用epoll对init子进程的信号进行监听
初始化系统属性,使用mmap共享内存
开启属性服务,并注册到epoll中
加载系统启动脚本"init.rc"
解析启动脚本,启动相关服务
重点介绍下init.rc文件的解析
//system/core/init/init.cppstatic 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.rcimport /init.environ.rcimport /init.usb.rcimport /init.${ro.hardware}.rcimport /init.${ro.zygote}.rcimport /init.trace.rcon early-initstart ueventdmkdir /mnt 0775 root systemon initmount tmpfs none /sys/fs/cgroup mode=0750,uid=0,gid=1000mkdir /sys/fs/cgroup/memory 0750 root systemmount cgroup none /sys/fs/cgroup/memory memoryon property:sys.boot_from_charger_mode=1class_stop chargertrigger late-initservice ueventd /sbin/ueventdclass corecriticalseclabel u:r:ueventd:s0service logd /system/bin/logdclass coresocket logd stream 0666 logd logdsocket logdr seqpacket 0666 logd logdsocket logdw dgram 0222 logd logdseclabel u:r:logd:s0service console /system/bin/shclass coreconsoledisableduser shellseclabel u:r:shell:s0service adbd /sbin/adbd --root_seclabel=u:r:su:s0class coresocket adbd stream 660 system systemdisabledseclabel u:r:adbd:s0service servicemanager /system/bin/servicemanagerclass coreuser systemgroup systemcriticalonrestart restart healthdonrestart restart zygoteonrestart restart mediaonrestart restart surfaceflingeronrestart restart drmon late-inittrigger early-fstrigger fstrigger post-fstrigger late-fstrigger post-fs-datatrigger load_persist_props_action// 这里启动zygote-starttrigger zygote-starttrigger firmware_mounts_completetrigger early-boottrigger boot
可以看到,在解析init.rc的配置中,在late-init阶段启动了Zygote进程。
Android_boot_5_zygote.png
Zygote进程是Android中所有Java进程的父进程。Zygote进程在Init进程启动过程中被以service服务的形式启动。Zygote进程相关的.rc配置文件为init.zygote64.rc或者init.zygote32.rc。以init.zygote64.rc为例,其内容如下
// system/core/rootdir/init.zygote64.rcservice zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-serverclass mainpriority -20user rootgroup root readproc reserved_disksocket zygote stream 660 root systemsocket usap_pool_primary stream 660 root systemonrestart exec_background - system system -- /ssystem/bin/vdc volume abort_fuseonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cmeraserveronrestart restart mediaonrestart restart netdonrestart setprop sys.android.reboot 1writepid /dev/cpuset/foreground/taskscritical window=${zygote.critical_window.minute:-off} target=zygote-fatal
init进程解析init.zygote64.rc配置文件之后,会调用app_process
// frameworks/base/cmds/app_process/app_main.cppint 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.cppvoid 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@UnsupportedAppUsagepublic static void main(String argv[]) {//zygote进程会fork出system_server进程if (startSystemServer) {Runnable r = forkSystemServer(abiList, socketName, zygoteServer);// zygote进程中,r == null;zygote子进程(如system_server进程)中, r != nullif (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子进程的时候可以共享虚拟机和资源,从而加快进程的启动速度,节省内存。
Android_boot_6_systemserver.png
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.javapublic static void main(String[] args) {new SystemServer().run();}private void run() {try {Looper.prepareMainLooper();// 初始化本地服务System.loadLibrary("android_servers");// 初始化系统上下文createSystemContext();// 创建SystemServiceManagermSystemServiceManager = 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广播。至此,系统层面启动流程结束。
通过下图再回顾下整个开机流程
要想进行开机速度的优化,我们需要分析开机时间的分布,从而找出异常耗时的地方,从而进行实际的优化工作。下面介绍如何分析开机时间。
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"init_process_log.png
行末数字即为此刻距开机时刻的时间间隔。每行代表开机的各个关键阶段。
| 阶段 | 描述 |
|---|---|
| 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.cppvoid 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.javapublic 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.javaprivate 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.javapublic 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.javapublic 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.javapublic 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.javapublic 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.javapublic 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.javapublic 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@Overridepublic void enableScreenAfterBoot(boolean booted) {writeBootProgressEnableScreen(SystemClock.uptimeMillis());mWindowManager.enableScreenAfterBoot();synchronized (mGlobalLock) {updateEventDispatchingLocked(booted);}}
sf_stop_bootanim
// frameworks/native/services/surfaceflinger/SurfaceFlinger.cppvoid 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.javaprivate void performEnableScreen() {EventLogTags.writeWmBootAnimationDone(SystemClock.uptimeMillis());Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim");}
可以将测试机与对比机抓取的此log数据制作成表格,制作成折线图,可以更加直观的观察到耗时异常的流程。
boot_time_chart_1.png
通过"boot_progress_"关键字分析日志,粒度较大,只能定位出大概的耗时流程,之后还需分析流程内部具体的耗时情况。开机各流程内部也有相应的日志,可以进行更加细致的分析。例如在SystemServiceManager.java类中启动服务时,会打印启动某项服务的日志。通过查看某个服务A与下一个服务的日志时间,可以计算出启动服务A的耗时。
a0ca34f607cf9c51134adcfebc515333.png
// frameworks/base/services/core/java/com/android/server/SystemServiceManager.javapublic <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工具可以进行更加直观的分析。
bootchart是一个能对GNU/Linux boot过程进行性能分析并把结果直观化的开源工具,在系统启动过程中自动收集 CPU 占用率、磁盘吞吐率、进程等信息,并以图形方式显示分析结果,可用作指导优化系统启动过程。BootChart包含数据收集工具和图像产生工具,数据收集工具在原始的BootChart中是独立的shell程序,但在Android中,数据收集工具被集成到了init程序中。
以下涉及到的命令,请自行应该参数的path。
抓取bootchart数据
bootchart开始生成数据的源码
// system/core/init/bootchart.cppstatic 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/enabledadb reboot
在设备启动后,提取启动图表:
adb pull /data/bootchart获取到bootchart数据之后进行打包
tar -czf bootchart.tgz *分析数据
下载Boot Chart包并解压,使用bootchart.jar解析生成的文件输出图片
java -jar bootchart.jar bootchart.tgzBootchart.png
从生成的图片可以更加直观详细的看到开机耗时以及硬件使用情况。个人认为,bootchart的分析应该是以PIXEL或者开机速度正常机子的bootchart为参考来对照分析。使用完之后,记得删除enabled文件以防每次开机都收集启动数据。
抓取开机阶段的trace,也就是boottrace,是一种重要的分析开机的手段。抓取方式如下:
将手机中的atrace.rc拉取下来,并备份;
adb pull /system/etc/init/atrace.rc在文件atrace.rc末尾添加
on property:persist.debug.atrace.boottrace=1start boottraceservice 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 scheddisabledoneshot
将修改后的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文件来分析了。
在Android S升级Android T的过程中,遇到一个开机速度的问题。同一个项目的手机,在升级Android T之后,比之前的开机时间大概要多18秒左右。
首先在手机开机之后,查看boot_progress相关日志如下
07-15 04:13:35.244 I/boot_progress_start( 1059): 404007-15 04:13:35.934 I/boot_progress_preload_start( 1059): 473007-15 04:13:37.255 I/boot_progress_preload_end( 1059): 605107-15 04:13:37.636 I/boot_progress_system_run( 2221): 643207-15 04:13:38.260 I/boot_progress_pms_start( 2221): 705607-15 04:13:38.473 I/boot_progress_pms_system_scan_start( 2221): 726907-15 04:13:38.797 I/boot_progress_pms_data_scan_start( 2221): 759307-15 04:13:38.803 I/boot_progress_pms_scan_end( 2221): 759907-15 04:13:38.894 I/boot_progress_pms_ready( 2221): 769007-15 04:13:58.006 I/boot_progress_ams_ready( 2221): 2680207-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进程的运行情况。
9d055601ad4ebe5eddec1aff0249da2c.png
查看trace文件发现,主要是因为在启动AudioService的时候耗时较长。startService相关的日志也表明了这一点。
be264267394792edc34e531924092587.png
可见,启动AudioService耗时15.5秒左右。通过在AudioService相关的代码里面添加log,最终定位到为AudioService驱动在Android T上的问题。转交Audio模块处理之后,开机时间正常。
作者:努比亚技术团队链接:https://www.jianshu.com/p/3f23e027b591