LWN:BPF 通用iterator的实现!

关注了就能看到更多这么棒的文章哦~

Generic iterators for BPF

By Jonathan Corbet
March 17, 2023
DeepL assisted translation
https://lwn.net/Articles/926041/

会被加载到内核中的 BPF 程序通常是用 C 语言编写的,但是,这些程序运行的环境跟 C 语言环境差异越来越大了。BPF 虚拟机和相关的 verifier 现在进行了越来越多的检查,希望让使 BPF 代码可以更安全地运行。在 BPF 中增加 iterator(迭代器)机制的建议突出了这个特征,也突出显示了 BPF 对程序员的限制。

BPF verifier 在程序加载时所进行的许多检查之一,就是要能确保程序会在一个合理的时间段内终止,这个过程需要模拟程序执行过程。这种限制使得在 BPF 程序中支持循环就是一个很有挑战性的事情;从 5.3 版本开始才有可能使用循环。即使增加了这个功能,要说服验证器相信一个循环会终止也是一个挑战;这个麻烦就因出了像 bpf_loop()这样的新的功能,它把一些简单情况下的循环逻辑放到内核的 C 代码中。

不过,并不是所有的问题都可以通过像 bpf_loop() 这样的简单函数来解决。BPF 程序中的许多循环只是在一组对象中进行 iterate 迭代,而 BPF 开发者希望有更简单的方法来完成这些目标。虽然许多语言都有某种内置的对一个集合进行迭代的概念,但 C 语言并没有。如上所述,BPF 并不是真正的 C 语言,Andrii Nakryiko 的一个 patch set 就通过向 BPF 虚拟机添加 iteration 机制重申了(reiterate)这一点。

在支持特定类型的 iteration 概念的那些语言中,通常有一组方法来实现新的迭代器类型;它们可以被认为是 "开始 iteration"、"下一个项目 "和 "结束 iteration"。这里提出的 BPF 机制也遵循这一模式。支持迭代的代码必须在内核中编写(用真正的 C 语言),它必须提供四样东西,首先是一个表示迭代器本身的结构类型;这个结构的大小必须是 8 字节的倍数。迭代器结构将有一个类似 bpf_iter_foo 这样的名字,并且将包含迭代器维持其状态所需的任何数据。

"new" 函数(或 "构造函数")必须被称为 bpf_iter_foo_new()。它的第一个参数将是一个 iteration 类型的结构(必须在 BPF 程序中声明以及实例化);它可以接受任意数量的其它参数。这个函数需要初始化 iterator,并返回 0 或负数错误码;如果初始化失败,iterator 仍然必须要正确地设置,以便后续调用来实现正确行为。

"下一项 " 的这个函数是 bpf_iter_foo_next();它接受 iterator 作为其唯一的参数,并返回一个指向下一个元素的指针(无论 iterator 是使用什么类型)。即使只是返回一个普通整数的 iterator 也必须返回一个指向该整数的指针。返回一个空指针就表示迭代已经完成,或者出现了某种错误。

bpf_iter_foo_destroy()函数(destructor)将 iterator 结构的指针作为唯一的参数,并返回 void;它完成了 iteration,并进行必需的清理。

所有这些函数都必须被声明为 kfuncs,会用一些 flag 来表示它们的特殊作用。构造函数必须标记为 KF_ITER_NEW,next 函数标记为 KF_ITER_NEXT|KF_ITER_NULL,而析构函数标记为 KF_ITER_DESTROY。

有了这个基础结构,verifier 就可以对 iterator 进行一系列检查,首先要求构造函数必须在任何其他操作之前被调用。对 next 函数的调用将被检查,以确保程序最终会有迭代结束的 null 返回值。verifier 确保在最后调用析构函数,此后不再访问这个 iterator。它还使用 type 信息来确保一个特定类型的 iterator 只被传递给声明为处理该类型的函数。

BPF 子系统对实现 iterator 的 C 代码也有一些要求,包括下一个函数必须在合理的调用次数后返回 null 的规则。由于 verifier 不能知道一个 iterator 所执行的循环可能运行多少次,因此它对 BPF 程序执行的指令数量进行强制限制的能力就被削弱了;iterator 必须通过不让程序无限期地运行来提供帮助。

该系列的 patch 增加了一个机制,强制确保 iterator 类型(必须以 bpf_iter_开头)和相关函数的命名规则,这些函数必须通过在 iterator 类型名称后面加上_new(), _next(), 或_destroy()来构建。每个函数的参数和返回类型也被检查;如果检查失败,函数的注册也会失败。

这个实现的一个有点是,就 verifier 而言,iterator 是可以完全自我描述的。具体来说,这意味着将来不需要改变 verifier 本身来增加新的 iterator 类型,只要它们符合这种模式。

作为所有这些工作的例子,这组 patch 包括了一个 "number" iterator 例子,它简单地遍历一系列的整数。在 BPF 里使用的时候就像这样:

struct bpf_iter_num it;
int *v;

bpf_iter_num_new(&it, 2, 5);
while ((v = bpf_iter_num_next(&it))) {
    bpf_printk("X = %d", *v);
}
bpf_iter_num_destroy(&it);

这段代码执行的循环体中 *v 会持有从 2 到 4 的数值,包括这两端。

当然,以这种方式遍历一个 count 并没有什么特别让人激动的功能;毕竟已经可以用 bpf_loop()来完成相同功能了,或者在上面这种情况下,通过编写一个 for 循环就可以完成。我们希望这个功能有更多的高级用例,也许是在 extensible scheduler class 中,但这并没有在 patch 中给出明确说明。这一些可以等该 patch 合并后再出现;鉴于目前缺乏明显的反对意见,估计很快就会合并。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

format,png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值