eBPF 动态 Map
BPF 动态 Map是 eBPF 中支持运行时动态创建、销毁、调整大小的 Map 类型,区别于传统静态 Map(编译时固定大小、固定键值类型)。
它是 Linux 5.10+ 内核引入的核心能力,彻底解决了传统 eBPF Map 「静态不灵活、资源浪费、无法动态扩容」的痛点,广泛用于云原生、网络观测、安全监控等场景。
- 动态创建 / 销毁:无需在 BPF 程序编译时定义,可在用户态用
bpf_map_create()随时创建、关闭销毁 - 动态调整大小:支持运行时扩容 / 缩容(部分类型)
- 动态类型:键值大小、Map 最大元素数都可在运行时指定
- 内核版本要求:Linux 5.10+,主流发行版(CentOS 8+/Ubuntu 20.04+)已支持
动态 Map vs 传统静态 Map
| 特性 | 传统静态 Map | BPF 动态 Map |
|---|---|---|
| 创建时机 | 编译时固定定义 | 运行时动态创建 |
| 大小 / 键值类型 | 编译时写死 | 运行时自由指定 |
| 生命周期 | 随 BPF 程序 / 挂载点 | 独立生命周期,手动管理 |
| 内核依赖 | 全版本支持 | Linux 5.10+ |
| 共享能力 | 可共享但不灵活 | 天然支持多程序 / 进程共享 |
支持的动态 Map 类型
所有标准 BPF Map 都支持动态创建,最常用:
- BPF_MAP_TYPE_HASH:哈希表(最常用)
- BPF_MAP_TYPE_ARRAY:数组 Map
- BPF_MAP_TYPE_PERCPU_HASH / PERCPU_ARRAY:Per-CPU 高性能 Map
- BPF_MAP_TYPE_RINGBUF:高性能环形缓冲区(观测数据上报)
动态 Map 核心 API(用户态 C 语言)
动态 Map 完全通过用户态系统调用创建 / 操作,BPF 程序通过文件描述符 (fd)访问。
1. 创建动态 Map
#include <bpf/bpf.h> #include <bpf/libbpf.h> // 核心函数:动态创建 Map int bpf_map_create(enum bpf_map_type map_type, const char *map_name, // Map 名称(调试用) __u32 key_size, // 键大小(字节) __u32 value_size, // 值大小(字节) __u32 max_entries, // 最大元素数 const struct bpf_map_create_attr *attr);- 返回值:Map 文件描述符(fd),后续所有操作都用这个 fd
- 关闭:
close(fd)会自动销毁 Map(无引用时)
2. 操作动态 Map
// 增/改 int bpf_map_update_elem(int fd, const void *key, const void *value, __u64 flags); // 查 int bpf_map_lookup_elem(int fd, const void *key, void *value); // 删 int bpf_map_delete_elem(int fd, const void *key);完整可运行示例
场景:用户态动态创建 HASH Map,BPF 程序读取 Map 数据
1. BPF 程序(内核态,dyn_map.bpf.c)
BPF 程序不定义 Map,只通过外部 fd引用动态 Map:
// 用 extern 声明外部动态 Map(核心!) struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, __u32); __type(value, __u64); } dyn_map SEC(".maps"); // 跟踪点:读取动态 Map 数据 SEC("tracepoint/syscalls/sys_enter_open") int trace_enter_open(struct trace_event_raw_sys_enter* ctx) { __u32 key = 100; __u64 *val; // 从动态 Map 中查找值 val = bpf_map_lookup_elem(&dyn_map, &key); if (val) { bpf_printk("dyn map value: %llu\n", *val); } return 0; } char LICENSE[] SEC("license") = "GPL";2. 用户态程序(加载 BPF + 创建动态 Map,dyn_map.c)
#include <stdio.h> #include <stdlib.h> #include <libbpf/libbpf.h> #include "dyn_map.skel.h" int main() { struct dyn_map_bpf *skel; int map_fd, err; __u32 key = 100; __u64 value = 9988; // 1. 加载 BPF 骨架程序 skel = dyn_map_bpf__open_and_load(); if (!skel) { fprintf(stderr, "加载 BPF 失败\n"); return 1; } // 2. 动态创建 HASH Map(运行时指定参数!) map_fd = bpf_map_create( BPF_MAP_TYPE_HASH, // Map 类型 "my_dynamic_map", // 名称 sizeof(__u32), // 键大小 sizeof(__u64), // 值大小 1024, // 最大元素数 NULL // 扩展属性 ); if (map_fd < 0) { perror("创建动态 Map 失败"); return 1; } printf("动态 Map 创建成功,fd=%d\n", map_fd); // 3. 向动态 Map 写入数据 bpf_map_update_elem(map_fd, &key, &value, BPF_ANY); // 4. 将动态 Map 绑定到 BPF 程序(替换 extern Map) skel->maps.dyn_map = bpf_map__from_fd(map_fd); // 5. 附加跟踪点 err = dyn_map_bpf__attach(skel); if (err) { fprintf(stderr, "附加失败\n"); return 1; } printf("运行中... 按 Ctrl+C 退出\n"); pause(); // 清理 dyn_map_bpf__destroy(skel); close(map_fd); return 0; }BPF 程序中动态创建 Map(进阶)
内核 5.18+ 支持:BPF 程序内部动态创建 Map
使用bpf_map_new()/bpf_map_put()实现内核态动态 Map:
// 内核态动态创建 Map struct bpf_map *map = bpf_map_new( BPF_MAP_TYPE_HASH, sizeof(__u32), sizeof(__u64), 128, 0 ); // 使用完释放 bpf_map_put(map);适用场景:BPF 程序按需创建临时 Map,用完即销毁。
动态 Map 高级特性
1. Map 共享
动态 Map 天生支持共享:
- 多个 BPF 程序可绑定同一个 Map fd
- 多进程可通过
bpf_map_get_fd_by_id()根据 Map ID 共享
2. 内核态 / 用户态双向操作
- 用户态:创建、增删改查
- 内核态:查找、更新、删除
- 完美解耦「资源管理」和「数据处理」
3. 持久化与命名
- 支持BPF FS 命名:
/sys/fs/bpf/my_map,重启前持久化 - 支持
bpf_map_get_fd_by_name()按名获取
使用限制与注意事项
- 内核版本:最低Linux 5.10,动态创建全类型;5.18+ 支持内核态创建
- 权限:需要
CAP_BPF、CAP_PERFMON权限(root 运行) - 资源限制:受
/proc/sys/kernel/bpf_*系统参数限制 - 生命周期:最后一个 fd 关闭后,Map 自动销毁
总结
- 动态 Map = 运行时创建 + 灵活配置 + 独立生命周期,彻底替代静态 Map
- 核心用法:用户态用
bpf_map_create()创建 → 绑定到 BPF 程序 → 双向操作 - 适用场景:动态观测、多程序共享数据、按需分配内存、云原生可观测性
- 依赖:Linux 5.10+、libbpf 1.0+
