LWN:BPF 里新的共享内存方案!
共 3773字,需浏览 8分钟
·
2024-04-11 05:08
关注了就能看到更多这么棒的文章哦~
A proposal for shared memory in BPF programs
By Daroc Alden
February 21, 2024
Gemini translation
https://lwn.net/Articles/961941/
2021 年 2 月 6 日,Alexei Starovoitov 提交了一系列的补丁(patch series)给 Linux 内核,以添加 bpf_arena
,这是一种新的共享内存类型,用于 BPF 程序与用户空间之间的通信。Starovoitov 期望这种 arena 对于用户空间和 BPF 程序之间的双向通信以及作为 BPF 程序的额外堆空间将会非常有用。这有助于直接实现复杂数据结构而不依赖于内核提供的 BPF 程序。Starovoitov 以 Google的ghOSt项目为例,这个项目是他的工作的一个例子和灵感来源。
BPF 程序已经有几种与用户空间通信的方式,包括环形缓冲区、哈希映射和数组映射。然而,每种方法都存在一些问题。环形缓冲区(ring buffer)可以用于将性能测量或跟踪事件发送到用户空间进程,但不能从用户空间接收数据。哈希映射(hash map)可以用于此目的,但从用户空间访问它们需要进行bpf()系统调用。数组映射(array map)可以使用mmap()将它们映射到用户空间进程的地址空间中,但 Starovoitov 指出它们的“缺点是数组的整个内存在开始时就被预留下来”。数组映射(以及新的 arena)存储在不可分页的内核内存中,所以未使用的页面会产生明显的资源利用效率上的浪费。
他的补丁系列允许 BPF 程序创建最多 4GB 大小的 arena。与数组映射不同,这些 arena 不会预先分配页面。BPF 程序可以使用 bpf_arena_alloc_pages()
向 arena 添加页面,并且当用户空间程序在 arena 内触发页面错误(page fault)时,页面将被自动添加进来。
无缝指针(seamless pointers)
该补丁系列以一种不寻常的方式处理 arena 内的指针,确保 arena 内部的结构可以指向 arena 的其他区域,并且这对用户空间程序和 BPF 程序都可以无缝地工作,而不需要意识到存在隐式的转换。尽管这两种程序完全有不同的指针表示,但它们都不需要意识到存在隐式转换。BPF 程序将 arena 内的指针表示为 32 位指针,处于单独的地址空间中(验证器 BPF verifier 会确保其不用作普通指针,反之也一样),但用户空间程序将指针视为其体系结构的普通指针。用户空间表示实际存储在 arena 内存中。内核映射 arena 空间时,保证用户空间指针的低 32 位始终与 BPF 指针匹配,以加快两种表示之间的转换。
例如,这个系列包括一个程序作为测试套件的一部分,在 BPF 中实现了一个哈希表,使用链表来把属于同一类的项目串起来。可以在内核中完成哈希表的生成,然后从用户空间访问,反之亦然,两者都能够跟随数据结构中的指针。
该补丁系列引入了两个函数 bpf_cast_kern()
和 bpf_cast_user()
,用于在内核表示的指针和用户空间表示的指针之间进行转换。有一个相关的补丁来为LLVM的BPF 后端自动插入正确的转换动作,以确保用户空间版本是存储在 arena 内存中的版本。该补丁系列引入了一个新的标志( BPF_F_NO_USER_CONV
),以允许 BPF 程序关闭此行为。未执行指针转换的 arena 仍然可以映射到用户空间,但用户空间程序将无法跟随其中包含的指针。
审查意见
Barret Rhoden指出了这种转换实现的一个细节问题。补丁系列的初始版本在 arena 中留下了一个空洞(取决于 arena 在用户空间的映射位置),以便 BPF 不会生成以 0x00000000 结尾的指针。这样的对象在转换为 32 位指针时,在 BPF 程序中具有全零表示,这可能与空指针混淆并引发问题。Rhoden 指出,如果 BPF 程序尝试访问缺失的页面,将会引发页面错误并终止程序,他指出如果有人意外使用了这个零页面,我们将遇到更严重的问题。Starovoitov 同意了这一观点,并表示他将在系列的第二版中删除缺失的页面,因为该逻辑“在带来更多伤害而不是好处”。移除 arena 中的空洞后,BPF 程序需要避免将对象放在 0 地址处然后尝试获取指向它的指针,这可以通过在 arena 开头添加一些填充来轻松实现。
确保内核和用户空间就 arena 指针的低 32 位达成一致是有必要的,因为这可以使 BPF 即时(JIT)编译器生成的代码更简单,因此更快。如果用户空间可以将 arena 映射到任意地址,就像这个补丁系列的初始版本中的情况一样,这将使得内核中 arena 的表示相对复杂一些,并且可能需要额外的逻辑来清晰地处理 arena 地址的环绕。Rhoden 和 Starovoitov 继续讨论这个细节,并最终得出结论:没有理由支持将 arenas 真正映射到任意地址。Rhoden评论说:“将 4GB 映射对齐到 4GB 边界的限制非常合理。”
Lorenzo Stoakes对补丁系列中的分配页方式提出了异议,因为它使用vmap_pages_range()来为 arena 分配页面,而这是内核虚拟内存分配器的一个内部函数。 Stoakes 说:“我在 Vmap()中看到很多检查,而 vmap_pages_range()中并没有。”他问“我们是否可以将其公开给你以及其他核心内核用户?”
Johannes Weiner 回应说“vmap API 通常是公开的”,并且“新的 BPF 代码需要 vmap_pages_range()的功能,以便将分别管理的页面数组逐步映射到其 vmap 区域”。他接着指出,在其他外部使用该函数的代码消失后,该函数从原来的公开改成了私有。Christoph Hellwig在对话的另一部分表示不满:“我们需要保持 vmalloc 内部细节只在内部,并且不要在我们将其大致形成之后破坏这些抽象。”
在审查 BPF 代码的内部更改时,Andrii Nakryiko对新的arenas如何计算其大小提出了关切。现有的 BPF 映射记录键的大小、值的大小和可以容纳在映射中的总条目数。这对于哈希映射和数组映射效果很好,但对于新的 arenas 来说不太合适。Starovoitov 决定将 arenas 表示为具有 8 字节键和值大小的形式,“以便将来能够扩展它并允许 map_lookup/update/delete 执行一些有用的操作”。Nakryiko 断言他们“应该可能使 bpf_map_mmap_sz()意识到特定的 map 类型,并根据此进行不同的计算”,并指出 arenas 不太可能使用正常的 BPF 接口来进行 map 条目的查找。
Donald Hunter 质疑为什么在代码中将 arenas 表示为一种新的 map 类型,问是否“这是唯一可以重用内核/用户空间连接的方式?”Starovoitov 回答说,现有的 BPF 程序可用的许多映射都不支持某些映射操作。尤其是布隆过滤器(bloom filter)和环形缓冲区(两种现有的映射类型,在某些方面与新的 arenas 相似)不支持查找、更新或删除操作。他继续说,arenas“可能是我们将添加的最后一种映射,因为几乎任何算法都可以在 arena 中实现”。
Starovoitov 迅速吸收了这些反馈意见,并发布了补丁系列的第二版。然而,他没有解决 Hellwig 对暴露虚拟内存分配代码底层细节的担忧。Hellwig重申他的立场,称:“vmap 区域不能随意被调用者滥用”。Starovoitov回应说,如果暴露 vmap_pages_range()
函数是无法接受的,Hellwig 应该提出一个替代方案。Linus Torvalds 插话说,提出可接受的解决方案不是维护人员的责任;“需要提出新需求的人才需要寻找一个可接受的解决方案”。
对于这个版本的补丁系列的讨论仍在进行中,但除了 Hellwig 关于暴露虚拟内存分配代码的底层细节的担忧之外,其他大部分问题都相对较小,或已经得到解决。在 BPF 程序和用户空间代码之间能够无缝共享内存是一个有吸引力的前景,因此很可能这项工作最终会完成实现,即便需要找到一种新的方式来按需为 BPF arena 分配页面。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~