linux - perf_event_open 溢出信号

标签 linux signals perf

我想计算(或多或少)一段代码的确切指令数量。此外,我希望在通过特定数量的指令后收到信号。

为此,我使用了由 perf_event_open .

我正在使用联机帮助页建议的第二种方式来实现溢出信号:

Signal overflow

Events can be set to deliver a signal when a threshold is crossed. The signal handler is set up using the poll(2), select(2), epoll(2) and fcntl(2), system calls.

[...]

The other way is by use of the PERF_EVENT_IOC_REFRESH ioctl. This ioctl adds to a counter that decrements each time the event overflows. When nonzero, a POLL_IN signal is sent on overflow, but once the value reaches 0, a signal is sent of type POLL_HUP and the underlying event is disabled.

PERF_EVENT_IOC_REFRESH ioctl的进一步解释:

PERF_EVENT_IOC_REFRESH

Non-inherited overflow counters can use this to enable a counter for a number of overflows specified by the argument, after which it is disabled. Subsequent calls of this ioctl add the argument value to the current count. A signal with POLL_IN set will happen on each overflow until the count reaches 0; when that happens a signal with POLL_HUP set is sent and the event is disabled. Using an argument of 0 is considered undefined behavior.

一个非常简单的示例如下所示:

#define _GNU_SOURCE 1

#include <asm/unistd.h>
#include <fcntl.h>
#include <linux/perf_event.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

long perf_event_open(struct perf_event_attr* event_attr, pid_t pid, int cpu, int group_fd, unsigned long flags)
{
    return syscall(__NR_perf_event_open, event_attr, pid, cpu, group_fd, flags);
}

static void perf_event_handler(int signum, siginfo_t* info, void* ucontext) {
    if(info->si_code != POLL_HUP) {
        // Only POLL_HUP should happen.
        exit(EXIT_FAILURE);
    }

    ioctl(info->si_fd, PERF_EVENT_IOC_REFRESH, 1);
}

int main(int argc, char** argv)
{
    // Configure signal handler
    struct sigaction sa;
    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_sigaction = perf_event_handler;
    sa.sa_flags = SA_SIGINFO;

    // Setup signal handler
    if (sigaction(SIGIO, &sa, NULL) < 0) {
        fprintf(stderr,"Error setting up signal handler\n");
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    // Configure perf_event_attr struct
    struct perf_event_attr pe;
    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;     // Count retired hardware instructions
    pe.disabled = 1;        // Event is initially disabled
    pe.sample_type = PERF_SAMPLE_IP;
    pe.sample_period = 1000;
    pe.exclude_kernel = 1;      // excluding events that happen in the kernel-space
    pe.exclude_hv = 1;          // excluding events that happen in the hypervisor

    pid_t pid = 0;  // measure the current process/thread
    int cpu = -1;   // measure on any cpu
    int group_fd = -1;
    unsigned long flags = 0;

    int fd = perf_event_open(&pe, pid, cpu, group_fd, flags);
    if (fd == -1) {
        fprintf(stderr, "Error opening leader %llx\n", pe.config);
        perror("perf_event_open");
        exit(EXIT_FAILURE);
    }

    // Setup event handler for overflow signals
    fcntl(fd, F_SETFL, O_NONBLOCK|O_ASYNC);
    fcntl(fd, F_SETSIG, SIGIO);
    fcntl(fd, F_SETOWN, getpid());

    ioctl(fd, PERF_EVENT_IOC_RESET, 0);     // Reset event counter to 0
    ioctl(fd, PERF_EVENT_IOC_REFRESH, 1);   // 

// Start monitoring

    long loopCount = 1000000;
    long c = 0;
    long i = 0;

    // Some sample payload.
    for(i = 0; i < loopCount; i++) {
        c += 1;
    }

// End monitoring

    ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);   // Disable event

    long long counter;
    read(fd, &counter, sizeof(long long));  // Read event counter value

    printf("Used %lld instructions\n", counter);

    close(fd);
}

所以基本上我在做以下事情:

  1. 为 SIGIO 信号设置信号处理程序
  2. 使用 perf_event_open 创建一个新的性能计数器(返回一个文件描述符)
  3. 使用fcntl向文件描述符添加信号发送行为。
  4. 运行负载循环以执行许多指令。

当执行有效负载循环时,在某个时刻将执行 1000 条指令(sample_interval)。根据perf_event_open manpage这会触发溢出,然后递减内部计数器。 一旦该计数器达到零,“将发送一个 POLL_HUP 类型的信号,并禁用底层事件。”

当发送信号时,停止当前进程/线程的控制流,并执行信号处理程序。场景:

  1. 已执行 1000 条指令。
  2. 事件自动停用并发送信号。
  3. 信号立即传送,进程的控制流停止并执行信号处理程序。

这种情况意味着两件事:

  • 计数指令的最终数量将始终等于完全不使用信号的示例。
  • 为信号处理程序保存的指令指针(可以通过 ucontext 访问)将直接指向导致溢出的指令。

基本上你可以说,信号行为可以被视为同步

这是我想要实现的完美语义。

但是,就我而言,我配置的信号通常是相当异步的,并且可能需要一些时间才能最终交付并执行信号处理程序。这对我来说可能是个问题。

例如,考虑以下场景:

  1. 已执行 1000 条指令。
  2. 事件自动停用并发送信号。
  3. 一些指令通过
  4. 传递信号,停止进程的控制流并执行信号处理程序。

这种情况意味着两件事:

  • 计数指令的最终数量将少于根本不使用信号的示例。
  • 为信号处理程序保存的指令指针将指向导致溢出的指令或指向其后的任何指令

到目前为止,我已经对上面的示例进行了大量测试,没有遇到支持第一种情况的遗漏指令。

但是,我真的很想知道我是否可以依赖这个假设。 内核中发生了什么?

最佳答案

I want to count the (more or less) exact amount of instructions for some piece of code. Additionally, I want to receive a Signal after a specific amount of instructions passed.

您有两个可能相互冲突的任务。当你想要计数(一些硬件事件的精确数量)时,只需在计数模式下使用你的 CPU 的性能监控单元(不要设置 sample_period/sample_freq of perf_event_attr 使用的结构)并将测量代码放在目标程序中(就像在您的示例中所做的那样)。在这种模式下根据man page of perf_event_open不会产生溢出(CPU的PMU通常是64位宽,使用采样模式时不设置小负值也不会溢出):

Overflows are generated only by sampling events (sample_period must a nonzero value).

要对部分程序进行计数,请使用 perf_event_open 的 ioctl 返回 fd,如 man page 中所述

perf_event ioctl calls - Various ioctls act on perf_event_open() file descriptors: PERF_EVENT_IOC_ENABLE ... PERF_EVENT_IOC_DISABLE ... PERF_EVENT_IOC_RESET

您可以使用 rdpmc(在 x86 上)或通过 fd 上的 read 系统调用读取当前值,就像来自 the man page 的简短示例一样:

   #include <stdlib.h>
   #include <stdio.h>
   #include <unistd.h>
   #include <string.h>
   #include <sys/ioctl.h>
   #include <linux/perf_event.h>
   #include <asm/unistd.h>

   static long
   perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
                   int cpu, int group_fd, unsigned long flags)
   {
       int ret;

       ret = syscall(__NR_perf_event_open, hw_event, pid, cpu,
                      group_fd, flags);
       return ret;
   }

   int
   main(int argc, char **argv)
   {
       struct perf_event_attr pe;
       long long count;
       int fd;

       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 = 1;
       pe.exclude_kernel = 1;
       pe.exclude_hv = 1;

       fd = perf_event_open(&pe, 0, -1, -1, 0);
       if (fd == -1) {
          fprintf(stderr, "Error opening leader %llx\n", pe.config);
          exit(EXIT_FAILURE);
       }

       ioctl(fd, PERF_EVENT_IOC_RESET, 0);
       ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);

       printf("Measuring instruction count for this printf\n");
       /* Place target code here instead of printf */

       ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
       read(fd, &count, sizeof(long long));

       printf("Used %lld instructions\n", count);

       close(fd);
   }

Additionally, I want to receive a Signal after a specific amount of instructions passed.

您真的想要获得信号,还是只需要每执行 1000 条指令就需要指令指针?如果您想收集指针,请使用带采样模式的perf_even_open,但从其他程序执行以禁用事件收集代码的测量。此外,如果您不对每次溢出使用信号(大量的内核跟踪器交互以及从/切换到内核),而是使用 perf_events 的功能来收集多个溢出事件,那么它对您的目标程序的负面影响也会更小进入单个 mmap 缓冲区并轮询此缓冲区。在 PMU perf 中断处理程序的溢出中断将被调用以将指令指针保存到缓冲区中,然后计数将被重置并且程序将返回执行。在您的示例中,perf 中断处理程序将唤醒您的程序,它将执行多个系统调用,返回内核,然后内核将重新启动目标代码(因此每个样本的开销大于使用 mmap 和解析它)。使用 precise_ip 标志,您可以激活 PMU 的高级采样(如果它有这样的模式,如 PEBS 和 PREC_DIST in intel x86/em64t for some counters like INST_RETIRED, UOPS_RETIRED, BR_INST_RETIRED, BR_MISP_RETIRED, MEM_UOPS_RETIRED, MEM_LOAD_UOPS_RETIRED , MEM_LOAD_UOPS_LLC_HIT_RETIRED 和 simple hackcycles ;或像 AMD x86/amd64 的 IBS;关于 PEBS and IBS 的论文),当指令地址直接由硬件保存时,具有低滑动。一些非常先进的 PMU 能够在硬件中进行采样,将多个事件的溢出信息连续存储,并自动重置计数器而无需软件中断(precise_ip 上的一些描述是 in the same paper )。

我不知道在 perf_events 子系统和您的 CPU 中是否有可能同时激活两个 perf_event 任务:既计算目标进程中的事件,又同时从其他进程采样。使用高级 PMU,这在硬件中是可能的,现代内核中的 perf_events 可能允许它。但是您没有提供有关您的内核版本以及您的 CPU 供应商和系列的详细信息,因此我们无法回答这部分。

您也可以尝试其他 API 来访问 PMU,例如 PAPI 或 likwid (https://github.com/RRZE-HPC/likwid)。其中一些可能会直接读取 PMU 寄存器(有时是 MSR),并且可能允许在启用计数时同时进行采样。

关于linux - perf_event_open 溢出信号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24474397/

相关文章:

linux-kernel - 如何读取x86 intel处理器的PMC(Performance Monitoring Counter)

PHP:如何测试在 Linux 上向本地主机发送邮件并实际接收邮件?

linux - 将 grep 结果保存到远程机器上的文件中

linux - LBR vs DWARF vs fp 的性能记录选择有什么作用?

c++ - 程序可以在调用 kill 函数之前返回/终止吗?

Python - 轮询变量

c - 使用 perf 或其他方式获取 C 程序的运行时间(或其他统计信息)

node.js - IDE 在 PATH 中找不到 Node 二进制文件,但在 `echo $PATH` (Ubuntu Linux) 期间显示

linux - 如何使用终端在 Linux 上更改 git 帐户?

signals - 阻塞函数和 EINTR