尧图精选

Linux 内核 BPF_MAP_TYPE_CGROUP_STORAGE 深度解析:cgroup 本地存储的用法、语义与 5.9 版本行为变化

🕒 发布时间:2026/9/14 12:56:36 📁 来源:尧图网络
Linux 内核 BPF_MAP_TYPE_CGROUP_STORAGE 深度解析cgroup 本地存储的用法、语义与 5.9 版本行为变化【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linuxBPF_MAP_TYPE_CGROUP_STORAGE是 eBPF 为 cgroup 场景提供的本地定长存储map 类型以 BPF 程序所附着的 cgroup 为标识为该 cgroup 分配一块私有内存让 cgroup BPF 程序无需借助通用哈希表即可高速、简洁地读写与特定 cgroup 绑定的状态。本文基于内核仓库中的 官方文档完整覆盖该 map 的 key 类型、bpf_get_local_storage调用方式、Linux 5.9 前后的生命周期语义差异与用户态访问限制并结合 kernel/bpf/local_storage.c、kernel/bpf/cgroup.c、kernel/bpf/syscall.c 的源码实现说明其分配时机、per-CPU 变体的内存布局与自旋锁同步机制帮助你在编写 cgroup BPF 程序时正确选择 key 类型并规避 5.9 版本的行为陷阱。基本概念与启用条件BPF_MAP_TYPE_CGROUP_STORAGE表示一种local fix-sized storage本地定长存储。根据 Documentation/bpf/map_cgroup_storage.rst 的定义它有两个硬性前提配置前提只有在内核配置了CONFIG_CGROUP_BPF时才可用。同名的 Kconfig 选项同时启用可附着到 cgroup 的 BPF 程序能力即该 map 类型与 cgroup BPF 程序能力是同一开关控制的附着前提只有附着attach到 cgroup 上的 BPF 程序才能使用它存储以程序所附着的 cgroup为标识。它相对通用哈希表如BPF_MAP_TYPE_HASH的优势有两点一是访问更快——无需哈希表查找内核直接按附着关系定位存储二是使用更简单——哈希表方案要求用户自己跟踪哪些 cgroup 还活着并手动维护条目而 CGROUP_STORAGE 由内核在附着/分离生命周期内自动管理存储的创建与销毁。此外还有一个变体BPF_MAP_TYPE_PERCPU_CGROUP_STORAGEper-CPU 变体为每个存储的每个 CPU 各自维护一块独立内存区域而普通变体对每个存储只有一块共享内存区域。这一差异在用户态访问时有可验证的量化依据——kernel/bpf/syscall.c 中bpf_map_value_size()对 per-CPU 变体返回round_up(value_size, 8) * num_possible_cpus()即实际传输的 value 按 8 字节对齐后乘以所有可能 CPU 的数量。key 类型两种取法与共享语义该 map 的 key 支持两种类型均可在 include/uapi/linux/bpf.h 中找到定义struct bpf_cgroup_storage_key { __u64 cgroup_inode_id; /* cgroup inode id */ __u32 attach_type; /* program attach type (enum bpf_attach_type) */ };cgroup_inode_idcgroup 目录的 inode idattach_type程序的附着类型enum bpf_attach_type。两种 key 类型的选择直接决定存储的共享粒度key 类型存储隔离粒度引入版本__u64 cgroup_inode_id同一 cgroup 同一 map 下所有 attach type 共享同一块存储Linux 5.9struct bpf_cgroup_storage_key不同 attach type 的程序互相隔离各自看到不同的存储5.9 之前即支持也就是说Linux 5.9 新增的__u64 cgroup_inode_id简化了多个附着类型希望共享同一份数据的场景此时内核在比较时只使用 key 的 cgroup inode idattach type 被忽略。程序内访问bpf_get_local_storage在 BPF 程序中访问存储统一通过 helperbpf_get_local_storage完成其 uapi 声明见 include/uapi/linux/bpf.hvoid *bpf_get_local_storage(void *map, u64 flags)flags字段保留给未来使用当前必须传 0。返回指针即该 cgroup 存储的 value 首地址。注意内核不提供任何隐式同步。同一块 CGROUP_STORAGE 可以被跨 CPU 的多个程序并发访问并发安全需要程序员自行保证。BPF 基础设施为此提供struct bpf_spin_lock同步原语仓库中可直接参考 tools/testing/selftests/bpf/progs/test_spin_lock.c 的用法。从源码结构看BPF_SPIN_LOCK字段类型在 map 创建时被明确列入 CGROUP_STORAGE 的白名单——kernel/bpf/syscall.c 中只有 HASH、RHASH、ARRAY、CGROUP_STORAGE、SK_STORAGE、INODE_STORAGE、TASK_STORAGE 等类型允许携带自旋锁字段其余类型返回-EOPNOTSUPP。用户态读取带自旋锁的 map 元素时同样需要带BPF_F_LOCK标志。示例一以 struct bpf_cgroup_storage_key 为 key以下 BPF 侧代码完整继承自 官方文档声明了一个以bpf_cgroup_storage_key为 key、__u32为 value 的 CGROUP_STORAGE map并在程序中对存储做原子自增#include bpf/bpf.h struct { __uint(type, BPF_MAP_TYPE_CGROUP_STORAGE); __type(key, struct bpf_cgroup_storage_key); __type(value, __u32); } cgroup_storage SEC(.maps); int program(struct __sk_buff *skb) { __u32 *ptr bpf_get_local_storage(cgroup_storage, 0); __sync_fetch_and_add(ptr, 1); return 0; }对应的用户态访问代码以inode id attach type二元组构造 key调用bpf_map_lookup_elem读取指定附着点的存储#include linux/bpf.h #include linux/libbpf.h __u32 map_lookup(struct bpf_map *map, __u64 cgrp, enum bpf_attach_type type) { struct bpf_cgroup_storage_key key { .cgroup_inode_id cgrp, .attach_type type, }; __u32 value; bpf_map_lookup_elem(bpf_map__fd(map), key, value); // error checking omitted return value; }示例二以 __u64 cgroup_inode_id 为 key5.9 共享模式如果希望同一 cgroup 下所有 attach type 共享一份存储key 直接声明为__u64#include bpf/bpf.h struct { __uint(type, BPF_MAP_TYPE_CGROUP_STORAGE); __type(key, __u64); __type(value, __u32); } cgroup_storage SEC(.maps); int program(struct __sk_buff *skb) { __u32 *ptr bpf_get_local_storage(cgroup_storage, 0); __sync_fetch_and_add(ptr, 1); return 0; }用户态则无需构造结构体直接把 cgroup inode id 作为 key 传入即可原文示例保留了type参数签名但 5.9 共享语义下内核只比较第一个值#include linux/bpf.h #include linux/libbpf.h __u32 map_lookup(struct bpf_map *map, __u64 cgrp, enum bpf_attach_type type) { __u32 value; bpf_map_lookup_elem(bpf_map__fd(map), cgrp, value); // error checking omitted return value; }生命周期语义Linux 5.9 前后的关键差异这是本文档最核心的技术内容也是跨内核版本迁移时最容易踩坑的地方。5.9 之前每附着点一份存储map 与程序一对一存储的生命周期精确到每次附着per-attachment。一个 CGROUP_STORAGE map 在同一时刻最多只能被一个已加载程序使用程序可以附着到多个 cgroup、或拥有多个 attach type每一次附着都会新建一块清零的存储分离detach时存储立即释放这种一对一绑定导致无法在多个 BPF 程序之间共享同一 cgroup 的存储。5.9 起存储可被多程序共享程序附着到 cgroup 时内核仅在map 中尚不存在 (cgroup, attach type) 对应条目时创建新存储否则复用旧存储若 map 采用共享 key__u64则比较时直接忽略 attach type分离不再直接释放存储。存储只在两种情况下被释放map 本身被销毁或所附着的 cgroup 被销毁detach 只是让 map 的引用计数减少可能间接使 map 引用归零从而释放 map 内全部存储。从源码可以印证这一附着期分配行为kernel/bpf/cgroup.c 在 cgroup BPF 程序附着路径中调用bpf_cgroup_storage_alloc(prog, stype)分配存储而该函数的实现位于 kernel/bpf/local_storage.c。但注意 5.9 并未放开一个程序用多个存储 map的限制一个 BPF 程序最多只能关联一个BPF_MAP_TYPE_CGROUP_STORAGE也最多只能关联一个BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE两类各限一个。放开的是 map 到程序的共享方向而不是程序到 map 的引用方向。附着点绑定规则存储在 attach 时刻绑定。即使程序附着在父 cgroup 上、而触发点发生在子 cgroup所用存储依然属于父 cgroup。这意味着在触发事件的 cgroup 上计数这类需求不能依靠 CGROUP_STORAGE 自动获得需要显式附着到目标层级。用户态操作限制跨所有内核版本用户态通过 BPF map API 操作该 map 时有两条硬限制原文以struct bpf_cgroup_storage_key形式表述的附着参数在 5.9 共享模式下退化为只比较 cgroup inode id因此可以直接传__u64不能创建新条目也不能删除已有条目。条目的增删完全由内核在附着/分离与 cgroup 销毁流程中驱动用户态仅能对这些内核管理的条目执行 lookup / update读取或更新存储内容程序 test runBPF_PROG_TEST_RUN始终使用一块临时存储不会触碰真实 cgroup 的存储可用于安全地单元测试程序逻辑。实践要点小结需要每个 cgroup 一份私有计数器/状态且要求高性能时优先选择 CGROUP_STORAGE 而非 HASH 表省去手动生命周期管理多个程序要共享同一 cgroup 数据、且运行在 5.9 内核上时用__u64key需要在不同 attach type 之间隔离状态时用struct bpf_cgroup_storage_key跨 CPU 并发写同一存储时必须自行加锁struct bpf_spin_lock并参考 selftest 示例 的锁使用方式依赖detach 即清零的旧逻辑在内核升级到 5.9 后不再成立升级时务必检查是否依赖了 5.9 前的 per-attachment 语义。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →