搞性能剖析的人迟早会遇到一个坎:perf工具明明装好了,但跑perf record -e cycles:pp要么直接报错,要么采出来的数据全是调度噪音。这类问题十有八九不是perf用得不对,而是内核编译阶段就把硬件性能事件能力给做了减法。我在给x86平台以及Pixel 4的Android 10内核做性能调优时,把PERF选项和PEBS这套东西完整过了一遍,踩了不少坑,也理清了整条链路。这篇就当一次实操记录,讲清楚三件事:内核编译时PERF相关配置怎么开、PEBS是什么级别的能力、以及怎么可靠地判断当前CPU到底有没有真的开启PEBS。适合做服务器性能优化、嵌入式Linux调优以及Android内核定制的人参考,尤其适合那些卡在“perf采样不准”和“事件不支持”上的人。
1. 为什么内核编译要关注PERF和PEBS
1.1 一个被忽略的编译开关,决定你的采样数据上限
很多人的理解里,内核编译只影响系统能否启动、驱动是否正常,性能分析工具是用户态的事。这个理解在十年前勉强成立,现在完全不够用了。perf_events框架从Linux 2.6.31合入主线之后,硬件PMU的驱动、采样缓冲、事件调度全都做在内核里。如果你编译内核时把CONFIG_PERF_EVENTS关掉,或者相关依赖没有选上,后面在用户态用perf工具就会到处碰壁。
最常见的现象是:执行perf命令提示“No permission”或“operation not permitted”,或者perf list里看不到任何硬件事件。我见过不止一个人拿着发行版内核在排查,最后发现是内核配置里根本没编进硬件性能事件驱动。还有一个容易被忽略的细节:CONFIG_PERF_EVENTS=y只是打开了框架,真正让CPU的PMC(Performance Monitoring Counter)能被访问到,还需要对应架构的代码和硬件驱动支持。在x86平台上,CONFIG_CPU_SUP_INTEL、CONFIG_CPU_SUP_AMD这些选项如果不开启,PMU驱动会直接放弃注册。
所以我的建议是:性能优化这件事,应该从编译阶段就认真对待,而不是等出问题了再回头查配置。一个正确的内核配置,能决定你采样的精度上限;而PEBS,就是这个上限里最值钱的一块。
1.2 PEBS到底帮你省了什么:从一次中断说起
先解释PEBS(Precise Event-Based Sampling)在硬件层面解决了什么问题。传统perf采样是周期性触发中断,在中断处理程序里读取当前IP、寄存器等信息,然后写入环形缓冲区。这个方案的缺点很明显:中断有延迟,中断处理过程本身会改变寄存器上下文,导致采到的IP可能已经偏离事件发生位置几十甚至几百条指令;而且高频中断对系统的扰动很大,你统计出来的性能数据本身就带着失真。
PEBS的做法完全不一样。CPU内置了一个叫DS(Debug Store)的机制,内核可以在内存里配置一块专用缓冲区,当指定事件触发时,硬件直接把采样记录(包含精确的指令指针、寄存器快照、时间戳等)写入这块缓冲区,整个过程不经过中断处理。等到缓冲区攒满一定数量,PMU再产生一次中断,内核做批量拷贝。这样一来,中断次数减少了一个数量级,采样点距离真实事件的位置也精确得多。
在Skylake及以后的Intel处理器上,PEBS还支持不同的record format,也就是常说的PEBS fmt3、fmt4。内核启动日志里能看到类似“PEBS fmt3+, 64-deep LBR”的输出,fmt数字代表硬件支持的PEBS记录版本,越高说明记录的字段越丰富。做精细的微架构分析,比如精确到指令级的采样、branch分析,没有PEBS基本没法看。
1.3 从Pixel 4的Android 10内核编译说起
这里必须先泼一盆冷水:PEBS是Intel x86的专属能力,ARM平台没有PEBS,ARM的PMU用的是另外一套模型。如果你搜索“pixel 4 android 10.0 内核编译”,然后想在Pixel 4上找PEBS,大概率会失望。Pixel 4用的是高通骁龙855(SM8150),CPU核心是Cortex-A76/A55,它的性能计数器虽然完整,但缺少PEBS这样的硬件精准采样机制;ARMv8平台的对应物叫SPE(Statistical Profiling Extension),那是另一套东西。
那为什么我还要提Android 10内核编译?因为Android内核定制同样是性能分析的热门场景,而PEBS判断方法里很多思路(看dmesg、看sysfs、用perf事件验证)在ARM平台也完全适用,只是最终验证的特征字符串不同。所以这篇内容的核心方法论是跨平台的:先在内核编译时把perf框架打开,再通过启动日志和perf实测确认硬件能力,最后用perf record的方式验证精准采样是否真正可用。这样在任何平台都不会迷路。
2. 内核编译前:把PERF选项的关系链理清
2.1 核心三件套:CONFIG_PERF_EVENTS / CONFIG_HW_PERF_EVENTS / CONFIG_CPU_SUP_INTEL
编译一个支持性能分析的内核,至少要让下面这几个选项处于开启状态。第一个是CONFIG_PERF_EVENTS,它是整个perf框架的总开关,在menuconfig里位于“General setup -> Kernel performance events”(早期版本叫“Performance events support”)。这个选项一旦关闭,perf工具基本就是个空壳,系统事件、软件事件全都不可用。
第二个是CONFIG_HW_PERF_EVENTS,它控制硬件性能计数器相关代码。这个选项一般会跟CONFIG_PERF_EVENTS联动,但要留意某些vendor内核可能把它设为模块,或者干脆没让用户看到。在menuconfig里它通常跟随架构相关配置出现,最可靠的方式不是凭记忆翻菜单,而是用/键直接搜HW_PERF_EVENTS定位。
第三个容易被忽略的是CONFIG_CPU_SUP_INTEL。这个选项位于“Processor type and features”,负责把Intel CPU特性检测和微架构相关代码编进内核。PMU驱动在初始化时会检查CPU vendor,如果这里没开,内核可能不会注册Intel PMU,那后面的PEBS就不可能启用。AMD平台对应的是CONFIG_CPU_SUP_AMD,如果你想在一台AMD机器上做完整性能分析,这个也不能落下。
2.2 真正影响PEBS的选项:不只是config,还有硬件和固件
除了config之外,PEBS的可用性还受硬件和固件双重制约。首先是硬件必须支持:Intel的PEBS从Nehalem架构开始引入,早期只支持部分事件,到Skylake之后基本支持全部核心事件。这个判断不能光看型号猜,要看内核日志或者实际perf行为。其次是BIOS/固件层面,服务器厂商有时会在BIOS里提供“Performance Monitoring”或“PEBS”开关,尤其是虚拟化场景,如果BIOS关闭了PMU相关特性,内核里怎么开config都没用。
还有一个经常被忽略的选项是CONFIG_PERF_EVENTS_INTEL_UNCORE,它负责Intel uncore PMU驱动,也就是管理L3缓存、内存控制器、QPI/UPI链路上的硬件计数器。这个对PEBS没有直接影响,但如果你要做系统级性能分析,uncore事件往往比core事件更能说明瓶颈。我的建议是:既然内核都自己编译了,这类额外PMU驱动能开就开,反正是编译期成本,运行期只有你主动用才会消耗资源。
2.3 menuconfig里怎么快速定位这些选项
很多发行版内核默认开启了PERF相关配置,但自定义内核或Android vendor内核不一定。用menuconfig定位有个技巧:打开make menuconfig后,直接按/键进入搜索,输入PERF_EVENTS能看到这个symbol出现在哪些菜单下面,以及依赖关系是什么。这个搜索功能会直接告诉你config PERF_EVENTS的依赖链,比一层层翻菜单高效得多。
定位到之后,建议把CONFIG_PERF_EVENTS_INTEL_UNCORE、CONFIG_PERF_EVENTS_AMD_UNCORE这些也都编入内核而不是编成模块。因为作为模块的话,如果initramfs里没有包含它,设备启动后modprobe时机不好控制,perf枚举事件时会发现PMU设备还没出现。
改完配置后,务必确认一下.config。我习惯直接跑:
grep -E "^CONFIG_PERF|^CONFIG_HW_PERF|^CONFIG_CPU_SUP_(INTEL|AMD)" .config看到CONFIG_PERF_EVENTS=y、CONFIG_HW_PERF_EVENTS=y才能放心往下走。如果你是在现有config基础上增量修改,要注意make olddefconfig时新选项可能会被重置,最好用make savedefconfig把最终生效的配置固化下来,避免下次make时配置漂移。
3. 实操:编译一个带PERF/PEBS支持的内核
3.1 通用x86内核编译流程
先从最常见的x86服务器环境开始。假设你已经拿到内核源码,第一步是生成或带入一个配置文件。我不推荐直接make defconfig,因为它生成的是通用配置,很多性能分析选项默认是关闭的;更稳妥的做法是拿当前系统的config作底子:
zcat /proc/config.gz > .config 2>/dev/null || cp /boot/config-$(uname -r) .config make olddefconfig如果编译的内核没有开启/proc/config.gz(也就是CONFIG_IKCONFIG_PROC未开),那就用/boot下的配置文件兜底。之后执行make menuconfig,按前面说的方式搜PERF_EVENTS,确认所有关键选项都是y。
这里有一条新手的习惯建议:做性能分析时,把CONFIG_DEBUG_INFO=y也打开,这样perf的符号解析和annotate功能才有数据源。另外CONFIG_KALLSYMS必须打开,否则perf report会一片“unknown symbol”。这两点是我踩过坑之后养成的习惯。
make -j$(nproc) bzImage modules sudo make modules_install sudo make install sudo update-grub重启后先看dmesg里的PMU注册信息,这一步放到第4节细说。
3.2 Pixel 4 / Android 10内核编译的补充
Android平台的编译流程和普通x86内核差别较大。以Pixel 4的Android 10内核为例(设备代号coral,内核版本4.14),需要用repo同步指定分支的内核源码:
repo init -u https://android.googlesource.com/kernel/manifest \ -b android-msm-coral-4.14-android10 repo sync -j$(nproc) -c同步完成后,内核源码根目录通常有build.sh,Pixel 4官方构建就是靠它。如果手动手工编译,需要准备aarch64的交叉编译链和Clang。Android 10时代的内核编译标准做法是用Android预编译的Clang工具链,环境变量大致如下:
export ARCH=arm64 export SUBARCH=arm64 export PATH=<clang目录>/bin:$PATH export CROSS_COMPILE=aarch64-linux-android- export CC=clang export CLANG_TRIPLE=aarch64-linux-gnu-然后按设备代号选择defconfig,Pixel 4的defconfig名称要看源码里vendor/build.sh的具体变量,通常是arch/arm64/configs/coral_defconfig一类的名字,再执行:
make -j$(nproc) Image.gz dtbo.img这里要提醒:Android 10时代还没强制GKI,所以你可以直接改内核config并编译完整kernel镜像,再通过fastboot刷入boot分区。不过vendor的defconfig往往会覆盖某些CONFIG值,改之前建议把原始defconfig备份一份,测试完恢复配置会轻松很多。
关于PERF选项,Android 10的内核里CONFIG_PERF_EVENTS=y一般是开的,但perf_event_paranoid这个sysctl在Android上默认值较高,普通app根本无法发起性能事件采样。如果你是做系统级调优,需要在root权限下把/proc/sys/kernel/perf_event_paranoid写成-1或2,并确认对应平台的PMU驱动有没有在dmesg里注册成功。
3.3 编译后别忘了同步perf工具
内核编完之后,很多人直接拿发行版自带的perf工具继续干活。如果发行版perf版本比内核新很多,通常还能兼容;但如果旧很多,解析新内核的perf event数据可能会出现“unknown”或者采样字段缺失。我建议顺手从内核源码编译一份perf,目录就在tools/perf:
cd tools/perf make -j$(nproc) sudo cp perf /usr/local/bin/这样perf和内核版本严格对应,perf report的字段解析、perf annotate的指令级信息都更可靠。如果目标是Android设备,可以用simpleperf(Android NDK自带的性能工具),它不需要root也能做部分采样,但对PEBS这种硬件特性同样无解,ARM平台本来就只能靠自己的PMU事件。
4. 判断CPU是否开启PEBS:四种实测方法
4.1 dmesg是最直接也最靠谱的证据
判断PEBS是否真正可用,第一选择永远是看内核启动日志。Intel平台上,PMU驱动初始化成功时,dmesg里会有一行典型的输出:
[ 0.163584] Performance Events: PEBS fmt3+, 32-deep LBR, Skylake+ userspace, full-width counters, max period 0x7fffffffff, Intel PMU driver.这行里的“PEBS fmt3+”直接告诉你有PEBS,且记录格式是第三版。如果输出是“PEBS fmt4+”那就更好,说明支持更新的记录格式。看日志的命令很简单:
dmesg | grep -i -E "pebs|performance events|pmu"如果你看到的是“No PMU driver”或者“Failed to register PMU”,那基本可以断定PMU驱动没起来,PEBS肯定没有启用。这时候再去检查BIOS设置、内核config或者虚拟化配置。
4.2 用perf实测验证:precise事件能不能跑
日志有时会骗人,因为硬件支持PEBS不代表内核完整地把它暴露给了perf事件。最靠谱的方法是直接跑一个precise事件采样。perf中precise_ip的语义可以分为pp和ppp两档,分别对应不同强度的PEBS要求。常用的验证命令是:
perf record -e cycles:pp -a -- sleep 1正常的话会生成perf.data并且没有任何报错。如果内核不支持,你会看到类似这样的输出:
Error: The sys_perf_event_open() syscall returned with 22 (Invalid argument) for event (cpu/cycles/).一个更简单的检查是perf list 2>&1 | grep -i precise,但有些精简的perf list输出里不会体现precise标志,所以实测采样更加可靠。我在很多机器上测试下来,只要能成功执行perf record -e cycles:pp,后续用perf annotate看到的指令级热点就明显更干净,IRQ扰动少很多。
4.3 sysfs和辅助工具判断
除了dmesg和perf实测,还有两个辅助手段。第一个是sysfs下的PMU能力目录:
cat /sys/devices/cpu/caps/pmu_name如果输出的是skylake、icelake这类具体微架构名称,说明PMU驱动正常识别了CPU。如果是unknown或者直接没有这个文件,要么是太老的CPU,要么是PMU驱动注册失败。第二个辅助手段是用msr-tools读MSR寄存器:
apt install msr-tools sudo modprobe msr sudo rdmsr 0x3450x345是IA32_PERF_CAPABILITIES,读出的值里低位包含PEBS相关信息。不同的CPU型号这个值不同,我见过0x14、0x25、0x41等,单看数值不能简单判断,但如果根本没有这个MSR(读出来直接报错),说明CPU不支持PERF_CAPABILITIES,那PEBS基本没戏。顺便说一句,在虚拟化环境里,这个MSR可能被hypervisor隐藏,读不到并不代表物理CPU不支持,需要结合宿主实际情况判断。
4.4 把检测流程固化成脚本
把前面的思路串起来,放一个我平时用的检测脚本。它不依赖第三方工具,逻辑就是“日志证据 + 实际采样验证”两步走:
#!/bin/bash # check_pebs.sh echo "== 1. dmesg ==" dmesg | grep -i -E "pebs|pmu|performance events" | head -5 echo echo "== 2. perf precise sampling test ==" perf record -e cycles:pp -a -- sleep 0.1 2>&1 | tail -5 echo echo "== 3. sysfs pmu_name ==" cat /sys/devices/cpu/caps/pmu_name 2>/dev/null || echo "no pmu_name"脚本输出里如果能同时看到“PEBS fmt3+”、perf.data成功生成、pmu_name返回具体架构名,那PEBS就是真的可用。注意:采样命令如果提示“Permission denied”,先按下一节的方法调perf_event_paranoid,别急着判定硬件不支持。
| 检测手段 | 证据强度 | 适用场景 |
|---|---|---|
| dmesg | 强 | 判断PMU驱动是否注册、PEBS fmt版本 |
| perf record -e cycles:pp | 最强 | 验证perf事件链路是否完整 |
| /sys/devices/cpu/caps/pmu_name | 中 | 快速查看PMU microarchitecture |
| rdmsr 0x345 | 中 | 辅助判断硬件能力,虚拟环境慎用 |
5. 常见问题与排查技巧实战
5.1 编译阶段找不到PERF选项
自己裁剪内核时,menuconfig搜索PERF_EVENTS,有时会发现它高亮显示为N,或者干脆搜不到。这时候十有八九是某个父菜单没开,或者当前架构不支持该选项。比如在部分精简的RISC-V或MIPS架构上,PERF_EVENTS的依赖链可能不完整。通常的解决方式是:用menuconfig的搜索界面查看依赖关系,顺着依赖链把父选项打开。
还有一个常见情况:你改了配置,但重新make时被.config里的# CONFIG_PERF_EVENTS is not set顶回来了。这种时候执行make olddefconfig,再确认一次最终生效的config值。
5.2 Running时No permission:perf_event_paranoid的坑
perf采样提示权限问题时,第一件事永远检查sysctl:
cat /proc/sys/kernel/perf_event_paranoid这个数值的含义可以简化理解:-1完全不限制;0允许采样但不允许某些CPU独占操作;1允许统计但不允许采样;2只允许内核态受限访问;3会让普通用户更难过。做性能分析时设成-1或2就够用了:
sudo sysctl -w kernel.perf_event_paranoid=-1注意这个设置重启后失效。如果要把规则固化,可以在内核config里把CONFIG_SECURITY_PERF_EVENTS_RESTRICT设为n,或者用systemd的sysctl配置目录写入/etc/sysctl.d/99-perf.conf。
5.3 虚拟化环境:宿主支持PEBS,虚拟机却不行
这个坑我在云主机上踩过很多次。物理机上 dmesg 明确显示PEBS可用,但同一镜像跑到KVM虚拟机里,perf record -e cycles:pp直接报Invalid argument。原因很简单:PEBS是特权级硬件能力,hypervisor默认不把PMU和DS相关资源完全暴露给guest,现代内核在虚拟化环境里检测到 hypervisor 标志后会主动降级,有些事件的precise标志会静默变成0。
解决方案要么给VM配置PMU透传(比如libvirt里开启host-passthrough模式),要么在宿主上做采样而不是在guest里做。判断自己是否在虚拟机里可以看dmesg | grep hypervisor或/proc/cpuinfo里的hypervisor标志。
5.4 ARM平台(Pixel 4)没有PEBS,怎么替代
如果你就是要在Pixel 4这类骁龙平台做精准采样,别再纠结PEBS了。ARMv8.2之后的部分核心可以支持SPE,在perf里对应arm_spe事件,但Pixel 4的骁龙855(Cortex-A76/A55)具体驱动支持情况需要单独确认。更务实的选择是:
- 用
simpleperf做基于PMU的采样,它能读取ARM核心的通用事件,虽然精度不如PEBS,但对app开发者已经够用; - 用
perf stat做计数型分析,不依赖精准采样也能定位宏观瓶颈; - 如果一定要高精度,考虑找支持SPE的开发板或特定调优平台。
另外,Android 10对perf的限制不只是内核config,还有SELinux策略。即使你adb root后把perf_event_paranoid降下来,SELinux如果没放行perf相关的socket和syscall,一样会报权限异常。排查时记得看adb logcat -b events,或者暂时关闭SELinux(仅限调试环境)做对照。
5.5 日志显示PEBS enabled但采样数据明显异常
还有一种更隐蔽的情况:dmesg显示PEBS enabled,perf也能跑cycles:pp,但采样结果里几乎全是同一个地址,或者全是内核地址,用户态热点完全失真。这种多半是采样频率设定太高导致PEBS buffer频繁溢出,部分记录被打包丢弃;或者开启了KASLR(内核地址空间布局随机化),perf无法正确符号化内核地址。
对策是降低-F采样频率,比如从1000Hz降到200Hz再试一次,同时确认/proc/kallsyms可读。CONFIG_RANDOMIZE_BASE如果只是为了性能调优,可以在调试机上先关掉,但生产机建议保持开启,不要为了图方便削弱安全特性。还有一个年份较久的坑:部分旧内核里PEBS事件和某些uncore事件不能同时使能,会互相影响计数,导致precise记录出现漂移,这种时候把采样事件数量控制在1个通常能绕开。
6. 一点个人实践建议
内核编译阶段把PERF和PEBS配置正确,带来的收益不是立刻能感知的,它会持续作用在之后每一次性能分析中。我自己测试过的机器里,同一台服务器在开启PEBS之后,perf采样的中断开销大约降低了四成,指令级热点定位的准确率提升非常明显。如果你经常和perf打交道,建议把这个验证流程固化下来:内核编译配置里写死CONFIG_PERF_EVENTS=y和CONFIG_HW_PERF_EVENTS=y,启动后先跑一遍快速检测脚本,确认PEBS状态,再开始正经的性能分析。整套流程跑熟了,最多五分钟就能确认环境状态,省下的排查时间远超投入。
另外,如果你是在给Android设备做性能调优,也别因为平台没有PEBS就放弃perf框架。先把内核里的PERF选项、sysctl、SELinux这条链路打通,让普通采样能跑起来,再去看SPE这类硬件特性,一步步来,性能问题总能被量化出来。