perf_event框架之:ARM PMU硬件

许多体系结构都包含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的寄存器根据功能可以大致分为三类:

  1. 用于描述/控制系统PMU资源的寄存器,例如PMU的硬件版本号、事件计数器的个数、计数器支持的事件类型、使能/关闭PMU硬件等;
  2. 用于配置计数器的寄存器,包括cycle counter和performance counter的配置,这里的配置包括事件类型的配置、中断使能/屏蔽的配置以及计数器使能/屏蔽的配置等;
  3. 用于访问计数器的寄存器,包括访问计数器的计数值以及计数器的溢出状态等,中断处理例程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号计数器的计数值。

(三)用于配置中断状态的寄存器

  1. 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硬件的驱动。

转载:perf_event框架之:ARM PMU硬件 - 知乎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一句句解释分析细致讲解一下这段代码void netconf_entry(void) { int state = 0; int event = 0; UINT8 * pPacket = NULL; UINT32 dataLen = 0; char *pData = NULL; UINT8 ret = 0; struct np_module netopeer_module; struct np_module server_module; memset(&netopeer_module,0,sizeof(struct np_module)); memset(&server_module,0,sizeof(struct np_module)); state = Ros_GetCurState(); event = Ros_GetMsgId(); switch( state ) { case NETCONF_PROCESS_INIT: switch( event ) { case MSG_MasterPowerOn: case MSG_SlavePowerOn: //printf("netconf_entry MSG_SlavePowerOn\n"); Ros_SetNextState(NETCONF_PROCESS_RUNNING); break; case MSG_INIT_PROTOCOL_OK: Ros_SetNextState(NETCONF_PROCESS_RUNNING); break; default: break; } break; case NETCONF_PROCESS_RUNNING: switch( event ) { case MSG_DriverOK: netconf_init(); break; case MSG_NETCONF_TO_DCN: nc_verb_verbose("netconf_entry recv netconf to dcn msg\n"); break; case MSG_ALARM_TO_NETCONF: NcProcessAlarmMsg(); break; case MSG_LIBNETCONF_TO_NETOPEER: ncRcvLibnetconfMsgProc(); break; case MSG_NETCONF_LLDP_TIMER: Ros_SetTimer(netconfLLDPTimerID, NETCONF_MSG_TIME_LLDP, 0); ncLldpchange(); break; case MSG_NETCONF_15MIN_PERF_TIMER: Ros_SetTimer(netconf15MINPerfTimerID, NETCONF_MSG_15MIN_TIME_PERF, 0); packageOptInfo15Min(); packageIfStatisInfo15Min(); break; case MSG_NETCONF_24H_PERF_TIMER: Ros_SetTimer(netconf24hPerfTimerID, NETCONF_MSG_24H_TIME_PERF, 0); packageOptInfo24H(); packageIfStatisInfo24H(); break; case MSG_NETCONF_MEM_CHECK_TIMER: printS("MSG_NETCONF_MEM_CHECK_TIMER TIMEROUT\n"); memory_line_check(0); break; case MSG_NETCONF_TIMER: oam_perf_netconf_show_value_entry(); break; default: break; } break; default: break; } return EXIT_SUCCESS; }
07-14
下面是对这段代码的逐句解释和分析: ```c++ void netconf_entry(void) { int state = 0; int event = 0; UINT8 * pPacket = NULL; UINT32 dataLen = 0; char *pData = NULL; UINT8 ret = 0; struct np_module netopeer_module; struct np_module server_module; memset(&netopeer_module,0,sizeof(struct np_module)); memset(&server_module,0,sizeof(struct np_module)); ``` 这段代码定义了一个名为netconf_entry的函数,没有参数,返回类型为void。 接下来,定义了一系列变量,包括state和event(int类型)、pPacket(UINT8指针类型)、dataLen(UINT32类型)、pData(char指针类型)和ret(UINT8类型)。 然后,定义了两个结构体变量netopeer_module和server_module,并使用memset函数将其内容初始化为0。 ```c++ state = Ros_GetCurState(); event = Ros_GetMsgId(); switch( state ) { case NETCONF_PROCESS_INIT: switch( event ) { case MSG_MasterPowerOn: case MSG_SlavePowerOn: //printf("netconf_entry MSG_SlavePowerOn\n"); Ros_SetNextState(NETCONF_PROCESS_RUNNING); break; case MSG_INIT_PROTOCOL_OK: Ros_SetNextState(NETCONF_PROCESS_RUNNING); break; default: break; } break; case NETCONF_PROCESS_RUNNING: switch( event ) { case MSG_DriverOK: netconf_init(); break; case MSG_NETCONF_TO_DCN: nc_verb_verbose("netconf_entry recv netconf to dcn msg\n"); break; case MSG_ALARM_TO_NETCONF: NcProcessAlarmMsg(); break; case MSG_LIBNETCONF_TO_NETOPEER: ncRcvLibnetconfMsgProc(); break; case MSG_NETCONF_LLDP_TIMER: Ros_SetTimer(netconfLLDPTimerID, NETCONF_MSG_TIME_LLDP, 0); ncLldpchange(); break; case MSG_NETCONF_15MIN_PERF_TIMER: Ros_SetTimer(netconf15MINPerfTimerID, NETCONF_MSG_15MIN_TIME_PERF, 0); packageOptInfo15Min(); packageIfStatisInfo15Min(); break; case MSG_NETCONF_24H_PERF_TIMER: Ros_SetTimer(netconf24hPerfTimerID, NETCONF_MSG_24H_TIME_PERF, 0); packageOptInfo24H(); packageIfStatisInfo24H(); break; case MSG_NETCONF_MEM_CHECK_TIMER: printS("MSG_NETCONF_MEM_CHECK_TIMER TIMEROUT\n"); memory_line_check(0); break; case MSG_NETCONF_TIMER: oam_perf_netconf_show_value_entry(); break; default: break; } break; default: break; } return EXIT_SUCCESS; } ``` 接下来,代码根据当前状态(state)和事件(event)进行switch语句的判断。 如果state为NETCONF_PROCESS_INIT,根据event的不同进行不同的操作。如果event是MSG_MasterPowerOn或MSG_SlavePowerOn,则将下一个状态设置为NETCONF_PROCESS_RUNNING。如果event是MSG_INIT_PROTOCOL_OK,则同样将下一个状态设置为NETCONF_PROCESS_RUNNING。 如果state为NETCONF_PROCESS_RUNNING,根据event的不同进行不同的操作。如果event是MSG_DriverOK,则调用netconf_init函数。如果event是MSG_NETCONF_TO_DCN,则打印一条调试信息。如果event是MSG_ALARM_TO_NETCONF,则调用NcProcessAlarmMsg函数。如果event是MSG_LIBNETCONF_TO_NETOPEER,则调用ncRcvLibnetconfMsgProc函数。如果event是MSG_NETCONF_LLDP_TIMER,则设置一个定时器,并调用ncLldpchange函数。如果event是MSG_NETCONF_15MIN_PERF_TIMER,则设置一个定时器,并依次调用packageOptInfo15Min和packageIfStatisInfo15Min函数。如果event是MSG_NETCONF_24H_PERF_TIMER,则设置一个定时器,并依次调用packageOptInfo24H和packageIfStatisInfo24H函数。如果event是MSG_NETCONF_MEM_CHECK_TIMER,则打印一条信息,并调用memory_line_check函数。如果event是MSG_NETCONF_TIMER,则调用oam_perf_netconf_show_value_entry函数。 最后,函数返回EXIT_SUCCESS(整数值,表示成功)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值