Linux BPF kfunc 完全指南:从内核函数暴露、参数注解到生命周期管理
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
导读
kfunc(BPF Kernel Functions)是 Linux 内核暴露给 BPF 程序调用的一类内核函数,与稳定的 BPF helper 不同,kfunc 不提供接口稳定性承诺,可能随内核版本演化。本文基于内核源码树中的 Documentation/bpf/kfuncs.rst,系统讲解 kfunc 的定义方式(wrapper 与直用已有函数)、参数注解体系(__sz、__k、__uninit、__nullable等)、kfunc 标志位(KF_ACQUIRE、KF_RELEASE、KF_RCU 等)、注册流程、生命周期与弃用预期,并逐一剖析task_struct、cgroup等核心 kfunc 的源码实现。读完本文,你将能够在内核源码中识别、编写并注册一个类型安全的 kfunc,同时理解 verifier(校验器)对其施加的安全约束。
1. 什么是 kfunc
kfunc 是内核中专门暴露给 BPF 程序调用的函数。与 BPF helper 的关键区别在于:
- helper 有稳定的接口契约,跨内核版本保持兼容;
- kfunc 没有稳定的接口,可能从一个内核版本到另一个版本发生变化,因此调用 kfunc 的 BPF 程序需要随内核更新而同步更新(生命周期预期详见第 3 节)。
从 API 层次看,kfunc 提供的是"内核 ↔ 内核"接口,与 EXPORT_SYMBOL_GPL 导出的符号地位类似,其修改与删除由所在子系统的维护者决定,而非受 UAPI 稳定性规则约束。
2. 如何定义一个 kfunc
将内核函数暴露给 BPF 程序有两种途径:
- 将内核中已有的函数直接注册为 kfunc——当现有函数本身就适合 BPF 程序调用时;
- 新增一个 BPF wrapper 函数——当需要为参数附加注解、做安全检查或适配上下文时。
无论哪种方式,都必须保证 BPF 程序只能在合法上下文(context)中调用该函数,且 kfunc 的可见性可以按程序类型(program type)分别控制。
2.1 编写 wrapper kfunc
定义 wrapper kfunc 时,wrapper 函数应使用外部链接(extern linkage),因为 wrapper 本身不会在内核其他位置被调用,外部链接可以防止编译器将死代码优化掉。wrapper 不需要在头文件中提供原型。
典型写法如下:
/* Disables missing prototype warnings */ __bpf_kfunc_start_defs(); __bpf_kfunc struct task_struct *bpf_find_get_task_by_vpid(pid_t nr) { return find_get_task_by_vpid(nr); } __bpf_kfunc_end_defs();当需要为 kfunc 的参数附加注解时,wrapper 往往是必需的(见 2.3 节);否则可以直接将原函数注册给 BPF 子系统(见 2.4 节)。
从源码看,__bpf_kfunc_start_defs()与__bpf_kfunc_end_defs()在 include/linux/btf.h 中定义,其作用是压入诊断状态并忽略-Wmissing-declarations、-Wmissing-prototypes两类告警——因为全局 kfunc 的定义会进入 BTF,不必要求头文件声明:
#define __bpf_kfunc_start_defs() \ __diag_push(); \ __diag_ignore_all("-Wmissing-declarations", \ "Global kfuncs as their definitions will be in BTF");\ __diag_ignore_all("-Wmissing-prototypes", \ "Global kfuncs as their definitions will be in BTF") #define __bpf_kfunc_end_defs() __diag_pop()2.2 kfunc 参数:可信指针(trusted arguments)要求
默认情况下,所有 kfunc 都要求可信参数(trusted arguments),即:
- 所有指针参数必须有效;
- 指向 BTF 对象的指针必须以未修改形式传入:偏移为 0,且不能是通过遍历另一个指针得到的嵌套指针(例外见下文)。
内核对象指针中只有两类被视为"可信(trusted)":
- 作为 tracepoint 或 struct_ops 回调参数传入的指针;
- 由带
KF_ACQUIRE标志的 kfunc 返回的指针。
指向非 BTF 对象(例如标量指针)的指针也可以传给 kfunc,且允许非零偏移。"有效"指针的定义随时可能调整,完全没有 ABI 稳定性保证。
从可信指针遍历得到的嵌套指针默认不再可信,唯一的例外是:如果某个结构体类型中存在一个字段,只要其父指针有效,该字段就保证有效(trusted 或 rcu,含义见 2.5.6 节 KF_RCU),则可用如下宏向 verifier 声明这一事实:
BTF_TYPE_SAFE_TRUSTEDBTF_TYPE_SAFE_RCUBTF_TYPE_SAFE_RCU_OR_NULL
用法分两步:
- 将有效指针类型包进
BTF_TYPE_SAFE_*宏; - 指定有效嵌套字段的类型与名称,该字段必须与原始类型定义中的字段完全一致。
BTF_TYPE_SAFE_TRUSTED(struct socket) { struct sock *sk; };BTF_TYPE_SAFE_RCU(struct task_struct) { const cpumask_t *cpus_ptr; struct css_set __rcu *cgroups; struct task_struct __rcu *real_parent; struct task_struct *group_leader; };用BTF_TYPE_SAFE_*宏声明出的新类型还必须被"发射"到 BTF 中,例如BTF_TYPE_SAFE_TRUSTED(struct socket)通过BTF_TYPE_EMIT()在type_is_trusted()函数中完成发射:
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct socket));源码佐证:这些宏的定义与使用位于 kernel/bpf/verifier.c(如#define BTF_TYPE_SAFE_RCU(__type) __PASTE(__type, __safe_rcu)),同一文件中BTF_TYPE_SAFE_RCU(struct task_struct)、BTF_TYPE_SAFE_TRUSTED(struct file)等均有实际使用,并通过 include/linux/btf.h 的BTF_TYPE_EMIT(type) ((void)(type *)0)完成发射。
2.3 参数注解(Annotating kfunc parameters)
与 BPF helper 类似,verifier 有时需要额外的上下文信息来让 kfunc 的使用更安全、更有用。方法是在 kfunc 参数名后追加__tag后缀,其中 tag 是受支持的注解之一。
2.3.1__sz:内存与大小配对
指示参数列表中某对参数构成"内存 + 大小"组合:
__bpf_kfunc void bpf_memzero(void *mem, int mem__sz) { ... }verifier 会把第一个参数当作PTR_TO_MEM,第二个参数当作其大小。默认(无__sz)情况下,大小取指针所指向类型的大小;并且没有__sz注解时,kfunc 不能接受 void 指针。
2.3.2__k:已知常量标量
仅用于标量参数,表示该标量必须是 verifier 可知的常量——它不是大小参数,但常量的取值与程序安全相关:
__bpf_kfunc void *bpf_obj_new(u32 local_type_id__k, ...) { ... }bpf_obj_new用local_type_id在程序的 BTF 中查该类型 ID 的大小并返回带大小的指针。每个类型 ID 大小不同,因此在 verifier 状态剪枝(state pruning)检查时,值不同的调用必须被视为不同调用。凡是"非大小参数、但常量值影响程序安全"的标量参数,都应使用__k后缀。
2.3.3__uninit:未初始化参数
表示该参数会被当作未初始化处理:
__bpf_kfunc int bpf_dynptr_from_skb(..., struct bpf_dynptr_kern *ptr__uninit) { ... }此处 dynptr 被视为未初始化的 dynptr。没有该注解时,若传入的 dynptr 未初始化,verifier 会拒绝程序。
2.3.4__nullable:允许 NULL 指针
表示指针参数可以为 NULL,verifier 允许为该参数传入 NULL:
__bpf_kfunc void bpf_task_release(struct task_struct *task__nullable) { ... }task 指针可能为 NULL,kfunc 自身负责在解引用前检查 NULL。
__nullable可与其他注解组合。例如与__sz/__szk组合用于内存与大小配对时,传入 NULL 指针时 verifier 会跳过大小校验,但仍会处理大小参数以提取常量大小信息:
__bpf_kfunc void *bpf_dynptr_slice(..., void *buffer__nullable, u32 buffer__szk)buffer 可以为 NULL;若非 NULL,则必须至少为buffer__szk字节。kfunc 在使用前负责检查 NULL。
2.3.5__nonown_allowed:允许非拥有引用
表示参数可以是非拥有引用(non-owning reference):
__bpf_kfunc int bpf_list_add(..., struct bpf_list_node *prev__nonown_allowed, ...) { ... }对于prev__nonown_allowed参数(解析为KF_ARG_PTR_TO_LIST_NODE),__nonown_allowed后缀保留通常的拥有指针(owning-pointer)规则,同时允许传入没有ref_obj_id的非拥有引用(例如bpf_list_front()/bpf_list_back()的返回值)。
2.3.6__str:常量字符串
表示参数是常量字符串:
__bpf_kfunc bpf_get_file_xattr(..., const char *name__str, ...) { ... }调用方式为直接传字符串字面量,或传全局字符数组:
bpf_get_file_xattr(..., "xattr_name", ...);const char name[] = "xattr_name"; /* This need to be global */ int BPF_PROG(...) { ... bpf_get_file_xattr(..., name, ...); ... }2.3.7__const_map与__map:map 参数
两者都用于struct bpf_map *参数,区分"verifier 已知的 map"与"不透明 map":
__const_map:map 必须在校验期已知,即 BPF 程序直接引用的具体 map fd:
__bpf_kfunc int bpf_wq_init(struct bpf_wq *wq, void *p__const_map, unsigned int flags) { ... }__map:不透明的struct bpf_map *,可在运行时解析,参数可以是 map fd 或PTR_TO_BTF_ID类型的struct bpf_map指针:
__bpf_kfunc void *bpf_arena_alloc_pages(void *p__map, ...) { ... }2.3.8__arena与__arena__nullable:arena 指针参数
两者都表示指针参数指向调用程序的 arena。JIT 会在调用点对值做重定位(rebase),使 kfunc 收到可直接解引用的内核地址,访问规则见 2.8 节(单次未检查访问最多越过指针GUARD_SZ / 2,即 32 KiB)。
__arena:重定位无条件执行,参数永不为 NULL——低 32 位全零的值会以 arena 基地址(arena 偏移 0)形式到达。kfunc不得检查该参数是否为 NULL。__arena__nullable:上述值以 NULL 形式到达,kfunc 在解引用前必须检查。
__bpf_kfunc int bpf_process_item(struct item *item__arena) { ... }调用此类 kfunc 要求程序使用 arena map 且 JIT 支持 arena 参数(目前为 x86-64 与 arm64),否则校验失败。程序可传任意值而不危及内核;传不指向 arena 的值属于程序 bug。
这两个后缀在 struct_ops stub 函数的参数上有相同含义,只是转换方向相反:内核调用方传入内核 arena 地址,trampoline 在保存参数时进行转换,使回调收到可直接解引用的 arena 指针。__arena要求内核调用方不得传 NULL;__arena__nullable下 NULL 内核指针以 NULL 到达。不过,使用这类指针前并不强制向 verifier 证明其非 NULL,这与程序内(或任何其他来源获得的)arena 指针的既有语义一致。
2.4 直接使用已有内核函数(Using an existing kernel function)
当内核中已有函数适合 BPF 程序消费时,可以直接将其注册给 BPF 子系统。但依然需要仔细审查 BPF 程序调用它的上下文是否合法、是否安全。
2.5 为 kfunc 添加标志(Annotating kfuncs)
除了参数注解,verifier 还需要了解 kfunc 本身的类型信息。做法是通过BTF_KFUNCS_START/BTF_KFUNCS_END与BTF_ID_FLAGS定义一组带标志的 kfunc 集合:
BTF_KFUNCS_START(bpf_task_set) BTF_ID_FLAGS(func, bpf_get_task_pid, KF_ACQUIRE | KF_RET_NULL) BTF_ID_FLAGS(func, bpf_put_pid, KF_RELEASE) BTF_KFUNCS_END(bpf_task_set)该集合对每个列出的 kfunc 编码其 BTF ID 及标志;也允许不指定任何标志。
这些宏在 include/linux/btf_ids.h 中定义,BTF_KFUNCS_START(name)展开为__BTF_SET8_START(name, local, BTF_SET8_KFUNCS),BTF_KFUNCS_END(name)展开为BTF_SET8_END(name),最终把 kfunc 的 BTF ID 与标志编码进专用的btf_id_set8集合,供 verifier 查询。所有 KF_ 标志位常量集中在 include/linux/btf.h。
kfunc 定义还应始终使用__bpf_kfunc宏注解,防止编译器内联 kfunc,或防止函数因在内核其余部分未被使用而在 LTO 构建中被删除。开发者不应手动添加注解来规避这些问题——如果需要某个注解才能避免,那属于宏定义的 bug,应将该注解加入宏定义以保护所有 kfunc。例如:
__bpf_kfunc struct task_struct *bpf_get_task_pid(s32 pid) { ... }从源码看,__bpf_kfunc在 include/linux/btf.h 中定义为:
#define __bpf_kfunc __used __retain __noclone noinline__used/__retain确保符号被保留,__noclone noinline防止编译器克隆或内联,从而保证 BTF ID 解析可用。
此外,kfunc 不能声明为static:kfunc 可能被定义它的编译单元之外的 BPF 程序*.c文件调用,必须保留外部可见名称供 BTF ID 查找。static链接允许编译器重命名函数,会破坏基于 BTF 的 kfunc 解析。另外,sparse 可能对未被引用的 kfunc 提示应设为 static,这类告警应忽略。
2.5.1 KF_ACQUIRE:返回引用计数对象
指示 kfunc 返回指向引用计数对象的指针。verifier 会确保该指针最终通过 release kfunc 释放,或通过调用bpf_kptr_xchg转入 map 中的引用 kptr。否则,在程序所有可达状态中仍存在未释放引用时,verifier 会判定程序加载失败。
2.5.2 KF_RET_NULL:返回值可能为 NULL
指示 kfunc 返回的指针可能为 NULL,强制调用方在使用(解引用或传给其他 helper)前做 NULL 检查。该标志常与 KF_ACQUIRE 搭配,但两者相互正交。
2.5.3 KF_RELEASE:释放传入指针
指示 kfunc 释放传入的指针。一次只能传入一个引用指针;调用后,被释放指针的所有副本都会失效。
2.5.4 KF_SLEEPABLE:可睡眠
用于可能睡眠的 kfunc,只能被可睡眠的 BPF 程序(设置了BPF_F_SLEEPABLE)调用。
2.5.5 KF_DESTRUCTIVE:破坏性操作
指示调用会对系统造成破坏(例如导致系统重启或 panic)。这类调用有额外限制,目前要求CAP_SYS_BOOT能力,未来可能增加更多限制。
2.5.6 KF_RCU:接受 RCU 指针
允许 kfunc 退出默认的可信参数要求,接受保证更弱的 RCU 指针。标记 KF_RCU 的 kfunc 期望PTR_TRUSTED或MEM_RCU参数。verifier 保证对象有效、不会 use-after-free;指针非 NULL,但对象的引用计数可能已降为 0。kfunc 需要考虑refcnt != 0检查,尤其是返回 KF_ACQUIRE 指针时。注意:KF_RCU 的 KF_ACQUIRE kfunc 很可能也应该是 KF_RET_NULL。
2.5.7 KF_RCU_PROTECTED:必须在 RCU 临界区调用
指示 kfunc 必须在 RCU 临界区中调用。非可睡眠程序中默认满足此条件;可睡眠程序必须显式调用bpf_rcu_read_lock保证。
若该 kfunc 返回指针值,此标志还强制返回指针受 RCU 保护,只能在 RCU 临界区激活期间使用。
该标志与 KF_RCU 不同:KF_RCU 只保证其参数至少是 RCU 保护的指针,可能传递性地蕴含 RCU 保护,但无法覆盖"需要 RCU 保护却不接受 RCU 保护参数"的 kfunc 场景。
2.5.8 KF_DEPRECATED:已弃用
用于计划在后续内核版本中变更或移除的 kfunc。标记 KF_DEPRECATED 的 kfunc 应在其 kernel doc 中记录相关信息,通常包括:预期的剩余寿命、可替代的新功能建议(若有)、以及移除原因。
注意:某些情况下 KF_DEPRECATED kfunc 可能继续被支持并移除该标志,但总体而言,添加 KF_DEPRECATED 标志后再移除它,比一开始就不添加要困难得多。如第 3 节所述,依赖特定 kfunc 的用户应尽早公开自己的用例,并参与上游关于保留、修改、弃用或移除这些 kfunc 的讨论。
2.5.9 KF_IMPLICIT_ARGS:隐式参数
指示 kfunc 的BPF 签名与内核签名不同,隐式参数的值由 verifier 在加载时提供。
只有特定类型的参数可以是隐式的,目前仅支持struct bpf_prog_aux *。
带 KF_IMPLICIT_ARGS 的 kfunc 在 BTF 中因此有两种类型:一个与内核声明匹配(按惯例函数名带_impl后缀),另一个匹配预期的 BPF API。verifier 只允许调用不带隐式参数签名的_impl之外版本。
声明示例:
__bpf_kfunc int bpf_task_work_schedule_signal(struct task_struct *task, struct bpf_task_work *tw, void *map__const_map, bpf_task_work_callback_t callback, struct bpf_prog_aux *aux) { ... }BPF 程序中的使用示例(注意最后一个参数被省略):
/* note that the last argument is omitted */ bpf_task_work_schedule_signal(task, &work->tw, &arrmap, task_work_callback);2.6 注册 kfunc(Registering the kfuncs)
kfunc 准备好之后,最后一步是向 BPF 子系统注册,使其可见。注册是按 BPF 程序类型进行的:
BTF_KFUNCS_START(bpf_task_set) BTF_ID_FLAGS(func, bpf_get_task_pid, KF_ACQUIRE | KF_RET_NULL) BTF_ID_FLAGS(func, bpf_put_pid, KF_RELEASE) BTF_KFUNCS_END(bpf_task_set) static const struct btf_kfunc_id_set bpf_task_kfunc_set = { .owner = THIS_MODULE, .set = &bpf_task_set, }; static int init_subsystem(void) { return register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_task_kfunc_set); } late_initcall(init_subsystem);其中btf_kfunc_id_set结构(含.owner模块所有者与.set指向的btf_id_set8集合)定义在 include/linux/btf.h。
在内核构建时,resolve_btfids工具会找出所有用BTF_KFUNCS_START()声明的 kfunc,并把 BTF 注解发射进内核 BTF:为每个 kfunc 发射bpf_kfuncBTF decl tag;当 kfunc 带KF_FASTCALL标志时额外发射bpf_fastcalldecl tag;对使用 arena 指针的返回值/参数(见 2.3.8 与 2.8 节)发射address_space(1)类型属性。
实际例子:在 kernel/bpf/helpers.c 中定义了generic_btf_ids、common_btf_ids等集合,并在 kernel/bpf/helpers.c 中将generic_kfunc_set注册到BPF_PROG_TYPE_TRACING、BPF_PROG_TYPE_SCHED_CLS、BPF_PROG_TYPE_XDP、BPF_PROG_TYPE_STRUCT_OPS、BPF_PROG_TYPE_SYSCALL、BPF_PROG_TYPE_CGROUP_SKB等多种程序类型,将common_kfunc_set注册到BPF_PROG_TYPE_UNSPEC,可见同一套 kfunc 可同时暴露给多种程序类型。
2.7 用___init指定 no-cast 别名
verifier 始终强制 BPF 程序传给 kfunc 的指针 BTF 类型与 kfunc 定义中的指针类型匹配;但按 C 标准等价的类型即使 BTF ID 不同,也允许传给同一 kfunc 参数。
例如对如下类型:
struct bpf_cpumask { cpumask_t cpumask; refcount_t usage; };verifier 允许把struct bpf_cpumask *传给接收cpumask_t *(struct cpumask *的 typedef)的 kfunc,例如struct cpumask *和struct bpf_cpumask *都可传给bpf_cpumask_test_cpu()。
但某些场景不希望出现这种类型别名行为,struct nf_conn___init即为一例:
struct nf_conn___init { struct nf_conn ct; };按 C 标准两者等价,但把任一种传给可信 kfunc 并不总是安全的:struct nf_conn___init表示一个已分配但尚未初始化的struct nf_conn,把struct nf_conn___init *传给期望完整初始化struct nf_conn *的 kfunc(如bpf_ct_change_timeout())是不安全的。
为此,当两个类型名称完全相同、其中一个带___init后缀时,verifier 会强制严格的PTR_TO_BTF_ID类型匹配。
2.8 通过 kfunc 参数访问 arena 内存
arena 内任意地址的读写不会导致内核 oops。未分配的 arena 页由 scratch page 惰性支撑,访问错误会通过程序的 BPF stream 报告为错误——只影响 BPF 程序本身的正确性,内核保持完好。
arena 之后跟随一个GUARD_SZ / 2(32 KiB)的 guard 区域,同样覆盖该恢复机制。因此,kfunc 拿到 arena 指针后,可以在不做边界检查的情况下最多访问指针之后GUARD_SZ / 2的范围;更大的访问必须显式校验范围。
3. kfunc 生命周期预期(Lifecycle Expectations)
kfunc 提供的是"内核 ↔ 内核"API,不受内核 ↔ 用户 UAPI 严格稳定性限制的约束。它们可类比EXPORT_SYMBOL_GPL,所在子系统的维护者认为必要时可以修改或移除。
与任何内核变更一样,维护者不会无故修改或移除 kfunc。是否变更取决于多种因素:kfunc 被使用的广泛程度、在内核中存在的时间、是否存在替代 kfunc、所在子系统的稳定性惯例,以及继续支持它的技术成本。
由此带来几个推论:
a)广泛使用或存在已久的 kfunc,被维护者修改或移除的难度更大。拥有大量用户、价值显著的 kfunc 会激励维护者投入时间与复杂度去支持它们。因此在 BPF 程序中使用 kfunc 的开发者,应主动说明这些 kfunc 的使用方式与原因,并在上游讨论发生时积极参与。
b)与EXPORT_SYMBOL_GPL导出的常规内核符号不同,调用 kfunc 的 BPF 程序一般不属于内核源码树,所以 kfunc 变更时无法像上游驱动那样就地修改调用方。这是 BPF 符号的预期行为,树外 BPF 程序的使用应被视为修改/移除决策的相关因素,BPF 社区会在必要时积极参与上游讨论,确保此类用户的视角被考虑。
c)kfunc永远不会获得硬性稳定性保证。BPF API 不会、也永远不会仅因稳定性理由硬性阻止内核变更。是否修改或移除 kfunc 是多变量技术决策,基于上述数据点逐案做出。无警告地移除或变更 kfunc 不会是常见情况,且必然有充分理由——但使用 kfunc 就必须接受这种可能性。
3.1 kfunc 弃用流程
虽然维护者有时必须立即修改/移除 kfunc 以适配子系统变更,但通常 kfunc 会经历更长、更稳妥的弃用过程。例如:新 kfunc 提供优于旧 kfunc 的功能时,旧 kfunc 可能被弃用一段时间以允许用户迁移;若某 kfunc 没有已知用户,也可能在弃用期后直接移除(不提供替代 API),给用户留出向维护者声明实际使用的窗口。
常见路径是 kfunc 先经历弃用期而非无警告变更。一旦标记 KF_DEPRECATED,按如下流程移除:
- 弃用 kfunc 的相关信息记录在其 kernel doc 中,通常包括预期剩余寿命、可替代的新功能建议(或为何无替代品的解释)等;
- 弃用 kfunc 在标记后于内核中保留一段时间,时长逐案决定,通常取决于使用广泛度、存在时间与迁移难度。该弃用期是"尽力而为",特殊情况下可能在完整弃用期结束前移除;
- 弃用期结束后移除 kfunc,此后调用它的 BPF 程序会被 verifier 拒绝。
4. 核心 kfunc(Core kfuncs)
BPF 子系统提供若干"核心" kfunc,可适用于各种用例与程序,文档见 Documentation/bpf/kfuncs.rst。以下结合源码剖析核心实现。
4.1 struct task_struct * 相关 kfunc
有一组 kfunc 允许把struct task_struct *对象当作 kptr 使用:bpf_task_acquire与bpf_task_release(kernel doc 见 kernel/bpf/helpers.c)。它们适用于对 tracepoint 参数或 struct_ops 回调参数传入的struct task_struct *获取/释放引用。
/** * A trivial example tracepoint program that shows how to * acquire and release a struct task_struct * pointer. */ SEC("tp_btf/task_newtask") int BPF_PROG(task_acquire_release_example, struct task_struct *task, u64 clone_flags) { struct task_struct *acquired; acquired = bpf_task_acquire(task); if (acquired) /* * In a typical program you'd do something like store * the task in a map, and the map will automatically * release it later. Here, we release it manually. */ bpf_task_release(acquired); return 0; }源码实现印证了文档语义:bpf_task_acquire()通过refcount_inc_not_zero(&p->rcu_users)尝试递增引用计数,失败返回 NULL;bpf_task_release()调用put_task_struct_rcu_user(p)释放引用(见 kernel/bpf/helpers.c)。
在struct task_struct *对象上获取的引用受RCU 保护。因此在 RCU 读区域内,可以不经获取引用直接访问 map value 中嵌入的 task 指针:
#define private(name) SEC(".data." #name) __hidden __attribute__((aligned(8))) private(TASK) static struct task_struct *global; /** * A trivial example showing how to access a task stored * in a map using RCU. */ SEC("tp_btf/task_newtask") int BPF_PROG(task_rcu_read_example, struct task_struct *task, u64 clone_flags) { struct task_struct *local_copy; bpf_rcu_read_lock(); local_copy = global; if (local_copy) /* * We could also pass local_copy to kfuncs or helper functions here, * as we're guaranteed that local_copy will be valid until we exit * the RCU read region below. */ bpf_printk("Global task %s is valid", local_copy->comm); else bpf_printk("No global task found"); bpf_rcu_read_unlock(); /* At this point we can no longer reference local_copy. */ return 0; }BPF 程序还可以按 pid 查找 task。当调用方没有可对其执行bpf_task_acquire()的可信struct task_struct *指针时,这一能力非常有用:
SEC("tp_btf/task_newtask") int BPF_PROG(task_get_pid_example, struct task_struct *task, u64 clone_flags) { struct task_struct *lookup; lookup = bpf_task_from_pid(task->pid); if (!lookup) /* A task should always be found, as %task is a tracepoint arg. */ return -ENOENT; if (lookup->pid != task->pid) { /* bpf_task_from_pid() looks up the task via its * globally-unique pid from the init_pid_ns. Thus, * the pid of the lookup task should always be the * same as the input task. */ bpf_task_release(lookup); return -EINVAL; } /* bpf_task_from_pid() returns an acquired reference, * so it must be dropped before returning from the * tracepoint handler. */ bpf_task_release(lookup); return 0; }其实现位于 kernel/bpf/helpers.c:在rcu_read_lock()保护下通过find_task_by_pid_ns(pid, &init_pid_ns)在根 pid 命名空间的 idr 中查找,找到后调用bpf_task_acquire()获取引用再返回——所以返回的是已获取引用的指针,调用方必须释放。同文件还提供了按当前命名空间 vpid 查找的bpf_task_from_vpid()(kernel/bpf/helpers.c)。
4.2 struct cgroup * 相关 kfunc
struct cgroup *对象同样有 acquire/release 函数:bpf_cgroup_acquire与bpf_cgroup_release(kernel doc 见 kernel/bpf/helpers.c),用法与 task 版本完全一致,此处不再举例。
其他可用 kfunc:bpf_cgroup_ancestor()用于访问 cgroup 的祖先,bpf_cgroup_from_id()用于按 ID 查找 cgroup,两者都返回 cgroup kptr。理想情况下 BPF 应允许程序内直接内存加载完成此事,但目前 verifier 还做不到。bpf_cgroup_ancestor()用法示例:
/** * Simple tracepoint example that illustrates how a cgroup's * ancestor can be accessed using bpf_cgroup_ancestor(). */ SEC("tp_btf/cgroup_mkdir") int BPF_PROG(cgrp_ancestor_example, struct cgroup *cgrp, const char *path) { struct cgroup *parent; /* The parent cgroup resides at the level before the current cgroup's level. */ parent = bpf_cgroup_ancestor(cgrp, cgrp->level - 1); if (!parent) return -ENOENT; bpf_printk("Parent id is %d", parent->self.id); /* Return the parent cgroup that was acquired above. */ bpf_cgroup_release(parent); return 0; }其实现位于 kernel/bpf/helpers.c,bpf_cgroup_from_id()位于同文件 kernel/bpf/helpers.c。注意文档注释强调:若在 RCU 读区域内调用bpf_cgroup_release(),即使 cgroup 引用计数降为 0,也能保证在当期宽限期结束前不会被释放。
4.3 struct cpumask * 相关 kfunc
BPF 提供一组用于查询、分配、修改和销毁struct cpumask *对象的 kfunc,详见文档 Documentation/bpf/cpumasks.rst(cpumasks文档)。这也是 2.7 节类型别名(struct bpf_cpumask与struct cpumask)讨论的实际使用场景之一。
结语
kfunc 是内核向 BPF 生态开放强大能力的重要机制,代价是接口不稳定。理解其定义、注解、标志与注册全流程,能帮助你写出类型安全、经得起 verifier 严格检查的 BPF 程序,也能让你在面临 kfunc 演化时做出正确的迁移决策。进一步阅读可参考同目录下的 Documentation/bpf/ 相关文档(如 cpumasks、arena 等专题),以及本文反复引用的 include/linux/btf.h、include/linux/btf_ids.h、kernel/bpf/helpers.c 与 kernel/bpf/verifier.c 源码。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考