2、eBPF基础:eBPF架构、eBPF程序类型、eBPF Map与Helper函数

2.1 eBPF 整体架构

eBPF(extended Berkeley Packet Filter)是一种在内核中运行沙箱化程序的技术,无需修改内核源码或加载内核模块即可安全地扩展内核能力。其核心架构由用户空间、内核空间以及验证器/即时编译器(JIT)三部分组成。

架构要点: eBPF 程序通过 bpf() 系统调用从用户态加载到内核,经过验证器(Verifier)确保安全后,由 JIT 编译为原生机器码执行。程序通过 eBPF Map 与用户态交换数据,通过 Helper 函数访问内核功能。
eBPF 整体架构 用户空间(User Space) eBPF 加载器(bpftool / libbpf) 用户态程序(Cilium Agent / Hubble CLI) bpf() 系统调用 内核空间(Kernel Space) 验证器(Verifier) JIT 编译器 eBPF 程序运行时 内核钩子(Tracepoints / kprobes / XDP / cgroup) eBPF Map / Perf Event 数据消费端

2.2 eBPF 程序类型

eBPF 支持多种程序类型,每种类型绑定到特定的内核事件或钩子点。在 Hubble 流量可视化场景中,最常用的是 XDP、TC、kprobe 和 tracepoint 类型。

程序类型 挂载点 触发时机 Hubble 用途
XDP 网卡驱动层 数据包到达网卡时(最早) 高性能包过滤、负载均衡
TC(流量控制) 内核网络协议栈 数据包进出协议栈时 包标记、转发、丢弃统计
kprobe 内核函数入口/出口 内核函数被调用时 跟踪系统调用、网络事件
tracepoint 内核静态跟踪点 预定义内核事件发生时 稳定可靠的网络事件采集
cgroup 套接字 cgroup 层级 进程套接字操作时 容器级网络策略与监控
perf_event 性能计数器 硬件/软件性能事件 性能指标采集
实践建议: 在 Cilium/Hubble 中,XDP 和 TC 程序负责数据面包处理,tracepoint 和 kprobe 用于控制面事件采集。配额校准场景通常使用 TC 程序在出口方向统计流量,结合 tracepoint 跟踪连接生命周期。

2.3 eBPF Map 与数据交换

eBPF Map 是内核与用户空间之间、以及不同 eBPF 程序之间共享数据的核心机制。Map 以键值对形式存储,支持多种数据类型。

常用 Map 类型

Map 类型 语义 典型用途
BPF_MAP_TYPE_HASH 哈希表 连接跟踪表、策略规则表
BPF_MAP_TYPE_ARRAY 数组 统计计数器、固定配置
BPF_MAP_TYPE_PERCPU_HASH Per-CPU 哈希 高性能统计,避免锁竞争
BPF_MAP_TYPE_PERCPU_ARRAY Per-CPU 数组 逐 CPU 计数器
BPF_MAP_TYPE_LRU_HASH LRU 哈希 有限大小的连接缓存
BPF_MAP_TYPE_RINGBUF 环形缓冲区 高效事件推送(Hubble 事件流)

Map 操作示例

// 定义 Map(内核态)
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 65536);
    __type(key, struct ip_key);
    __type(value, struct flow_stats);
} flow_map SEC(".maps");

// 内核态更新 Map
static __always_inline void update_flow(struct ip_key *key, struct flow_stats *stats) {
    bpf_map_update_elem(&flow_map, key, stats, BPF_ANY);
}

// 用户态读取 Map(通过 libbpf)
struct flow_stats *value = bpf_map_lookup_elem(bpf_obj->maps.flow_map, &key);
if (value) {
    printf("bytes: %lu\n", value->bytes);
}
eBPF Map 数据交换流 eBPF 程序(内核态) 写入/更新 Map eBPF Map 键值对存储 用户态程序 读取 Map Map 数据流说明 1. eBPF 程序在内核态将流量统计写入 Map(如 flow_map) 2. 用户态 Hubble 组件通过 bpf_map_lookup_elem 周期性读取 3. Per-CPU Map 避免锁竞争,适合高频率计数器场景

2.4 Helper 函数

eBPF Helper 函数是内核提供给 eBPF 程序调用的预定义接口,用于访问内核数据结构、操作网络包、管理 Map 等。Helper 函数保证了 eBPF 程序的安全性和可移植性。

常用 Helper 函数分类

分类 函数示例 功能描述
Map 操作 bpf_map_lookup_elem
bpf_map_update_elem
bpf_map_delete_elem
Map 的增删改查
包数据访问 bpf_skb_load_bytes
bpf_skb_store_bytes
bpf_skb_pull_data
读取/修改网络包数据
时间与统计 bpf_ktime_get_ns
bpf_get_current_pid_tgid
bpf_get_current_comm
获取时间戳、进程信息
网络转发 bpf_redirect
bpf_redirect_map
bpf_clone_redirect
数据包重定向
事件通知 bpf_perf_event_output
bpf_ringbuf_output
向用户态推送事件
辅助功能 bpf_get_prandom_u32
bpf_trace_printk
随机数、调试输出
注意事项: Helper 函数的可用性取决于 eBPF 程序类型。例如,bpf_redirect 只能在 XDP 和 TC 程序中使用;bpf_perf_event_output 在大多数程序类型中可用,但参数格式不同。编写程序前应查阅内核版本对应的 Helper 函数列表。

配额校准场景中的典型 Helper 使用

// TC 出口方向统计流量
SEC("tc/egress")
int tc_egress(struct __sk_buff *skb) {
    struct ip_key key = {};
    struct flow_stats *stats;

    // 提取 IP 五元组
    bpf_skb_load_bytes(skb, ETH_HLEN, &key, sizeof(key));

    // 查找连接跟踪表
    stats = bpf_map_lookup_elem(&flow_map, &key);
    if (!stats) {
        struct flow_stats new = {0};
        bpf_map_update_elem(&flow_map, &key, &new, BPF_NOEXIST);
        stats = bpf_map_lookup_elem(&flow_map, &key);
        if (!stats) return TC_ACT_OK;
    }

    // 更新字节计数(原子操作)
    __sync_fetch_and_add(&stats->bytes, skb->len);

    // 推送事件到用户态
    bpf_ringbuf_output(&event_ring, &key, sizeof(key), 0);

    return TC_ACT_OK;
}

2.5 小结

  • eBPF 架构:用户态加载 → 验证器安全检查 → JIT 编译 → 内核钩子执行 → Map/事件回传。
  • 程序类型:XDP/TC 用于数据面,tracepoint/kprobe 用于控制面,cgroup 用于容器级策略。
  • Map 机制:Hash/Array/Per-CPU/Ringbuf 等类型满足不同性能与语义需求。
  • Helper 函数:提供安全的内核接口,是 eBPF 程序与内核交互的唯一途径。