我想计算某些代码的(或多或少)准确数量的指令。此外,我希望在通过特定数量的指令后收到信号。
为此,我使用了提供的溢出信号行为 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);
}
所以基本上我做了以下事情:
perf_event_open
创建一个新的性能计数器(返回文件描述符)fcntl
将信号发送行为添加到文件描述符。执行有效负载循环时,某些时候将执行1000条指令(sample_interval
)。根据{{3}},这会触发溢出,然后递减内部计数器。
一旦此计数器达到零,&#34;发送类型为POLL_HUP的信号,并且基础事件被禁用。&#34;
当发送信号时,停止当前进程/线程的控制流程,并执行信号处理程序。情形:
这种情况意味着两件事:
ucontext
访问)将直接指向指向导致溢出的指令。 基本上你可以说,信号行为可以被视为同步。
这是我想要实现的完美语义。
然而,就我而言,我配置的信号通常是异步的,并且可能会经过一段时间,直到它最终被传递并执行信号处理程序。这可能对我造成问题。
例如,请考虑以下情形:
这种情况意味着两件事:
到目前为止,我已经在上面的示例中进行了大量测试,并且没有经历错过了支持第一种情况的说明。
但是,我真的很想知道,我是否可以依赖这个假设。 内核会发生什么?
答案 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_openioctl
个
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 counters到cycles
;或者像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),并且可能在启用计数时允许同时采样。