2. tracepoint 基础概念

2.1 什么是 tracepoint

Tracepoint(跟踪点)是 Linux 内核中一种静态定义的、可动态启用的观测点。开发者在内核源代码的关键路径上预先插入一个“空桩”,当对应的跟踪事件被用户态工具(如 perf、bpftrace)激活时,内核会执行注册的回调函数,从而采集函数参数、时间戳、进程上下文等信息。

Tracepoint 的核心设计哲学是:零开销(zero overhead)。当没有消费者时,tracepoint 只占用一个 NOP 指令或一个条件跳转,几乎不影响内核性能;只有当用户显式开启跟踪时,才会执行实际的回调逻辑。

关键特性: Tracepoint 是内核 ABI 的一部分,一旦合入主线,其签名(函数名、参数类型、参数顺序)必须保持向后兼容。因此 tracepoint 适合作为长期稳定的观测接口。
Tracepoint 工作原理 Tracepoint 工作原理 内核函数 trace_xxx() (空桩 / NOP) 后续逻辑 注册的回调函数 perf / bpftrace 零开销原则 无消费者时仅执行 NOP

2.2 静态跟踪点 vs 动态跟踪点

Linux 内核提供了两种主要的跟踪点机制:静态跟踪点(tracepoint)和动态跟踪点(kprobe/uprobe)。两者在稳定性、性能、使用场景上有显著差异。

对比维度 静态跟踪点 (tracepoint) 动态跟踪点 (kprobe/uprobe)
定义方式 内核源代码中预先声明,编译时固定 运行时动态插入,无需修改内核代码
稳定性 高,属于内核 ABI,签名长期稳定 低,依赖内核内部实现,版本升级可能失效
性能开销 极低(无消费者时接近零) 较高(每次命中需处理断点/跳转)
可观测范围 仅限开发者预埋的点 任意函数入口/出口/指令位置
参数访问 直接访问结构化参数,类型安全 需手动解析寄存器/栈,易出错
典型场景 系统调用、调度、网络、文件系统等核心路径 临时调试、未知函数探查、热修复验证
选型建议: 对于生产环境长期监控,优先使用静态 tracepoint;对于临时排查或内核版本频繁变动的环境,使用动态 kprobe 更灵活。
静态跟踪点 vs 动态跟踪点 静态跟踪点 vs 动态跟踪点 静态跟踪点 (tracepoint) 内核源代码:DECLARE_TRACE / DEFINE_TRACE 编译时生成 tracepoint 桩 运行时:NOP → 回调(按需激活) 动态跟踪点 (kprobe/uprobe) 内核二进制:已编译的指令流 运行时替换指令为断点 (int3) 断点触发 → 执行回调 → 恢复执行 静态跟踪点:稳定、安全、适合生产;动态跟踪点:灵活、通用、适合调试

2.3 tracepoint 在内核中的声明与定义机制

Linux 内核使用一套宏体系来声明和定义 tracepoint。这套体系位于 include/linux/tracepoint.h 和 include/trace/ 目录下,核心宏包括:

  • DECLARE_TRACE() — 在头文件中声明一个 tracepoint,生成函数原型和外部符号。
  • DEFINE_TRACE() — 在 C 文件中定义 tracepoint,生成实际的桩函数和回调链表。
  • TRACE_EVENT() — 更高级的封装,自动生成 tracepoint、perf 事件、ftrace 格式文件等。

2.3.1 声明阶段:DECLARE_TRACE

在头文件(如 include/trace/events/sched.h)中使用 DECLARE_TRACE 声明一个 tracepoint,使其对所有编译单元可见:

/* include/trace/events/sched.h */
DECLARE_TRACE(sched_switch,
    TP_PROTO(bool preempt, struct task_struct *prev, struct task_struct *next),
    TP_ARGS(preempt, prev, next));

宏展开后,会生成一个 extern 的函数桩声明,以及一个用于注册回调的结构体。

2.3.2 定义阶段:DEFINE_TRACE

在 C 文件(如 kernel/sched/core.c)中使用 DEFINE_TRACE 提供实际定义:

/* kernel/sched/core.c */
DEFINE_TRACE(sched_switch,
    TP_PROTO(bool preempt, struct task_struct *prev, struct task_struct *next),
    TP_ARGS(preempt, prev, next));

宏展开后生成:

  • 一个 static 的 tracepoint 结构体,包含回调链表头。
  • 一个内联函数 trace_sched_switch(),内部检查是否有注册的回调,有则遍历执行。
  • 当没有回调时,整个函数退化为一个 if (false) 分支,编译器优化为 NOP。

2.3.3 调用点:trace_()

在内核代码的关键路径上调用生成的桩函数:

/* kernel/sched/core.c — context_switch() 内部 */
trace_sched_switch(preempt, prev, next);

这个调用点就是实际触发跟踪的位置。用户态工具通过 register_trace_() 注册回调后,每次执行到这里都会调用回调函数。

注意事项: 调用 trace_() 时传递的参数必须与 TP_PROTO 中声明的类型完全一致。内核不会对 tracepoint 参数做类型检查,类型不匹配可能导致内核崩溃或数据损坏。
Tracepoint 声明与定义流程 Tracepoint 声明与定义流程 头文件 (.h) DECLARE_TRACE() C 文件 (.c) DEFINE_TRACE() 内核对象 tracepoint 结构体 宏展开后生成 tracepoint 结构体 + trace_xxx() 内联函数 + 注册/注销接口 调用点:trace_sched_switch(preempt, prev, next) 位于 context_switch() 函数内部,每次进程切换时触发 register_trace_sched_switch(callback) unregister_trace_sched_switch(callback)

2.4 小结

  • Tracepoint 是内核中预定义的静态观测点,零开销、稳定、适合生产环境。
  • 静态 vs 动态:静态 tracepoint 稳定但覆盖有限;动态 kprobe 灵活但有性能开销和兼容性风险。
  • 声明与定义:使用 DECLARE_TRACE 在头文件中声明,DEFINE_TRACE 在 C 文件中定义,通过 trace_() 调用。
  • 下一章将深入 TRACE_EVENT 宏体系,展示如何用更简洁的方式定义完整的 tracepoint 事件。