跳到主要内容

4.5 基于 Kprobe 的事件跟踪——内部细节

还记得上一节结尾留下的那个念想吗?有没有一种办法,不用写一行 C 代码,不用编译内核模块,就能给内核里的任意函数「装个窃听器」?

答案是肯定的,而且这个办法比你想象的要近得多。它就藏在你平时可能路过的 /sys/kernel/debug/tracing 目录里。

这个机制叫 kprobe-based event tracing(基于 kprobe 的事件跟踪)。

这一节本质上在讲什么

这一节表面上是在教你怎么用 tracefs 文件系统,实际上是在揭示 perfeBPF 这些高级工具背后的「降维打击」手段。当你运行一个复杂的 perf 脚本时,它本质上就是在这一节要讲的文件系统里写写画画。

让我们揭开这层幕布。


事件跟踪框架的真相

当你运行 perf probe 或者那些自动化脚本时,你以为它们在搞什么黑魔法,其实它们只是在做一件非常机械的事情:利用内核的 ftrace 基础设施 来注册探针。

你可能注意到了,上一节我们查看 /sys/kernel/debug/kprobes/list 时,右边有一列 [FTRACE]。现在这个谜题解开了:kprobe 的底层实现,有一部分是借用了 ftrace 的机制。

在这个框架里,有一块专门叫 kprobe events,它是 ftrace 大系统的一个子集。它的核心思想是把「探针」抽象成「事件」。你定义一个事件,内核帮你把探针挂上去,然后把数据收集到统一的 trace buffer 里。


在事件跟踪框架下追踪内置函数

一切的前提是内核开启了 CONFIG_KPROBE_EVENTS=y。好消息是,大部分发行版内核,哪怕是生产环境的,默认都开了这个。

让我们潜入那个目录:

$ ls /sys/kernel/tracing/events

这里有一堆文件夹,代表了内核里各种子系统已经暴露出来的「事件类」。

一个小小的路径问题

你可能会看到 /sys/kernel/debug/tracing/sys/kernel/tracing 两个路径。 其实它们通常指向同一个东西。有时候生产环境为了安全会把 debugfs 给挂掉不让人看,但 tracefs (/sys/kernel/tracing) 往往还是留着给性能工具用的。 后面我们为了通顺,统称为 <tracing>

看看下面这张图,感受一下这棵树的繁茂程度:

(📷 Figure 4.10 – Screenshot showing the kernel's event tracing - pseudo files and folders)

这里面全是现成的。

来试一个:kmalloc

拿最常用的内存分配函数 kmalloc 开刀。在图 4.10 的底部,你能看到 events/kmem/kmalloc 这个目录。这就是内核帮你预置好的监控点。

不需要写代码,只需要:

# 打开监控
echo 1 > <tracing>/events/kmem/kmalloc/enable

# 查看输出
cat <tracing>/trace_pipe

trace_pipe 是个特殊的文件,它就像 tail -f 一样,数据流实时往外喷。而普通的 trace 文件是快照。

这时候,系统里任何一个模块调用 kmalloc,都会被记录下来。你会看到类似这样的输出刷屏:

<...>-1234 [001] .... 12345.678901: kmalloc: call_site=ptr_spin_lock+0x4/0x20 ptr=ffff88000001 size=64 bytes_req=64 bytes_alloc=64 gfp_flags=GFP_KERNEL

这里的信息极其丰富:

  • 谁调用的 (call_site)
  • 分配了多大 (size)
  • 实际拿了多大 (bytes_alloc)
  • 分配标志是什么 (gfp_flags)

这些字段的格式定义,其实都在那个目录下的 format 伪文件里写着。perf 这些工具就是靠解析这个文件来知道怎么打印数据的。

擦屁股

看完了,记得关掉,否则 trace buffer 会爆:

echo 0 > <tracing>/events/kmem/kmalloc/enable
echo > <tracing>/trace

(📷 Figure 4.11 – Truncated screenshot showing an example of easily tracing the kmalloc routine)

就这么简单。没有模块加载,没有 printk,没有内核崩溃风险。


好了,陷阱来了

这一节到目前为止,我们玩的是「现成的」——也就是内核开发者们好心帮你预先在代码里埋好的 Tracepoints

图 4.10 里列出的那些目录,都是静态埋点。这就像你买了一栋带家具的房子,家具是开发商配好的。

但现实往往是残酷的

如果你想监控的那个函数——比如你自己写的内核模块里的某个函数,或者某个冷门内核函数——根本没出现在 /sys/kernel/tracing/events 目录下怎么办?

如果目录里没有,你就没法用上面的 enable 方法。

这就是下一节要解决的终极问题: 当你面对的是一套没有埋点的「毛坯房」时,怎么动态地在墙上打洞?

下一节,我们将进入真正的动态 kprobe 领域——没有现成的目录,一切都靠你自己创建。