perf 与 eBPF:关系与「埋点」思路的演进

perf 子系统和 eBPF 并非两个孤立的子系统,而是共享基础设施、互相协作的伙伴。本文从两者在内核中的协作关系出发,再对比它们在「埋点」与数据处理思路上的本质区别,并对照主线内核代码做简要核对。

一、perf 与 eBPF 的紧密关系

1. 共享内核基础设施:perf_events 是基石

eBPF 的很多核心功能都建立在 perf_events 子系统提供的机制之上;perf_events 为 eBPF 的高效数据输出和硬件性能计数读取提供了通道。

数据输出通道:BPF_MAP_TYPE_PERF_EVENT_ARRAY

当 eBPF 程序需要向用户空间发送大量数据时(例如追踪每次系统调用的参数),通常不直接操作文件或网络,而是通过一类特殊的 eBPF Map——BPF_MAP_TYPE_PERF_EVENT_ARRAY

内核中该 Map 类型的实现位于 kernel/bpf/arraymap.c,例如 perf_event_array_map_opsperf_event_fd_array_get_ptr 等)负责将 Map 中的 fd 解析为 perf_event 指针并与 ring buffer 关联;kernel/bpf/verifier.ckernel/bpf/syscall.c 中对 BPF_MAP_TYPE_PERF_EVENT_ARRAY 的校验与更新逻辑也与之对应。

读取性能计数器:bpf_perf_event_read 系列

eBPF 程序还可以通过 perf 子系统读取性能数据。辅助函数 bpf_perf_event_read()bpf_perf_event_read_value() 用于读取由 perf_events 管理的硬件性能计数器(如 CPU 周期、缓存未命中等)的值,从而在 eBPF 中把自定义追踪逻辑与底层硬件性能数据结合。例如 tools/perf/util/bpf_skel/bpf_prog_profiler.bpf.cbperf_leader.bpf.c 中就有对 bpf_perf_event_read_value() 的典型用法。

2. 程序类型协作:BPF_PROG_TYPE_PERF_EVENT

内核定义了专门的 eBPF 程序类型 BPF_PROG_TYPE_PERF_EVENT,允许将 eBPF 程序直接附加到某个 perf 事件上。

3. 用户空间工具整合:从「BPF 事件」到「BPF 脚手架」

在用户空间工具 perf 中,与 eBPF 的集成方式也在演进。

4. 安全与权限统一:CAP_PERFMON

从权限模型看,perf 与 eBPF 的追踪能力由同一 capability 约束。内核在 include/uapi/linux/capability.h 中定义:

/*
 * Allow system performance and observability privileged operations
 * using perf_events, i915_perf and other kernel subsystems
 */
#define CAP_PERFMON    38

同一文件中注释说明:CAP_PERFMONCAP_BPF 共同用于放宽对追踪类 BPF 程序的限制(如指针转整数、部分 speculation 加固的绕过、bpf_probe_read / bpf_trace_printk 等),且「CAP_PERFMON and CAP_BPF are required to load tracing programs」。因此,拥有 CAP_PERFMON 的进程既可以做 perf 采样,也可以在具备 CAP_BPF 等条件下加载用于追踪的 eBPF 程序,两者在权限上统一。

小结:关系总览

关系层面 描述
基础设施共享 eBPF 依赖 perf 的环形缓冲区硬件计数器,通过 BPF_MAP_TYPE_PERF_EVENT_ARRAYbpf_perf_event_read 等实现高效数据交互。
程序类型协作 BPF_PROG_TYPE_PERF_EVENT 允许将 eBPF 程序作为 perf 事件的溢出处理器,实现自定义采样逻辑。
工具整合 perf 从早期的「BPF 事件」演进为使用 libbpf 的 BPF skeleton 加载 eBPF 程序。
安全模型 CAP_PERFMONCAP_BPF 共同控制对 perf_events 与 eBPF 追踪能力的访问。

二、「埋点」思路的演进:预制传感器 vs 可编程探头

「埋点」是两者工作的基础,但埋点方式与后续数据处理思路有本质区别

1. 什么是「埋点」?

无论是 perf 还是 eBPF,核心都是在内核(及用户态)关键路径上放置探测点,在事件发生时(系统调用、网络包、函数调用等)采集信息。这些探测点是可观测性的数据源。

2. perf 的埋点思路:预制传感器

perf 主要利用已有的事件源与埋点:

perf 的角色更接近「仪表盘操作员」:知道所有预制传感器在哪里、如何读,并以较低开销(尤其是采样)汇总成报告。

3. kprobe 与 uprobe:机制与内核支持

eBPF 的「动态埋点」能力建立在内核的 kprobeuprobe 机制之上。二者允许在不重新编译内核或目标程序的前提下,在运行时把探测点挂在任意内核函数或用户态地址上,下面结合主线内核代码说明其含义与实现要点。

kprobe(Kernel Probe)

kprobe 用于在内核任意函数(或指定偏移)处插入探测。调用方只需提供符号名(如 do_sys_open)或「模块 + 偏移」;内核在注册时通过 kallsyms_lookup_name()(见 kernel/kprobes.c)解析出该符号的地址,无需在编译期固定探测位置。

相关定义与流程集中在 kernel/kprobes.c(通用逻辑、哈希表 kprobe_table、注册/卸载)、include/linux/kprobes.hstruct kprobeaddrsymbol_nameoffsetopcodeainsn 等),以及各架构的 arch/*/kernel/kprobes/(如 arch_arm_kprobe、指令槽与单步)。

uprobe(User-space Probe)

uprobe 用于在用户态程序的指定虚拟地址处插入探测。通常用「可执行文件 inode + 文件内偏移」或「path + offset」描述位置;同一偏移可对应多个已映射该文件的进程,内核会按 mmap 在各自地址空间写入断点。

uprobe 的消费者通过 struct uprobe_consumerhandlerret_handlerfilter)挂到 struct uprobe 上;eBPF 等会复用这套基础设施,把 BPF 程序作为 consumer 挂到同一 uprobe。

小结:动态的含义与依赖

机制 探测对象 「动态」体现 内核关键支持
kprobe 内核函数(符号或地址) 地址在 register_kprobe 时由 kallsyms 等解析,无需编译期埋点 断点替换(arch_arm/disarm)、指令槽单步、可选跳转优化
uprobe 用户态(文件 + 偏移 → 各进程 VMA) register_uprobe 时指定 offset,按 mmap 在运行时插入断点 用户态页写断点(set_swbp/set_orig_insn)、XOL 执行原指令

eBPF 的 kprobe/uprobe 程序类型(如 BPF_PROG_TYPE_KPROBE)即是在上述机制之上,把「断点命中后的处理」换成经 verifier 校验的 BPF 字节码,从而在保持动态性的同时提供可编程、安全的内核/用户态探测能力123

4. eBPF 的埋点思路:可编程探头

eBPF 在「埋点」上的不同在于动态与可编程

对比总结

特性 perf eBPF
埋点类型 主要依赖预制的硬件事件、软件事件和静态 tracepoint。 既可用预制 tracepoint,更核心的是动态 kprobe/uprobe。
数据处理 主要在用户空间;内核负责采集和输出原始/轻度聚合数据。 内核与用户空间协同;可在内核执行聚合、过滤、统计,只下发结果或关键数据。
灵活性 相对固定,只能获取预设格式的数据。 高;可访问函数上下文、参数、返回值,并按需编写处理逻辑。
编程模型 通过命令行参数与预定义事件配置。 用 C 等编写小程序,经内核验证后执行。

因此,两者都建立在「埋点」之上,但 eBPF 的突破在于:在埋点之上增加了动态创建探测点、以及在内核中安全执行自定义处理逻辑的能力,从「读仪表」演进到「可编程探头」。

References

  1. kernel/kprobes.c - kprobe 通用逻辑:注册/卸载、kallsyms 解析、指令槽与 arm/disarm 

  2. arch/x86/kernel/kprobes/core.c - arch_arm_kprobe / arch_disarm_kprobe - x86 上 kprobe 断点写入与恢复(INT3 / text_poke) 

  3. kernel/events/uprobes.c - uprobe 实现:set_swbp/set_orig_insn、XOL(xol_area)、install_breakpoint 

My Github Page: https://github.com/liweinan

Powered by Jekyll and Theme by solid

If you have any question want to ask or find bugs regarding with my blog posts, please report it here:
https://github.com/liweinan/liweinan.github.io/issues