使用perf_event_open+mmap环形缓冲区时,如何定义内核返回的结构类型?
我来帮你拆解这个问题——这确实是perf_event_open里容易踩坑的点,尤其是刚上手做周期性采集的时候。
核心:所有记录都以perf_event_header开头
不管你配置的是什么事件类型,环形缓冲区里的每一条记录必然以struct perf_event_header作为起始。这个结构体里的type字段是关键,它直接告诉你这条记录属于什么类型(比如采样记录、MMAP映射记录、进程名称变更记录等),你需要先解析这个字段,再去匹配对应的具体结构。
struct perf_event_header { u32 type; // 记录类型,比如PERF_RECORD_SAMPLE、PERF_RECORD_MMAP等 u16 misc; // 辅助标记,比如是否是内核态事件 u16 size; // 整条记录的总大小 };
控制返回结构类型的perf_event_attr关键成员
你要的“控制返回结构类型”的配置,主要集中在perf_event_attr的这几个成员上:
1. sample_type(最核心)
这个字段是一个位掩码,决定了**采样记录(PERF_RECORD_SAMPLE)**里包含哪些数据维度。比如你想记录进程ID、时间戳、CPU编号,就可以设置:
attr.sample_type = PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_CPU;
不同的位掩码组合,对应采样记录在perf_event_header之后的结构会完全不同。比如上面的组合,后续数据就是:
// 紧跟perf_event_header之后的采样数据 struct { u32 pid, tid; // 对应PERF_SAMPLE_TID u64 time; // 对应PERF_SAMPLE_TIME u32 cpu, res; // 对应PERF_SAMPLE_CPU };
常见的sample_type位包括PERF_SAMPLE_CALLCHAIN(调用栈)、PERF_SAMPLE_BRANCH_STACK(分支记录)、PERF_SAMPLE_RAW(原始硬件事件数据)等。
2. read_format
如果你用PERF_EVENT_IOC_READ命令读取事件,或者设置了水位触发/频率触发的周期性读取,这个字段控制**读取记录(PERF_RECORD_READ)**的格式。比如你想同时拿到总计数、时间戳、CPU编号,就设置:
attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TIME | PERF_FORMAT_CPU;
对应的PERF_RECORD_READ结构会包含这些字段。
3. branch_sample_type
只有当sample_type包含PERF_SAMPLE_BRANCH_STACK时,这个字段才生效。它是另一个位掩码,控制分支记录的细节:比如是否记录用户态分支、内核态分支、调用/返回路径、异常跳转等。对应的结构是struct perf_branch_entry,字段会根据这个掩码变化。
4. 辅助过滤字段(间接影响记录内容)
比如exclude_user、exclude_kernel、exclude_hv这些字段,它们不会改变记录的结构类型,但会过滤掉特定场景的事件,间接影响你拿到的记录数量和内容。
如何匹配记录类型与对应结构体
拿到环形缓冲区的数据后,流程是这样的:
- 先读取
perf_event_header,通过type判断记录类型; - 如果是元数据记录(比如
PERF_RECORD_MMAP、PERF_RECORD_COMM、PERF_RECORD_EXIT等),这些结构是固定的,直接用内核头文件里对应的结构体解析即可(比如struct perf_record_mmap); - 如果是采样记录(
PERF_RECORD_SAMPLE),根据你之前设置的sample_type掩码,逐个解析对应的字段; - 如果是读取记录(
PERF_RECORD_READ),根据read_format掩码解析对应的字段。
实用提示
- 一定要参考你当前运行内核版本的
linux/perf_event.h头文件——不同内核版本的perf结构可能有细微变化(比如某些字段的长度、新增的位掩码); - 可以先做小范围测试:比如只设置
PERF_SAMPLE_TID,然后打印解析出的pid/tid,验证结构是否正确,再逐步添加更多sample_type位; - 如果用
perf record命令生成数据,再用perf script解析,可以反向验证你的结构解析是否正确——perf工具本身就是基于perf_event_open实现的。
内容的提问来源于stack exchange,提问作者Apitronix

