1. 从硬件寄存器到性能洞察:ARM PMU在AM62L上的实战指南
如果你正在为AM62L这颗嵌入式处理器上的应用性能优化而头疼,觉得代码跑得不够快,功耗又下不来,但又苦于没有足够精细的数据来定位问题,那么ARM的性能监控单元(PMU)就是你一直在找的那把“手术刀”。它不像软件层面的perf工具那样隔着一层抽象,而是直接深入到CPU的微架构层面,告诉你每个时钟周期里到底发生了什么:指令在哪一级缓存卡住了?分支预测又失败了多少次?这些硬核数据,是任何高级分析工具都无法替代的。
AM62L Sitara™处理器集成了ARM Cortex-A系列核心,其PMU完全遵循ARMv8-A架构的PMUv3规范。这意味着,你面对的是一套标准化、功能强大的硬件性能计数器体系。但手册里密密麻麻的寄存器位域描述,往往让人望而却步。别担心,这篇文章的目的,就是帮你把TI那上千页技术参考手册(TRM)里关于PMU寄存器的章节“翻译”成可以直接操作的实战指南。我们会从最核心的控制寄存器PMCR_EL0开始,一步步拆解如何启用计数器、选择监控事件、读取数据,并最终将这些原始计数转化为有意义的性能洞察。无论你是做底层驱动的系统工程师,还是需要优化关键算法的应用开发者,理解并善用PMU,都能让你对系统的认知提升一个维度。
2. ARM PMU核心架构与AM62L实现概览
在深入寄存器细节之前,我们有必要先建立对ARM PMU(Performance Monitor Unit)整体架构的理解。你可以把它想象成CPU内部一个高度专业化的“仪表盘”。这个仪表盘不是模拟的,而是由一系列硬件计数器、配置寄存器和状态寄存器构成的精密电路。它的核心任务是:当CPU内部发生某个特定的事件(比如执行了一条指令、访问了一次缓存、发生了一次分支预测错误)时,对应的硬件计数器就自动加1。
ARM的PMUv3架构将这套“仪表盘”标准化了。它定义了一组系统寄存器,主要位于EL0(用户态)和EL1(内核态)可访问的范围内,使得从应用层到操作系统内核都能以统一的方式对其进行编程。一个典型的PMU通常包含以下关键资源:
- 一个固定的周期计数器(Cycle Counter,
PMCCNTR_EL0):这个计数器专门用来记录CPU核心运行的时钟周期数,是衡量“时间”最直接的硬件依据。 - 多个通用事件计数器(Event Counter,
PMEVCNTR<n>_EL0):数量取决于具体实现(AM62L的Cortex-A核心通常提供6个)。每个都可以被独立配置为监控一个特定的事件,比如L1数据缓存失效次数。 - 一组控制与状态寄存器:用于全局开关、计数器选择、溢出中断管理等,这正是本文要重点剖析的
PMCR_EL0等寄存器。 - 事件编号体系:ARM定义了一套标准的事件编号(如
0x011代表CPU_CYCLES),并通过PMCEIDx_EL0寄存器告知软件当前硬件支持哪些事件。
AM62L的PMU特性:根据TI提供的寄存器信息,我们可以推断出AM62L中ARM核心的PMU实现情况。PMCEID0_EL0的复位值是0x67FFBFFF,这是一个非常重要的信息。将其转换为二进制并对照事件表,我们可以知道该处理器支持从SW_INCR(0x000) 到L1D_CACHE_ALLOCATE(0x01F) 在内的多个架构性和微架构性事件。而PMCEID1_EL0复位值为0,仅支持事件0x020(L2D_CACHE_ALLOCATE)。这表明该PMU支持33个标准事件,涵盖了指令执行、分支预测、缓存访问、内存操作等关键性能维度,功能相当全面。
PMU的工作流程,简单来说分为三步:配置、采样和分析。配置阶段,我们通过PMCR_EL0打开总开关,通过PMSELR_EL0选择事件,通过PMXEVTYPER_EL0将事件绑定到某个通用计数器。采样阶段,计数器开始累加,我们可以定期读取PMCCNTR_EL0或PMEVCNTR<n>_EL0的值。分析阶段,则结合采样间隔、事件含义和代码逻辑,找出性能瓶颈。例如,如果发现BR_MIS_PRED(分支预测失败)事件计数异常高,那么优化分支判断逻辑或调整代码布局就可能带来显著收益。
3. 核心控制寄存器PMCR_EL0详解与配置策略
PMCR_EL0(Performance Monitors Control Register)是整个PMU的“总闸门”和“控制中心”。所有对PMU的操作几乎都从这里开始。它的位域虽然不多,但每一个都至关重要。我们结合AM62L手册中的描述,逐一拆解。
3.1 寄存器位域功能全解析
PMCR_EL0是一个32位寄存器,其有效控制位集中在低8位(bit 7-0)。高24位(bit 31-11, 10-7)为保留位(RES0),写入任何值都会被忽略,读出为0。
E (bit 0) - 全局使能位:这是最重要的一个位。它控制着PMU中所有计数器(包括周期计数器和所有事件计数器)的使能状态。注意,这里的“使能”指的是计数器是否被允许计数,而非计数器是否开始计数。具体来说:
E=0:所有计数器被禁止。即使你通过其他寄存器(如PMCNTENSET_EL0)设置了某个计数器开始计数,只要E=0,该计数器就不会累加。E=1:所有计数器被允许计数。此时,每个计数器是否真正开始计数,由PMCNTENSET_EL0寄存器中对应的位单独控制。
实操心得:在初始化PMU时,标准的操作顺序是:先配置事件选择等参数,然后置位
PMCNTENSET_EL0启用特定计数器,最后才将PMCR_EL0.E置1。这样可以避免计数器在配置过程中产生不可控的计数。停止采样时,则应先清除PMCR_EL0.E,再清除PMCNTENSET_EL0。P (bit 1) 与 C (bit 2) - 复位控制位:这两个是只写(WO)位,读操作永远返回0(RAZ)。写入1会触发相应的复位操作。
P(Event counter reset):写入1,将所有事件计数器(PMEVCNTR<n>_EL0)复位为0。周期计数器PMCCNTR_EL0不受影响。C(Cycle counter reset):写入1,将周期计数器PMCCNTR_EL0复位为0。
重要提示:手册明确指出,复位计数器不会清除溢出状态位(
PMOVSR寄存器中的对应位)。如果你在中断服务程序中处理溢出,复位计数器前需要手动清除溢出标志。D (bit 3) - 时钟分频器:此位控制周期计数器
PMCCNTR_EL0的计数频率。D=0:PMCCNTR_EL0在每个CPU时钟周期递增(1:1)。D=1:PMCCNTR_EL0每64个CPU时钟周期递增一次(1:64)。注意:手册提到,如果LC位(长周期计数器使能)为1,则D位被忽略,计数器始终以1:1频率计数。ARM不推荐使用D=1,因为这会导致周期计数精度严重下降,不利于精细的性能分析。在绝大多数性能剖析场景下,都应保持D=0。
X (bit 4) - 事件导出使能:这是一个与高级调试和追踪功能相关的位。当
X=1时,允许PMU将事件导出到一个“实现定义”的事件流总线,供其他调试组件(如追踪单元ETM)使用。如果处理器没有实现此类事件总线,该位为RAZ/WI(读出为0,写入忽略)。对于AM62L,需要结合ETM章节确认其支持情况。在单纯的性能计数场景下,通常无需关心此位。DP (bit 5) - 事件计数禁止时停用周期计数器:此位决定了当事件计数被禁止时(例如,由于安全状态或异常级别设置),周期计数器
PMCCNTR_EL0的行为。DP=0:即使事件计数被禁止,只要周期计数器本身是使能的,它就继续计数。DP=1:当事件计数被禁止时���周期计数器也停止计数。 这个设置通常与系统安全策略相关。在非安全世界(Normal World)的应用性能分析中,通常保持默认值0即可。
LC (bit 6) - 长周期计数器使能:此位控制周期计数器溢出行为的判定。
LC=0:当PMCCNTR_EL0[31]从1变为0时,触发溢出(即32位溢出)。ARM已弃用此模式。LC=1:当PMCCNTR_EL0[63]从1变为0时,触发溢出(即64位溢出)。这是推荐且现代的模式。 将LC设为1,可以充分利用64位周期计数器(PMCCNTR_EL0本身是64位寄存器)的巨大范围,避免在高频CPU上因32位溢出而过早触发中断,这对于长时间的性能监控至关重要。
3.2 AM62L上的配置示例与代码片段
理解了每个位的含义后,我们来看如何在AM62L上对PMCR_EL0进行典型配置。以下是一个以内联汇编或内核模块代码形式呈现的初始化示例:
// 假设我们处于EL1(内核态)或具有特权的EL0环境 static inline void pmu_initialize(void) { uint64_t pmcr_val; // 1. 首先读取PMCR的当前值 asm volatile("mrs %0, pmcr_el0" : "=r" (pmcr_val)); // 2. 配置我们需要的位域: // - 清除E位,确保先关闭所有计数器 // - 设置LC=1,启用64位溢出模式(推荐) // - 确保D=0,周期计数器全速计数 // - 保持其他位(如DP, X)为默认值0 pmcr_val &= ~(1ULL << 0); // 清除E (bit 0) pmcr_val |= (1ULL << 6); // 设置LC (bit 6) pmcr_val &= ~(1ULL << 3); // 确保D=0 (bit 3) // 3. 将配置写回PMCR_EL0 asm volatile("msr pmcr_el0, %0" :: "r" (pmcr_val)); // 4. 可选:复位所有计数器,从一个干净的状态开始 asm volatile("msr pmcr_el0, %0" :: "r" (pmcr_val | (1 << 1) | (1 << 2))); // 设置P和C位进行复位 // 注意:P和C是自清零的,写入后即生效,无需再次清除。 }注意事项:对
PMCR_EL0的写入通常需要一定的处理器周期才能生效。在紧接其后的代码中立即读取计数器,可能会读到旧值或不稳定的值。在关键的性能测量代码段前后,可以插入isb(指令同步屏障)来确保所有系统寄存器操作完成。
4. 事件发现与选择:PMCEIDx_EL0寄存器深度解读
配置好PMU的总开关后,下一步就是告诉它:“你到底要数什么?” ARM PMU支持数十种硬件事件,但并非所有事件在所有处理器实现中都可用。PMCEID0_EL0和PMCEID1_EL0(Performance Monitors Common Event Identification Register)就是用来查询当前CPU支持哪些事件的“能力清单”。
4.1 PMCEID0_EL0:核心事件能力寄存器
这是最主要的事件能力寄存器。它是一个32位寄存器,每一位对应一个事件编号(从0x000到0x01F)。如果某一位为1,表示该事件被硬件实现;为0则表示不支持。
AM62L手册给出其复位值为0x67FFBFFF。我们将其转换为二进制并对照手册中的事件表进行解析,可以得到一份详细的支持事件列表:
| 位索引 (Bit) | 事件编号 (Event Number) | 事件助记符 (Event Mnemonic) | 含义简述 |
|---|---|---|---|
| 31 | 0x01F | L1D_CACHE_ALLOCATE | L1数据缓存分配 |
| 30 | 0x01E | CHAIN | 事件链(微架构特定) |
| 29 | 0x01D | BUS_CYCLES | 总线周期 |
| ... | ... | ... | ... |
| 17 | 0x011 | CPU_CYCLES | CPU时钟周期数 |
| 16 | 0x010 | BR_MIS_PRED | 分支预测失败 |
| 15 | 0x00F | UNALIGNED_LDST_RETIRED | 未对齐的加载/存储指令退休 |
| 14 | 0x00E | BR_RETURN_RETIRED | 返回指令退休 |
| 13 | 0x00D | BR_IMMED_RETIRED | 立即数分支指令退休 |
| 12 | 0x00C | PC_WRITE_RETIRED | 写PC的指令退休 |
| 11 | 0x00B | CID_WRITE_RETIRED | 写上下文ID的指令退休 |
| 10 | 0x00A | EXC_RETURN | 异常返回 |
| 9 | 0x009 | EXC_TAKEN | 异常发生 |
| 8 | 0x008 | INST_RETIRED | 指令退休数 |
| 7 | 0x007 | ST_RETIRED | 存储指令退休 |
| 6 | 0x006 | LD_RETIRED | 加载指令退休 |
| 5 | 0x005 | L1D_TLB_REFILL | L1数据TLB重填 |
| 4 | 0x004 | L1D_CACHE | L1数据缓存访问 |
| 3 | 0x003 | L1D_CACHE_REFILL | L1数据缓存重填(即失效) |
| 2 | 0x002 | L1I_TLB_REFILL | L1指令TLB重填 |
| 1 | 0x001 | L1I_CACHE_REFILL | L1指令缓存重填 |
| 0 | 0x000 | SW_INCR | 软件可递增计数器(用于测试) |
如何查询和使用:在编程时,你不需要手动计算这个位图。标准的做法是使用PMCEID0_EL0寄存器来检测事件是否可用。例如,在Linux内核的arch/arm64/include/asm/pmu.h中,通常会有类似pmceid0 = read_pmceid0()的操作,然后通过位与运算来检查:if (pmceid0 & (1UL << ARMV8_PMUV3_PERFCTR_CPU_CYCLES))。
4.2 PMCEID1_EL0:扩展事件能力寄存器
PMCEID1_EL0用于表示事件编号从0x020开始的事件。根据AM62L手册,其复位值为0,仅bit 0对应的事件0x020(L2D_CACHE_ALLOCATE) 可能被支持(但复位值为0表示默认不支持?这里需要结合芯片勘误表或更确切的配置信息,有时复位值不代表最终能力,需在运行时确认)。在实际使用中,应先读取该寄存器,检查bit 0是否为1来判断是否支持L2缓存分配事件的监控。
4.3 事件选择与计数器绑定流程
知道了支持哪些事件后,我们需要将特定事件绑定到一个通用的性能计数器上。这个过程通常涉及两个寄存器:
- PMSELR_EL0(Performance Counter Selection Register):选择你要配置的是第几个通用事件计数器(例如,选择计数器0-5)。
- PMXEVTYPER_EL0(Performance Event Type Register):当你通过
PMSELR_EL0选定了某个计数器后,对此寄存器的读写操作就对应于该计数器的事件类型配置。你需要将想要监控的事件编号(如0x011for CPU_CYCLES)写入此寄存器。
一个典型的配置序列如下(以监控CPU周期数到计数器0为例):
// 1. 选择通用事件计数器0 asm volatile("msr pmselr_el0, %0" :: "r" (0)); // 2. 将CPU_CYCLES事件(0x011)配置到选中的计数器 asm volatile("msr pmxevtyper_el0, %0" :: "r" (0x011)); // 3. 通过PMCNTENSET_EL0使能计数器0 asm volatile("msr pmcntenset_el0, %0" :: "r" (1 << 0)); // 设置bit 0 // 4. 确保PMCR_EL0.E已全局使能(见上一节)常见问题:
PMXEVTYPER_EL0是一个“别名”寄存器,它的物理实体是PMEVTYPER<n>_EL0。通过PMSELR_EL0进行选择,可以避免为每个计数器都提供独立的、占用大量编码空间的寄存器,这是一种节省系统寄存器编码空间的常见设计。
5. 身份、锁定与集成模式:其他关键PMU寄存器解析
除了核心的控制和事件寄存器,PMU还有一组用于识别组件、管理访问权限和控制特殊模式的寄存器。在驱动开发或安全敏感的应用中,这些寄存器同样重要。
5.1 组件识别寄存器组 (PMDEVARCH, PMDEVTYPE, PMPIDRx, PMCIDRx)
这些寄存器是ARM CoreSight架构的一部分,用于软件自动发现和识别调试与追踪组件。
- PMDEVARCH (Device Architecture Register):复位值
0x47702A16。这个值解码后告诉我们:这是一个ARM公司(JEP106 ID0x4 0x3B)设计的、符合ARMv8-A架构(ARCHID部分0x2A16表示PMUv3)的性能监控组件。操作系统或调试工具在启动时,可以通过��取此寄存器来确认PMU的存在和版本。 - PMDEVTYPE (Device Type Register):复位值
0x16。MAJOR=0x6表示这是一个性能监控组件,SUB=0x1表示它是一个处理器附属组件。 - PMPIDR0-3, PMCIDR0-3:这些是外设和组件标识寄存器,包含了设计厂商、部件号、版本号等详细信息。例如,
PMPIDR0.PART_0=0xD3,PMPIDR1.DES_0=0xB, PART_1=0x9等,共同构成了该PMU组件的唯一标识符。它们主要用于ARM的调试生态系统,在一般的性能分析编程中很少直接使用。
5.2 访问控制与锁定寄存器 (PMLAR, PMLSR)
为了防止PMU配置被意外或恶意修改,ARM提供了软件锁定机制。
- PMLAR (Lock Access Register):这是一个锁控制寄存器。要解锁PMU寄存器组(使其可写),必须向
PMLAR写入特定的密钥值0xC5ACCE55。写入任何其他值都会立即上锁。这是一个只写寄存器,读取值无意义。 - PMLSR (Lock Status Register):用于查询当前的锁定状态。
SLI (bit 0):软件锁定是否实现。在AM62L上,复位值为1,表示已实现。SLK (bit 1):当前的软件锁状态。1表示已锁定(禁止写),0表示已解锁(允许写)。NTT (bit 2):保留,读出为0。
典型的上锁/解锁操作流程:
// 解锁PMU寄存器组 *(volatile uint32_t *) (pmu_base_addr + PMLAR_OFFSET) = 0xC5ACCE55; // 现在可以配置PMCR_EL0, PMCEID等寄存器了 configure_pmu_registers(); // 配置完成后,重新上锁,防止意外修改 *(volatile uint32_t *) (pmu_base_addr + PMLAR_OFFSET) = 0x0;注意事项:通过内存映射接口(如AM62L手册中给出的物理地址
0x000730030E04)访问PMU寄存器时,才需要操作PMLAR/PMLSR。如果直接通过ARM系统寄存器(如msr pmcr_el0, x0)访问,则不受此锁影响。这通常意味着,在EL1或EL2的底层驱动中,我们直接使用msr/mrs指令;而在EL0用户态,或通过外部调试器访问内存映射区域时,才需要考虑锁机制。
5.3 集成模式控制寄存器 (PMITCTRL)
PMITCTRL寄存器只有一个有效位IME(Integration Mode Enable)。当IME置1时,PMU会进入一个“集成模式”,该模式的行为是“实现定义”的,通常用于芯片生产测试、拓扑发现或硅后验证等场景。在正常的操作系统或应用程序运行时,必须确保IME=0(正常操作模式)。将其误设为1可能导致PMU行为异常,无法进行有效的性能监控。
5.4 设备亲和性寄存器 (PMDEVAFF0/1)
PMDEVAFF0和PMDEVAFF1分别保存了MPIDR_EL1(多处理器亲和性寄存器)的低32位和高32位的只读副本。MPIDR_EL1是ARM架构中用于唯一标识一个处理器的寄存器。通过读取这两个寄存器,软件可以确认当前正在访问的是哪个CPU核心的PMU。在多核系统中,每个核心都有自己独立的PMU寄存器组,这个信息对于将性能数据正确地关联到特定核心至关重要。
6. 实战:在AM62L Linux用户态进行PMU数据采集
理论最终要服务于实践。我们来看一个具体的例子:如何在AM62L的Linux用户态程序中,利用PMU来测量一小段代码的CPU周期数和L1数据缓存失效次数。这里我们假设系统内核已经启用了用户态PMU访问(通常需要配置内核选项CONFIG_PERF_EVENTS和CONFIG_HW_PERF_EVENTS,并设置PMUSERENR_EL0寄存器相应位)。
由于直接使用内联汇编操作PMU寄存器在用户态较为复杂且容易出错,更常见和推荐的方法是使用Linux内核提供的**perf_event_open系统调用接口**。这个接口封装了底层PMU的细节,提供了安全、便捷的访问方式。
下面是一个简化的C语言示例,展示其核心步骤:
#include <linux/perf_event.h> #include <sys/syscall.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <errno.h> static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu, int group_fd, unsigned long flags) { return syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags); } int measure_cache_misses() { struct perf_event_attr pe_cycles, pe_l1d_refill; int fd_cycles, fd_l1d; long long count_cycles, count_l1d; memset(&pe_cycles, 0, sizeof(pe_cycles)); memset(&pe_l1d_refill, 0, sizeof(pe_l1d_refill)); // 1. 配置CPU周期数事件 pe_cycles.type = PERF_TYPE_HARDWARE; // 使用硬件PMU pe_cycles.size = sizeof(pe_cycles); pe_cycles.config = PERF_COUNT_HW_CPU_CYCLES; // 对应ARM事件0x011 pe_cycles.disabled = 1; // 初始禁用 pe_cycles.exclude_kernel = 1; // 排除内核态事件 pe_cycles.exclude_hv = 1; // 排除hypervisor事件 pe_cycles.read_format = PERF_FORMAT_GROUP; // 可以组读取 // 2. 配置L1D缓存重填事件 pe_l1d_refill.type = PERF_TYPE_HARDWARE; pe_l1d_refill.size = sizeof(pe_l1d_refill); pe_l1d_refill.config = PERF_COUNT_HW_CACHE_REFERENCES; // 注意:PERF标准事件可能需要转换。更精确的方式是使用RAW类型。 // 对于ARM PMU事件0x003 (L1D_CACHE_REFILL),更推荐: // pe_l1d_refill.type = PERF_TYPE_RAW; // pe_l1d_refill.config = 0x003; // 直接使用ARM事件编号 pe_l1d_refill.disabled = 1; pe_l1d_refill.exclude_kernel = 1; pe_l1d_refill.exclude_hv = 1; pe_l1d_refill.read_format = PERF_FORMAT_GROUP; // 3. 打开性能计数器文件描述符 fd_cycles = perf_event_open(&pe_cycles, 0, -1, -1, 0); if (fd_cycles == -1) { fprintf(stderr, "Error opening cycle counter: %s\n", strerror(errno)); return -1; } // 将L1D事件与周期计数器放在同一组,方便同时启停 fd_l1d = perf_event_open(&pe_l1d_refill, 0, -1, fd_cycles, 0); if (fd_l1d == -1) { fprintf(stderr, "Error opening L1D refill counter: %s\n", strerror(errno)); close(fd_cycles); return -1; } // 4. 启用计数器组 ioctl(fd_cycles, PERF_EVENT_IOC_ENABLE, 0); // 5. 执行待测量的代码段 // your_function_to_measure(); // 6. 禁用计数器并读取数据 ioctl(fd_cycles, PERF_EVENT_IOC_DISABLE, 0); struct read_format { u64 nr; // 本组中开启的计数器数量 u64 time_enabled; // 启用时间 u64 time_running; // 运行时间 u64 value[2]; // 计数器值,按打开顺序排列 } rf; read(fd_cycles, &rf, sizeof(rf)); count_cycles = rf.value[0]; count_l1d = rf.value[1]; printf("CPU Cycles: %lld\n", count_cycles); printf("L1D Cache Refills: %lld\n", count_l1d); printf("L1D Miss Rate (per 1000 cycles): %.2f\n", (double)count_l1d * 1000.0 / (double)count_cycles); // 7. 清理 close(fd_l1d); close(fd_cycles); return 0; }关键点解析与避坑指南:
- 事件映射:
perf框架定义的通用事件(如PERF_COUNT_HW_CACHE_REFERENCES)在不同架构下映射的具体PMU事件可能不同。为了获得精确且与架构手册对应的事件,强烈建议使用PERF_TYPE_RAW类型,并直接指定ARM PMU的事件编号(如0x003)。你可以通过perf list命令查看内核支持哪些raw事件,或者查阅内核源码arch/arm64/include/asm/perf_event.h中的映射表。 - 计数器复用与溢出:AM62L的通用事件计数器数量有限(比如6个)。
perf子系统会智能地管理这些硬件资源,在事件多于计数器时进行时间复用。但这也意味着测量的连续性可能被打断。对于需要精确、长时间监控的场景,可以考虑使用采样(sampling)模式,让内核在计数器溢出时产生中断并记录样本。 - 内核与用户态隔离:通过
exclude_kernel和exclude_hv标志,我们可以确保只测量用户态代码的性能,排除操作系统调度和中断的��扰。这对于分析应用程序本身的性能至关重要。 - 多核考量:
perf_event_open的cpu参数可以指定绑定的CPU核心。在多线程程序中,为了准确测量线程在特定核心上的行为,可能需要结合pthread_setaffinity_np将线程绑定到核心,然后对该核心进行测量。 - 权限问题:默认情况下,用户态使用PMU/
perf可能需要CAP_SYS_ADMIN能力,或者需要设置/proc/sys/kernel/perf_event_paranoid的值小于2。在嵌入式产品中部署时,需要规划好权限管理策略。
通过perf接口,我们绕过了直接操作PMCR_EL0、PMCEID0_EL0等寄存器的复杂性,但背后依然是这些寄存器在工作。理解它们的原理,能帮助你在perf工具输出异常时进行底层调试,也能让你在编写裸机程序或深度定制的RTOS时,有能力直接驾驭PMU这颗强大的芯片内性能探针。