找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3207

积分

0

好友

414

主题
发表于 2026-2-12 19:38:09 | 查看: 30| 回复: 0

内核的 kfunc 机制是一种用于将内核函数直接暴露给 BPF 程序调用的方法。在当前的 Linux 内核中,存在超过 300 个 kfunc,其功能范围非常广泛,涵盖了从字符串处理(例如 bpf_strnlen())到自定义调度器(例如 scx_bpf_kick_cpu())等诸多领域。有时候,这些 kfunc 需要访问某些 BPF 程序无法直接获取的上下文信息,因此也就无法作为常规参数传递进来。Ihor Solodrai 最近提交的隐式参数补丁集正是为了解决这个问题而进行的又一次尝试。

BPF子系统将大量上下文信息维护在一个庞大的 struct bpf_prog_aux 结构体中。多年以来,这个结构体不断扩展,几乎囊括了所有可能需要的信息,例如程序正在使用的 BPF map 列表、加载以来的时长、当前的栈深度以及数十个其他字段。你可以将其看作是某些面向对象编程语言中,传递给方法的 selfthis 对象的对等物。许多 kfunc 需要使用这个结构体,但 BPF 程序自身却无法直接访问它。

那么,BPF 子系统过去是如何处理这个问题的呢?答案是通过一种并不算特别优雅的方式——__prog 注解。我们以被定义为宏的 bpf_stream_vprintk() 为例来看看:

#define bpf_stream_printk(stream_id, fmt, args...)                 \
    ({                                                             \
        static const char ___fmt[] = fmt;                          \
        unsigned long long ___param[___bpf_narg(args)];            \
                                                                    \
        ___bpf_fill(___param, args);                               \
        bpf_stream_vprintk_impl(stream_id, ___fmt, ___param,       \
                                sizeof(___param), NULL);           \
    })

这个宏会将传入的 args 组合打包起来,然后调用下面这个定义的 bpf_stream_vprintk_impl 函数:

__bpf_kfunc int bpf_stream_vprintk_impl(int stream_id, const char *fmt__str,
                                        const void *args, u32 len__sz,
                                        void *aux__prog);

BPF 验证器(BPF verifier) 看到最后一个参数名称以 __prog 结尾时,它便会执行一个“魔术操作”:将传入的任何内容(在上述宏中就是 NULL)神奇地替换为指向 bpf_prog_aux 结构体的指针。这种方法虽然能实现功能,但对于不熟悉内情的开发者来说,其背后的逻辑相当不直观。

Solodrai 在 2025 年底最初尝试通过“魔术函数”(magic function)抽象来推广这种机制。在当时的实现中,__prog 后缀被改为了 __magic,并且内核版本的 API(带有 __impl 后缀的版本)是自动生成的。这个想法本身获得了不少认可,但具体的实现细节收到了很多反馈建议。因此,这套补丁集从那时起已经经过了多次演进。

根据目前的工作进展,需要隐式参数的 kfunc 现在可以像普通的内核调用函数一样进行声明了。在应用该补丁集之后,bpf_stream_vprintk() 的声明如下所示:

__bpf_kfunc int bpf_stream_vprintk(int stream_id, const char *fmt__str,
                                   const void *args, u32 len__sz,
                                   struct bpf_prog_aux *aux);

看,现在指向 bpf_prog_aux 结构体的指针 aux 已经没有任何“魔法”后缀装饰了。那么,如何告知 BPF 子系统这个 kfunc 的特殊性呢?答案是通过在声明它时附加一个标志,如下所示:

BTF_ID_FLAGS(func, bpf_stream_vprintk, KF_IMPLICIT_ARGS)

KF_IMPLICIT_ARGS 这个标志就表示该函数拥有一个对 BPF 程序不可见的隐式参数。

当内核加载一个包含一个或多个 kfunc 调用的 BPF 程序时,验证器会在内核构建时生成的 BPF 类型格式(BPF Type Format, BTF)数据中查找这些 kfunc。这些 BTF 数据描述了 kfunc 的原型等信息,使验证器能够确保 BPF 程序的调用是正确无误的。这套补丁系列使得内核的构建过程能够以两种方式修改为 KF_IMPLICIT_ARGS kfunc 生成的 BTF 数据:

  • BTF 数据中会增加一个新函数的条目,该条目使用 kfunc 的名称并附加了 _impl 后缀。此条目具有声明的 kfunc 完整原型,并链接到内核中的实际函数实现。
  • 同时,使用原始名称(不带 _impl 后缀)的 kfunc 条目会被修改,从而省略掉最后一个参数(即 struct bpf_prog_aux *)。这就是最终用于与 BPF 程序进行匹配的 BTF 数据。

因此,每当验证器处理一个带有隐式参数的 kfunc 调用时,它会悄悄修改这个调用,将 bpf_prog_aux 结构体指针作为最后一个参数添加进去。这本质上是将 BPF 程序看到的“简化版” API 映射到内核中实际存在的“完整版”实现上。最终,BPF 程序可以使用直观、干净的 API 来调用 kfunc,而不需要那些包装宏来隐藏后台的复杂工作;同时,内核函数也可以按照其实际被使用的名称和原型来声明。

KF_IMPLICIT_ARGS 使用的是复数形式,这意味着将来可能会支持多个隐式参数。不过,目前唯一有效的用法就是将 struct bpf_prog_aux 指针作为最后一个参数。Solodrai 表示,如果未来证明这还不够,“将其扩展到更多类型也很简单”。但鉴于目前显而易见的做法是把所有可能需要的东西都塞进 struct bpf_prog_aux 这个“百宝箱”里,我们或许永远不需要其他类型的隐式参数。

最后,你可能会想,为什么不从一开始就隐式地把 bpf_prog_aux 指针传递给所有的 kfunc 呢?就目前而言,这个新特性将使那些真正需要它的 kfunc 能够轻松且透明地获取到该指针。实际上,已经有一些其他的补丁(例如 sched_ext 子调度器系列)依赖于此特性,因此它很可能在不久的将来被合并到主线内核中。对此技术方向感兴趣的朋友,可以关注云栈社区的相关讨论,了解更多关于内核和网络编程的深度内容。




上一篇:VITA-E 双模型架构如何实现机器人实时并行交互?具身智能新突破
下一篇:网系回收技术首测:中国航天以“隔空彩排”开启火箭回收新赛道
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-23 11:43 , Processed in 0.609694 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表