【问题标题】:Reading performance registers from the kernel从内核读取性能寄存器
【发布时间】:2019-08-01 16:25:18
【问题描述】:

我想读取某些性能计数器。我知道有像 perf 这样的工具,可以在用户空间本身为我完成,我希望代码在 Linux 内核中。

我想编写一种机制来监控 Intel(R) Core(TM) i7-3770 CPU 上的性能计数器。除了使用之外,我还使用 Ubuntu 内核 4.19.2。我从easyperf得到了以下方法

这是我阅读说明的部分代码。

  struct perf_event_attr *attr
  memset (&pe, 0, sizeof (struct perf_event_attr));
  pe.type = PERF_TYPE_HARDWARE;
  pe.size = sizeof (struct perf_event_attr);
  pe.config = PERF_COUNT_HW_INSTRUCTIONS;
  pe.disabled = 0;
  pe.exclude_kernel = 0;
  pe.exclude_user = 0;
  pe.exclude_hv = 0;
  pe.exclude_idle = 0;

  fd = syscall(__NR_perf_event_open, hw, pid, cpu, grp, flags);

  uint64_t perf_read(int fd) {
    uint64_t val;
    int rc;
    rc = read(fd, &val, sizeof(val));
    assert(rc == sizeof(val));
    return val;
  }

我想在内核代码(context switch 函数)中放入相同的行并检查正在读取的值。

我的最终目标是找出一种方法来读取进程的性能计数器,每次它切换到另一个进程时,都从内核 (4.19.2) 本身。

为了实现这一点,我查看了系统调用号 __NR_perf_event_open 的代码。可以找到here 为了使其可用,我将其中的代码复制为一个单独的函数,在同一个文件中将其命名为 perf_event_open() 并导出。

现在问题是每当我以与上面相同的方式调用 perf_event_open() 时,返回的描述符是 -2。检查error codes,我发现错误是ENOENT。在perf_event_open() man page中,这个错误的原因被定义为错误的type字段。

由于文件描述符与打开它们的进程相关联,如何从内核中使用它们?是否有另一种方法可以将 pmu 配置为在不涉及文件描述符的情况下开始计数?

【问题讨论】:

  • 你不需要内联汇编; gcc 有一个__builtin_rdpmc(int)。但是你的内联汇编看起来是正确的,所以这不会改变任何东西。 (注意gcc.gnu.org/bugzilla/show_bug.cgi?id=87550:在 gcc6.5 / 7.4 / 8.3 之前,内置忽略了volatile。)
  • 您必须显示您在用户模式和内核模式下使用的整个代码。我怀疑您在用户模式下使用的代码启用了指令退休固定功能计数器,但您在内核模式下使用的代码没有。
  • 您可能会发现在 NanoBench 中查看它是如何完成的会很有帮助。
  • 您仍然只显示使用rdpmc 的代码。您没有显示任何对 PMU 进行编程的代码。您在对我的回答的评论中链接了software.intel.com/en-us/forums/…,但您甚至还没有在您的问题中提到做任何事情来使计数器计数。只是你从那里得到了rdmpc 代码本身。
  • 您必须首先使用rdpmc 启用或编程要读取的计数器。甚至您的用户模式代码也不起作用;它只会打印零。您认为它有效的原因是因为您使用%ld 格式来打印double 值,它基本上将零重新解释为一个大整数。正确的代码是来自英特尔论坛上 Kumar C 在“Thu, 11/17/2016 - 17:41”上发表的评论中的代码。

标签: linux-kernel x86 performancecounter perf intel-pmu


【解决方案1】:

您可能不希望在上下文切换函数中重新编程计数器的开销。

最简单的方法是从用户空间进行系统调用以对 PMU 进行编程(计算某些事件,可能将其设置为在内核模式下计数,但 不是用户空间,只是这样计数器溢出较少)。

然后在您的自定义内核代码中使用两次rdpmc(以获取启动/停止计数)。计数器将继续运行,我猜内核性能代码将在它回绕时处理中断。 (或者当它的 PEBS 缓冲区已满时。)

IDK 如果可以对计数器进行编程,使其在不中断的情况下进行包装,对于像这样的用例,您不关心总计或基于样本的分析,而只想使用rdpmc。如果是这样,那就这样做吧。


旧答案,解决您的旧问题,该问题基于错误的printf 格式字符串,即使您也没有计算用户空间中的任何内容,该字符串仍在打印非零垃圾。

您的内联汇编看起来是正确的,所以问题是在您的代码运行的上下文中,PMU 计数器被编程为在内核模式下计数到底是什么。

perf 在上下文切换时虚拟化 PMU 计数器,从而产生perf stat 计数单个进程的错觉,即使它跨 CPU 迁移也是如此。除非您使用perf -a 来获取系统范围的计数,否则 PMU 可能不会被编程为计数任何东西,因此即使在其他时间它被编程为计数快速变化的事件(如周期),多次读取都会给出0或说明。


您确定您已将 perf 设置为计算用户 + 内核事件,而不仅仅是用户空间事件吗?

perf stat 将显示类似instructions:u 而不是instructions 如果它将自身限制在用户空间。 (如果您没有将 sysctl kernel.perf_event_paranoid 降低到 0 或不让用户空间了解有关内核的任何内容的安全默认值,这是非 root 用户的默认值。)

硬件支持将计数器编程为仅在 CPL != 0 时计数(即不在环 0/内核模式下)。kernel.perf_event_paranoid 的较高值限制 perf API 不允许编程在内核+用户模式下计数的计数器,但即使使用paranoid = -1,也可以通过这种方式对其进行编程。如果这就是你编写计数器的方式,那么这就解释了一切。

我们需要查看您编写计数器的代码。这不会自动发生。

当没有进程使用 PAPI 函数启用每个进程或系统范围的计数器时,内核不会让计数器一直运行;这会产生中断,使系统变慢而无益。

【讨论】:

  • 目前,我将 kernel.perf_event_paranoid 设置为 -1。这会影响从内核读取计数器吗?我认为这只是一种允许非 root 用户使用 perf 的方法。我会检查它并稍后更新。
  • @NikhileshSingh:它会影响内核对 PMU 的编程方式。您用于读取 PMU 的代码是正确的,因此问题是当您在内核模式下运行 rdpmc 时,该计数器索引被编程为计数。
  • @PeterCordes:有硬件支持在内核中不计数。具体来说,每个计数器的配置都有一对标志(一个用于“启用计数 CPL=0”,另一个用于“启用计数 CPL!=0”),以及可能的附加标志(例如“来自内核中的所有逻辑 CPU 或来自一个逻辑核心中的 CPU 和哪个”,以及“伪造/非伪造”等),具体取决于恶意攻击者用于计时边信道的事件类型...... ;-)
  • @NikhileshSingh:我只知道底层硬件,不知道 Linux 提供的抽象/便携接口(你正在使用的接口,至少在你没有违反合同的情况下)直接使用rdpmc);但我只花了 60 秒就发现 perf_event_open() 的 sample_type 参数包含一个 exclude_user 标志和一个 exclude_kernel 标志。
  • @PeterCordes" 我不知道为什么 OP 想要这个 - 我假设(希望?)这是某种 Spectre 或 Rowhammer 缓解措施(例如,内核监控缓存未命中或分支错误预测,如果出现超过阈值)。改进软件的性能测量是使性能变差的巨大浪费 - 有很多这种愚蠢的例子(例如,人们在 Linux 中添加“分支提示”,但几年后发现他们都错了并且受到伤害在较新的 CPU 上的性能,人们避免在 Netburst/Williamette 上切换,人们认为“rep movsb”很慢,..)。
猜你喜欢
  • 2014-02-19
  • 2019-07-13
  • 1970-01-01
  • 2014-10-29
  • 2023-01-12
  • 1970-01-01
  • 2020-09-28
  • 2012-01-21
  • 1970-01-01
相关资源
最近更新 更多