108. perf_event_open - 配置和打开性能事件 見出しへのリンク
1. 函数介绍 見出しへのリンク
perf_event_open
是一个 Linux 系统调用,用于配置和打开性能监控事件。它是 Linux Performance Events Subsystem (perf) 的核心接口,允许应用程序监控各种硬件和软件性能指标,如 CPU 周期、缓存未命中、分支预测、系统调用等。
2. 函数原型 見出しへのリンク
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>
#include <sys/syscall.h>
#include <unistd.h>
int perf_event_open(struct perf_event_attr *attr,
pid_t pid, int cpu, int group_fd, unsigned long flags);
注意:这不是标准 C 库函数,需要通过
syscall()
调用。
3. 功能 見出しへのリンク
创建和配置性能监控事件,返回一个文件描述符用于读取性能数据。支持监控硬件性能计数器、软件事件、跟踪点等多种类型的性能事件。
4. 参数 見出しへのリンク
struct perf_event_attr *attr
: 指向性能事件属性结构体的指针pid_t pid
: 目标进程 ID0
: 当前进程-1
: 所有进程- 正整数: 指定进程 ID
int cpu
: 目标 CPU 核心-1
: 所有 CPU 核心- 非负整数: 指定 CPU 核心编号
int group_fd
: 事件组文件描述符-1
: 不加入事件组- 其他: 加入指定的事件组
unsigned long flags
: 控制标志0
: 基本行为PERF_FLAG_FD_CLOEXEC
: 设置 close-on-exec 标志PERF_FLAG_FD_NO_GROUP
: 不加入组PERF_FLAG_ONE_SHOT
: 一次性事件PERF_FLAG_PID_CGROUP
: 使用 cgroup PID
5. perf_event_attr 结构体定义 見出しへのリンク
struct perf_event_attr {
__u32 type; /* 事件类型 */
__u64 size; /* 结构体大小 */
__u64 config; /* 事件配置 */
union {
__u64 sample_period; /* 采样周期 */
__u64 sample_freq; /* 采样频率 */
};
__u64 sample_type; /* 采样类型 */
__u64 read_format; /* 读取格式 */
__u64 disabled : 1, /* 初始禁用 */
inherit : 1, /* 子进程继承 */
pinned : 1, /* 固定在计数器上 */
exclusive : 1, /* 独占模式 */
exclude_user : 1, /* 排除用户态 */
exclude_kernel : 1, /* 排除内核态 */
exclude_hv : 1, /* 排除虚拟机监视器 */
exclude_idle : 1, /* 排除空闲态 */
// ... 更多标志位
__reserved_1 : 55;
__u32 bp_type; /* 断点类型 */
union {
__u64 bp_addr; /* 断点地址 */
__u64 config1; /* 扩展配置 */
};
union {
__u64 bp_len; /* 断点长度 */
__u64 config2; /* 扩展配置 */
};
__u64 branch_sample_type; /* 分支采样类型 */
__u64 sample_regs_user; /* 用户态寄存器采样 */
__u64 sample_stack_user; /* 用户态栈采样 */
__s32 clockid; /* 时钟 ID */
__u32 aux_watermark; /* 辅助水印 */
__u32 sample_max_stack; /* 最大栈深度 */
__u32 __reserved_2; /* 保留字段 */
};
6. 事件类型 見出しへのリンク
// 主要事件类型:
#define PERF_TYPE_HARDWARE 0 /* 硬件事件 */
#define PERF_TYPE_SOFTWARE 1 /* 软件事件 */
#define PERF_TYPE_TRACEPOINT 2 /* 跟踪点事件 */
#define PERF_TYPE_HW_CACHE 3 /* 硬件缓存事件 */
#define PERF_TYPE_RAW 4 /* 原始硬件事件 */
#define PERF_TYPE_BREAKPOINT 5 /* 断点事件 */
// 硬件事件:
#define PERF_COUNT_HW_CPU_CYCLES 0 /* CPU 周期 */
#define PERF_COUNT_HW_INSTRUCTIONS 1 /* 指令数 */
#define PERF_COUNT_HW_CACHE_REFERENCES 2 /* 缓存引用 */
#define PERF_COUNT_HW_CACHE_MISSES 3 /* 缓存未命中 */
#define PERF_COUNT_HW_BRANCH_INSTRUCTIONS 4 /* 分支指令 */
#define PERF_COUNT_HW_BRANCH_MISSES 5 /* 分支未命中 */
#define PERF_COUNT_HW_BUS_CYCLES 6 /* 总线周期 */
#define PERF_COUNT_HW_STALLED_CYCLES_FRONTEND 7 /* 前端停顿周期 */
#define PERF_COUNT_HW_STALLED_CYCLES_BACKEND 8 /* 后端停顿周期 */
#define PERF_COUNT_HW_REF_CPU_CYCLES 9 /* 参考 CPU 周期 */
// 软件事件:
#define PERF_COUNT_SW_CPU_CLOCK 0 /* CPU 时钟 */
#define PERF_COUNT_SW_TASK_CLOCK 1 /* 任务时钟 */
#define PERF_COUNT_SW_PAGE_FAULTS 2 /* 页面错误 */
#define PERF_COUNT_SW_CONTEXT_SWITCHES 3 /* 上下文切换 */
#define PERF_COUNT_SW_CPU_MIGRATIONS 4 /* CPU 迁移 */
#define PERF_COUNT_SW_PAGE_FAULTS_MIN 5 /* 次要页面错误 */
#define PERF_COUNT_SW_PAGE_FAULTS_MAJ 6 /* 主要页面错误 */
#define PERF_COUNT_SW_ALIGNMENT_FAULTS 7 /* 对齐错误 */
#define PERF_COUNT_SW_EMULATION_FAULTS 8 /* 仿真错误 */
#define PERF_COUNT_SW_DUMMY 9 /* 虚拟事件 */
7. 返回值 見出しへのリンク
- 成功时返回新的文件描述符(非负整数)
- 失败时返回 -1,并设置
errno
8. 常见 errno 错误码 見出しへのリンク
EACCES
: 权限不足EBADF
: 无效的group_fd
EINVAL
: 参数无效EMFILE
: 进程已打开的文件描述符达到上限ENFILE
: 系统已打开的文件描述符达到上限ENOMEM
: 内存不足ENODEV
: 指定的事件类型或配置不支持EPERM
: 操作被拒绝(需要特权权限)ESRCH
: 指定的进程不存在E2BIG
: 事件过多或配置过大EOPNOTSUPP
: 操作不支持
9. 相似函数,或关联函数 見出しへのリンク
read()
: 读取性能事件数据ioctl()
: 控制性能事件mmap()
: 内存映射性能数据缓冲区perf_event_open()
相关 ioctl 命令:PERF_EVENT_IOC_RESET
: 重置计数器PERF_EVENT_IOC_ENABLE
: 启用事件PERF_EVENT_IOC_DISABLE
: 禁用事件PERF_EVENT_IOC_REFRESH
: 刷新事件PERF_EVENT_IOC_PERIOD
: 设置采样周期
/usr/bin/perf
: 用户态性能分析工具/proc/sys/kernel/perf_event_paranoid
: 性能事件安全级别/proc/sys/kernel/perf_event_max_sample_rate
: 最大采样率
10. 示例代码 見出しへのリンク
示例1:基本使用 - 硬件性能计数器监控 見出しへのリンク
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#ifndef SYS_perf_event_open
# define SYS_perf_event_open 298 // x86_64 架构下的系统调用号
#endif
// perf_event_open 系统调用包装函数
int perf_event_open_wrapper(struct perf_event_attr *attr,
pid_t pid, int cpu, int group_fd, unsigned long flags) {
return syscall(SYS_perf_event_open, attr, pid, cpu, group_fd, flags);
}
void print_perf_event_info(int fd, const char *event_name) {
long long count;
if (read(fd, &count, sizeof(count)) == sizeof(count)) {
printf("%-30s: %lld\n", event_name, count);
} else {
printf("%-30s: 读取失败\n", event_name);
}
}
int main() {
printf("=== perf_event_open 基本使用演示 ===\n");
// 检查权限
int paranoid_level = 0;
FILE *fp = fopen("/proc/sys/kernel/perf_event_paranoid", "r");
if (fp) {
fscanf(fp, "%d", ¶noid_level);
fclose(fp);
printf("性能事件安全级别: %d\n", paranoid_level);
if (paranoid_level > 0 && geteuid() != 0) {
printf("警告: 需要 root 权限或降低安全级别才能监控其他进程\n");
}
}
// 配置硬件性能事件属性
struct perf_event_attr pe;
memset(&pe, 0, sizeof(pe));
pe.size = sizeof(pe);
pe.type = PERF_TYPE_HARDWARE;
pe.config = PERF_COUNT_HW_INSTRUCTIONS;
pe.disabled = 1; // 初始禁用
pe.exclude_kernel = 1; // 排除内核态
pe.exclude_hv = 1; // 排除虚拟机监视器
// 打开性能事件(监控当前进程,所有 CPU)
int fd_instructions = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (fd_instructions == -1) {
if (errno == EACCES) {
printf("错误: 权限不足,需要 root 权限或调整 /proc/sys/kernel/perf_event_paranoid\n");
printf("解决方法:\n");
printf(" 1. 使用 sudo 运行程序\n");
printf(" 2. 执行: echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid\n");
exit(EXIT_FAILURE);
} else {
printf("打开指令计数器失败: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
}
printf("✓ 成功打开指令计数器 (fd: %d)\n", fd_instructions);
// 配置 CPU 周期事件
pe.config = PERF_COUNT_HW_CPU_CYCLES;
int fd_cycles = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (fd_cycles == -1) {
printf("打开 CPU 周期计数器失败: %s\n", strerror(errno));
close(fd_instructions);
exit(EXIT_FAILURE);
}
printf("✓ 成功打开 CPU 周期计数器 (fd: %d)\n", fd_cycles);
// 配置缓存未命中事件
pe.config = PERF_COUNT_HW_CACHE_MISSES;
int fd_cache_misses = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (fd_cache_misses == -1) {
if (errno != ENODEV) {
printf("打开缓存未命中计数器失败: %s\n", strerror(errno));
} else {
printf("警告: 系统不支持缓存未命中计数器\n");
}
} else {
printf("✓ 成功打开缓存未命中计数器 (fd: %d)\n", fd_cache_misses);
}
// 启用所有事件
if (ioctl(fd_instructions, PERF_EVENT_IOC_RESET, 0) == -1 ||
ioctl(fd_cycles, PERF_EVENT_IOC_RESET, 0) == -1 ||
(fd_cache_misses != -1 && ioctl(fd_cache_misses, PERF_EVENT_IOC_RESET, 0) == -1)) {
perror("重置计数器失败");
}
if (ioctl(fd_instructions, PERF_EVENT_IOC_ENABLE, 0) == -1 ||
ioctl(fd_cycles, PERF_EVENT_IOC_ENABLE, 0) == -1 ||
(fd_cache_misses != -1 && ioctl(fd_cache_misses, PERF_EVENT_IOC_ENABLE, 0) == -1)) {
perror("启用计数器失败");
}
printf("✓ 成功启用所有性能计数器\n");
// 执行一些工作负载
printf("\n执行工作负载...\n");
volatile long sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i * i;
if (i % 100000 == 0) {
printf(" 进度: %d%%\n", i / 10000);
}
}
printf("工作负载完成\n");
// 禁用所有事件
if (ioctl(fd_instructions, PERF_EVENT_IOC_DISABLE, 0) == -1 ||
ioctl(fd_cycles, PERF_EVENT_IOC_DISABLE, 0) == -1 ||
(fd_cache_misses != -1 && ioctl(fd_cache_misses, PERF_EVENT_IOC_DISABLE, 0) == -1)) {
perror("禁用计数器失败");
}
printf("\n=== 性能统计结果 ===\n");
// 读取并显示结果
print_perf_event_info(fd_instructions, "指令数");
print_perf_event_info(fd_cycles, "CPU 周期");
if (fd_cache_misses != -1) {
print_perf_event_info(fd_cache_misses, "缓存未命中");
}
// 计算 CPI (Cycles Per Instruction)
long long instructions, cycles;
if (read(fd_instructions, &instructions, sizeof(instructions)) == sizeof(instructions) &&
read(fd_cycles, &cycles, sizeof(cycles)) == sizeof(cycles)) {
if (instructions > 0) {
double cpi = (double)cycles / instructions;
printf("%-30s: %.2f\n", "CPI (每指令周期数)", cpi);
}
}
// 清理资源
close(fd_instructions);
close(fd_cycles);
if (fd_cache_misses != -1) {
close(fd_cache_misses);
}
printf("✓ 性能监控完成\n");
return 0;
}
示例2:软件事件和系统调用监控 見出しへのリンク
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#ifndef SYS_perf_event_open
# define SYS_perf_event_open 298
#endif
int perf_event_open_wrapper(struct perf_event_attr *attr,
pid_t pid, int cpu, int group_fd, unsigned long flags) {
return syscall(SYS_perf_event_open, attr, pid, cpu, group_fd, flags);
}
void demonstrate_software_events() {
printf("=== 软件事件监控演示 ===\n");
struct perf_event_attr pe;
memset(&pe, 0, sizeof(pe));
pe.size = sizeof(pe);
pe.type = PERF_TYPE_SOFTWARE;
pe.disabled = 1;
pe.exclude_kernel = 0; // 包含内核态
pe.exclude_hv = 1;
// 监控页面错误
pe.config = PERF_COUNT_SW_PAGE_FAULTS;
int fd_page_faults = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (fd_page_faults == -1) {
printf("打开页面错误计数器失败: %s\n", strerror(errno));
return;
}
printf("✓ 成功打开页面错误计数器 (fd: %d)\n", fd_page_faults);
// 监控上下文切换
pe.config = PERF_COUNT_SW_CONTEXT_SWITCHES;
int fd_context_switches = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (fd_context_switches == -1) {
printf("打开上下文切换计数器失败: %s\n", strerror(errno));
} else {
printf("✓ 成功打开上下文切换计数器 (fd: %d)\n", fd_context_switches);
}
// 监控 CPU 迁移
pe.config = PERF_COUNT_SW_CPU_MIGRATIONS;
int fd_cpu_migrations = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (fd_cpu_migrations == -1) {
printf("打开 CPU 迁移计数器失败: %s\n", strerror(errno));
} else {
printf("✓ 成功打开 CPU 迁移计数器 (fd: %d)\n", fd_cpu_migrations);
}
// 启用所有事件
ioctl(fd_page_faults, PERF_EVENT_IOC_RESET, 0);
if (fd_context_switches != -1) ioctl(fd_context_switches, PERF_EVENT_IOC_RESET, 0);
if (fd_cpu_migrations != -1) ioctl(fd_cpu_migrations, PERF_EVENT_IOC_RESET, 0);
ioctl(fd_page_faults, PERF_EVENT_IOC_ENABLE, 0);
if (fd_context_switches != -1) ioctl(fd_context_switches, PERF_EVENT_IOC_ENABLE, 0);
if (fd_cpu_migrations != -1) ioctl(fd_cpu_migrations, PERF_EVENT_IOC_ENABLE, 0);
printf("✓ 成功启用软件事件监控\n");
// 执行一些会产生系统调用和页面错误的操作
printf("\n执行会产生系统调用和页面错误的操作...\n");
// 分配大量内存以产生页面错误
size_t alloc_size = 10 * 1024 * 1024; // 10MB
char *memory = malloc(alloc_size);
if (memory) {
printf(" 分配 %zu MB 内存\n", alloc_size / (1024 * 1024));
// 访问内存以触发页面错误
for (size_t i = 0; i < alloc_size; i += 4096) {
memory[i] = (char)(i % 256);
}
printf(" 访问内存以触发页面错误\n");
free(memory);
}
// 执行系统调用
for (int i = 0; i < 1000; i++) {
getuid(); // 轻量级系统调用
if (i % 200 == 0) {
printf(" 执行系统调用: %d\n", i);
}
}
// 触发上下文切换
printf(" 触发上下文切换...\n");
sleep(1);
// 禁用所有事件
ioctl(fd_page_faults, PERF_EVENT_IOC_DISABLE, 0);
if (fd_context_switches != -1) ioctl(fd_context_switches, PERF_EVENT_IOC_DISABLE, 0);
if (fd_cpu_migrations != -1) ioctl(fd_cpu_migrations, PERF_EVENT_IOC_DISABLE, 0);
printf("\n=== 软件事件统计结果 ===\n");
// 读取并显示结果
long long count;
if (read(fd_page_faults, &count, sizeof(count)) == sizeof(count)) {
printf("页面错误次数: %lld\n", count);
}
if (fd_context_switches != -1) {
if (read(fd_context_switches, &count, sizeof(count)) == sizeof(count)) {
printf("上下文切换次数: %lld\n", count);
}
}
if (fd_cpu_migrations != -1) {
if (read(fd_cpu_migrations, &count, sizeof(count)) == sizeof(count)) {
printf("CPU 迁移次数: %lld\n", count);
}
}
// 清理资源
close(fd_page_faultes);
if (fd_context_switches != -1) close(fd_context_switches);
if (fd_cpu_migrations != -1) close(fd_cpu_migrations);
printf("✓ 软件事件监控完成\n");
}
void demonstrate_sampling() {
printf("\n=== 采样模式演示 ===\n");
struct perf_event_attr pe;
memset(&pe, 0, sizeof(pe));
pe.size = sizeof(pe);
pe.type = PERF_TYPE_HARDWARE;
pe.config = PERF_COUNT_HW_INSTRUCTIONS;
pe.sample_period = 100000; // 每 100,000 条指令采样一次
pe.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME;
pe.disabled = 1;
pe.exclude_kernel = 1;
pe.exclude_hv = 1;
int fd = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (fd == -1) {
printf("打开采样事件失败: %s\n", strerror(errno));
return;
}
printf("✓ 成功打开采样事件 (fd: %d)\n", fd);
printf(" 采样周期: 每 %llu 条指令\n", (unsigned long long)pe.sample_period);
// 启用采样
ioctl(fd, PERF_EVENT_IOC_RESET, 0);
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
printf("执行工作负载以生成采样数据...\n");
volatile long sum = 0;
for (int i = 0; i < 5000000; i++) {
sum += i;
}
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
// 读取计数器值
long long count;
if (read(fd, &count, sizeof(count)) == sizeof(count)) {
printf("总指令数: %lld\n", count);
printf("预计采样次数: %lld\n", count / pe.sample_period);
}
close(fd);
}
int main() {
printf("=== perf_event_open 软件事件和采样演示 ===\n");
// 检查权限
if (geteuid() != 0) {
printf("警告: 某些功能可能需要 root 权限\n");
}
demonstrate_software_events();
demonstrate_sampling();
return 0;
}
示例3:事件组和高级监控 見出しへのリンク
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#ifndef SYS_perf_event_open
# define SYS_perf_event_open 298
#endif
int perf_event_open_wrapper(struct perf_event_attr *attr,
pid_t pid, int cpu, int group_fd, unsigned long flags) {
return syscall(SYS_perf_event_open, attr, pid, cpu, group_fd, flags);
}
void demonstrate_event_groups() {
printf("=== 事件组监控演示 ===\n");
struct perf_event_attr pe;
memset(&pe, 0, sizeof(pe));
pe.size = sizeof(pe);
pe.type = PERF_TYPE_HARDWARE;
pe.disabled = 1;
pe.exclude_kernel = 1;
pe.exclude_hv = 1;
// 创建事件组组长(指令数)
pe.config = PERF_COUNT_HW_INSTRUCTIONS;
int group_leader_fd = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (group_leader_fd == -1) {
printf("创建事件组组长失败: %s\n", strerror(errno));
return;
}
printf("✓ 创建事件组组长 (指令数, fd: %d)\n", group_leader_fd);
// 添加 CPU 周期事件到组
pe.config = PERF_COUNT_HW_CPU_CYCLES;
int cycles_fd = perf_event_open_wrapper(&pe, 0, -1, group_leader_fd, 0);
if (cycles_fd == -1) {
printf("添加 CPU 周期事件到组失败: %s\n", strerror(errno));
} else {
printf("✓ 添加 CPU 周期事件到组 (fd: %d)\n", cycles_fd);
}
// 添加缓存引用事件到组
pe.config = PERF_COUNT_HW_CACHE_REFERENCES;
int cache_refs_fd = perf_event_open_wrapper(&pe, 0, -1, group_leader_fd, 0);
if (cache_refs_fd == -1) {
if (errno != ENODEV) {
printf("添加缓存引用事件到组失败: %s\n", strerror(errno));
}
} else {
printf("✓ 添加缓存引用事件到组 (fd: %d)\n", cache_refs_fd);
}
// 启用整个事件组
if (ioctl(group_leader_fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1 ||
ioctl(group_leader_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
perror("启用事件组失败");
close(group_leader_fd);
if (cycles_fd != -1) close(cycles_fd);
if (cache_refs_fd != -1) close(cache_refs_fd);
return;
}
printf("✓ 成功启用事件组\n");
// 执行工作负载
printf("\n执行工作负载...\n");
volatile long sum = 0;
for (int i = 0; i < 2000000; i++) {
sum += i * i;
if (i % 500000 == 0) {
printf(" 进度: %d%%\n", i / 20000);
}
}
printf("工作负载完成\n");
// 禁用整个事件组
if (ioctl(group_leader_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) {
perror("禁用事件组失败");
}
printf("\n=== 事件组统计结果 ===\n");
// 读取组数据格式
pe.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | PERF_FORMAT_GROUP;
// 重新打开事件以获取组数据
close(group_leader_fd);
if (cycles_fd != -1) close(cycles_fd);
if (cache_refs_fd != -1) close(cache_refs_fd);
// 创建新的组事件用于读取
pe.config = PERF_COUNT_HW_INSTRUCTIONS;
group_leader_fd = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (group_leader_fd != -1) {
pe.config = PERF_COUNT_HW_CPU_CYCLES;
cycles_fd = perf_event_open_wrapper(&pe, 0, -1, group_leader_fd, 0);
pe.config = PERF_COUNT_HW_CACHE_REFERENCES;
cache_refs_fd = perf_event_open_wrapper(&pe, 0, -1, group_leader_fd, 0);
// 启用并执行简短测试
ioctl(group_leader_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP);
volatile int dummy = 0;
for (int i = 0; i < 100000; i++) {
dummy += i;
}
ioctl(group_leader_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
// 读取组数据
struct {
uint64_t nr;
uint64_t time_enabled;
uint64_t time_running;
struct {
uint64_t value;
uint64_t id;
} values[3];
} group_data;
ssize_t bytes_read = read(group_leader_fd, &group_data, sizeof(group_data));
if (bytes_read == sizeof(group_data)) {
printf("组数据读取成功 (%zd 字节)\n", bytes_read);
printf(" 事件数量: %lu\n", (unsigned long)group_data.nr);
printf(" 启用时间: %lu ns\n", (unsigned long)group_data.time_enabled);
printf(" 运行时间: %lu ns\n", (unsigned long)group_data.time_running);
for (uint64_t i = 0; i < group_data.nr; i++) {
const char *event_names[] = {"指令数", "CPU周期", "缓存引用"};
if (i < 3) {
printf(" %s: %lu\n", event_names[i], (unsigned long)group_data.values[i].value);
}
}
}
}
// 清理资源
if (group_leader_fd != -1) close(group_leader_fd);
if (cycles_fd != -1) close(cycles_fd);
if (cache_refs_fd != -1) close(cache_refs_fd);
printf("✓ 事件组监控完成\n");
}
void demonstrate_multiplexing() {
printf("\n=== 事件复用演示 ===\n");
printf("现代 CPU 的硬件性能计数器数量有限\n");
printf("当监控的事件数量超过可用计数器时,内核会自动进行事件复用\n");
printf("这会导致计数器值需要通过比例估算\n");
// 显示系统信息
printf("\n系统硬件计数器信息:\n");
system("cat /sys/bus/event_source/devices/*/format/* 2>/dev/null | head -10 || echo '无法读取硬件计数器信息'");
// 显示可用事件
printf("\n可用性能事件:\n");
system("perf list 2>/dev/null | head -15 || echo 'perf 工具不可用'");
}
int main() {
printf("=== perf_event_open 事件组和高级监控 ===\n");
demonstrate_event_groups();
demonstrate_multiplexing();
return 0;
}
示例4:性能分析和监控工具 見出しへのリンク
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>
#ifndef SYS_perf_event_open
# define SYS_perf_event_open 298
#endif
volatile sig_atomic_t monitoring = 1;
void signal_handler(int sig) {
printf("\n收到信号 %d,停止监控\n", sig);
monitoring = 0;
}
int perf_event_open_wrapper(struct perf_event_attr *attr,
pid_t pid, int cpu, int group_fd, unsigned long flags) {
return syscall(SYS_perf_event_open, attr, pid, cpu, group_fd, flags);
}
typedef struct {
int fd;
const char *name;
long long initial_value;
} perf_counter_t;
void reset_counters(perf_counter_t *counters, int count) {
for (int i = 0; i < count; i++) {
if (counters[i].fd != -1) {
ioctl(counters[i].fd, PERF_EVENT_IOC_RESET, 0);
}
}
}
void enable_counters(perf_counter_t *counters, int count) {
for (int i = 0; i < count; i++) {
if (counters[i].fd != -1) {
ioctl(counters[i].fd, PERF_EVENT_IOC_ENABLE, 0);
}
}
}
void disable_counters(perf_counter_t *counters, int count) {
for (int i = 0; i < count; i++) {
if (counters[i].fd != -1) {
ioctl(counters[i].fd, PERF_EVENT_IOC_DISABLE, 0);
}
}
}
void read_counters(perf_counter_t *counters, int count) {
for (int i = 0; i < count; i++) {
if (counters[i].fd != -1) {
long long value;
if (read(counters[i].fd, &value, sizeof(value)) == sizeof(value)) {
counters[i].initial_value = value;
}
}
}
}
void print_counter_stats(perf_counter_t *counters, int count, double elapsed_seconds) {
printf("\n=== 性能统计 (持续时间: %.2f 秒) ===\n", elapsed_seconds);
printf("%-25s %-15s %-15s\n", "事件名称", "计数值", "每秒速率");
printf("%-25s %-15s %-15s\n", "--------", "------", "------");
for (int i = 0; i < count; i++) {
if (counters[i].fd != -1) {
long long value = counters[i].initial_value;
double rate = elapsed_seconds > 0 ? value / elapsed_seconds : 0;
printf("%-25s %-15lld %-15.0f\n",
counters[i].name, value, rate);
}
}
}
void interactive_performance_monitor() {
printf("=== 交互式性能监控工具 ===\n");
// 设置信号处理
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// 创建性能计数器
perf_counter_t counters[] = {
{-1, "指令数", 0},
{-1, "CPU 周期", 0},
{-1, "缓存引用", 0},
{-1, "缓存未命中", 0},
{-1, "分支指令", 0},
{-1, "分支未命中", 0}
};
int counter_count = sizeof(counters) / sizeof(counters[0]);
// 配置和打开性能事件
struct perf_event_attr pe;
memset(&pe, 0, sizeof(pe));
pe.size = sizeof(pe);
pe.type = PERF_TYPE_HARDWARE;
pe.disabled = 1;
pe.exclude_kernel = 0; // 包含内核态
pe.exclude_hv = 1;
printf("创建性能计数器:\n");
// 指令数
pe.config = PERF_COUNT_HW_INSTRUCTIONS;
counters[0].fd = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (counters[0].fd != -1) {
printf(" ✓ %s\n", counters[0].name);
}
// CPU 周期
pe.config = PERF_COUNT_HW_CPU_CYCLES;
counters[1].fd = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (counters[1].fd != -1) {
printf(" ✓ %s\n", counters[1].name);
}
// 缓存引用
pe.config = PERF_COUNT_HW_CACHE_REFERENCES;
counters[2].fd = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (counters[2].fd != -1) {
printf(" ✓ %s\n", counters[2].name);
}
// 缓存未命中
pe.config = PERF_COUNT_HW_CACHE_MISSES;
counters[3].fd = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (counters[3].fd != -1) {
printf(" ✓ %s\n", counters[3].name);
}
// 分支指令
pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
counters[4].fd = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (counters[4].fd != -1) {
printf(" ✓ %s\n", counters[4].name);
}
// 分支未命中
pe.config = PERF_COUNT_HW_BRANCH_MISSES;
counters[5].fd = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
if (counters[5].fd != -1) {
printf(" ✓ %s\n", counters[5].name);
}
// 启用所有计数器
reset_counters(counters, counter_count);
enable_counters(counters, counter_count);
printf("\n开始性能监控 (按 Ctrl+C 停止)...\n");
printf("%-15s %-15s %-15s %-15s\n", "时间", "指令数", "CPU周期", "缓存未命中");
printf("%-15s %-15s %-15s %-15s\n", "----", "----", "----", "----");
time_t start_time = time(NULL);
int sample_count = 0;
while (monitoring) {
sleep(1);
sample_count++;
// 读取当前值并显示
if (sample_count % 5 == 0) { // 每5秒显示一次详细信息
long long values[6] = {0};
int valid_counters = 0;
for (int i = 0; i < counter_count; i++) {
if (counters[i].fd != -1) {
long long value;
if (read(counters[i].fd, &value, sizeof(value)) == sizeof(value)) {
values[i] = value;
valid_counters++;
}
}
}
if (valid_counters > 0) {
time_t current_time = time(NULL);
printf("%-15ld %-15lld %-15lld %-15lld\n",
(long)(current_time - start_time),
values[0], values[1], values[3]);
}
}
}
// 禁用计数器
disable_counters(counters, counter_count);
// 读取最终值
read_counters(counters, counter_count);
// 显示最终统计
time_t end_time = time(NULL);
double elapsed_time = difftime(end_time, start_time);
print_counter_stats(counters, counter_count, elapsed_time);
// 计算比率
if (counters[0].initial_value > 0 && counters[1].initial_value > 0) {
double cpi = (double)counters[1].initial_value / counters[0].initial_value;
printf("CPI (每指令周期数): %.2f\n", cpi);
}
if (counters[2].initial_value > 0 && counters[3].initial_value > 0) {
double miss_rate = (double)counters[3].initial_value / counters[2].initial_value * 100;
printf("缓存未命中率: %.2f%%\n", miss_rate);
}
// 清理资源
for (int i = 0; i < counter_count; i++) {
if (counters[i].fd != -1) {
close(counters[i].fd);
}
}
printf("✓ 性能监控完成\n");
}
void demonstrate_system_wide_monitoring() {
printf("\n=== 系统范围监控演示 ===\n");
printf("系统范围性能监控需要特殊权限\n");
if (geteuid() != 0) {
printf("警告: 需要 root 权限进行系统范围监控\n");
return;
}
// 检查安全级别
FILE *fp = fopen("/proc/sys/kernel/perf_event_paranoid", "r");
if (fp) {
int paranoid_level;
if (fscanf(fp, "%d", ¶noid_level) == 1) {
printf("当前安全级别: %d\n", paranoid_level);
if (paranoid_level > 0) {
printf("建议将安全级别设置为 -1 以允许系统范围监控\n");
printf("执行: echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid\n");
}
}
fclose(fp);
}
printf("系统范围监控特性:\n");
printf("• 监控所有进程的性能\n");
printf("• 监控特定 CPU 核心\n");
printf("• 需要 root 权限\n");
printf("• 可能影响系统性能\n");
printf("• 需要适当的安全配置\n");
}
int main() {
printf("=== perf_event_open 性能分析和监控工具 ===\n");
// 检查权限
printf("进程信息:\n");
printf(" PID: %d\n", getpid());
printf(" UID: %d\n", getuid());
printf(" EUID: %d\n", geteuid());
// 检查安全级别
FILE *fp = fopen("/proc/sys/kernel/perf_event_paranoid", "r");
if (fp) {
int paranoid_level;
if (fscanf(fp, "%d", ¶noid_level) == 1) {
printf(" 性能事件安全级别: %d\n", paranoid_level);
}
fclose(fp);
}
// 演示系统范围监控
demonstrate_system_wide_monitoring();
// 启动交互式监控器
char choice;
printf("\n是否启动交互式性能监控器? (y/N): ");
if (scanf(" %c", &choice) == 1 && (choice == 'y' || choice == 'Y')) {
interactive_performance_monitor();
}
return 0;
}
11. 高级特性和配置 見出しへのリンク
// perf_event_attr 的高级配置示例:
// 采样配置
struct perf_event_attr sampling_pe = {
.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_INSTRUCTIONS,
.sample_period = 100000, // 每 100,000 条指令采样
.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME,
.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING,
.disabled = 1,
.inherit = 1, // 子进程继承
.pinned = 1, // 固定在计数器上
.exclusive = 1, // 独占模式
.exclude_user = 0, // 包含用户态
.exclude_kernel = 1, // 排除内核态
.exclude_hv = 1, // 排除虚拟机监视器
.exclude_idle = 1, // 排除空闲态
};
// 精确定位采样
struct perf_event_attr precise_pe = {
.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_INSTRUCTIONS,
.precise_ip = 2, // 需要精确采样
.sample_period = 1000,
.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ADDR | PERF_SAMPLE_WEIGHT,
};
// 软件事件配置
struct perf_event_attr software_pe = {
.type = PERF_TYPE_SOFTWARE,
.config = PERF_COUNT_SW_PAGE_FAULTS_MIN,
.sample_period = 100,
.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID,
};
12. 实际应用场景 見出しへのリンク
场景1:应用程序性能分析 見出しへのリンク
void application_profiling() {
// 监控应用程序的关键性能指标
struct perf_event_attr pe = {
.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_INSTRUCTIONS,
.disabled = 1,
.exclude_kernel = 1,
.exclude_hv = 1,
};
int fd = perf_event_open_wrapper(&pe, 0, -1, -1, 0);
// 分析应用程序的指令执行效率
}
场景2:数据库系统优化 見出しへのリンク
void database_performance_monitoring() {
// 监控数据库查询的缓存效率和分支预测
struct perf_event_attr cache_pe = {
.type = PERF_TYPE_HW_CACHE,
.config = (PERF_COUNT_HW_CACHE_L1D |
(PERF_COUNT_HW_CACHE_OP_READ << 8) |
(PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
.sample_period = 1000,
};
// 优化查询计划和缓存策略
}
场景3:实时系统性能监控 見出しへのリンク
void real_time_system_monitoring() {
// 监控实时系统的上下文切换和中断延迟
struct perf_event_attr rt_pe = {
.type = PERF_TYPE_SOFTWARE,
.config = PERF_COUNT_SW_CONTEXT_SWITCHES,
.sample_period = 1,
};
// 确保系统满足实时性要求
}
13. 注意事项 見出しへのリンク
使用 perf_event_open
时需要注意:
- 权限要求: 某些操作需要 root 权限或适当的安全级别配置
- 系统支持: 不是所有事件类型在所有系统上都支持
- 性能影响: 频繁的性能监控可能影响系统性能
- 硬件限制: 硬件性能计数器数量有限,可能导致事件复用
- 安全考虑: 性能数据可能包含敏感信息
- 资源管理: 及时关闭文件描述符避免资源泄漏
14. 系统配置检查 見出しへのリンク
# 检查性能事件支持
grep -i perf /boot/config-$(uname -r)
# 查看安全级别
cat /proc/sys/kernel/perf_event_paranoid
# 查看硬件计数器信息
cat /sys/bus/event_source/devices/*/format/*
# 检查可用事件
perf list 2>/dev/null | head -20
# 查看系统调用支持
ausyscall perf_event_open
# 检查内核版本
uname -r
15. 编译和运行 見出しへのリンク
# 编译支持 perf_event_open 的程序
gcc -D_GNU_SOURCE perf_monitor.c -o perf_monitor
# 运行需要权限的监控程序
sudo ./perf_monitor
# 调整安全级别(临时)
echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
# 永久调整安全级别
echo 'kernel.perf_event_paranoid = -1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
总结 見出しへのリンク
perf_event_open
是 Linux 系统中强大的性能监控工具:
关键特性:
- 硬件支持: 直接访问 CPU 硬件性能计数器
- 软件事件: 监控系统调用、页面错误等软件事件
- 灵活配置: 支持采样、分组、过滤等多种配置
- 安全控制: 通过安全级别和权限控制访问
- 标准化: 提供统一的性能监控接口
主要应用:
- 应用程序性能分析和优化
- 系统性能监控和诊断
- 数据库和 Web 服务器优化
- 实时系统性能验证
- 编译器和运行时系统优化
使用要点:
- 理解不同事件类型的特点和用途
- 正确配置安全级别和权限
- 合理使用采样和事件分组
- 注意硬件限制和事件复用
- 及时清理资源避免泄漏
正确使用 perf_event_open
可以为系统性能优化提供精确的数据支持,是现代 Linux 系统管理和性能工程的重要工具。