做运维或者搞后端的同学,大概率都遇到过这种尴尬:线上服务出问题了,你想看看内核里到底发生了什么,结果发现——看不到。内核就像个黑盒,你只能在外围猜。

以前想窥探内核,路子就那么几条:改内核源码重新编译,风险大得吓人;或者装个内核模块,一不留神就把系统搞崩。直到 eBPF 出现,这事儿才算是有了正经解法。

eBPF 到底是啥

全称 extended Berkeley Packet Filter,扩展的伯克利包过滤器。名字听着绕,其实它最早就是 BPF——贝尔实验室搞出来的一个抓网络包的工具。那时候的 BPF 很单纯,只能过滤网络数据包。但后来内核社区那帮人把它扩展了一下,能力一下子就打开了:网络监控、安全过滤、性能分析、虚拟化,什么都能掺一脚。2014 年随着 Linux 3.18 首次亮相,想完整用上它,内核至少得 4.4 以上。

说白了,eBPF 干的事就是:让你在不修改内核代码、不加载内核模块的前提下,把一段自己写的逻辑塞进内核里跑。

它是怎么做到的

讲道理,这套流程设计得相当精巧。你写一段 C 代码,用 Clang/LLVM 编译成字节码,然后通过系统调用把这段字节码加载进内核。注意,内核不是傻子,不会你给什么它就跑什么——它先得过一道叫 Verifier 的安全检查,确认你的代码不会干坏事(比如乱访问内存、随便调系统调用),通过了才放行。最后再经过 JIT 编译,把字节码转成机器指令,执行效率直接拉满。

代码进去了,总得有个触发时机吧?这就是 hook(钩子)的作用。eBPF 程序可以挂在内核的各种事件上:系统调用、函数进出、网络数据包到达、kprobes/uprobes 探针……事件一发生,你的程序就被调用。

还有个关键组件叫 eBPF Maps,本质就是个键值存储,让 eBPF 程序在多次调用之间保持状态,还能跟用户态的程序共享数据。你统计的数据就是靠它传出来的。

打开网易新闻 查看精彩图片

实战:几十行代码监控内核

光说不练假把式。现在最流行的工具链是 BCC(BPF Compiler Collection),它的玩法很有意思——在 Python 里嵌一段 C 代码,BCC 帮你编译、加载、挂载,一气呵成。

比如你想统计系统每秒发出去多少 TCP 包,核心逻辑其实就十几行:定义一个 BPF_HASH 当计数器,在这个函数上挂个探针,每次调用就加一。跑起来之后,控制台每秒打印一次计数。

tcp_sendmsg

再进阶一点,还能算 TCP 发包的耗时:发送时记录时间戳存进 Map,接收时取出来一减,延迟就出来了。这段代码我当年调试了三个多小时,各种诡异的坑——结构体忘初始化、类型不匹配、头文件缺引用……所以个人建议,能用现成的就别自己造轮子。

打开网易新闻 查看精彩图片

BCC 自带的工具其实已经够用了:execsnoop 监控进程执行、opensnoop 监控文件打开、tcptop 看 TCP 流量排行、profile 分析 CPU 占用……内核里发生什么事,基本一览无余。

我个人觉得,eBPF 是近几年 Linux 生态里最值得学的技术之一,现在火得一塌糊涂的云原生网络方案 Cilium,底层就是它。K8s 集群里的网络排查、服务网格、安全观测,到处都有它的身影。门槛确实有——得懂点底层系统知识,还得会写代码——但一旦上手,你等于给内核装了个透视镜。

你在项目里用过 eBPF 吗?或者踩过什么坑?评论区聊聊。