2、eBPF基础:eBPF发展历史、核心架构(BPF虚拟机、Map、Helper)、eBPF程序类型
2.1 eBPF 发展历史
eBPF(extended Berkeley Packet Filter)起源于 1992 年由 Steven McCanne 和 Van Jacobson 在 BSD 系统上提出的 BPF(Berkeley Packet Filter)。BPF 最初设计用于高效网络数据包过滤,通过在内核态运行一个简单的虚拟机,避免了将数据包从内核拷贝到用户态再丢弃的低效路径。
2014 年,Alexei Starovoitov 将 BPF 引入 Linux 内核,并进行了革命性扩展,形成了今天的 eBPF。关键里程碑如下:
| 时间 | 内核版本 | 里程碑事件 |
|---|---|---|
| 1992 | — | BPF 诞生于 BSD 系统,用于网络包过滤(tcpdump 核心) |
| 2014 | 3.18 | eBPF 首次合入 Linux 主线,扩展寄存器数量(从 2 个到 10 个) |
| 2015 | 4.1 | 引入 BPF Maps,支持有状态处理 |
| 2016 | 4.8 | 添加 BPF Helper 函数,允许 eBPF 程序安全调用内核服务 |
| 2017 | 4.15 | BPF 跟踪(tracing)能力成熟,支持 kprobe/uprobe/tracepoint |
| 2018 | 4.18 | BPF 网络能力增强,XDP 和 TC 成为生产级特性 |
| 2020+ | 5.x | BPF CO-RE(Compile Once – Run Everywhere)、BPF 调度器、LSM BPF |
关键认知: eBPF 不是 BPF 的简单升级,而是将 BPF 从一个“网络包过滤器”重新定义为一个“内核可编程沙箱”。它允许用户在不修改内核源码、不加载内核模块的前提下,安全高效地注入自定义逻辑。
2.2 eBPF 核心架构
eBPF 的核心架构由三大部分组成:BPF 虚拟机、BPF Maps 和 BPF Helper 函数。三者协同工作,构成了 eBPF 程序从加载、验证到执行和安全交互的完整链路。
2.2.1 BPF 虚拟机
BPF 虚拟机是一个轻量级的、运行在内核态的寄存器虚拟机。与 x86/ARM 等通用架构不同,它被刻意设计为简单、可验证且安全。
- 寄存器模型: 包含 10 个通用寄存器(R0–R9)和 1 个帧指针寄存器(R10)。R0 用于返回值,R1–R5 用于函数参数传递,R6–R9 为被调用者保存寄存器。
- 指令集: 仅包含 100 多条指令,分为 8 类(ALU、跳转、内存、加载/存储等)。指令固定 64 位长度,便于解码和验证。
- 栈空间: 最大 512 字节,不允许动态栈扩展,确保验证器能静态分析所有内存访问边界。
- 执行模式: 支持解释执行和 JIT(Just-In-Time)编译执行。JIT 将 BPF 字节码直接翻译为本地机器码,性能接近原生内核函数。
性能对比: 在 XDP 场景下,eBPF 程序通过 JIT 编译后,包处理性能可达 20–30 Mpps(百万包/秒),而传统 iptables 规则链通常只能达到 1–5 Mpps。
2.2.2 BPF Maps
BPF Maps 是 eBPF 程序与用户态之间、以及不同 eBPF 程序之间共享数据的关键机制。Maps 以键值对形式存储数据,支持多种数据结构:
| Map 类型 | 描述 | 典型用途 |
|---|---|---|
| BPF_MAP_TYPE_HASH | 通用哈希表,键值长度可变 | 连接跟踪表、策略规则表 |
| BPF_MAP_TYPE_ARRAY | 固定大小数组,索引为键 | 计数器、统计指标 |
| BPF_MAP_TYPE_LRU_HASH | 带 LRU 淘汰机制的哈希表 | 会话缓存、流表 |
| BPF_MAP_TYPE_PERCPU_HASH | Per-CPU 哈希表,避免锁竞争 | 高并发计数器 |
| BPF_MAP_TYPE_STACK | 后进先出栈 | 临时数据存储 |
| BPF_MAP_TYPE_QUEUE | 先进先出队列 | 事件缓冲 |
| BPF_MAP_TYPE_PROG_ARRAY | 存储 eBPF 程序文件描述符 | 尾调用(Tail Call)跳转表 |
2.2.3 BPF Helper 函数
eBPF 程序不能直接调用内核函数,必须通过 Helper 函数 与内核交互。Helper 是内核预定义的一组安全接口,每个 Helper 都有明确的参数类型和返回值约束,由验证器严格检查。
常用 Helper 函数示例:
// 获取当前进程的 PID 和 TGID
u64 bpf_get_current_pid_tgid(void);
// 获取当前 CPU 编号
u32 bpf_get_smp_processor_id(void);
// 将数据从内核空间安全拷贝到 BPF 栈
long bpf_probe_read(void *dst, u32 size, const void *unsafe_ptr);
// 更新 Map 中的键值对
long bpf_map_update_elem(struct bpf_map *map, const void *key,
const void *value, u64 flags);
// 发送网络数据包(XDP 场景)
long bpf_xdp_adjust_head(struct xdp_md *xdp, int delta);
安全边界: eBPF 程序不能调用任意内核函数。所有 Helper 函数必须在内核源码中显式注册,并经过安全审查。验证器会确保 Helper 的参数类型、内存访问范围完全合法,杜绝内核崩溃或提权风险。
2.3 eBPF 程序类型
eBPF 程序类型决定了程序挂载的事件源、可访问的上下文结构以及可调用的 Helper 函数集合。Linux 内核目前支持超过 30 种程序类型,以下是与 Kubernetes 网络策略自动化配置最相关的几种:
| 程序类型 | 挂载点 | 上下文 | K8s 网络场景用途 |
|---|---|---|---|
| BPF_PROG_TYPE_XDP | 网卡驱动层(最早 hook) | struct xdp_md | DDoS 防护、源地址验证、快速丢包 |
| BPF_PROG_TYPE_SCHED_CLS | TC(Traffic Control)入/出方向 | struct __sk_buff | Pod 网络策略执行、流量加密、负载均衡 |
| BPF_PROG_TYPE_CGROUP_SKB | cgroup v2 网络控制器 | struct __sk_buff | 容器粒度网络策略、带宽限制 |
| BPF_PROG_TYPE_KPROBE | 内核函数入口/出口 | struct pt_regs | 监控系统调用、连接跟踪事件采集 |
| BPF_PROG_TYPE_TRACEPOINT | 内核静态 tracepoint | 自定义结构体 | 网络事件日志、性能指标收集 |
| BPF_PROG_TYPE_SOCKET_FILTER | 原始套接字 | struct __sk_buff | 节点级网络监控、DNS 过滤 |
2.4 本章小结
- 发展历史: eBPF 从 1992 年的网络包过滤器,演变为 2014 年后的内核可编程平台,成为云原生基础设施的关键技术。
- 核心架构: BPF 虚拟机提供安全执行环境,BPF Maps 实现有状态数据共享,BPF Helper 函数作为与内核交互的唯一安全通道。
- 程序类型: 不同程序类型对应不同的内核事件源和上下文,XDP 和 TC 是 K8s 网络策略自动化配置的核心挂载点。
学习路径提示: 理解 eBPF 程序类型的选择直接影响后续课程中策略规则的挂载位置和性能表现。在 K8s 环境中,BPF_PROG_TYPE_SCHED_CLS 和 BPF_PROG_TYPE_XDP 将是我们的主要战场。