免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

用Perfetto精准分析Android 14开机流程:从Zygote到SystemServer的耗时拆解

用Perfetto精准分析Android 14开机流程:从Zygote到SystemServer的耗时拆解 如果你跟我一样干过Android系统性能优化一定遇到过这种局面客户或者领导说开机太慢但你既不能靠感觉拍脑袋也不能光盯着秒表看数字。慢在哪是Kernel拉起太慢还是init执行太慢是Zygote预加载拖了后腿还是SystemServer里某个服务堵塞了整条链路不拆开看是说不清的。这时候Perfetto就是最能打的工具我甚至见过有人把它手误搜成perftto照样能找到一堆资料——名字拼错都不影响它好用。这篇内容就围绕Android 14开机流程讲讲怎么用Perfetto抓开机trace、怎么分析关键耗时点以及我踩过的那些坑。很多人以为开机流程分析就是把trace拉出来看一眼CPU占用率其实远没有这么简单。Android开机是一条从Bootloader一路延伸到BOOT_COMPLETED广播的长链路中间还涉及硬件初始化、分区挂载、SELinux策略加载、init脚本解析、native服务启动、ART虚拟机预热、system_server内部几十个服务的启动。任何一个环节出问题整体开机时间都会受影响。而Perfetto的优势在于它能把这条链路中几乎所有可观测的行为按时间线铺在同一个视图里CPU调度、线程状态、Binder调用、锁等待、I/O、系统事件、服务启动标记一目了然。1. Android 14开机流程分析的核心思路1.1 从Bootloader到BOOT_COMPLETED开机到底经历了什么Android开机的完整链路笼统讲可以分为六个大阶段Bootloader阶段芯片厂商的bootloader如高通平台的XBL、ABL先跑起来完成内存初始化、显示初始化加载并校验boot分区镜像最终把控制权交给Linux内核。硬件厂商的代码在这个阶段占比很大Perfetto一般抓不到太多内部细节只能根据启动时间戳估算损耗。Linux内核启动内核开始初始化设备驱动、创建内存管理、调度器等核心子系统最后挂载根文件系统拉起第一个用户态进程init。Android 14的内核阶段还涉及GKIGeneric Kernel Image带来的变化驱动模块化之后有些厂商会把启动相关的驱动编进内核有些放在boot分区动态加载这会影响内核启动耗时。init进程解析init.rc和导入平台的rc文件挂载分区、设置SELinux策略、启动属性服务、拉起zygote、surfaceflinger、servicemanager等一系列native关键进程。Android 14在这方面做了一些并行化部分服务启动不再严格按串行依赖等待但引入的复杂性也让分析难度变高了一点点。Zygote启动Zygote进程启动时做的事非常重要创建ART虚拟机、预加载系统类、预加载系统资源、预热各种SharedPreference和主题资源等。这一阶段是开机优化中经常被开刀的环节因为预加载的东西通常只多不少。SystemServer启动Zygote fork出system_server进程它负责启动AMSActivityManagerService、WMSWindowManagerService、PMSPackageManagerService等核心服务。Android 14里服务更多线程更复杂服务间的Binder等待和锁竞争也更容易成为隐藏瓶颈。开机完成广播SystemServer里一系列服务启动完会发出BOOT_COMPLETED广播第三方应用收到之后才能做开机自启逻辑桌面Launcher开始首帧绘制用户才会感觉“系统起来了”。在做开机流程分析时真正要盯的不是“整个开机用了多少秒”而是每一步用了多少秒、每一步里面什么操作最耗时以及步骤之间是否存在无谓的等待。比如Zygote阶段CPU明明在跑但跑的内容却是重复的资源加载再比如SystemServer阶段某个线程长时间处于阻塞状态另一头Binder调用一直在等这两个现象的性质完全不同。1.2 Perfetto能做什么做不到什么Perfetto是目前Android系统默认的性能分析与跟踪工具它替换掉了早年间的systrace。在开机流程分析这个场景里它的核心能力可以总结成四点多数据源的时间线同步ftrace的CPU调度、线程切换、Binder事务、lock contention、cpu_frequency变化、内存回收这些不同类型的数据会被整理到统一的时间线上直接看到“这个时刻CPU在跑谁的代码”非常适合分析“卡点”。系统事件和性能标记Perfetto会采集Android系统的一些关键事件比如不同阶段开机事件的标记点这些标记点会出现在trace中。分析时可以直接按标记点对齐不需要自己买秒表卡时间。进程生命周期跟踪init启动的每个native进程、Zygote fork出的每个App进程它们的创建时间、退出时间、CPU使用情况都能查到。SQL分析接口trace文件导入Perfetto UI之后可以用SQL筛选和聚合数据。处理几百兆的trace时SQL比肉眼在时间轴上滚动高效得多。但Perfetto也不是万能的。Bootloader内部执行了什么、内核解压镜像耗时、硬件初始化时序这些在没有额外埋点或硬件日志的情况下抓不到。另外有些厂商自己的慢启动服务如果没有输出任何perfetto事件也没法用trace直接解释你得结合logcat和kernel ring buffer一起看。所以别指望一个trace能回答所有问题它的价值在于把“可观测的系统行为”变成数据剩下的事情还是要靠人来分析。2. 开机抓trace的环境准备与配置2.1 为什么选择Perfetto而不是旧版systrace先说说为什么现在分析开机流程不需要再抱着systrace那套不放了。systrace本质上是基于ftrace的封装展示能力有限trace一大就卡《Android 14》上很多场景已经跑不顺畅。Perfetto作为systrace的继任者从Android 10开始逐步成为系统跟踪的默认方案Android 12之后基本全面接管。直接说差异我用一个表格罗列下对比项Perfetto旧版systrace数据源ftrace、atrace、heap_profile、power等统一采集依赖atrace和ftrace扩展性弱配置文件使用protobuf/text格式的trace config极其灵活命令行参数控制不了太多细节文件大小支持几百MB甚至上GB的trace大了容易丢帧、打不开分析界面ui.perfetto.dev响应快支持SQL查询老界面卡、慢、缺功能开机自动抓取支持通过boottrace配置实现早期启动自动抓取有boot选项但功能简陋社区与文档官方文档齐全更新勤快处于维持状态如果你做Android 14开机流程分析还去用systrace那种老路子不是不行但很多细节看不到。举个最简单的例子systrace里你很难快速统计某个进程在开机30秒内到底有多少时间处于不可中断睡眠态D状态而在Perfetto里一句SQL就出来了差距很明显。2.2 开启开机自动抓取的正确姿势要分析开机流程必须保证trace从开机最早期就开始抓。如果等系统启动完成之后再手动开启Perfetto早就错过了Zygote和SystemServer的启动过程。AOSP在Android 10之后内置了“开机自动抓trace”的能力简单说就是让perfetto的守护进程traced在启动早期就运行并根据一个预先放置的配置自动开始采集。我这边在Android 14的userdebug版本上验证过一套可行的流程其他版本思路一致细节略有差异。第一步确认设备支持。你需要一个userdebug或eng版本的Androidrelease版的user版本权限不够很多属性设不了。另外确认一下设备里有/system/bin/boottrace这个可执行文件如果厂商裁剪过可能没有。第二步开启traced持久运行。执行下面这些命令adb root adb shell setprop persist.traced.enable 1 adb shell setprop persist.traced_perf.enable 1persist.traced.enable的作用是让traced在init阶段就被拉起来而不是等用户手动触发。persist.traced_perf.enable对应的是perfetto的性能采样功能如果不需要采样CPU性能数据可以不开但开了也无妨。第三步准备perfetto配置。先本地创建一个文本文件比如boottrace.pbtx内容是一个简单的trace config。我平时用的基础配置长这样duration_ms: 60000 buffers { size_kb: 131072 } data_sources { config { name: linux.ftrace ftrace_config { ftrace_events: sched_switch ftrace_events: sched_wakeup ftrace_events: sched_process_exit ftrace_events: sched_process_free ftrace_events: binder/* ftrace_events: irq/irq_handler_entry ftrace_events: irq/irq_handler_exit ftrace_events: power/cpu_frequency ftrace_events: power/cpu_idle ftrace_events: kmem/rss_stat ftrace_events: mm_vmscan_direct_reclaim_begin ftrace_events: mm_vmscan_direct_reclaim_end } } } data_sources { config { name: android.process_stats } }这里我解释下几个关键字段。duration_ms: 60000表示抓取60秒一般冷启动到桌面也就一二十秒60秒足够覆盖完整开机阶段还留有余量。buffers.size_kb设置为131072KB即128MB开机trace事件量大buffer太小会导致后续数据被覆盖。ftrace_events里我重点选了sched和binder这两个是分析线程调度和锁等待的基础power/cpu_frequency用来观察CPU频率爬升是否及时mm_vmscan_direct_reclaim用来定位是否有内存回收导致的卡顿。第四步把配置push到设备上并设置开机自动抓取的开关adb push boottrace.pbtx /data/misc/perfetto-configs/boottrace.pbtx adb shell setprop persist.perfetto.boottrace 1 adb rebootpersist.perfetto.boottrace在部分平台上可能不生效这种情况可以把配置放到/data/misc/perfetto-traces/下或者直接改源码里boottrace默认读取的路径。不同厂商会有差异但核心逻辑是init启动traced之后boottrace模块会读取预置的配置并启动一次perfetto采集。如果重启后没抓到文件可以用adb shell ls /data/misc/perfetto-traces/确认产物路径再根据平台实现调整。有一点要提醒开机自动抓取的配置尽量精简不要一下挂上几百个ftrace事件。开机阶段本身事件量就大事件太多会因为perfetto自身也需要CPU而拖慢开机反而干扰你要分析的系统表现。我平时就保持上面那组核心事件够用。3. 实录开机trace的抓取、导入与关键信息提取3.1 完整抓取步骤与产物确认设备重启后等它进入桌面再等个30秒让perfetto把剩余数据写完因为配置文件里设置了60秒duration。然后执行adb shell ls -lh /data/misc/perfetto-traces/正常情况下能看到一个类似boottrace.perfetto-trace的文件大小从几十MB到一百多MB都有可能。如果没看到文件多半是boottrace没被触发或者traced没起来后面第五部分会专门讲排查办法。确认文件存在后把它拉到本地adb pull /data/misc/perfetto-traces/boottrace.perfetto-trace ./boot.perfetto-trace拉取可能稍微有点慢文件大是正常的不用慌。顺便说一句如果你只想大概看下启动阶段有没有明显异常不想搞这么重的trace也可以先靠adb logcat -b events | grep -i boot看事件输出。但真正定位问题还是得拿完整的perftrace分析两个手段用途不一样。3.2 在Perfetto UI里快速定位开机关键时间点把boot.perfetto-trace拖进浏览器打开 https://ui.perfetto.dev 等它加载完你会看到一段很长的时间线。关键不是从头看到尾而是先找到几个锚点。第一个锚点是开机事件的标记点。在UI顶部有个搜索框可以在输入框里搜索BootCompleted或者boot_completePerfetto支持符号搜索会定位到对应的事件。更通用一点的方法是在搜索框里输BootUI会列出所有和Boot相关的slice和事件其中就包括Android 14各个系统服务阶段的时间点。第二个锚点是Zygote的启动时间。在搜索框里输入zygote可以在进程列表中定位到zygote进程然后看它的CPU轨道的起始位置。如果你想看得更细可以搜索ZygoteInit这个Java方法类名会出现在trace里是Zygote主线程开始执行的标志。第三个锚点是system_server。搜索system_server找到它的pid和线程轨然后重点观察它的主线程和其他核心线程比如ActivityManager、PackageManager、WindowManager相关线程在启动阶段有没有长时间处于不可运行状态。定位到这些锚点之后我把时间轴缩小先看全局从kernel启动到zygote启动再到system_server启动再到BOOT_COMPLETED这四段时间分别有多长。如果某一端特别长就深入到那一段去分析。3.3 用SQL命令把耗时量化出来光用眼睛滚动时间轴效率太低尤其trace文件大、线程多的时候。Perfetto UI内置了SQL分析引擎这是我最喜欢的功能。下面这几个查询是我每次做开机分析必用的。查询开机BOOT_COMPLETED事件的发生时间trace内的时间戳单位为纳秒select ts, name from slice where name BootCompleted or name like %BOOT_COMPLETED% order by ts limit 5;如果看到输出为空可能是具体实现里事件名不同可以搜索boot_completed或BOOT_COMPLETED大写变体。查询某个进程在开机阶段处于不可中断睡眠状态D状态的总时长比如system_serverselect process.name, thread_state.state, sum(dur) / 1000000000.0 as total_seconds from thread_state join thread using (utid) join process using (upid) where process.name system_server and thread_state.state D group by process.name, thread_state.state order by total_seconds desc;这个结果能直接告诉你system_server到底有多少时间卡在内核态的I/O等待上。如果一个服务线程有几百毫秒甚至几秒的D状态那说明它一直在等I/O优化方向要往存储、磁盘I/O上想。查询CPU频率的变化看系统在开机阶段有没有因为调频策略太保守导致频率爬升慢select ts, value from counter c join counter_track t on c.track_id t.id where t.name like %cpufreq% limit 2000;当然这个也可以用UI的“CPU Frequency”轨道直接看但SQL的好处是可以和耗时数据做进一步关联比如算开机阶段平均频率、频率低于某个阈值的时长等。还有一类查询特别适合做对比分析Zygote进程启动到system_server进程启动的间隔。先查出两个进程各自的pid和启动时间点然后相减。select name, pid, start_ts from process where name in (zygote, system_server, bootanimation);这样每次抓完trace都能拿到一组量化好的数据方便做横向对比。4. 定位开机慢的根因三个实战排查案例4.1 案例一Zygote阶段I/O抖动导致预加载耗时翻倍有一台测试机冷启动一直比同配置的兄弟机型慢近三秒。抓trace之后先用SQL查了各个启动节点的耗时发现多出来的三秒主要集中在zygote阶段。zygote进程从启动到完成fork system_server耗时接近5秒正常应该2秒多。于是把范围缩到zygote主线程看它在每个时间片里到底在跑什么。结果发现zygote主线程在预加载过程中多次长时间进入D状态每次都是几十到几百毫秒。这说明I/O是最大嫌疑。继续对照ftrace里的mm_vmscan事件以及内核日志定位到是系统在开机早期进行了大量页缓存回收导致正在读取的class文件被反复换出换入I/O路径变长。这个问题的根因实际上是另一个后台服务在开机阶段扫描了外部存储目录不停触发page cache和内存回收。后续通过调整那个服务在开机阶段的启动优先级避开与zygote预加载争抢I/O开机时间恢复了正常。所以我想强调一点trace里看到“Zygote阶段慢”不要马上认定是预加载的类太多要先看线程状态分布。如果D状态占比高优先查I/O如果都处于R状态且跑满CPU才考虑减少预加载内容。4.2 案例二SystemServer里有个服务锁等待拖慢了BOOT_COMPLETED另一个案例是开机时间本身不明显恶化但BOOT_COMPLETED广播一直晚发导致很多应用开机后启动慢体感“桌面出来了但通知栏卡半天”。从trace里发现system_server的某个核心服务线程在开机阶段做了很长一段Binder等待。Perfetto里Binder相关事件带transaction id能看出是哪个进程在调用哪个服务。追踪下去发现问题出在一个厂商自研服务向一个低频服务同步查询配置而那个低频服务线程池被打满请求排队。相当于一条路上堵车其他车全在路口等。这个场景里Perfetto最有价值的点是把“谁在等谁”的因果关系还原了出来。单看调用方的线程栈你只会觉得它在等一个Binder返回顺着Binder事件找到被调方线程再到被调方的线程状态才能看到排队。排查锁竞争和Binder超时的时候这个思路通用。改造方案是把那个同步请求改成异步初始化让厂商自研服务在开机阶段不阻塞等待低频服务的结果只在确实用到的时候实时查询。BOOT_COMPLETED发出时间提前了约600ms。4.3 案例三CPU频率拉不起来导致整体拖慢还有一次很典型的调度问题。设备不热、负载也不高但开机各阶段整体都比正常值慢15%左右看起来每个阶段都不是特别离谱合在一起就慢了很多。查trace里的cpu_frequency轨道发现开机前10秒CPU大核频率一直被压在最低档只有负载冲高之后才慢慢爬升。这个问题的原因是调频策略的采样周期和开机阶段的负载特征不匹配。开机阶段的特点是突发性短任务多比如多次快速读文件、快速fork进程单次负载脉冲很短调频器还没反应就结束了导致CPU一直跑了低频率。这种问题在Perfetto里判断起来很快一眼看CPU频率轨道有没有出现“该高不高”的时间段再配合调度器sched_switch事件看关键进程跑在哪个CPU、什么频率下。修复方向是调整cpufreq的调频参数让它在高负载场景下更快响应同时配合schedutil场景把大核唤醒阈值调低。这个案例提醒我专注单个进程耗时也可能会被掩盖全局看CPU频率、内存、I/O这些系统资源使用情况往往能发现更底层的性能瓶颈。4.4 对比实验优化前后怎么用同一套trace量化收益无论是做优化方案还是写性能报告对比数据必须有说服力。我习惯的做法是同一个设备、同一版本软件优化前抓一次trace优化后抓一次trace保存好两次的原始文件。然后跑同一个SQL集合把下面这些指标做成表格开机总耗时从内核启动到BOOT_COMPLETEDZygote耗时system_server耗时system_server内D状态总时长Zygote主线程R状态总时长启动阶段平均CPU频率对比时要注意排除干扰变量。比如测试机后台有没有装其他应用、存储剩余空间是否变化、是否连接了充电器这些都会影响开机速度。最好连续抓三到五次取中位数而不是拿一次结果就下结论。用同样的trace分析流程跑完对比之后优化收益就很直观了。比如“Zygote阶段耗时从4120ms降到2960ms其中主线程D状态总时长从1800ms降到420msbooot_completed提前1100ms”这种描述远比“开机感觉快了一些”有说服力。5. 常见问题速查与避坑指南5.1 开机阶段没有自动抓取到trace常见原因有三个。第一个是persist.traced.enable没有设成功重启后traced没起来可以在重启后执行adb shell ps -A | grep traced确认。第二个是boottrace配置文件没被读到路径不对或者权限不对检查/data/misc/perfetto-configs/下文件owner是否为root。第三个是平台裁剪掉了boottrace服务/system/bin/boottrace不存在这种情况需要走源码定制或者退而求其次在init.rc里手动加一个执行perfetto的service来模拟。如果你只是临时分析也可以不依赖boottrace先把设备正常开机然后adb root在设备上手动启动perfetto同时设置一个breakpoint不行开机早期已经过去了。所以临时方案只适合分析“开机后应用启动”这类场景不适用于完整开机流程。5.2 trace文件太大UI打开卡顿抓开机trace通常不会小但如果你同时开了太多ftrace eventsbuffer又设得很大文件可能几百MB。Perfetto UI能打开但很卡。我的建议是抓取阶段就控制数据量每周ftrace event不要太多养成“最小可用事件集”习惯。分析阶段如果文件太大先用perfetto命令行工具做一次预处理只保留需要的track或者事件类型。另外perfetto支持通过配置文件里的TrackEvent数据源做更精细的裁剪开机分析一般用linux.ftrace加少量atrace就足够。5.3 怎么快速从trace里找到真正的“卡点”不要一上来就在UI里逐线程翻那会看到眼花。我习惯分三步走第一步先看总览分布搜索BootCompleted、zygote、system_server这些关键事件把整条时间线切成几段确定哪一段最可疑。第二步看可疑时段内哪些线程长期处于非R状态。SQL查D状态、S状态不可中断睡眠和可中断睡眠的线程耗时。第三步针对排在Top的线程看它们的调用栈、子事件和Binder事务。这一层能看到具体的代码逻辑。如果三步走完还是没定位到根因再用排除法关闭一个候选的后台服务重新跑trace对比数据。5.4 厂商定制环境下的一些特例国产ROM、车机、电视等定制系统里开机流程经常被改得面目全非。有的把SystemServer里某些服务移到了其他进程中启动有的在init机关阶段加了很多自定义shell命令有的甚至改了Zygote的预加载逻辑。这种情况下AOSP原生的boottrace配置不一定能在同一时间点触发需要开发同学提供该平台的启动日志。遇到定制环境我建议先花一点时间看init.rc和服务管理器配置把平台自己的启动节点摸清楚再设计抓trace方案。trace本身只是一面镜子镜子放哪里、拍到哪些内容仍然取决于你对系统的理解。写在最后的实操体会分析开机流程这件事工具只占一半另一半是分析思路和对系统架构的理解。Perfetto能让我们把原本黑盒一样的开机过程变成一条可以放大、搜索、切片的时间线但拿到trace之后还是要靠“提出假设、验证假设”的循环去找根因。不要一上来就想找一个“一键定位”的功能系统性能优化没有银弹。我个人的习惯是每台机器改动前先抓一次基准trace存着改完再抓一次数据永远是讨论的基础。希望你也能用Perfetto把自己手头设备的问题揪出来。
返回列表