EN

Android Systrace 基础知识 - CPU Info 解读

字数统计: 5.1k阅读时长: 20 min
2019/12/21
loading

本文是 Systrace 系列文章的第十二篇,主要是对 Systrace 中的 CPU 信息区域(Kernel)进行简单介绍,简单介绍了如何在 Systrace 中查看 Kernel 模块输出的 CPU 相关的信息,了解 CPU 频率、调度、锁频、锁核相关的信息

本系列的目的是通过 Systrace 这个工具,从另外一个角度来看待 Android 系统整体的运行,同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章,但是总是记不住代码,或者不清楚其运行的流程,也许从 Systrace 这个图形化的角度,你可以理解的更深入一些。

版本说明(2026-10-13 核查):本文保留 2019 年的 Systrace 图例。芯片型号、CPU 编号、cpuset 路径和功耗状态都与设备及系统版本有关;旧参数表有误的地方已就近标注。Framework/工具示例按 AOSP android-10.0.0_r1 与 android-16.0.0_r1 对照,内核语义参考 Linux 6.6。文内设备检查与绑核命令未在本次审查中执行。

本文目录

系列文章目录

  1. Systrace 简介
  2. Systrace 基础知识 - Systrace 预备知识
  3. Systrace 基础知识 - Why 60 fps ?
  4. Systrace 基础知识 - SystemServer 解读
  5. Systrace 基础知识 - SurfaceFlinger 解读
  6. Systrace 基础知识 - Input 解读
  7. Systrace 基础知识 - Vsync 解读
  8. Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解
  9. Systrace 基础知识 - MainThread 和 RenderThread 解读
  10. Systrace 基础知识 - Binder 和锁竞争解读
  11. Systrace 基础知识 - Triple Buffer 解读
  12. Systrace 基础知识 - CPU Info 解读
  13. Systrace 流畅性实战 1 :了解卡顿原理
  14. Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析
  15. Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问
  16. Systrace 响应速度实战 1 :了解响应速度原理
  17. Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例
  18. Systrace 响应速度实战 3 :响应速度延伸知识
  19. Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇
  20. Systrace 线程 CPU 运行状态分析技巧 - Running 篇
  21. Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇

CPU 区域图例

这篇文章可与 Perfetto 9:CPU 信息解读 对照阅读。先定位目标线程的 Runnable、Running 和睡眠区间,再关联实际运行 CPU、频率、idle 状态及同时竞争的任务。Perfetto 的 CPU/线程轨道适合查看时序,sched、thread_state 和 counter 等数据适合统计;具体表结构以所用 Trace Processor 版本为准。

下面是一台旧 Android 设备的八逻辑 CPU 图例。原文将它标为骁龙 845,但截图本身不能证明 SoC 型号,后文的三档核心标注也不能用于反推这张图的设备信息。

CPU 区域图例

常用信息可以分为四类:

  1. CPU 轨道:由调度事件重建哪个线程何时占用这个逻辑 CPU。线程颜色不是性能等级。
  2. Clock Frequency:采集到的频率事件或采样值;图中 2419200 按 kHz 解释为 2.4192 GHz,不是利用率。
  3. Clock Frequency Limits:允许频率的上下限,不能当成这一刻的实际频率,也不能单凭它判断是谁发出了 boost。
  4. C-State/idle:CPU 空闲状态;旧查看器与原始事件的编号可能不同,后文单独说明。

顶部 CPU usage 是汇总利用率视图,需看统计窗口和归一化口径。整机利用率不高,也可能有单个关键线程长期 Runnable 或某个核心已满。高频不等于一直在执行,CPU 空闲时还可能保留进入 idle 前最后记录的频率。

碰到一个任务跑得慢,我们很自然会问:是不是跑到了小核上?是不是频率不够?像指纹解锁这样的场景,能不能放到大核去跑?带着这些问题看 Trace,再依次查等待调度、等待锁/Binder/I/O、实际 CPU 执行和关键路径。线程在小核上或频率较低只是线索,不能直接证明调度错误;迁到大核、提高频率也不能消除阻塞依赖。频率事件通常在变化时发出,Trace 开头缺值不等于 0 Hz,可按采集支持情况结合 sysfs 轮询补充。见 Perfetto CPU frequency 与 idle 数据。

基础线程状态解读见 Systrace 预备知识。

核心架构

为了阅读旧图,这里沿用同构、大小核和大中小核三种说法,它们不是对所有现代 SoC 的完整分类。核心微架构、性能档位、共享缓存、调频 policy 和 Linux CPU 编号是不同概念;相同架构的核也可能分属不同频率档位。

大小核架构

图中的 0–3、4–6、7 是某个样本的标注,不能套到所有设备。“后台任务只能跑小核”也取决于当时 cpuset、affinity 和调度策略,而不是核心硬件自带的规则。

非大小核架构

同构表示采用相同类型的 CPU 核心,不代表所有核始终同频、同功耗,或必须一起开关。例如 Helio A22采用四个 Cortex-A53。是否共享频率控制、能否独立 idle/online,要再看平台实现。

原文举的骁龙 636 不适合作为同构示例:官方产品资料明确是 4 个 performance + 4 个 efficiency 核心,Kryo 260 八核,也不是后文曾写的“4 小核 + 2 大核”。

大小核架构

这类设计提供不同性能/能效档位,调度器在负载、可运行 CPU 和功耗约束下安排任务。不能默认 CPU 0–3 总是小核、4–7 总是大核,也不能仅按主频跨微架构比较性能。

骁龙 845 官方规格是 Kryo 385,4 个 performance + 4 个 efficiency 核心,最高频率标为 2.8 GHz。下图保留当年的结构示意;图中峰值和性能提升比例不是本次设备测量结果,Kryo 产品名也不应简单等同于原封不动的 Cortex 核实现。

845

高性能核与高能效核的数量并不固定,例如骁龙 670 是 2+6。至于够不够用,就看你舍不舍得花银子,毕竟一分价钱一分货,高通爸爸也不是做福利的。工程分析则需要看具体负载和实际设备。

下面这张旧参数表已不适合作为规格依据,且包含错误。 保留它用于识别原图,核实后的更正列在图后:

型号 可核实的官方信息 旧图需要更正之处
Snapdragon 670 Kryo 360,2 performance + 6 efficiency;Adreno 615 不是图中的 4+4,也不是 Adreno 620
Snapdragon 460 Kryo 240 八核;Adreno 610 不是图中的 Kryo 360 Silver 与 Adreno 605
图中 Snapdragon 640 本次未找到可核实该栏的正式产品规格 不把该栏作为已发布产品参数引用

来源:670 产品资料、460 产品资料。调频档位应以设备实际暴露的 policy/频率表为准。

大中小核架构

以下保留几个历史例子:

  • 骁龙 855 的 Kryo 485 按 1 Prime + 3 performance + 4 efficiency 分档,官方技术说明列出的频率分别为 2.84、2.42、1.80 GHz。
  • Helio X30为 2 个 Cortex-A73 + 4 个 Cortex-A53 + 4 个 Cortex-A35。
  • 麒麟 980为 2 个 Cortex-A76 2.6 GHz + 2 个 Cortex-A76 1.92 GHz + 4 个 Cortex-A55 1.8 GHz。同样叫 A76,也可以有不同性能档位。

“超大核”“中核”是便于理解的称呼,不是固定数量或统一的 Linux 调度类。下面的旧对比图中,855 的 2.41/1.78 GHz 等数值与官方标称并不完全相同;未给出测量条件时,不应据此比较峰值或推算性能。

2019 年发布的骁龙 865 延续 1+3+4 分档,下图标出了 Prime 2.84 GHz。我当时还猜了一句,不知道 865 Plus 会不会搞到 3 GHz。后来有了结果:2020 年发布的 865 Plus官方标称 Prime 最高 3.1 GHz。

较新的设计也不必保留传统的 A5x 小核。例如 2025 年 Snapdragon 8 Elite Gen 5 产品资料列出自研 Oryon 的 2 Prime(最高 4.6 GHz)+ 6 Performance(最高 3.62 GHz)。因此,读新 Trace 时应重新确认 SoC、CPU topology、capacity 与调频 policy,不能沿用旧图的编号表。

绑核

绑核通常是限制某个线程允许在哪些 CPU 上运行。它可以减少迁移或限制后台任务,也可能因缩小可选 CPU 集合而增加排队。固定在一个核上并不保证独占该核,不会阻止抢占,也不保证任务更快。

配置 CPUset

cpuset 为一组任务约束可用 CPU 和内存节点。Android 旧系统常见 top-app、foreground、background、system-background 等组;名称不代表固定的 CPU 掩码。foreground/boost 属于部分旧实现,不能当作所有版本必有的组。

原文把某组 0-7、0-3 配置称为“Google 官方默认”不准确。Android 10 的 init.rc先从根组复制可用集合,并要求设备配置设置合适的 CPU。下面是 init 语法节选,不是 shell 命令:

1
2
3
mkdir /dev/cpuset/top-app
copy /dev/cpuset/cpus /dev/cpuset/top-app/cpus
copy /dev/cpuset/mems /dev/cpuset/top-app/mems

旧 cgroup v1 路径是小写 /dev/cpuset/.../cpus。在确有这些节点的设备上,可以只读查看,例如:

1
2
3
4
5
6
7
$ adb shell cat /dev/cpuset/top-app/cpus
0-7
$ adb shell cat /dev/cpuset/top-app/tasks
1687
1689
1690
3559

这里的输出保留为历史示例,tasks 中是任务/线程 TID,不是“四个应用进程”。组成员和可用 CPU 会动态变化。还可结合 /proc/<TID>/cgroup 和 /proc/<TID>/status 的 Cpus_allowed_list 查看目标线程;路径中的 <TID> 需替换为真实线程号。

cgroup v2 使用 cpuset.cpus、cpuset.cpus.effective 等节点,成员语义也要区分 cgroup.procs 和 cgroup.threads;不要给 v2 路径机械拼上 v1 的 tasks。Android 可以同时使用 v1/v2 控制器,实际挂载与配置以设备为准。见 Linux 6.6 cgroup v2 文档。

进程也可以在启动时指定组。以下是 Android 10 lmkd.rc的完整服务声明:

1
2
3
4
5
6
7
8
service lmkd /system/bin/lmkd
class core
user lmkd
group lmkd system readproc
capabilities DAC_OVERRIDE KILL IPC_LOCK SYS_NICE SYS_RESOURCE
critical
socket lmkd seqpacket 0660 system system
writepid /dev/cpuset/system-background/tasks

Android 12 起 init 的 task_profiles 替代已弃用的 writepid 用法;Android 16 的 lmkd.rc使用 task_profiles ServiceCapacityLow。该版本默认 profile 的 JoinCgroup 指向 cpuset/system-background,设备/API/vendor 配置还可覆盖,不能只凭 profile 名断定运行在哪些核上。见 AOSP task profiles。

应用进程会随状态调整调度分组。下面是 Android 10 Process.java的常量节选,属于隐藏的 Framework 合同,不是普通应用可任意调用的绑核 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* Default thread group -
* has meaning with setProcessGroup() only, cannot be used with setThreadGroup().
* When used with setProcessGroup(), the group of each thread in the process
* is conditionally changed based on that thread's current priority, as follows:
* threads with priority numerically less than THREAD_PRIORITY_BACKGROUND
* are moved to foreground thread group. All other threads are left unchanged.
* @hide
*/
public static final int THREAD_GROUP_DEFAULT = -1;

/**
* Background thread group - All threads in
* this group are scheduled with a reduced share of the CPU.
* Value is same as constant SP_BACKGROUND of enum SchedPolicy.
* FIXME rename to THREAD_GROUP_BACKGROUND.
* @hide
*/
public static final int THREAD_GROUP_BG_NONINTERACTIVE = 0;

/**
* Foreground thread group - All threads in
* this group are scheduled with a normal share of the CPU.
* Value is same as constant SP_FOREGROUND of enum SchedPolicy.
* Not used at this level.
* @hide
**/
private static final int THREAD_GROUP_FOREGROUND = 1;

/**
* System thread group.
* @hide
**/
public static final int THREAD_GROUP_SYSTEM = 2;

/**
* Application audio thread group.
* @hide
**/
public static final int THREAD_GROUP_AUDIO_APP = 3;

/**
* System audio thread group.
* @hide
**/
public static final int THREAD_GROUP_AUDIO_SYS = 4;

/**
* Thread group for top foreground app.
* @hide
**/
public static final int THREAD_GROUP_TOP_APP = 5;

/**
* Thread group for RT app.
* @hide
**/
public static final int THREAD_GROUP_RT_APP = 6;

/**
* Thread group for bound foreground services that should
* have additional CPU restrictions during screen off
* @hide
**/
public static final int THREAD_GROUP_RESTRICTED = 7;

这些常量不是一份 CPU 编号表,也不保证每项都有同名 cpuset 目录;THREAD_GROUP_RT_APP 的名字本身不等于设置了 SCHED_FIFO。Android 10 的 OomAdjuster 会计算 sched group,并通过 handler 执行 setProcessGroup 等操作,需连同 native 实现及设备配置检查,而不是把 oom_adj 数值当作 CPU 优先级。

配置 affinity

taskset 是调用 sched_getaffinity / sched_setaffinity 的命令行工具。Linux affinity 是每线程属性;把进程主线程的掩码改了,不代表所有已有线程都已修改。有效 CPU 集合还受 cpuset 与在线 CPU 限制,权限也需满足。sched_setaffinity 手册说明了这些约束。

taskset 的用法

Android 10/16 的 Toybox taskset使用十六进制掩码,支持 -p 和 -a,不支持 util-linux 版本的 -c CPU 列表选项。下面是设备 shell 中的命令形式,TID、PID、COMMAND 都是待替换的占位符;设置操作仅用于有权限的受控实验:

目的 Android Toybox 形式
查看一个线程允许使用的 CPU taskset -p TID
查看一个进程当前各线程的掩码 taskset -ap PID
将一个线程限制在 CPU 3 taskset -p 8 TID
在 CPU 3 的亲和力约束下启动命令 taskset 8 COMMAND

最低位对应 CPU 0,所以十六进制 5(二进制 0101)表示 CPU 0 和 CPU 2;CPU 3 的掩码是十六进制 8。这里显示的是允许集合,不是线程此刻实际运行的 CPU。需要判断实际执行位置时,回到调度轨道。桌面 Linux 上支持 -c 的 util-linux taskset -pc 3 PID 是另一种接口,不能直接照搬到这些 Android Toybox 版本。

调度算法

允许集合只规定“能去哪”,调度器还要决定“何时跑、选择哪个 CPU”。Linux 6.6 的 EAS可结合 CPU capacity、负载与能耗模型进行选择;厂商也可能采用不同策略。观察到某线程连续在同一个核执行,不足以证明它被绑核。先检查 affinity/cpuset 与可选 CPU,再判断调度决策是否有问题。

锁频

CPU 频率调节、任务放置和场景性能提示相互关联,但没有统一的“小核逐档拉满,再迁中核,再迁大核”固定流程。应用启动等延迟敏感场景可能请求性能提升,具体是否提高最低频率、调整 uclamp、改变放置或保持多久,由系统、驱动和厂商策略决定。

原文列举的启动、安装、转屏、窗口动画、列表 fling 和游戏可以是优化场景,不代表 Android 保证每种场景都把 CPU/GPU/I/O/BUS 拉到最高。温控、电源模式和平台限额仍会限制最终结果。Linux 6.6 uclamp影响性能需求信号,在 schedutil 下可影响选频,但不是固定 MHz 或独占大核的承诺。

下面是旧高通平台的频率限制图:

选中记录给出的范围为 768000–2016000 kHz,即 768 MHz–2.016 GHz。最低和最高值不同,因此这张图显示的是一个允许范围,不是锁在某个单一频率;也不能仅凭范围判定触发者、温度或实际执行速度。

Linux CPUFreq 往往以共享 policy 管理一组 CPU。related_cpus 可以帮助识别这个范围;scaling_cur_freq 在许多驱动中是最近请求的频率,未必等于精确的硬件即时频率。分析要结合实际频率事件、idle、工作量和温控证据,见 CPUFreq 文档。

普通应用从 Android 12/API 31 起可在支持的设备上使用 PerformanceHintManager向系统报告关联线程的工作时长目标与完成情况。这是性能提示机制,不是应用直接锁核、锁频的接口。

CPU 状态

下面是旧查看器的 C-State 轨道;0、1、2、3 是这个样本的显示值,不是所有 Android CPU 都有四种固定状态:

必须先分清原始事件和查看器转换。旧版 Catapult power_parser对 cpu_idle 的处理是:原始 4294967295(0xffffffff,退出 idle)显示为 0,其余原始 state 加 1。所以采用这条解析路径时,图中的 1 对应原始 state 0,图中的 3 对应原始 state 2。

Perfetto SQL 的 cpuidle counter 保留原始值:0 是进入 idle state 0,4294967295 才是退出 idle;不能把旧图的 0 直接套过来。缺少进入/退出事件时,边界状态应视为未知。

这些 state 是平台 cpuidle 驱动提供的索引。可以按设备权限只读查看对应 CPU 的 /sys/devices/system/cpu/cpuN/cpuidle/stateN/ 下的 name、desc、latency、residency 等信息,确认其含义与单位;latency 和 residency 通常以微秒表示。进入更深状态往往在节能与唤醒延迟之间取舍,但事件记录并不总能证明硬件最终真的进入了请求状态。见 Linux 6.6 CPU idle 文档。

CPU idle 与 CPU offline/hotplug 是两种机制,现代内核仍支持 CPU hotplug,具体设备是否开放及如何使用另看实现。idle 也不等于整机 suspend。原文从其他平台引入的 HLT、固定 10 ns/100 ns 唤醒时间和“节能 70%”不能套到这些 ARM 轨道,因此不再作为本图的解释。

Systrace 中的详细信息

旧 Systrace HTML 内嵌了可按文本查看的 Trace;当前 Chrome 是否兼容旧交互查看器需另行确认。Perfetto protobuf 则需要相应工具解码,不能把二进制直接当同一种文本格式。

下面保留原始调度事件:

1
appEventThread-8193  [001] d..2 1638545.400415: sched_switch: prev_comm=appEventThread prev_pid=8193 prev_prio=97 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120

按这条记录逐项解释:

字段 含义
appEventThread-8193 当前任务名称与 Linux TID;8193 不一定是进程 TGID
[001] 事件发生在逻辑 CPU 1,不是 CPU 0
d..2 这里是四个字符字段,不是四个二进制位:IRQ disabled、未显示 resched 标志、普通上下文、preempt depth 2
1638545.400415 秒为单位的 Trace 时间戳,时间域由 trace clock/格式决定;不是这次调度耗时,也不是日历时间
prev_* / next_* 被切出的任务与将运行的任务;这里切到 CPU 1 的 idle 任务 swapper/1(TID 0)
prev_state=S 被切出的线程进入可中断睡眠;不能把它解释为仍 Runnable 而被别的任务抢占
prev_prio=97 / next_prio=120 内核调度优先级表示,不是直接的 nice 值;也不能只凭一个数字区分全部调度策略

d..2 记录的是事件发生时的上下文,不能据此把整段应用执行都算作关闭中断或禁止抢占。其他内核版本可能增加标志字段。格式说明见 Linux ftrace 文档。

其他原始样例与解读:

  1. sched_waking: comm=kworker/u16:4 pid=17373 prio=120 target_cpu=003:开始唤醒 TID 17373,目标 CPU 3;不代表它已经开始 Running,后续可能迁移。
  2. sched_blocked_reason: pid=17373 iowait=0 caller=rpmh_write_batch+0x638/0x7d0:该扩展事件给出阻塞位置及 iowait 标记。0 不足以排除所有 I/O/硬件等待;符号偏移也不是源码行号,此事件并非所有内核都有。
  3. cpu_idle: state=0 cpu_id=3:CPU 3 进入原始 idle state 0,不是进入活动 C0。
  4. softirq_raise: vec=6 [action=TASKLET]:发起对应 softirq,实际处理要再看 entry/exit,不等于已完成。
  5. cpu_frequency_limits: min=1555200 max=1785600 cpu_id=0
  6. cpu_frequency_limits: min=710400 max=2419200 cpu_id=4
  7. cpu_frequency_limits: min=825600 max=2841600 cpu_id=7

最后三项是以 kHz 表示的频率限制事件。cpu_id 指向该记录的 CPU/policy 代表,不保证只影响一个核;它们不能替代实际频率样本。读图、读原始事件、核对对应版本源码,三者应能互相解释。

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

参考

  1. Linux sched_setaffinity:线程亲和力
  2. AOSP cgroup 与 task profiles
  3. Linux 6.6 CPUFreq
  4. Linux 6.6 CPUIdle
  5. Perfetto CPU frequency 与 idle

春笋

拍了张照片,觉得还不错,分享给大家

CATALOG
  1. 1. 本文目录
  • 系列文章目录
  • CPU 区域图例
  • 核心架构
    1. 1. 非大小核架构
    2. 2. 大小核架构
    3. 3. 大中小核架构
  • 绑核
    1. 1. 配置 CPUset
    2. 2. 配置 affinity
      1. 2.1. taskset 的用法
    3. 3. 调度算法
  • 锁频
  • CPU 状态
  • Systrace 中的详细信息
  • 关于我 && 博客
  • 参考
  • 春笋