
搞性能剖析的人迟早会遇到一个坎perf工具明明装好了但跑perf record -e cycles:pp要么直接报错要么采出来的数据全是调度噪音。这类问题十有八九不是perf用得不对而是内核编译阶段就把硬件性能事件能力给做了减法。我在给x86平台以及Pixel 4的Android 10内核做性能调优时把PERF选项和PEBS这套东西完整过了一遍踩了不少坑也理清了整条链路。这篇就当一次实操记录讲清楚三件事内核编译时PERF相关配置怎么开、PEBS是什么级别的能力、以及怎么可靠地判断当前CPU到底有没有真的开启PEBS。适合做服务器性能优化、嵌入式Linux调优以及Android内核定制的人参考尤其适合那些卡在“perf采样不准”和“事件不支持”上的人。1. 为什么内核编译要关注PERF和PEBS1.1 一个被忽略的编译开关决定你的采样数据上限很多人的理解里内核编译只影响系统能否启动、驱动是否正常性能分析工具是用户态的事。这个理解在十年前勉强成立现在完全不够用了。perf_events框架从Linux 2.6.31合入主线之后硬件PMU的驱动、采样缓冲、事件调度全都做在内核里。如果你编译内核时把CONFIG_PERF_EVENTS关掉或者相关依赖没有选上后面在用户态用perf工具就会到处碰壁。最常见的现象是执行perf命令提示“No permission”或“operation not permitted”或者perf list里看不到任何硬件事件。我见过不止一个人拿着发行版内核在排查最后发现是内核配置里根本没编进硬件性能事件驱动。还有一个容易被忽略的细节CONFIG_PERF_EVENTSy只是打开了框架真正让CPU的PMCPerformance Monitoring Counter能被访问到还需要对应架构的代码和硬件驱动支持。在x86平台上CONFIG_CPU_SUP_INTEL、CONFIG_CPU_SUP_AMD这些选项如果不开启PMU驱动会直接放弃注册。所以我的建议是性能优化这件事应该从编译阶段就认真对待而不是等出问题了再回头查配置。一个正确的内核配置能决定你采样的精度上限而PEBS就是这个上限里最值钱的一块。1.2 PEBS到底帮你省了什么从一次中断说起先解释PEBSPrecise Event-Based Sampling在硬件层面解决了什么问题。传统perf采样是周期性触发中断在中断处理程序里读取当前IP、寄存器等信息然后写入环形缓冲区。这个方案的缺点很明显中断有延迟中断处理过程本身会改变寄存器上下文导致采到的IP可能已经偏离事件发生位置几十甚至几百条指令而且高频中断对系统的扰动很大你统计出来的性能数据本身就带着失真。PEBS的做法完全不一样。CPU内置了一个叫DSDebug 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平台没有PEBSARM的PMU用的是另外一套模型。如果你搜索“pixel 4 android 10.0 内核编译”然后想在Pixel 4上找PEBS大概率会失望。Pixel 4用的是高通骁龙855SM8150CPU核心是Cortex-A76/A55它的性能计数器虽然完整但缺少PEBS这样的硬件精准采样机制ARMv8平台的对应物叫SPEStatistical 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_EVENTSy、CONFIG_HW_PERF_EVENTSy才能放心往下走。如果你是在现有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_INFOy也打开这样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.shPixel 4官方构建就是靠它。如果手动手工编译需要准备aarch64的交叉编译链和Clang。Android 10时代的内核编译标准做法是用Android预编译的Clang工具链环境变量大致如下export ARCHarm64 export SUBARCHarm64 export PATHclang目录/bin:$PATH export CROSS_COMPILEaarch64-linux-android- export CCclang export CLANG_TRIPLEaarch64-linux-gnu-然后按设备代号选择defconfigPixel 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_EVENTSy一般是开的但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/perfcd tools/perf make -j$(nproc) sudo cp perf /usr/local/bin/这样perf和内核版本严格对应perf report的字段解析、perf annotate的指令级信息都更可靠。如果目标是Android设备可以用simpleperfAndroid 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 21 | 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 21 | 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 microarchitecturerdmsr 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 permissionperf_event_paranoid的坑perf采样提示权限问题时第一件事永远检查sysctlcat /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的骁龙855Cortex-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 enabledperf也能跑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_EVENTSy和CONFIG_HW_PERF_EVENTSy启动后先跑一遍快速检测脚本确认PEBS状态再开始正经的性能分析。整套流程跑熟了最多五分钟就能确认环境状态省下的排查时间远超投入。另外如果你是在给Android设备做性能调优也别因为平台没有PEBS就放弃perf框架。先把内核里的PERF选项、sysctl、SELinux这条链路打通让普通采样能跑起来再去看SPE这类硬件特性一步步来性能问题总能被量化出来。