perf_event_open总是returns-1

perf_event_open always returns -1

我运行下面的程序调用了perf_event_open系统调用: Linux sama-desktop 3.18.0-20-rpi2 #21-Ubuntu SMP PREEMPT Sun Apr 5 01:56:02 UTC 2015 armv7l armv7l armv7l GNU/Linux

程序:

#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);
}

which returns 打开领导者时出错。我检查了 fd,它看起来总是 returns -1。

我使用了 perf 系统调用手册中的第二个示例,它有同样的问题(错误打开领导者由 fd=-1 触发)。这是手册中 perf 的示例代码:

  #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");

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

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

       close(fd);
   }

我还编写了自己的代码来检查 perf 是否在访问 PMU 寄存器时出现问题。因此,我制作了一个内核模块来启用用户模式访问 PMU 寄存器。

内核模式只执行以下:

    asm volatile("mrc p15, 0, %0, c9, c14, 0" :: "r"(1));
    asm volatile("mcr p15, 0, %0, c9, c14, 0" :: "r"(1));

然后我尝试 运行 perf_event_open

init(void)
{
        static struct perf_event_attr attr;
        attr.type = PERF_TYPE_HARDWARE;
//      attr.config = PERF_COUNT_HW_INSTRUCTIONS;
        attr.config = PERF_COUNT_HW_CPU_CYCLES;
        fddev = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0);
        printf("The fddev value is %d", fddev);
}

其中 returns -1。我还使用了 this repo,这又是 fd returns -1。

我还查看了 kallsyms 以确保 perf_event_open 的系统调用存在。

root@sama-desktop:/home/sama# cat /proc/kallsyms | grep "perf_event_open"
800f3178 T SyS_perf_event_open
800f3178 T sys_perf_event_open

这是 /boot/config-3.18.0-20-rpi2:

的输出
# 
# Kernel Performance Events And Counters
#
CONFIG_PERF_EVENTS=y
# CONFIG_DEBUG_PERF_USE_VMALLOC is not set
CONFIG_VM_EVENT_COUNTERS=y
# CONFIG_COMPAT_BRK is not set
CONFIG_SLAB=y
# CONFIG_SLUB is not set
# CONFIG_SLOB is not set
# CONFIG_SYSTEM_TRUSTED_KEYRING is not set
CONFIG_PROFILING=y
CONFIG_TRACEPOINTS=y
CONFIG_OPROFILE=m
CONFIG_HAVE_OPROFILE=y
CONFIG_KPROBES=y
CONFIG_JUMP_LABEL=y
CONFIG_UPROBES=y
# CONFIG_HAVE_64BIT_ALIGNED_ACCESS is not set
CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
CONFIG_ARCH_USE_BUILTIN_BSWAP=y
CONFIG_KRETPROBES=y
CONFIG_HAVE_KPROBES=y
CONFIG_HAVE_KRETPROBES=y
CONFIG_HAVE_ARCH_TRACEHOOK=y
CONFIG_HAVE_DMA_ATTRS=y
CONFIG_HAVE_DMA_CONTIGUOUS=y
CONFIG_GENERIC_SMP_IDLE_THREAD=y
CONFIG_GENERIC_IDLE_POLL_SETUP=y
CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y
CONFIG_HAVE_CLK=y
CONFIG_HAVE_DMA_API_DEBUG=y
CONFIG_HAVE_HW_BREAKPOINT=y
CONFIG_HAVE_PERF_REGS=y
CONFIG_HAVE_PERF_USER_STACK_DUMP=y
CONFIG_HAVE_ARCH_JUMP_LABEL=y
CONFIG_ARCH_WANT_IPC_PARSE_VERSION=y
CONFIG_HAVE_ARCH_SECCOMP_FILTER=y
CONFIG_SECCOMP_FILTER=y
CONFIG_HAVE_CC_STACKPROTECTOR=y
CONFIG_CC_STACKPROTECTOR=y
# CONFIG_CC_STACKPROTECTOR_NONE is not set
CONFIG_CC_STACKPROTECTOR_REGULAR=y
# CONFIG_CC_STACKPROTECTOR_STRONG is not set
CONFIG_HAVE_CONTEXT_TRACKING=y
CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y
CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y
CONFIG_HAVE_MOD_ARCH_SPECIFIC=y
CONFIG_MODULES_USE_ELF_REL=y
CONFIG_CLONE_BACKWARDS=y
CONFIG_OLD_SIGSUSPEND3=y
CONFIG_OLD_SIGACTION=y

这里是 dmesg 的输出:

root@sama-desktop:/boot# dmesg | grep "perf"
[    0.003891] Initializing cgroup subsys perf_event

这是设备树的输出:

root@sama-desktop:# ls -la /sys/bus/event_source/devices
total 0
drwxr-xr-x 2 root root 0 jul 18 20:15 .
drwxr-xr-x 4 root root 0 jan  1  1970 ..
lrwxrwxrwx 1 root root 0 jan  1  1970 breakpoint -> ../../../devices/breakpoint
lrwxrwxrwx 1 root root 0 jan  1  1970 software -> ../../../devices/software
lrwxrwxrwx 1 root root 0 jan  1  1970 tracepoint -> ../../../devices/tracepoint

真不知道为什么perf_event_openreturns-1。

由于 dmesg 和 sysfs 中缺少任何相关内容,现在应该很明显没有向内核描述 PMU。因此 perf events 对您要求的硬件事件一无所知,因此它无法打开它也就不足为奇了。您需要做的是确保内核 确实 知道 PMU,以便驱动程序选择它 - 所述驱动程序应该已经通过 CONFIG_HW_PERF_EVENTS 内置,这默认情况下使用 CONFIG_PERF_EVENTS 并且在您的配置中看起来不会被禁用,但可能值得仔细检查。

看起来像the PMU is described in the devicetree in their 3.18 kernel, so my best guess is that your board might be booting using the legacy boardfile rather than FDT. I don't know much about Raspberry Pi specifics, but judging by this fairly exhaustive article(我会说直接跳到第 3.1 节),重新配置引导加载程序以使用 FDT 似乎相对简单。

对于像我一样使用 Yocto i.MX6 构建来解决这个问题但与 OP 不同的人 确实 在 dmesg 中有类似的东西:

hw perfevents: enabled with armv7_cortex_a9 PMU driver, 7 counters available

尝试在示例代码中注释掉这两行:

pe.exclude_kernel = 1;
pe.exclude_hv = 1;

对我来说,这使得 perf_event_open 调用成功并且我能够收集数据。

我刚和你遇到了同样的问题,现在我的问题是程序的权限问题,你应该对运行使用更高的权限,这样才能一切正常。 我使用 sudo ./a.out,然后一切都是 fine.Maybe 这将有所帮助。

我在使用 Rasp B+(ARM1176JZF-S 处理器,一个实现 ARM11 ARM 架构 v6 的整数内核)时遇到了这个问题。

除非启用内核 (reference and instructions on this publication),显然 perf_event_open 可以从 ARMv7 开始工作。