
做内核层的性能分析第一步永远是看着perf list发呆能列出一大串事件标签但真正跑到“数据有参考意义”这个层面往往会发现内核根本没有给你开硬件采样接口。前阵子我在一台 Pixel 4 上刷 Android 10.0 内核想用 perf 抓几个关键硬件事件结果连cycles这种最基础的周期计数都拿不到查了一圈才定位到问题出在内核编译时 PERF 相关选项没有打进去。这篇文章就把整条链路完整梳理一遍编译阶段怎么把 CONFIG_PERF_EVENTS 和相关硬件 PMU 配置正确加进内核系统运行起来以后又怎么判断底层硬件精确采样能力PEBS 是其中被问得最多的一个到底开了没有。适合正在做 Android 内核改造、或者想在嵌入式/服务器上把 perf_event 用完整的读者内容偏实操有代码、有命令、有排查思路。1. 先把 PERF 和 PEBS 的关系理清楚1.1 PERF 是框架PEBS 是硬件能力PERFperf_event 子系统不是一个独立计数器而是 Linux 内核里一套统一的性能事件基础设施。你在命令行敲perf record、perf stat最终都是通过perf_event_open()系统调用来访问内核里的事件对象。内核把这套框架抽象成三类事件源软件事件比如 CPU 迁移、上下文切换、硬件事件比如 CPU cycle、cache miss、tracepoint 事件内核函数调用点埋桩。看清楚这一层很重要因为perf list能输出一大堆事件并不代表底层硬件采样可用。框架是通的但硬件驱动有没有注册、PMU 初始化有没有成功是另一回事。PEBSPrecise Event-Based Sampling是 Intel CPU 的一个硬件级采样机制它的最大特点是当指定事件触发采样时CPU 会把完整的硬件现场指令地址、寄存器值、时间戳、延迟记录等直接写入内存里的 Debug Store 区域而不是每次触发一个传统中断再由内核中断处理程序去读寄存器。这个设计大幅降低了干扰拿到的指令地址也几乎可以精确到实际触发事件的某条指令而不是中断之后被扭曲的那个位置。所以 PEBS 能不能用取决于三件事叠加CPU 型号是否支持、内核是否在编译时把 PMU 驱动和 DS 逻辑编进去、当前系统权限是否放行/proc/sys/kernel/perf_event_paranoid。把这两个概念放一起就很容易踩到第一个误区看到perf list里有 hardware event以为 PEBS 也开了或者反过来觉得 CONFIG_PERF_EVENTS 没开就判定硬件采样能力不存在。前者是软件框架后者是 CPU 能力中间还隔着一层内核驱动和一层权限控制排查时必须分层看。1.2 动手之前先确认平台x86 和 ARM 在“精准采样”上不一样第二件必须先说清楚的事PEBS 是 Intel x86 平台上的专属名词。AMD 上对应的机制叫 IBSInstruction-Based SamplingARM 平台上有 SPEStatistical Profiling Extension它们做的事情高度类似——由硬件把详细采样记录直接写进内存减少软件中断对采样结果的污染——但判断方式完全不同。想在 Pixel 4 上找到“PEBS 开关”就像在安卓手机上找 Intel 的睿频加速概念根本对应不上。Pixel 4 用的是高通骁龙 855SM8150CPU 是大中小核架构的 Cortex-A76/A55内核版本是 4.14整个平台属于 ARM64。这颗 SoC 没有 Intel PEBS但这不影响我们做 perf 级性能分析。内核里的 perf_event 框架是跨架构统一的你在编译阶段要做的事情完全类似打开 CONFIG_PERF_EVENTS打开平台对应的 PMU 驱动打开可选的统计扩展驱动ARM 上对应 arm_spe。跑起来以后用同样的 precise 测试手法去验证硬件精确采样能力到底存不存在只是 x86 上观察 PEBSARM 上观察 SPE 节点。平台不同观察点不同但排查顺序完全一致。2. 内核编译中 PERF 相关选项的完整梳理2.1 必开项与推荐项清单先给一张配置对照表。这些选项大多数在kernel/events/Kconfig、drivers/perf/Kconfig和arch/arch/Kconfig里定义不同内核版本略有差异但名字基本稳定。配置项作用平台建议CONFIG_PERF_EVENTS打开内核 perf_event 框架所有y缺失时一切 perf 功能都不存在CONFIG_HW_PERF_EVENTS启用硬件性能计数器驱动所有y缺失时 cycles 等硬件事件不可用CONFIG_ARM_PMUARM64 PMU 驱动ARMyarm64 上跑硬件事件必须CONFIG_ARM_SPE_PMUARM SPE 采样驱动ARM有硬件支持时 yCONFIG_PERF_EVENTS_INTEL_UNCOREIntel uncore 计数器x86排查内存带宽、跨核心事件时 yCONFIG_PERF_EVENTS_AMD_UNCOREAMD uncore 计数器x86对应 AMD EPYC 等平台CONFIG_PERF_EVENTS_INTEL_CSTATEIntel C-state 事件x86低功耗分析时 yCONFIG_PERF_EVENTS_INTEL_POWERIntel RAPL 计数事件x86功耗分析时 yCONFIG_IKCONFIG_PROC把内核配置导出为 /proc/config.gz所有y强烈建议CONFIG_DEBUG_FS挂载 debugfs所有PMU 调试信息常走这里CONFIG_PERF_CGROUPS按 cgroup 过滤 perf 事件所有Android 容器/进程组场景常用逐项解释一下关键点。CONFIG_PERF_EVENTS 是总开关它被关闭时下面那些硬件 PMU 驱动即使编译进去也不会被初始化。CONFIG_HW_PERF_EVENTS 是受 CONFIG_PERF_EVENTS 依赖的二级开关负责把 CPU PMU 注册进框架x86 上它一般会被自动 select但在 ARM/Android 手写的 defconfig 里经常会被漏掉。CONFIG_ARM_PMU 是 arm64 架构使用硬件计数器必不可少的驱动我在不止一个厂商内核里见过它没打开的情况症状就是 perf list 里软件事件和 tracepoint 都在但一跑硬件事件立刻报 invalid 或 unsupported。CONFIG_IKCONFIG_PROC 特别值得单独说一句。打开它以后内核配置会以 gzip 压缩形式导出到/proc/config.gz这是事后验证“编译开关到底打没打进去”最可靠的途径。如果没有它你就只能靠反汇编内核符号表或者猜排查效率会低很多。调试期我几乎一律设为 y哪怕多占几 KB 的 kernel image 也值得。2.2 用 defconfig 直接修改比 menuconfig 更适合批量编译改内核配置有三条路make menuconfig、直接编辑.config、直接编辑defconfig。个人开发机上怎么舒服怎么来但如果你想在 Android 内核这种多设备、多构建脚本的环境里把事情做干净我强烈建议直接改 defconfig。menuconfig 最终会把修改写回.config但.config内容极其庞杂diff 出来几百行无关变量review 的人根本不知道你真正改了啥。defconfig 是构建入口改动一行就是一行一目了然。以 Pixel 4 的内核源码为例通常在对应设备目录下能找到xxx_defconfig文件名称以你拉的代码 tag 为准。找到后直接追加需要的配置插入到文件末尾即可。有一点必须先说明defconfig 不是最终生效的.config。构建系统加载 defconfig 后还会运行olddefconfig自动把依赖项补全。比如你写了 CONFIG_PERF_EVENTSyolddefconfig 会尝试把select到它的 CONFIG_HW_PERF_EVENTS 也置为 y但不同内核版本的依赖链完善程度不一样有些老版本不会自动补齐所以比较保险的做法是相关选项显式全部写上不要赌自动依赖。如果你在内核 5.10 的 Android GKI 环境里还会遇到另一种玩法config fragment。构建脚本通过BUILD_CONFIG和KERNEL_CONFIGS变量把多个 fragment 合并成最终配置。这时改一个小片段文件比改全量 defconfig 更优雅缺点是 fragment 之间的优先级规则比较绕。我自己的原则很简单4.14 及更老的内核直接改 defconfig新内核优先用 fragment。改完以后构建之前先手动执行一次配置生成命令make xxx_defconfig然后立刻 grep 生成的.config确认改动进去了不要直接一把梭编完再回头查。3. 在 Pixel 4 / Android 10 上实操从源码到刷机验证3.1 准备编译环境和内核源码Android 官方推荐用 AOSP 环境编译内核但对个人研究来说不需要那么重。核心其实只有三样一份匹配的内核源码、一套能用的交叉编译工具链、一台已经解锁 bootloader 的 Pixel 4。拉代码的时候注意 tag 要和设备上固件版本对应Pixel 4 的 Android 10.0 内核一般挂在android-msm-coral-4.14系列分支用 repo 或 git clone 都可以。只改配置的话不拉完整 AOSP单独 clone 内核仓库就够了。工具链这块Android 官方构建已经全面切到 clang我也建议直接用 clang lld。在 x86 主机编译 arm64 内核需要一个够新的 clang建议 12 以上和配套的 sysroot。如果你的发行版包管理器里 clang 版本太老宁可去下载预编译的 toolchain 也不要拿旧版本硬编。内核源码里对编译器版本有一定要求特别是 4.14 这种老内核在过新或过旧的编译器下都可能出现奇怪报错。这里有一个很实际的经验编译内核不要在跨文件系统挂载的 Windows 目录下跑。内核构建有大量小文件随机 I/O在 WSL 的 ext4 分区上跑还好在/mnt/c这种挂载目录上跑会慢到怀疑人生。我用物理 Linux 机器或者云主机首次全量内核大约 10 分钟改完配置增量编译只要两三分钟。时间成本差一个量级。3.2 修改配置并编译直接给出我常用的操作流程照着走基本不会跑偏。进入内核源码根目录先找到构建脚本实际使用的 defconfig 路径。Pixel 4 的构建脚本一般在build.sh或common/build.config.*里指定配置名这一步不要跳。编辑 defconfig追加以下内容CONFIG_PERF_EVENTSy CONFIG_HW_PERF_EVENTSy CONFIG_ARM_PMUy CONFIG_ARM_SPE_PMUy CONFIG_IKCONFIG_PROCy CONFIG_IKCONFIGy如果你的设备不是 ARM 而是 x86把这套换成CONFIG_PERF_EVENTSy CONFIG_HW_PERF_EVENTSy CONFIG_PERF_EVENTS_INTEL_UNCOREy CONFIG_PERF_EVENTS_INTEL_CSTATEy CONFIG_PERF_EVENTS_INTEL_POWERy CONFIG_IKCONFIG_PROCy CONFIG_IKCONFIGy执行配置生成export ARCHarm64 export CCclang export CROSS_COMPILEaarch64-linux-gnu- make 设备名_defconfig执行完立刻检查生成的.configgrep -E CONFIG_PERF_EVENTS|CONFIG_ARM_PMU|CONFIG_IKCONFIG .config看到y再继续往下走否则先回去查 defconfig 是否改对了。开始编译make -j$(nproc)如果是 Android 的 boot.img 构建流程最好在build.sh基础上加参数不要自创 make 命令。Android 10 的 Pixel 内核构建涉及 boot image 打包、DTB 合并、签名等步骤只编一个Image.gz-dtb并不等于能得到可刷写的 boot.img。用官方脚本是最不容易出幺蛾子的方式。3.3 刷入与验证编完的产物需要先打包成 boot.img再通过 fastboot 临时启动验证我一般不直接刷死。命令大致是这样# 先临时启动验证 fastboot boot boot.img # adb 进系统后立刻确认内核版本和配置 adb shell cat /proc/version adb shell zcat /proc/config.gz | grep -E PERF_EVENTS|ARM_PMU|IKCONFIGgrep 全绿说明编译层的开关确实进去了。接下来在设备上装一个对应架构的 perf 工具。Android 系统镜像里不一定带 perf或者版本和内核不匹配。我自己习惯先在源码树里交叉编译一份静态 perfmake -C tools/perf ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- LDFLAGS-static编出来的二进制不大直接adb push到/data/local/tmp/perf就能跑。注意 perf 工具的版本尽量贴近内核主版本4.14 的内核用 4.14 分支的 perf 源码最稳。工具版本差异过大的话某些事件解析会报 unknown很容易误判成内核问题。4. 如何判断 PEBS 是否开启这是最难的一步4.1 先从系统里找证据判断 PEBS“是否开启”要分成两个层面看内核在编译期有没有把对应驱动编进去以及 CPU 在运行期有没有暴露硬件能力。先说一个容易误导人的点x86 内核配置里其实没有叫CONFIG_PEBS的选项。PEBS 支持是在 Intel x86 PMU 驱动里自动检测的只要 CONFIG_PERF_EVENTSyCPU 是近十几年的主流 Intel 型号驱动初始化时就会自动把 PEBS 逻辑拉起。很多人搜遍 menuconfig 找不到 PEBS 开关就以为不支持其实根源就是不了解这个机制。运行时找证据最直接的地方是 sysfs。x86 平台上cat /sys/bus/event_source/devices/cpu/caps/pebs cat /sys/bus/event_source/devices/cpu/caps/pebs_format能正常读出内容说明 PMU 驱动已经初始化了 PEBS 功能并且在告诉 perf 事件框架当前采用的记录格式。另一个来源是 dmesgIntel PMU 初始化早期一般会打印类似perf: PEBS fmt3的日志内核把硬件检测结果直接打到启动信息里了。如果你在 ARM 平台上对应的观察点是/sys/bus/event_source/devices/arm_spe_0这个设备节点是否存在。节点存在说明 SPE 驱动加载且硬件实现了扩展节点不存在则说明要么配置没开要么 SoC 没有实现 SPE。4.2 用 perf 命令实测 precise 采样比翻 sysfs 更直接的办法是发起一次带 precise 属性的硬件事件采样。x86 平台上跑perf record -e cycles:p -a -- sleep 1 perf report --stdiocycles:p表示请求这个事件在采样时尽量精确precise level 1cycles:pp是 level 2cycles:ppp是 level 3也就是强制要求 PEBS 能力。如果你的 CPU 和内核都支持这条命令会正常采集并输出带调用栈的报告。如果硬件或驱动不支持 preciseperf 通常会报Error: The cycles:p event is not supported.看到报错先别急着下结论这里有个重要的排查误区报 unsupported 不一定等于 PEBS 没开启。perf 工具自身版本太老、不认识 PMU 导出的 precise 能力会有这个问题非 root 用户碰到perf_event_paranoid限制perf 表面也报 unsupported实际上是权限。所以正确顺序是先确认 root、再看 paranoid 值、再看 CPU 型号最后才判断硬件能力问题。ARM 平台上没有 PEBS但这套 precise 实测思路完全通用。SoC 实现了 SPE 的话perf list里会看到arm_spe_0事件源没有实现则什么都没有。不要指望软件层能凭空造出硬件采样能力ARM 上的 SPE 开关就是看设备节点和 perf list 的结果。4.3 深入一点通过 CPUID/MSR 验证x86 专属可跳过想完全绕开内核抽象从硬件层直接确认 PEBS 是否存在x86 上有两个手段CPUID 和 MSR。先说 CPUID。CPU 指令 CPUID Leaf 0x1 的 EDX bit 14 表示 DSDebug Store能力DS 是 PEBS 的前置条件更进一步的架构性能监控信息在 Leaf 0xA 里。不过最直接的还是读 IA32_PERF_CAPABILITIES 寄存器也就是 MSR 0x345它里面有 PEBS 是否支持、PEBS 记录格式等关键标志位。在 Linux 下最快的读法modprobe msr rdmsr 0x345rdmsr来自 msr-tools输出 64 位十六进制数。分析每一位时务必要对着 Intel SDM 查当前代次的具体定义不同 CPU 代次字段位置有差异靠记忆极易出错。没有 rdmsr 的环境中也可以写一个几十行的 C 程序open(/dev/cpu/0/msr)后pread8 个字节出来再按位解析。不过这里我要给一个更实用的建议x86 上直接跑cycles:ppp做实测往往比手动解析 MSR 更可靠。因为 MSR 里的标志位只能说明硬件有 PEBS 能力内核 PMU 驱动能不能把能力用起来还取决于 DS 区域管理逻辑、BTS 交互、PEBS record format 适配等一堆运行期细节。你在 MSR 里看到“理论上支持”不代表 perf 里“实际可用”所以判断最终结论时以 perf 实测为主sysfs/dmesg 为辅MSR 只作最后的补充证据。5. 我踩过的坑与排查经验5.1 perf_event_paranoid 权限陷阱这个坑我至少见过三次。症状是内核 CONFIG_PERF_EVENTS 开了perf list 也不报错但一跑硬件事件就 Permission denied或者直接告诉你这个 event unsupported。第一反应往往会去怀疑配置和硬件结果问题出在/proc/sys/kernel/perf_event_paranoid。这个值默认 2主流发行版甚至可能到 3非 root 用户连部分软件事件都碰不得。Android 上还要叠加一层 SELinux即使你 adb root 了shell domain 默认也可能没有perf_event_open的权限。定位很快cat /proc/sys/kernel/perf_event_paranoid想临时采样降到 1 基本够用允许 CPU 事件但限制内核采样完整调试可以降到 0 或 -1echo 0 /proc/sys/kernel/perf_event_paranoid注意这只是临时生效。让改动持久化正规做法是改 sysctl 配置或者 init.rc 里的属性Android 上还要在 sepolicy 里给对应 domain 加 perf_event 访问规则。我经常看到有人临时 echo 完就觉得自己搞定了结果下一次重启或者 OTA 之后问题复现又开始从内核配置怀疑。优先把持久化方案做对排查成本会低很多。5.2 内核配置改了却没生效另一个高频问题defconfig 里明明白白写了 CONFIG_PERF_EVENTSy刷完机看/proc/config.gz却是# CONFIG_PERF_EVENTS is not set。我认真排查过这个现象最普遍的原因是改错了文件。Android 多设备内核源码里经常存在多个 defconfig构建脚本通过build.config或 PRODUCT 变量选择其中一个。你改了一个角落里的 defconfig构建脚本用的却是另一个改动自然没生效。这类问题最有效的防御手段就是构建完成后立刻检查生成出来的最终.config里对应选项是什么。具体操作就是前面 3.2 节里那步 grep。如果增量构建更要在执行make defconfig之后马上验证别把“构建脚本会不会帮你带上”当成默认行为。还有一个冷门但真实的情况老内核的某些 defconfig 会把 CONFIG_PERF_EVENTS 设成m构建脚本又没有把模块打包进 boot.img结果框架等于没开。内核调试阶段我从不碰 m一律改成y。5.3 Android 上常见独立问题Android 上跑 perf 调研比桌面 Linux 多几件需要专门处理的事。第一是 perf 工具本身经常没预置。adb push 一个静态编译的 perf 最省事动态链接版本依赖设备上的 libc 和系统头文件一不留神就报版本不符。第二是 SELinux 叠加问题。即使perf_event_paranoid是 0SELinux 的 shell domain 也可能拒绝perf_event_open调用。遇到这种情况先用setenforce 0临时关闭 SELinux 定位问题边界确认是 SELinux 拦截后再补 sepolicy 规则而不是粗暴地长期关掉 SELinux。第三是 boot image 校验。修改过的内核要解锁 bootloader 并且 bootloader 版本别太旧Pixel 系列相对容易解锁但某些运营商定制区域会卡在 fastboot 拒绝写入那一步。另一个容易被忽略的点perf 工具版本和内核版本匹配。4.14 内核如果在 perf list 里看到一堆 unknown event不一定是 PMU 驱动问题很可能是用户空间 perf 工具太老不认识新 PMU 通过 sysfs 导出的格式描述。换一个同版本分支的 perf 编译出来复测往往问题就消失了。5.4 常见问题速查表现象最可能原因处理方式perf list 里没有 hardware eventsCONFIG_HW_PERF_EVENTS 未开修改 defconfig 后重新编译报 PMU Hardware doesnt support precise eventsCPU 不支持 PEBS/SPE 或驱动未初始化检查 sysfs 和 dmesg确认硬件能力报 Permission denied / Operation not permittedperf_event_paranoid 过高或 SELinux 拦截临时 echo 0 降级再按持久化方案处理/proc/config.gz 不存在未开 CONFIG_IKCONFIG_PROC重新配置编译或改用其他方式核验adb root 后 perf 仍然不能用SELinux shell domain 拒绝setenforce 0 定位再补 sepolicy allow改 defconfig 刷完没变化改错了构建脚本实际引用的 defconfig检查 build.config确认最终 .configperf 事件解析出现 unknownperf 工具版本和内核版本不匹配用同版本内核源码编译 perf说点个人体会。踩完这些坑之后我现在的固定流程已经收敛成三步先在源码树里编一个版本匹配的静态 perf再刷好内核后第一时间zcat /proc/config.gz确认编译选项最后用perf_event_paranoid0的条件跑一次cycles:ppp最小采样看它报不报 unsupported。这三步走完通常一分钟内就能判断出问题出在编译阶段、权限阶段还是硬件能力阶段。如果你手里本来就是 ARM 设备别在 Intel 的 PEBS 字眼上死磕直接查 PMU 驱动初始化状态、SPE 设备节点在不在、precise 采样能不能跑语义上等价操作上还更简单。这趟链路走通以后后面再做性能分析会顺畅很多——至少不会被“硬件事件看起来存在却跑不出来”这种事卡住一整天。