mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1643 字
5 分钟
eBPF Map:数据结构详解
2026-02-28

eBPF 程序运行在内核态,但它不是一座孤岛——它需要与用户态交换数据:追踪事件需要传递到用户态,配置策略需要从用户态下发,跨程序共享需要全局状态。这些需求都通过 eBPF Map 实现。

Map 是 eBPF 生态中最重要的基础设施之一。理解 Map 的类型、特性和使用方式,是编写任何非平凡 eBPF 程序的基础。

一、Map 的本质#

1.1 什么是 Map#

eBPF Map 是内核中的键值存储,可以被 eBPF 程序和用户态程序同时访问:

flowchart TB subgraph 用户空间 APP["用户态程序<br/>bpf() 系统调用"] end subgraph 内核空间 PROG1["eBPF 程序 A<br/>bpf_map_lookup_elem()"] PROG2["eBPF 程序 B<br/>bpf_map_update_elem()"] MAP["eBPF Map<br/>内核键值存储"] end APP <-->|"读写"| MAP PROG1 <-->|"读写"| MAP PROG2 <-->|"读写"| MAP style MAP fill:#e8f5e9,stroke:#2e7d32

1.2 Map 的通用属性#

所有 Map 类型共享以下属性:

属性说明
typeMap 类型(Hash、Array、RingBuf 等)
key_size键的大小(字节)
value_size值的大小(字节)
max_entries最大条目数
map_flags标志位(BPF_F_NO_PREALLOC 等)

1.3 Map 的定义方式#

// 方式一:使用 BTF 定义(推荐,CO-RE 兼容)
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, u32);
__type(value, struct event);
} my_map SEC(".maps");
// 方式二:使用 struct bpf_map_def(传统方式)
struct bpf_map_def SEC("maps") my_map = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(u32),
.value_size = sizeof(struct event),
.max_entries = 1024,
};
Note

方式一(BTF 定义)是当前推荐的方式,它与 CO-RE 兼容,libbpf 可以自动创建和管理 Map。方式二(bpf_map_def)是旧方式,不支持 CO-RE。

二、Hash Map#

2.1 特性#

BPF_MAP_TYPE_HASH 是最通用的 Map 类型,实现为内核哈希表:

特性说明
查找复杂度O(1) 平均
键类型任意(需指定 key_size)
值类型任意(需指定 value_size)
预分配默认预分配所有条目
并发内核 RCU + 自旋锁保护

2.2 使用示例:连接跟踪表#

// 连接跟踪 Map
struct conn_key {
u32 src_ip;
u32 dst_ip;
u16 src_port;
u16 dst_port;
u8 protocol;
};
struct conn_value {
u64 packets;
u64 bytes;
u64 last_seen;
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 65536);
__type(key, struct conn_key);
__type(value, struct conn_value);
} conn_track SEC(".maps");
SEC("xdp")
int track_connections(struct xdp_md *ctx)
{
struct conn_key key = {};
struct conn_value *val;
// 解析数据包填充 key
if (parse_packet(ctx, &key) < 0)
return XDP_PASS;
// 查找或创建连接记录
val = bpf_map_lookup_elem(&conn_track, &key);
if (val) {
// 更新已有记录
__sync_fetch_and_add(&val->packets, 1);
val->last_seen = bpf_ktime_get_ns();
} else {
// 插入新记录
struct conn_value new_val = {
.packets = 1,
.last_seen = bpf_ktime_get_ns(),
};
bpf_map_update_elem(&conn_track, &key, &new_val, BPF_ANY);
}
return XDP_PASS;
}

2.3 Hash Map 的标志#

标志说明
BPF_ANY0存在则更新,不存在则创建
BPF_NOEXIST1仅在键不存在时创建
BPF_EXIST2仅在键存在时更新
BPF_F_NO_PREALLOC1 << 0不预分配内存(节省内存,但查找可能失败)

三、Array Map#

3.1 特性#

BPF_MAP_TYPE_ARRAY 是固定大小的数组,键必须是 4 字节无符号整数(索引):

特性说明
查找复杂度O(1)
键类型u32(数组索引)
预分配始终预分配
初始值全零
用途配置项、全局计数器、跳转表

3.2 使用示例:配置项#

// 配置 Map
struct config {
u32 action; // 0=PASS, 1=DROP, 2=REDIRECT
u32 target_if; // 重定向目标接口
u32 rate_limit; // 速率限制(PPS)
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, struct config);
} config_map SEC(".maps");
SEC("xdp")
int xdp_filter(struct xdp_md *ctx)
{
u32 key = 0;
struct config *cfg = bpf_map_lookup_elem(&config_map, &key);
if (!cfg)
return XDP_PASS;
switch (cfg->action) {
case 0: return XDP_PASS;
case 1: return XDP_DROP;
case 2: return bpf_redirect(cfg->target_if, 0);
default: return XDP_PASS;
}
}

3.3 Prog Array:尾调用跳转表#

BPF_MAP_TYPE_PROG_ARRAY 是 Array Map 的特殊变体,值是 eBPF 程序的 fd,用于尾调用:

struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(max_entries, 8);
__type(key, u32);
__type(value, u32);
} prog_array SEC(".maps");
SEC("xdp")
int xdp_dispatcher(struct xdp_md *ctx)
{
// 根据协议分发到不同处理程序
u8 proto = get_protocol(ctx);
bpf_tail_call(ctx, &prog_array, proto);
return XDP_PASS; // 尾调用失败时的默认行为
}

四、Per-CPU Map#

4.1 为什么需要 Per-CPU#

在多核系统中,普通 Hash/Array Map 的更新操作需要获取自旋锁,在高并发场景下成为性能瓶颈。Per-CPU Map 为每个 CPU 维护独立的副本,更新时无需加锁:

flowchart TB subgraph 普通Map["普通 Map(需锁)"] CPU1a["CPU 1"] -->|"自旋锁"| MAP1["共享 Map"] CPU2a["CPU 2"] -->|"自旋锁"| MAP1 CPU3a["CPU 3"] -->|"自旋锁"| MAP1 end subgraph PerCPUMap["Per-CPU Map(无锁)"] CPU1b["CPU 1"] --> MAP1b["CPU 1 副本"] CPU2b["CPU 2"] --> MAP2b["CPU 2 副本"] CPU3b["CPU 3"] --> MAP3b["CPU 3 副本"] end style 普通Map fill:#ffcdd2,stroke:#c62828 style PerCPUMap fill:#c8e6c9,stroke:#2e7d32

4.2 使用示例:每 CPU 计数器#

// Per-CPU 计数器
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, u64);
} pkt_counter SEC(".maps");
SEC("xdp")
int count_packets(struct xdp_md *ctx)
{
u32 key = 0;
u64 *count = bpf_map_lookup_elem(&pkt_counter, &key);
if (count)
*count += 1; // 无锁更新!
return XDP_PASS;
}
// 用户态读取 Per-CPU Map
u32 key = 0;
u64 values[num_cpus];
bpf_map_lookup_elem(map_fd, &key, values);
u64 total = 0;
for (int i = 0; i < num_cpus; i++)
total += values[i];
printf("Total packets: %lu\n", total);

4.3 Per-CPU Map 的限制#

限制说明
值大小不能超过内核页面大小(4KB)
用户态读取需要提供 CPU 数量大小的缓冲区
一致性不同 CPU 的值可能不一致(最终一致)

五、Ring Buffer#

5.1 Ring Buffer vs Perf Buffer#

BPF_MAP_TYPE_RINGBUF(5.8+)是替代 BPF_MAP_TYPE_PERF_EVENT_ARRAY 的新一代事件传输机制:

特性Perf BufferRing Buffer
内存使用每 CPU 独立缓冲区全局共享缓冲区
事件大小固定(value_size)可变(每次可不同)
数据拷贝2 次1 次
内存浪费高(每 CPU 预分配)低(全局共享)
事件顺序无保证全局有序
推荐度旧方案,不推荐推荐

5.2 使用示例:事件传输#

struct event {
u32 pid;
u32 tid;
char comm[16];
char filename[256];
};
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024); // 256 KB
} events SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_openat")
int trace_openat(struct trace_event_raw_sys_enter *ctx)
{
struct event *e;
// 从 Ring Buffer 预留空间
e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
if (!e)
return 0;
// 填充事件数据
u64 pid_tgid = bpf_get_current_pid_tgid();
e->pid = pid_tgid >> 32;
e->tid = (u32)pid_tgid;
bpf_get_current_comm(&e->comm, sizeof(e->comm));
const char *filename = (const char *)ctx->args[1];
bpf_probe_read_user_str(&e->filename, sizeof(e->filename), filename);
// 提交事件
bpf_ringbuf_submit(e, 0);
return 0;
}

5.3 Ring Buffer 的优势#

flowchart LR subgraph PerfBuffer["Perf Buffer(旧)"] PB1["CPU 0 Buffer<br/>128KB"] --> PB_COPY["拷贝 1"] PB2["CPU 1 Buffer<br/>128KB"] --> PB_COPY PB3["CPU 2 Buffer<br/>128KB"] --> PB_COPY PB_COPY --> PB_USER["用户态<br/>事件可能乱序"] end subgraph RingBuffer["Ring Buffer(新)"] RB1["CPU 0 事件"] --> RB_SHARED["共享 Ring Buffer<br/>256KB"] RB2["CPU 1 事件"] --> RB_SHARED RB3["CPU 2 事件"] --> RB_SHARED RB_SHARED --> RB_USER["用户态<br/>全局有序"] end style PerfBuffer fill:#ffcdd2,stroke:#c62828 style RingBuffer fill:#c8e6c9,stroke:#2e7d32

六、其他 Map 类型#

6.1 LRU Hash#

BPF_MAP_TYPE_LRU_HASH 自动淘汰最近最少使用的条目,适合有容量限制的缓存:

struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 10000);
__type(key, struct conn_key);
__type(value, struct conn_value);
} conn_cache SEC(".maps");
// 当条目数达到 max_entries 时,自动淘汰最久未访问的条目
// 无需手动删除旧条目

6.2 Stack Trace#

BPF_MAP_TYPE_STACK_TRACE 存储调用栈,用于性能分析:

struct {
__uint(type, BPF_MAP_TYPE_STACK_TRACE);
__uint(max_entries, 10000);
__type(key, u32);
__type(value, struct stack_trace_t); // 自动定义
} stack_traces SEC(".maps");
SEC("kprobe/do_sys_open")
int trace_open(struct pt_regs *ctx)
{
u32 pid = bpf_get_current_pid_tgid() >> 32;
// 捕获当前调用栈
long stack_id = bpf_get_stackid(ctx, &stack_traces, BPF_F_FAST_STACK);
if (stack_id >= 0) {
// stack_id 可用于从用户态查询完整调用栈
}
return 0;
}

6.3 Socket Map#

BPF_MAP_TYPE_SOCKHASH / BPF_MAP_TYPE_SOCKMAP 用于 Socket 重定向,实现本地 Socket 通信绕过内核协议栈:

struct {
__uint(type, BPF_MAP_TYPE_SOCKHASH);
__uint(max_entries, 65536);
__type(key, struct sock_key);
__type(value, u32);
} sock_map SEC(".maps");
SEC("sk_msg")
int bpf_prog(struct sk_msg_md *msg)
{
// 根据 Socket 信息重定向到目标 Socket
struct sock_key key = {
.sip = msg->remote_ip4,
.dip = msg->local_ip4,
.sport = msg->remote_port,
.dport = msg->local_port,
};
bpf_msg_redirect_hash(msg, &sock_map, &key, BPF_F_INGRESS);
return SK_PASS;
}

6.4 完整 Map 类型参考#

Map 类型内核版本特点典型用途
HASH4.4通用哈希表连接跟踪、进程信息
ARRAY4.4固定大小数组配置项、全局计数器
PERCPU_HASH4.6每 CPU 哈希表高频更新统计
PERCPU_ARRAY4.6每 CPU 数组每 CPU 计数器
PROG_ARRAY4.6程序数组尾调用跳转表
PERF_EVENT_ARRAY4.4性能事件数组追踪数据传输(旧)
RINGBUF5.8环形缓冲区事件传输(推荐)
STACK_TRACE4.6调用栈存储性能分析
LRU_HASH4.10LRU 哈希有容量限制的缓存
LRU_PERCPU_HASH4.10LRU 每 CPU 哈希高频缓存
SOCKHASH4.18Socket 哈希Socket 重定向
SOCKMAP4.14Socket 数组Socket 重定向
DEVMAP4.14设备数组XDP 重定向
CPUMAP4.15CPU 数组XDP CPU 重定向
XSKMAP4.18XSK 数组AF_XDP Socket
QUEUE4.20FIFO 队列消息传递
STACK4.20LIFO 栈消息传递
SK_STORAGE5.2Socket 本地存储每 Socket 私有数据
INODE_STORAGE5.10Inode 本地存储每 Inode 私有数据
TASK_STORAGE5.11Task 本地存储每 Task 私有数据
STRUCT_OPS5.6结构体操作TCP 拥塞控制等

七、Map 的用户态操作#

7.1 通过 bpftool 操作 Map#

# 列出所有 Map
sudo bpftool map list
# 查看 Map 详细信息
sudo bpftool map show id 123
# 查看 Map 内容
sudo bpftool map dump id 123
# 更新 Map 条目
sudo bpftool map update id 123 key 0 0 0 0 value 1 0 0 0
# 查找特定条目
sudo bpftool map lookup id 123 key 0 0 0 0
# 删除条目
sudo bpftool map delete id 123 key 0 0 0 0

7.2 通过 libbpf 操作 Map#

#include <bpf/libbpf.h>
#include <bpf/bpf.h>
// 获取 Map fd
int map_fd = bpf_map__fd(skel->maps.my_map);
// 查找
u32 key = 1;
struct event value;
int err = bpf_map_lookup_elem(map_fd, &key, &value);
if (err == 0) {
printf("Found: pid=%u\n", value.pid);
}
// 更新
value.pid = 1234;
bpf_map_update_elem(map_fd, &key, &value, BPF_ANY);
// 遍历
u32 next_key;
while (bpf_map_get_next_key(map_fd, &key, &next_key) == 0) {
bpf_map_lookup_elem(map_fd, &next_key, &value);
printf("key=%u pid=%u\n", next_key, value.pid);
key = next_key;
}
// 删除
bpf_map_delete_elem(map_fd, &key);

7.3 通过 Go 操作 Map#

package main
import (
"fmt"
"github.com/cilium/ebpf"
)
func main() {
// 打开 Map
m, err := ebpf.NewMapFromFile("/sys/fs/bpf/my_map")
if err != nil {
panic(err)
}
defer m.Close()
// 查找
var key uint32 = 1
var value uint64
err = m.Lookup(&key, &value)
if err == nil {
fmt.Printf("Found: key=%d value=%d\n", key, value)
}
// 更新
value = 42
err = m.Update(&key, &value, ebpf.UpdateAny)
// 遍历
var iterKey uint32
iter := m.Iterate()
for iter.Next(&iterKey, &value) {
fmt.Printf("key=%d value=%d\n", iterKey, value)
}
}

八、Map 性能对比#

8.1 查找性能#

Map 类型查找延迟更新延迟适用场景
HASH~100ns~200ns通用
ARRAY~20ns~30ns索引已知
PERCPU_HASH~50ns~50ns高频更新
PERCPU_ARRAY~10ns~10ns计数器
RINGBUFN/A~50ns事件传输
LRU_HASH~150ns~300ns缓存

8.2 内存开销#

Map 类型max_entries=10000, key=4B, value=64B
HASH~700 KB(预分配)
ARRAY~640 KB(始终预分配)
PERCPU_HASH~700 KB × CPU 数
LRU_HASH~1.2 MB(含 LRU 元数据)
RINGBUF由 max_entries 决定
Warning

Per-CPU Map 的内存开销与 CPU 数成正比。在 128 核的服务器上,一个 Per-CPU Hash Map 的内存开销是普通 Hash Map 的 128 倍!使用前务必评估内存影响。

九、动手实践#

9.1 创建和操作 Map#

# 创建一个 Hash Map
sudo bpftool map create /sys/fs/bpf/my_hash \
type hash key 4 value 8 entries 1024 name my_hash
# 插入条目
sudo bpftool map update pinned /sys/fs/bpf/my_hash \
key 1 0 0 0 value 42 0 0 0 0 0 0 0
# 查找条目
sudo bpftool map lookup pinned /sys/fs/bpf/my_hash \
key 1 0 0 0
# 遍历所有条目
sudo bpftool map dump pinned /sys/fs/bpf/my_hash

9.2 使用 bpftrace 操作 Map#

# 统计每个进程的系统调用次数(使用 Map)
sudo bpftrace -e '
tracepoint:raw_syscalls:sys_enter {
@syscalls[comm] = count();
}
interval:s:5 {
print(@syscalls);
clear(@syscalls);
}'
# 统计每个进程的读字节数(使用 Map)
sudo bpftrace -e '
tracepoint:syscalls:sys_exit_read
/args->ret > 0/ {
@bytes[comm] = sum(args->ret);
}'

9.3 监控 Map 使用情况#

# 查看 Map 内存使用
sudo bpftool map show id 123
# 输出包含:bytes_used, max_entries, key_size, value_size
# 查看系统所有 eBPF Map 的内存占用
sudo bpftool map list
sudo bpftool map show

十、本章小结#

上一章剖析了eBPF 验证器的安全保证。 本章详细介绍了 eBPF Map 的所有主要类型:

主题核心要点关键词
Hash Map通用键值存储,O(1) 查找,适合连接跟踪、进程信息Hash Map
Array Map固定大小数组,O(1) 索引访问,适合配置项、计数器Array Map
Per-CPU Map每 CPU 独立副本,无锁更新,适合高频统计Per-CPU Map
Ring Buffer高效事件传输,全局有序,替代 Perf BufferRing Buffer
LRU Hash自动淘汰,适合有容量限制的缓存LRU Hash
Special MapProg Array(尾调用)、Stack Trace(调用栈)、Socket Map(Socket 重定向)Special Map

选择 Map 类型的决策树:

flowchart TD START["选择 Map 类型"] --> Q1{"需要传输事件到用户态?"} Q1 -->|"是"| RINGBUF["Ring Buffer"] Q1 -->|"否"| Q2{"键是连续整数索引?"} Q2 -->|"是"| Q3{"高频更新?"} Q2 -->|"否"| Q4{"需要自动淘汰?"} Q3 -->|"是"| PERCPU_ARR["Per-CPU Array"] Q3 -->|"否"| ARRAY["Array"] Q4 -->|"是"| LRU["LRU Hash"] Q4 -->|"否"| Q5{"高频更新?"} Q5 -->|"是"| PERCPU_HASH["Per-CPU Hash"] Q5 -->|"否"| HASH["Hash"] style RINGBUF fill:#c8e6c9,stroke:#2e7d32 style PERCPU_ARR fill:#c8e6c9,stroke:#2e7d32 style ARRAY fill:#c8e6c9,stroke:#2e7d32 style LRU fill:#c8e6c9,stroke:#2e7d32 style PERCPU_HASH fill:#c8e6c9,stroke:#2e7d32 style HASH fill:#c8e6c9,stroke:#2e7d32

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

eBPF Map:数据结构详解
https://blog.souloss.com/posts/ebpf/map-data-structures/
作者
Souloss
发布于
2026-02-28
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

相关文章 智能推荐
1
eBPF Hook 点:kprobe/tracepoint/uprobe
eBPF eBPF 程序的价值在于它能挂载到内核的各种检查点——Hook 点。本章详解三大 Hook 机制——kprobe(动态内核函数追踪)、tracepoint(静态追踪点)、uprobe(用户态函数追踪),以及 USDT 静态用户态追踪点,并通过实战代码展示每种 Hook 的使用方式与适用场景。
2
eBPF 可观测性
eBPF eBPF 最大的应用场景是可观测性——零侵入、低开销、内核级的全链路追踪。本章详解三大可观测性工具链——bpftrace(一行命令追踪内核)、BCC(Python 前端 + 丰富工具集)、Beyla(零侵入应用性能监控),并通过实战展示性能分析、分布式追踪、应用性能监控的完整工作流。
3
eBPF 在 Kubernetes
eBPF Kubernetes 是 eBPF 技术最大的应用场景——从 CNI 网络插件到 kube-proxy 替代,从 NetworkPolicy 增强到 Ambient Mesh,eBPF 正在重塑 K8s 的网络与安全基础设施。本章详解 eBPF CNI 对比、Cilium 的 K8s 集成、Ambient Mesh 无边车模式、多集群网络,以及 eBPF 在 K8s 中的生产部署最佳实践。
4
XDP:高性能数据包处理
eBPF XDP(eXpress Data Path)是 eBPF 在网络领域最耀眼的应用——它在网卡驱动层处理数据包,绕过内核协议栈,实现每秒数千万包的处理能力。本章详解 XDP 的架构、程序类型、四种返回动作、三种运行模式,以及 XDP 与 DPDK 的对比,并通过实战代码构建高性能负载均衡器。
5
eBPF 网络全景
eBPF eBPF 正在重新定义 Linux 网络栈——从连接跟踪、NAT 到 kube-proxy 替代,从 Socket Filter 到 Sk_msg 重定向,eBPF 提供了比 iptables/netfilter 更高性能、更灵活的网络方案。本章从宏观视角展示 eBPF 网络的全景,详解连接跟踪、NAT、kube-proxy 替代、Socket 层 eBPF,并对比 eBPF 与传统网络方案的架构差异。