当轮询一个文件描述符消耗N 个CPU周期时发出警报
下面的代码在我指示它发送信号时能工作,但那样很麻烦,因为我不仅要对很多线程这么做,还要对整个进程这么做。它本应在不使用任何信号或处理程序的情况下工作,而只是通过轮询由 syscall(__NR_perf_event_open, ...) 返回的fd来实现。有人传闻说如果没有mmap相关的部分,它曾经会失败,但对我来说它仍然不起作用。
我本以为输出中会出现大量“revents:
#include <linux/perf_event.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <poll.h>
#include <sys/ioctl.h>
void * sweat(void * p) {
uint64_t z=1;
for (int b=0;b<10;b++) {
for (int a=0;a<99999999;a++) {
//sched_yield();
z*=a;
}
}
return (void*)z;
}
void * sweat_forever(void * p) { while(1) sweat(0); }
pthread_t background(void * (*f)(void *)) { pthread_t pid; pthread_create(&pid, 0, f, 0); return pid; }
int main()
{
struct perf_event_attr pe;
memset(&pe, 0, sizeof(pe));
pe.type = PERF_TYPE_HARDWARE;
pe.size = sizeof(pe);
pe.config = PERF_COUNT_HW_CPU_CYCLES;
pe.sample_type = PERF_SAMPLE_IP;
pe.wakeup_events = 1;
pe.sample_period = 2000000; // threshold
pe.disabled = 1;
pe.exclude_kernel = 0;
pe.exclude_hv = 1;
pid_t pid = getpid(); //Whole process
//pid_t pid = 0; //This thread
int fd = syscall(__NR_perf_event_open, &pe, pid, -1, -1, 0);
if (fd == -1) {
perror("perf_event_open");
exit(1);
}
size_t page_size = sysconf(_SC_PAGESIZE);
struct perf_event_mmap_page * meta
= mmap(NULL, page_size * 2,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (meta == MAP_FAILED) {
perror("mmap");
exit(1);
}
ioctl(fd, PERF_EVENT_IOC_REFRESH, 100);
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
background(sweat_forever);
printf("Monitor waiting for PMU events...\n");
struct pollfd pfd = {
.fd = fd,
.events = POLLIN | POLLERR
};
while (1) {
int ret = poll(&pfd, 1, -1); //This doesn't return
if (ret < 0) {
perror("poll");
exit(1);
}
printf("revents:%x\n", pfd.revents );
usleep(1000);
}
return 0;
}
解决方案
The problem comes from the pid argument passed to perf_event_open(). You are passing the identifier of the main thread which does not run any instruction when calling poll(). Hence, the latter never returns.
The instruction counter that you want is the one of the secondary thread. So, you need to pass its task identifier: result of gettid(). Here is a simplistic version of the program which uses the task identifier of the thread. The thread needs to be started before calling perf_event_open() in order to get its task identifier stored in the global tid variable and make it usable by the main thread:
#define _GNU_SOURCE
#include <linux/perf_event.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <poll.h>
#include <sys/ioctl.h>
// Variable initialized to 0
pid_t tid;
void * sweat(void * p) {
uint64_t z=1;
for (int b=0;b<10;b++) {
for (int a=0;a<99999999;a++) {
//sched_yield();
z*=a;
}
}
return (void*)z;
}
void * sweat_forever(void * p) { tid = gettid(); while(1) sweat(0); }
pthread_t background(void * (*f)(void *)) { pthread_t pid; pthread_create(&pid, 0, f, 0); return pid; }
int main()
{
struct perf_event_attr pe;
memset(&pe, 0, sizeof(pe));
pe.type = PERF_TYPE_HARDWARE;
pe.size = sizeof(pe);
pe.config = PERF_COUNT_HW_CPU_CYCLES;
pe.sample_type = PERF_SAMPLE_IP;
pe.wakeup_events = 1;
pe.sample_period = 2000000; // threshold
pe.disabled = 1;
pe.exclude_kernel = 0;
pe.exclude_hv = 1;
//pid_t pid = getpid(); //Whole process
//pid_t pid = 0; //This thread
background(sweat_forever);
// Wait some time to make tid be updated by the thread
while(!tid)
usleep(10);
int fd = syscall(__NR_perf_event_open, &pe, tid, -1, -1, 0);
if (fd == -1) {
perror("perf_event_open");
exit(1);
}
size_t page_size = sysconf(_SC_PAGESIZE);
struct perf_event_mmap_page * meta
= mmap(NULL, page_size * 2,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (meta == MAP_FAILED) {
perror("mmap");
exit(1);
}
ioctl(fd, PERF_EVENT_IOC_REFRESH, 100);
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
//background(sweat_forever);
printf("Monitor waiting for PMU events...\n");
struct pollfd pfd = {
.fd = fd,
.events = POLLIN | POLLERR
};
while (1) {
int ret = poll(&pfd, 1, -1); //This doesn't return
if (ret < 0) {
perror("poll");
exit(1);
}
printf("revents:%x\n", pfd.revents );
usleep(1000);
}
return 0;
}