perf_event_open溢出信号

时间:2014-06-29 08:29:11

标签: linux signals perf

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

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

我使用manpage建议实现溢出信号的第二种方式:

  

信号溢出

     

事件可以设置为在达到阈值时传递信号   越过了。使用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

     

非继承溢出计数器可以使用它来启用a   计数器指定的多个溢出的计数器,   之后它被禁用。随后调用此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. 运行有效负载循环以执行许多指令。
  5. 执行有效负载循环时,某些时候将执行1000条指令(sample_interval)。根据{{​​3}},这会触发溢出,然后递减内部计数器。 一旦此计数器达到零,&#34;发送类型为POLL_HUP的信号,并且基础事件被禁用。&#34;

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

    1. 已执行1000条指令。
    2. 事件自动停用并发送信号。
    3. 信号立即发送,停止控制流程并执行信号处理程序。
    4. 这种情况意味着两件事:

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

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

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

      然而,就我而言,我配置的信号通常是异步的,并且可能会经过一段时间,直到它最终被传递并执行信号处理程序。这可能对我造成问题。

      例如,请考虑以下情形:

      1. 已执行1000条指令。
      2. 事件自动停用并发送信号。
      3. 传递更多指示
      4. 发送信号,停止控制流程并执行信号处理程序。
      5. 这种情况意味着两件事:

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

        到目前为止,我已经在上面的示例中进行了大量测试,并且没有经历错过了支持第一种情况的说明。

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

1 个答案:

答案 0 :(得分:3)

  

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

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

  

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

要计算部分程序,请按man page

中所述使用返回fd的perf_event_open ioctl
  

perf_event ioctl调用 - 各种ioctl作用于perf_event_open()文件描述符:PERF_EVENT_IOC_ENABLE ... PERF_EVENT_IOC_DISABLE ... PERF_EVENT_IOC_RESET

您可以使用rdpmc(在x86上)或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中断处理程序将唤醒你的程序,它将执行几个系统调用,返回内核然后内核将重新启动目标代码(因此每个样本的开销大于使用mmap并解析它)。使用precise_ip标志,您可以激活PMU的高级采样(如果它具有这样的模式,例如英特尔x86 / em64t中的{3}的PEBS和PREC_DIST,如INST_RETIRED,UOPS_RETIRED,BR_INST_RETIRED,BR_MISP_RETIRED,MEM_UOPS_RETIRED,MEM_LOAD_UOPS_RETIRED, MEM_LOAD_UOPS_LLC_HIT_RETIRED以及some counterscycles;或者像AMD x86 / amd64的IBS;关于simple hack的论文,当指令地址由具有低滑动的硬件直接保存时。一些非常先进的PMU能够在硬件中进行采样,在行中存储多个事件的溢出信息,并且无需软件中断即可自动复位计数器(precise_ip上的某些描述为PEBS and IBS)。

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

您也可以尝试其他API来访问PMU,如PAPI或likwid(in the same paper)。其中一些可能直接读取PMU寄存器(有时是MSR),并且可能在启用计数时允许同时采样。