一、eBPF 概述:Linux 内核的「可编程操作系统」

eBPF(Extended Berkeley Packet Filter)是一项革命性的内核技术,它允许用户在不修改内核源码、不加载内核模块的前提下,将自定义程序安全地注入到 Linux 内核中执行。自 2014 年 Alexei Starovoitov 将其从经典的 BPF(Berkeley Packet Filter)大幅扩展以来,eBPF 已从最初的网络包过滤演进为覆盖性能分析、安全监控、流量控制、可观测性等领域的通用内核编程框架。

传统内核开发的困境在于:修改内核源码需要漫长的发版周期,编写内核模块稳定性风险极高。eBPF 通过内核内建的验证器(Verifier)和 JIT 编译器,实现了安全且高性能的可编程能力。无论是 Cloudflare、Meta、Google 这样的互联网巨头,还是新兴的初创公司,都已将 eBPF 作为基础设施的核心组件。

二、eBPF 核心架构

2.1 执行模型

eBPF 程序遵循「事件驱动」模型——它们本身不会主动运行,而是在内核或用户态定义的钩子点(hook point)被触发时执行。完整的生命周期如下:

  1. 用户编写 eBPF 程序(C/Rust 等),通过 bpf() 系统调用加载到内核
  2. 内核验证器对程序进行全面安全检查(无越界访问、无无限循环、有限执行步数)
  3. JIT 编译器将 BPF 字节码翻译为本机指令(x86_64/ARM64)
  4. 程序被挂载到指定的 hook 点(kprobe/tracepoint/XDP 等)
  5. 事件触发时自动执行,结果通过 Maps 或直接输出传递给用户态

2.2 BPF 虚拟机

eBPF 运行在一个精简的 64 位虚拟机中,包含以下核心组件:

  • 11 个 64 位寄存器(R0-R10,R10 为帧指针只读)
  • 512 字节的栈空间(BPF 程序栈)
  • 无全局变量(通过 MAP 实现持久化状态)
  • 仅支持受控的函数调用(尾调用 静态函数内联)

BPF 指令集为 64 位定长编码,包含加载/存储、算术跳转、原子操作等类别,配合 BPF-to-BPF 函数调用和尾调用(tail call)实现了模块化的复杂逻辑。

三、eBPF Maps:内核态与用户态的桥梁

Maps 是 eBPF 程序的核心数据结构,既是数据存储也是通信通道。主要类型包括:

  • Hash Map:KV 查找,适用于连接跟踪、计数器
  • Array Map:固定大小数组,常作为配置接口
  • Ring Buffer:高效事件流,替代过时的 perf buffer
  • Perf Event Array:per-CPU 事件推送
  • LRU Maps:带容量驱逐的缓存
  • Program Array Map:尾调用跳转表

例如跟踪每秒 TCP 连接建立次数的典型场景:使用 Hash Map 存储每个源 IP 的连接计数,通过 Ring Buffer 将聚合事件推送到用户态分析。

四、eBPF 程序类型全景

eBPF 提供了覆盖内核各子系统的丰富程序类型,按挂载点分类:

4.1 网络类

  • XDP (eXpress Data Path):网卡驱动层最早点处理,可丢弃/重定向数据包,DDoS 防护的核心利器
  • TC (Traffic Control):内核协议栈 ingress/egress 两侧的分类与整形
  • Socket Filter:套接字层包过滤(经典 BPF 的延续)
  • cgroup SKB/Sock:基于 cgroup 的网络策略
  • Flow Dissector:自定义协议流解析

4.2 跟踪类

  • Kprobe/Kretprobe:动态插桩任意内核函数入口与返回
  • Tracepoint:内核预定义的静态事件点,稳定性优于 kprobe
  • Fentry/Fexit (BTF):基于 BTF 的类型安全函数入口插桩,性能最佳
  • _raw_tracepoint:高性能原始 tracepoint

4.3 安全类

  • LSM (Linux Security Module):安全决策钩子(SELinux/AppArmor 框架下)
  • BPF Iterator:迭代内核数据结构(进程、模块等)
  • Cgroup Sysctl/Attach:基于 cgroup 的控制系统调用

五、用户态交互:BCC 与 libbpf

用户态程序通过 bpf() 系统调用与内核交互,主流工具链分为两大阵营:

5.1 BCC (BPF Compiler Collection)

BCC 提供 Python/Lua 前端 Clang/LLVM 后端,运行时 JIT 编译,适合快速原型。典型工作流:

// BPF C 代码(内嵌于 Python)
int kprobe__tcp_sendmsg(struct pt_regs *ctx, struct sock *sk, struct msghdr *msg) {
    u32 pid = bpf_get_current_pid_tgid()                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部