【问题标题】:perf_event_open Overflow Signalperf_event_open 溢出信号
【发布时间】:2014-08-19 21:30:33
【问题描述】:

我想计算某些代码的(或多或少)确切的指令数量。此外,我想在特定数量的指令通过后接收信号。

为此,我使用了提供的溢出信号行为 perf_event_open.

我正在使用手册页建议的第二种方式来实现溢出信号:

信号溢出

可以设置事件以在达到阈值时传递信号 被交叉。信号处理程序是使用 poll(2)、select(2)、 epoll(2) 和 fcntl(2),系统调用。

[...]

另一种方法是使用 PERF_EVENT_IOC_REFRESH ioctl。这 ioctl 添加到每次事件溢出时递减的计数器。 当非零时,溢出时发送 POLL_IN 信号,但 一旦值 达到 0,发送 POLL_HUP 类型的信号和基础事件 已禁用。

PERF_EVENT_IOC_REFRESH ioctl的进一步解释:

PERF_EVENT_IOC_REFRESH

非继承的溢出计数器可以使用它来启用 参数指定的溢出次数的计数器, 之后它被禁用。此 ioctl 的后续调用 将参数值添加到当前计数。一个信号 POLL_IN 设置将在每次溢出时发生,直到计数 达到0;当发生这种情况时,设置了 POLL_HUP 的信号是 发送并且事件被禁用。使用 0 的参数是 被认为是未定义的行为。

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

#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. 信号已传递,进程的控制流停止并执行信号处理程序。

这种情况意味着两件事:

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

到目前为止,我已经对上面的示例进行了很多测试,并且没有遇到遗漏的说明,这将支持第一种情况。

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

【问题讨论】:

  • 如果你想“计算(或多或少)确切数量的某些代码的指令数量”,不要使用采样,而是使用硬件 pmu 的计数模式。使用 perf 时,perf_event_attr 中没有设置 sample_period/sample_freq。如果您想获得溢出事件的确切 IP,请使用 precise_ip 标志设置为 1 或 2。如果性能不允许您同时进行采样和计数,请尝试 likwid-perfctr(来自 github.com/RRZE- HPC/likwid)并检查您的 CPU 的 PMU 文档(是 x86、intel/amd 还是系列?)。我仍然不清楚真正的问题是什么,是否可以回答。

标签: linux signals perf


【解决方案1】:

我想计算某些代码的(或多或少)确切的指令数量。此外,我想在特定数量的指令通过后接收信号。

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

溢出仅由采样事件生成(sample_period 必须为非零值)。

要计算程序的一部分,请使用ioctls of perf_event_open 返回的 fd,如man page 中所述

perf_event ioctl 调用 - 各种 ioctl 作用于 perf_event_open() 文件描述符: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);
   }

另外,我想在特定数量的指令通过后接收信号。

您真的想获得信号,还是只需要在每执行 1000 条指令时获得指令指针?如果要收集指针,请使用perf_even_open 采样模式,但从其他程序执行 以禁用对事件收集代码的测量。此外,它对目标程序的负面影响较小,如果您不为每个溢出使用信号(具有大量内核跟踪器交互和从/到内核的切换),而是使用 perf_events 的功能来收集几个溢出事件进入单个 mmap 缓冲区并在此缓冲区上轮询。来自 PMU perf 中断处理程序的溢出中断将被调用以将指令指针保存到缓冲区中,然后计数将被重置,程序将返回执行。在您的示例中, perf 中断处理程序将唤醒您的程序,它将执行几个系统调用,返回内核,然后内核将重新启动目标代码(因此每个样本的开销大于使用 mmap 并解析它)。使用precise_ip 标志,您可以激活您的PMU 的高级采样(如果它具有这样的模式,例如intel x86/em64t 中的PEBS 和PREC_DIST for some counters,例如INST_RETIRED、UOPS_RETIRED、BR_INST_RETIRED、BR_MISP_RETIRED、MEM_UOPS_RETIRED、MEM_LOAD_UOPS_RETIRED、MEM_LOAD_UU 987654325@ 到cycles 也一样;或者像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),并且可以在启用计数的同时允许采样。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-31
    • 1970-01-01
    • 2012-02-29
    • 2016-05-22
    • 2018-07-22
    相关资源
    最近更新 更多