问题描述
由于gem5源代码和一些出版物,我知道ARM PMU是部分实现的。
我有一个二进制文件,该文件使用perf_event在ARM处理器下基于Linux的OS上访问PMU。它可以在ARM ISA下在具有Linux内核的gem5全系统仿真中使用perf_event吗?
到目前为止,我还没有找到正确的方法。如果有人知道,我将不胜感激!
解决方法
上下文
由于 gem5的未实现的功能,我无法使用性能监视单元( PMU )。可以在here上找到邮件列表上的参考。进行个人补丁后,可以通过perf_event
访问 PMU 。幸运的是,可以在here中看到类似的补丁即将在正式的 gem5 版本中发布。由于一条消息内的链接限制数量,该补丁将在另一个答案中进行描述。
如何使用 PMU
C 源代码
这是使用perf_event
的 C 源代码的最小工作示例,用于计算分支预测器单元在特定任务期间错误预测的分支数:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/perf_event.h>
int main(int argc,char **argv) {
/* File descriptor used to read mispredicted branches counter. */
static int perf_fd_branch_miss;
/* Initialize our perf_event_attr,representing one counter to be read. */
static struct perf_event_attr attr_branch_miss;
attr_branch_miss.size = sizeof(attr_branch_miss);
attr_branch_miss.exclude_kernel = 1;
attr_branch_miss.exclude_hv = 1;
attr_branch_miss.exclude_callchain_kernel = 1;
/* On a real system,you can do like this: */
attr_branch_miss.type = PERF_TYPE_HARDWARE;
attr_branch_miss.config = PERF_COUNT_HW_BRANCH_MISSES;
/* On a gem5 system,you have to do like this: */
attr_branch_miss.type = PERF_TYPE_RAW;
attr_branch_miss.config = 0x10;
/* Open the file descriptor corresponding to this counter. The counter
should start at this moment. */
if ((perf_fd_branch_miss = syscall(__NR_perf_event_open,&attr_branch_miss,-1,0)) == -1)
fprintf(stderr,"perf_event_open fail %d %d: %s\n",perf_fd_branch_miss,errno,strerror(errno));
/* Workload here,that means our specific task to profile. */
/* Get and close the performance counters. */
uint64_t counter_branch_miss = 0;
read(perf_fd_branch_miss,&counter_branch_miss,sizeof(counter_branch_miss));
close(perf_fd_branch_miss);
/* Display the result. */
printf("Number of mispredicted branches: %d\n",counter_branch_miss);
}
我不会详细介绍如何使用perf_event
,可用资源here,here,here,here。但是,关于上述代码的几点注意事项:
- 在实际硬件上,当使用
perf_event
和常见事件(在许多体系结构下可用的事件)时,建议使用perf_event
宏{{1 }}作为类型,并使用PERF_TYPE_HARDWARE
这样的宏来预测错误的分支数量,使用PERF_COUNT_HW_BRANCH_MISSES
来解决高速缓存未命中的数量,依此类推(请参见手册页以获取列表)。这是拥有可移植代码的最佳实践。 - 在当前(v20.0)的 gem5 模拟系统上, C 源代码必须使用
PERF_COUNT_HW_CACHE_MISSES
类型和体系结构事件ID来识别事件。此处,0x10是PERF_TYPE_RAW
事件的ID,在 ARMv8-A参考手册(here)中进行了描述。在手册中,描述了实际硬件中可用的所有事件。但是,它们并没有全部实现到 gem5 中。要查看 gem5 中已实现事件的列表,请参考0x0010,BR_MIS_PRED,Mispredicted or not predicted branch
文件。在后者中,行src/arch/arm/ArmPMU.py
对应于手册中描述的计数器声明。这不是正常现象,因此应修补 gem5 以便一天使用self.addEvent(ProbeEvent(self,0x10,bpred,"Misses"))
。
gem5 模拟脚本
这不是完整的 MWE 脚本(太长了!),只有添加到完整系统脚本中才能使用 PMU 的所需部分。我们使用 ArmSystem 作为系统,并使用 RealView 平台。
对于每个 CPU ( eg的每个 ISA (此处使用 ARM ISA ) (集群中的PERF_TYPE_HARDWARE
类中的DerivO3CPU
),我们在其中添加了一个 PMU ,它具有唯一的中断号和已经实现的架构事件。可以在SubSystem
中找到此功能的示例。
要选择一个中断号,请在平台中断映射中选择一个免费的 PPI 中断。在这里,我们根据configs/example/arm/devices.py
中断映射(RealView
)选择 PPI n°20。由于 PPI 中断是每个 Processing Element ( PE ,对应于我们上下文中的内核)的局部中断,因此对于所有 PE 没有任何冲突。要了解有关 PPI 中断的更多信息,请参阅 ARM here中的 GIC 指南。
在这里,我们可以看到系统未使用n°20中断(来自src/dev/arm/RealView.py
):
RealView.py
我们传递给Interrupts:
0- 15: Software generated interrupts (SGIs)
16- 31: On-chip private peripherals (PPIs)
25 : vgic
26 : generic_timer (hyp)
27 : generic_timer (virt)
28 : Reserved (Legacy FIQ)
我们的系统组件(addArchEvents
,dtb
等)以将 PMU 与它们链接,从而将 PMU 会将这些组件的内部计数器(称为 probes )用作系统的公开计数器。
itb
,
从2020年9月开始,为了使用 ARM PMU ,需要修补 gem5 。
编辑:自2020年11月起,现在已修补 gem5 并将其包含在下一发行版中。感谢开发人员!
如何修补 gem5
这不是一个干净的补丁(非常简单),它更旨在了解其工作原理。尽管如此,这是可与 gem5 源存储库中的git apply
一起应用的补丁:
diff --git i/src/arch/arm/ArmISA.py w/src/arch/arm/ArmISA.py
index 2641ec3fb..3d85c1b75 100644
--- i/src/arch/arm/ArmISA.py
+++ w/src/arch/arm/ArmISA.py
@@ -36,6 +36,7 @@
from m5.params import *
from m5.proxy import *
+from m5.SimObject import SimObject
from m5.objects.ArmPMU import ArmPMU
from m5.objects.ArmSystem import SveVectorLength
from m5.objects.BaseISA import BaseISA
@@ -49,6 +50,8 @@ class ArmISA(BaseISA):
cxx_class = 'ArmISA::ISA'
cxx_header = "arch/arm/isa.hh"
+ generateDeviceTree = SimObject.recurseDeviceTree
+
system = Param.System(Parent.any,"System this ISA object belongs to")
pmu = Param.ArmPMU(NULL,"Performance Monitoring Unit")
diff --git i/src/arch/arm/ArmPMU.py w/src/arch/arm/ArmPMU.py
index 047e908b3..58553fbf9 100644
--- i/src/arch/arm/ArmPMU.py
+++ w/src/arch/arm/ArmPMU.py
@@ -40,6 +40,7 @@ from m5.params import *
from m5.params import isNullPointer
from m5.proxy import *
from m5.objects.Gic import ArmInterruptPin
+from m5.util.fdthelper import *
class ProbeEvent(object):
def __init__(self,pmu,_eventId,obj,*listOfNames):
@@ -76,6 +77,17 @@ class ArmPMU(SimObject):
_events = None
+ def generateDeviceTree(self,state):
+ node = FdtNode("pmu")
+ node.appendCompatible("arm,armv8-pmuv3")
+ # gem5 uses GIC controller interrupt notation,where PPI interrupts
+ # start to 16. However,the Linux kernel start from 0,and used a tag
+ # (set to 1) to indicate the PPI interrupt type.
+ node.append(FdtPropertyWords("interrupts",[
+ 1,int(self.interrupt.num) - 16,0xf04
+ ]))
+ yield node
+
def addEvent(self,newObject):
if not (isinstance(newObject,ProbeEvent)
or isinstance(newObject,SoftwareIncrement)):
diff --git i/src/cpu/BaseCPU.py w/src/cpu/BaseCPU.py
index ab70d1d7f..66a49a038 100644
--- i/src/cpu/BaseCPU.py
+++ w/src/cpu/BaseCPU.py
@@ -302,6 +302,11 @@ class BaseCPU(ClockedObject):
node.appendPhandle(phandle_key)
cpus_node.append(node)
+ # Generate nodes from the BaseCPU children (and don't add them as
+ # subnode). Please note: this is mainly needed for the ISA class.
+ for child_node in self.recurseDeviceTree(state):
+ yield child_node
+
yield cpus_node
def __init__(self,**kwargs):
补丁解决的问题
Linux 内核使用作为常规文件的 Device Tree Blob ( DTB )声明内核所在的硬件在跑。这用于使内核在不同体系结构之间可移植,而无需为每个硬件更改重新编译。 DTB遵循设备树参考,并且是从设备树源( DTS )文件(常规文本文件)编译而成的。您可以了解更多here和here。
问题在于,应该通过 DTB 将 PMU 声明为 Linux 内核。您可以了解更多here和here。在模拟系统中,由于系统是由用户指定的,因此 gem5 必须自行生成 DTB 才能传递给内核,因此后者可以识别模拟硬件。但是,问题在于 gem5 不会为我们的 PMU 生成 DTB 条目。
补丁程序做什么
该补丁程序在 ISA 和 CPU 文件中添加了一个条目,以启用 DTB 生成递归以查找 PMU 。层次结构如下:CPU => ISA => PMU。然后,它将生成函数添加到 PMU 中,以生成唯一的 DTB 条目,以声明 PMU ,并使用适当的中断声明符号在内核中。
使用补丁运行模拟后,我们可以从 DTB 中看到 DTS ,如下所示:
cd m5out
# Decompile the DTB to get the DTS.
dtc -I dtb -O dts system.dtb > system.dts
# Find the PMU entry.
head system.dts
dtc
是随sudo apt-get install device-tree-compiler
安装的设备树编译器。我们最后在根节点(pmu
)下的/
DTB 条目结束了:
/dts-v1/;
/ {
#address-cells = <0x02>;
#size-cells = <0x02>;
interrupt-parent = <0x05>;
compatible = "arm,vexpress";
model = "V2P-CA15";
arm,hbi = <0x00>;
arm,vexpress,site = <0x0f>;
memory@80000000 {
device_type = "memory";
reg = <0x00 0x80000000 0x01 0x00>;
};
pmu {
compatible = "arm,armv8-pmuv3";
interrupts = <0x01 0x04 0xf04>;
};
cpus {
#address-cells = <0x01>;
#size-cells = <0x00>;
cpu@0 {
device_type = "cpu";
compatible = "gem5,arm-cpu";
[...]
在interrupts = <0x01 0x04 0xf04>;
行中,0x01
表示数字0x04
是 PPI 中断的编号(用数字{ {em} {5}中的{1}},20
的差异在补丁代码中进行了说明)。 16
对应于一个标志(0xf04
),该标志指示它是“活动的高电平敏感”中断;而一个位掩码(0x4
)则指示该中断应连接至所有中断。 PE 附加到 GIC 。您可以了解更多here。
如果补丁程序有效并且您的0xf
被正确声明,则您应该在启动时看到以下消息:
ArmPMU
,
快速补充Pierre的出色答案:
-
对于截至gem5 937241101fae2cd0755c43c33bab2537b47596a2的fs.py,仅适用于fs.py,如下所示:https://gem5-review.googlesource.com/c/public/gem5/+/37978/1/configs/example/fs.py
for cpu in test_sys.cpu: if buildEnv['TARGET_ISA'] in "arm": for isa in cpu.isa: isa.pmu = ArmPMU(interrupt=ArmPPI(num=20)) isa.pmu.addArchEvents( cpu=cpu,dtb=cpu.mmu.dtb,itb=cpu.mmu.itb,icache=getattr(cpu,"icache",None),dcache=getattr(cpu,"dcache",l2cache=getattr(test_sys,"l2",None))
-
一个C示例也可以在
中找到man perf_event_open