许多体系结构都包含PMU(Performance Monitoring Unit)硬件,用于跟踪、计数系统内部的一些底层硬件事件,如与CPU有关的事件(执行指令数、捕获异常数、时钟周期数等)、与cache有关的事件(data/inst./L1/L2 cache访问次数,miss次数等)以及与TLB有关的事件等。这些事件反映了程序执行期的行为,可以帮助我们对程序进行分析和调优。ARM架构的PMU称为Performance Monitors Extension,这个extension是ARM的可选组件。在这篇文章中,我们以ARMv8架构为例,介绍其使用的v3版本的PMU硬件架构——ARMv8-PMUv3。
从编程人员的角度来看,ARM PMU就是一组事件计数器(counter)和操作这些计数器的相应的控制寄存器。计数器分两类,一类是cycle counter,只能用于计数CYCLES事件,且每个PMU硬件只有一个该类型的counter;另一类是普通的performance counter,即事件计数器,数量不定,具体个数由实现决定(例如Cortex-A53有6个),可以通过控制寄存器配置计数的事件类型,类型当然也包括CYCLES事件。
ARMv8 PMUv3硬件架构
上面这张图是直接从ARM官网复制的,展示了Cortex-A53平台的PMU硬件架构。从图中可以看出Cortex-A53一共有1+6个计数器,其中cycle counter直接与CLKIN(ClockIN)连接,表明cycle counter只用于记录cpu cycle数;下面的6个performance counter接收系统内部其他硬件模块产生的事件,并根据配置选择其中的一种进行计数。在这些计数器中,cycle counter是64位计数器(也可作为32位计数器使用),performance counter都是32位计数器。当发生溢出时,计数器会产生overflow中断,PMU硬件会根据控制寄存器内设置的中断屏蔽位判断是否将该中断发送给CPU处理。从程序员的角度来看,寄存器就是硬件的编程接口,因此本文对ARM PMU的介绍,就围绕这些寄存器展开。
在对各寄存器进行详细介绍之前,我们先简单了解一下事件。处理器在运行过程中会产生很多硬件事件,在ARM中每类事件都对应一个编码。在配置performance counter的事件类型时,我们将这个编码值写入对应控制寄存器中,就可以让事件计数器对该事件进行计数。下图展示了部分事件与编码的对应关系,以及事件的含义:
ARM PMU event & encoding
ARM PMU的寄存器根据功能可以大致分为三类:
- 用于描述/控制系统PMU资源的寄存器,例如PMU的硬件版本号、事件计数器的个数、计数器支持的事件类型、使能/关闭PMU硬件等;
- 用于配置计数器的寄存器,包括cycle counter和performance counter的配置,这里的配置包括事件类型的配置、中断使能/屏蔽的配置以及计数器使能/屏蔽的配置等;
- 用于访问计数器的寄存器,包括访问计数器的计数值以及计数器的溢出状态等,中断处理例程isr通过访问溢出状态寄存器可以判断是哪个计数器发生溢出。
下面简单介绍其中几个寄存器:
(一)用于描述/控制系统PMU资源的寄存器
1. PMCR_EL0(Performance Monitors Control Register)
PMCR_EL0
64位寄存器,比较重要的field有:
- N:5,表示PMU counter个数,值为0时表示系统只有cycle counter可用。可以看出,ARM PMUv3最多支持31个performance counter,而cycle counter是必须支持的。
- C:1,写入1时重置cycle counter值,写入0时无作用;
- P:1,写入1时重置所有performance counter值,写入0时无作用;
- E:1,写入0时关闭PMU硬件,写入1时使能PMU硬件,单个counter使能与否还有专门的寄存器进行配置。
2. PMCEID0_EL0和PMCEID1_EL0
这是两个64bit的寄存器,用bitmap的形式定义了平台所支持的事件类型。PMCEID0_EL0寄存器的低32bit定义了编号从0x0000-0x001F这32种事件,高32bit定义了编号从0x4000-0x401F这32种事件;PMCEID1_EL0寄存器的低32bit定义了编号从0x0020-0x003F这32种事件,高32bit定义了编号从0x4020-0x403F这32种事件。当比特位为1时,表示PMU支持该类型的事件,为0则不支持。
(二)用于配置计数事件的寄存器
1. PMEVTYPER<n>_EL0和PMEVCNTR<n>_EL0
每个performance counter都有一对寄存器用于配置计数事件类型和计数值,其中n表示计数器的编号,取值范围为1~PMCR_EL0.N。举个例子,如果PMU中有6个performance counter,那么n的取值范围是[1, 6],我们可以通过访问PMEVCNTR2_EL0访问2号计数器的计数值。
(三)用于配置中断状态的寄存器
- PMINTENSET_EL1、PMINTENCLR_EL1、PMOVSSET_EL0和PMOVSCLR_EL0
这几个寄存器的bit field分布都相同,如上图所示,分别用来配置PMU counter中断的使能和关闭,以及检查PMU counter的溢出状态。其中C:1用来控制cycle counter,P:31用来控制performance counter。往PMINTENSET_EL1的相应位写1使能对应的counter,往PMINTENCLR_EL1的相应位写1关闭对应的counter。当计数器发生溢出时,会触发中断通知CPU溢出事件的发生。CPU通过读PMOVSSET_EL0可以知道哪(几)个counter发生溢出,通过写PMOVSCLR_EL0可以清除相应counter的溢出状态。ARM里很多功能的使能和关闭都是通过两个独立的寄存器进行操作,这样做可以减少一次读寄存器的开销。
上面只是ARM PMU寄存器的一个简单罗列,还有一些寄存器没有介绍,读者可以自行去ARM的手册查阅。下一章分析Linux perf_event架构的ARM PMU硬件的驱动。