使用 perf_event 和 gem5 中的 ARM PMU

Using perf_event with the ARM PMU inside gem5

我知道 ARM PMU 已部分实现,感谢 gem5 源代码和一些出版物。

我有一个二进制文件,它使用 perf_event 在 ARM 处理器下访问基于 Linux 的 OS 上的 PMU。它可以在带有 Linux 内核的 gem5 全系统模拟中使用 perf_event,在 ARM ISA 下吗?

到目前为止,我还没有找到正确的方法。如果有人知道,我将不胜感激!

上下文

由于 gem5 的 [=102],我无法使用 Performance Monitoring Unit (PMU) =] 未实现的功能。可以在 here. After a personal patch, the PMU is accessible through perf_event. Fortunately, a similar patch will be released in the official gem5 release soon, could be seen here 找到邮件列表中的参考。由于一条消息中的link数量限制,补丁将在另一个答案中描述。

如何使用PMU

C源代码

这是使用 perf_eventC 源代码的最小工作示例,用于计算特定任务期间分支预测器单元预测错误的分支数:

#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, 0, -1, -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 macros PERF_TYPE_HARDWARE 作为类型,并使用像 PERF_COUNT_HW_BRANCH_MISSES 这样的宏来表示错误预测的分支数, PERF_COUNT_HW_CACHE_MISSES 表示缓存未命中数,等等(请参阅手册页以获取列表) .这是拥有可移植代码的最佳做法。
  • gem5模拟系统上,目前(v20.0),C源代码必须使用PERF_TYPE_RAW类型和体系结构事件 ID 来标识事件。这里,0x10是0x0010, BR_MIS_PRED, Mispredicted or not predicted branch事件的ID,在ARMv8-A参考手册here)中有描述。在手册中,描述了真实硬件中可用的所有事件。然而,它们并没有全部实现到 gem5 中。要查看 gem5 中实现的事件列表,请参阅 src/arch/arm/ArmPMU.py 文件。在后者中,行 self.addEvent(ProbeEvent(self,0x10, bpred, "Misses")) 对应于手册中描述的计数器声明。这不是正常行为,因此应该修补 gem5 以允许有一天使用 PERF_TYPE_HARDWARE

gem5模拟脚本

这不是完整的 MWE 脚本(太长了!),只是需要添加到 full-system 脚本中以使用 PMU。我们使用 ArmSystem 作为系统,使用 RealView 平台。

对于每个ISA(我们在这里使用ARMISA)每个CPU例如,一个DerivO3CPU)在我们的集群中(这是一个SubSystem class ),我们向其添加一个 PMU,具有唯一的中断号和已经实现的架构事件。在 configs/example/arm/devices.py.

中可以找到此函数的示例

要选择中断号,请在平台中断映射中选择一个空闲的 PPI 中断。这里,我们根据RealView中断映射(src/dev/arm/RealView.py)选择PPIn°20。由于 PPI 中断是每个 处理元素 的本地中断(PE,对应于我们上下文中的内核),所有 PE 的中断号可以相同,不会有任何冲突。要了解有关 PPI 中断的更多信息,请参阅 GIC 指南 ARM here

这里可以看到中断n°20没有被系统使用(来自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)

我们将我们的系统组件(dtbitb 等)传递给 addArchEvents link PMU与它们一起,因此 PMU 将使用这些组件的内部计数器(称为 probes)作为系统的公开计数器。

for cpu in system.cpu_cluster.cpus:
    for isa in cpu.isa:
        isa.pmu = ArmPMU(interrupt=ArmPPI(num=20))
        # Add the implemented architectural events of gem5. We can
        # discover which events is implemented by looking at the file
        # "ArmPMU.py".
        isa.pmu.addArchEvents(
            cpu=cpu, dtb=cpu.dtb, itb=cpu.itb,
            icache=getattr(cpu, "icache", None),
            dcache=getattr(cpu, "dcache", None),
            l2cache=getattr(system.cpu_cluster, "l2", None))

截至 2020 年 9 月,gem5 需要打补丁才能使用 ARM PMU

编辑:截至 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 内核使用 设备树 Blob (DTB) ,这是一个常规文件,用于声明内核所在的硬件 运行。这用于使内核在不同体系结构之间可移植,而无需为每次硬件更改重新编译。 DTB 遵循 Device Tree Reference,并从 Device Tree Source (DTS) 文件编译而来,一个普通的文本文件。您可以了解更多 here and here.

问题是 PMU 应该通过 Linux 内核 声明 DTB。您可以了解更多 here and here。在模拟系统中,因为系统是用户指定的,gem5自己要生成一个DTB传递给内核,所以后者可以识别模拟硬件。但是,问题是 gem5 不会为我们的 PMU.[=31 生成 DTB 条目=]

补丁的作用

该补丁在 ISACPU 文件中添加了一个条目以启用 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中断号(一个在gem5中声明为2016的区别在patch code里面解释)。 0xf04 对应一个标志 (0x4) 表示它是一个“高电平有效 level-sensitive” 中断和一个位掩码 (0xf) 表示应该连接中断连接到 GIC 的所有 PE。您可以了解更多 here.

如果补丁有效并且您的 ArmPMU 声明正确,您应该会在启动时看到此消息:

  [    0.239967] hw perfevents: enabled with armv8_pmuv3 PMU driver, 32 counters available

Pierre 精彩回答的两个快速补充:

  • 对于 fs.py 作为 gem5 937241101fae2cd0755c43c33bab2537b47596a2,所缺少的只是应用于 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", None),
                    l2cache=getattr(test_sys, "l2", None))
    
  • C 示例也可以在 man perf_event_open

    中找到