尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Linux BPF kfunc 完全指南:从内核函数暴露、参数注解到生命周期管理

Linux BPF kfunc 完全指南:从内核函数暴露、参数注解到生命周期管理 Linux BPF kfunc 完全指南从内核函数暴露、参数注解到生命周期管理【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux导读kfuncBPF 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. 什么是 kfunckfunc 是内核中专门暴露给 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 指针表示指针参数可以为 NULLverifier 允许为该参数传入 NULL__bpf_kfunc void bpf_task_release(struct task_struct *task__nullable) { ... }task 指针可能为 NULLkfunc 自身负责在解引用前检查 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与__mapmap 参数两者都用于struct bpf_map *参数区分verifier 已知的 map与不透明 map__const_mapmap 必须在校验期已知即 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__nullablearena 指针参数两者都表示指针参数指向调用程序的 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 不能声明为statickfunc 可能被定义它的编译单元之外的 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 注册 kfuncRegistering the kfuncskfunc 准备好之后最后一步是向 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 / 232 KiB的 guard 区域同样覆盖该恢复机制。因此kfunc 拿到 arena 指针后可以在不做边界检查的情况下最多访问指针之后GUARD_SZ / 2的范围更大的访问必须显式校验范围。3. kfunc 生命周期预期Lifecycle Expectationskfunc 提供的是内核 ↔ 内核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. 核心 kfuncCore kfuncsBPF 子系统提供若干核心 kfunc可适用于各种用例与程序文档见 Documentation/bpf/kfuncs.rst。以下结合源码剖析核心实现。4.1 struct task_struct * 相关 kfunc有一组 kfunc 允许把struct task_struct *对象当作 kptr 使用bpf_task_acquire与bpf_task_releasekernel 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 youd 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)尝试递增引用计数失败返回 NULLbpf_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 were 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 * 相关 kfuncstruct cgroup *对象同样有 acquire/release 函数bpf_cgroup_acquire与bpf_cgroup_releasekernel doc 见 kernel/bpf/helpers.c用法与 task 版本完全一致此处不再举例。其他可用 kfuncbpf_cgroup_ancestor()用于访问 cgroup 的祖先bpf_cgroup_from_id()用于按 ID 查找 cgroup两者都返回 cgroup kptr。理想情况下 BPF 应允许程序内直接内存加载完成此事但目前 verifier 还做不到。bpf_cgroup_ancestor()用法示例/** * Simple tracepoint example that illustrates how a cgroups * 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 cgroups 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.cbpf_cgroup_from_id()位于同文件 kernel/bpf/helpers.c。注意文档注释强调若在 RCU 读区域内调用bpf_cgroup_release()即使 cgroup 引用计数降为 0也能保证在当期宽限期结束前不会被释放。4.3 struct cpumask * 相关 kfuncBPF 提供一组用于查询、分配、修改和销毁struct cpumask *对象的 kfunc详见文档 Documentation/bpf/cpumasks.rstcpumasks文档。这也是 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),仅供参考
返回列表