4.5 基于 Kprobe 的事件跟踪——内部细节
还记得上一节结尾留下的那个念想吗?有没有一种办法,不用写一行 C 代码,不用编译内核模块,就能给内核里的任意函数「装个窃听器」?
答案是肯定的,而且这个办法比你想象的要近得多。它就藏在你平时可能路过的 /sys/kernel/debug/tracing 目录里。
这个机制叫 kprobe-based event tracing(基于 kprobe 的事件跟踪)。
这一节本质上在讲什么
这一节表面上是在教你怎么用 tracefs 文件系统,实际上是在揭示 perf 和 eBPF 这些高级工具背后的「降维打击」手段。当你运行一个复杂的 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 领域——没有现成的目录,一切都靠你自己创建。