如何与 BPF 子系统交互

本文档为 BPF 子系统提供有关报告错误、提交补丁以及为稳定内核排队补丁的各种工作流程的信息。

有关提交补丁的一般信息,请参阅提交补丁:将代码放入内核的必要指南。本文档仅描述与 BPF 相关的其他细节。

报告错误

问:如何报告 BPF 内核代码的错误?

答:由于所有 BPF 内核开发以及 bpftool 和 iproute2 BPF 加载器开发都是通过 bpf 内核邮件列表进行的,因此请将发现的任何与 BPF 相关的问题报告给以下邮件列表

这可能还包括与 XDP、BPF 跟踪等相关的问题。

鉴于 netdev 的流量很大,请还将 BPF 维护人员添加到抄送(来自内核 MAINTAINERS 文件)

如果已经识别出有错误的提交,请确保在报告中也抄送实际的提交作者。他们通常可以通过内核的 git 树来识别。

请不要向 bugzilla.kernel.org 报告 BPF 问题,因为可以保证报告的问题会被忽略。

提交补丁

问:如何在发送 BPF 更改以供审核之前运行 BPF CI?

答:BPF CI 是基于 GitHub 的,托管在 https://github.com/kernel-patches/bpf。虽然 GitHub 也提供了可用于完成相同结果的 CLI,但在这里我们专注于基于 UI 的工作流程。

以下步骤说明如何为您的补丁启动 CI 运行

  • 在您自己的帐户中创建上述存储库的分支(一次性操作)

  • 在本地克隆该分支,检出一个新分支,跟踪 bpf-next 或 bpf 分支,并将您的待测试补丁应用在其之上

  • 将本地分支推送到您的分支,并针对 kernel-patches/bpf 的 bpf-next_base 或 bpf_base 分支分别创建拉取请求

创建拉取请求后不久,CI 工作流程将运行。请注意,容量与提交的上游补丁共享,因此,根据利用率,运行可能需要一段时间才能完成。

此外请注意,两个基本分支(bpf-next_base 和 bpf_base)将随着补丁被推送到它们跟踪的相应上游分支而更新。因此,您的补丁集也将自动(尝试)进行变基。此行为可能导致 CI 运行被中止并使用新的基线重新启动。

问:我需要将 BPF 补丁提交到哪个邮件列表?

答:请将您的 BPF 补丁提交到 bpf 内核邮件列表

如果您的补丁在各种不同的子系统(例如,网络、跟踪、安全等)中都有更改,请确保也抄送相关的内核邮件列表和维护人员,以便他们能够查看更改并为补丁提供其 Acked-by。

问:在哪里可以找到当前正在讨论的 BPF 子系统的补丁?

答:所有抄送给 netdev 的补丁都在 netdev patchwork 项目下排队等待审核

那些针对 BPF 的补丁被分配给“bpf”委托,由 BPF 维护人员进一步处理。当前正在审核的补丁队列可以在

一旦补丁已由整个 BPF 社区审核,并经 BPF 维护人员批准,其在 patchwork 中的状态将更改为“已接受”,并且提交者将通过邮件收到通知。这意味着补丁从 BPF 的角度来看效果良好,并且已应用到两个 BPF 内核树中的一个。

如果社区的反馈需要重新修改补丁,则其在 patchwork 中的状态将被设置为“需要更改”,并从当前的审核队列中清除。对于补丁将被拒绝或不适用于 BPF 树(但分配给“bpf”委托)的情况也是如此。

问:这些更改如何进入 Linux?

答:有两个 BPF 内核树(git 存储库)。一旦 BPF 维护人员接受了补丁,它们将被应用到两个 BPF 树中的一个

bpf 树本身仅用于修复,而 bpf-next 用于功能、清理或其他类型的改进(“类似于 next”的内容)。这类似于网络的 net 和 net-next 树。bpf 和 bpf-next 都将只有一个 master 分支,以便简化应该将补丁重新变基到的分支。

bpf 树中累积的 BPF 补丁将定期拉入 net 内核树中。同样,bpf-next 树中接受的累积 BPF 补丁将进入 net-next 树中。net 和 net-next 均由 David S. Miller 运行。从那里,它们将进入由 Linus Torvalds 运行的内核主线树。要阅读有关 net 和 net-next 合并到主线树的过程,请参阅有关 netdev 子系统的文档,网址为网络子系统 (netdev)

偶尔,为了防止合并冲突,我们可能会向其他树(例如跟踪)发送包含少量补丁的拉取请求,但 net 和 net-next 始终是集成目标的主要树。

拉取请求将包含累积补丁的高级摘要,并且可以通过以下主题行在 netdev 内核邮件列表中搜索(yyyy-mm-dd是拉取请求的日期)

pull-request: bpf yyyy-mm-dd
pull-request: bpf-next yyyy-mm-dd

问:我如何指定我的补丁应该应用到哪个树(bpf 还是 bpf-next)?

答:这个过程与 netdev 子系统文档中描述的完全相同,请参考 网络子系统 (netdev)。主题行必须指明补丁是修复还是“类似 next”的内容,以便让维护者知道它是针对 bpf 还是 bpf-next。

对于最终会进入 bpf -> net 树的修复,主题必须类似于

git format-patch --subject-prefix='PATCH bpf' start..finish

对于最终应该进入 bpf-next -> net-next 的特性/改进等,主题必须类似于

git format-patch --subject-prefix='PATCH bpf-next' start..finish

如果不确定补丁或补丁系列应该直接进入 bpf 或 net,还是直接进入 bpf-next 或 net-next,如果主题行说明目标是 net 或 net-next 也没问题。最终由维护者来决定补丁的分配。

如果明确补丁应该进入 bpf 或 bpf-next 树,请确保将补丁基于这些树进行 rebase,以减少潜在的冲突。

如果补丁或补丁系列必须进行修改并再次发送,也需要在主题前缀中添加版本号(v2, v3, ...)。

git format-patch --subject-prefix='PATCH bpf-next v2' start..finish

当对补丁系列提出更改请求时,始终将整个补丁系列连同反馈一起重新发送(永远不要在旧系列之上发送单独的 diff)。

问:当一个补丁被应用到 bpf 或 bpf-next 树时,意味着什么?

答:这意味着从 BPF 的角度来看,该补丁看起来适合包含在主线中。

请注意,这并不是最终判决,该补丁最终会自动被接受到 net 或 net-next 树中。

在 bpf 内核邮件列表中,评论可能随时出现。如果围绕某个补丁的讨论得出结论,认为它们不能按原样包含,我们将应用后续修复或将其完全从树中删除。因此,我们也会在认为必要时保留 rebase 树的权利。毕竟,树的目的是

  1. 积累和暂存 BPF 补丁,以便集成到 net 和 net-next 等树中,以及

  2. 在补丁进一步提交之前,在补丁上运行广泛的 BPF 测试套件和工作负载。

一旦 BPF pull request 被 David S. Miller 接受,补丁就会分别进入 net 或 net-next 树,并从那里进一步进入主线。同样,请参阅 网络子系统 (netdev) 的文档,了解更多信息,例如它们合并到主线的频率。

问:我需要等待多久才能收到 BPF 补丁的反馈?

答:我们尽量保持低延迟。通常的反馈时间约为 2 或 3 个工作日。这可能会因更改的复杂性和当前的补丁负载而有所不同。

问:你们多久向 net 或 net-next 等主要内核树发送 pull request?

答:pull request 会很频繁地发送,以避免在 bpf 或 bpf-next 中积累太多补丁。

通常情况下,预计每个树的 pull request 会在每周结束时定期发送。在某些情况下,根据当前的补丁负载或紧急程度,pull request 也可能会在一周中间发送。

问:合并窗口打开时,补丁会应用到 bpf-next 吗?

答:在合并窗口打开期间,bpf-next 将不会被处理。这与 net-next 补丁处理大致类似,因此请随时阅读 netdev 文档 网络子系统 (netdev) 以了解更多详细信息。

在合并窗口的这两周内,我们可能会要求您在 bpf-next 再次开放后重新发送您的补丁系列。一旦 Linus 在合并窗口后发布 v*-rc1,我们就会继续处理 bpf-next。

对于非内核邮件列表的订阅者,David S. Miller 还在 net-next 上运行一个状态页面,提供指导

问:验证器更改和测试用例

问:我做了一个 BPF 验证器更改,我需要为 BPF 内核 自测试 添加测试用例吗?

答:如果补丁更改了验证器的行为,那么是的,绝对有必要向 BPF 内核 自测试 套件添加测试用例。如果它们不存在,而我们认为需要它们,那么我们可能会在接受任何更改之前要求它们。

特别是,test_verifier.c 正在跟踪大量的 BPF 测试用例,包括 LLVM BPF 后端可能从受限的 C 代码中生成的大量极端情况。因此,添加测试用例对于确保未来的更改不会意外影响以前的用例至关重要。因此,将这些测试用例视为:未在 test_verifier.c 中跟踪的验证器行为可能会发生更改。

问:samples/bpf 的偏好 vs 自测试?

问:我应该何时将代码添加到 samples/bpf/,何时添加到 BPF 内核 自测试

答:一般来说,我们更倾向于向 BPF 内核 自测试 添加内容,而不是 samples/bpf/。原因很简单:内核自测试会定期由各种机器人运行,以测试内核回归。

我们添加到 BPF 自测试的测试用例越多,覆盖率就越高,并且那些意外中断的可能性就越小。并不是说 BPF 内核自测试不能演示如何使用特定功能。

也就是说,samples/bpf/ 可能是人们入门的好地方,因此建议将功能的简单演示放入 samples/bpf/,但高级功能和极端情况测试则放入内核自测试中。

如果您的示例看起来像一个测试用例,那么请选择 BPF 内核自测试!

问:我应该何时向 bpftool 添加代码?

答:bpftool(位于 tools/bpf/bpftool/ 下)的主要目的是为调试和内省内核中活动的 BPF 程序和映射提供一个中心用户空间工具。如果与 BPF 相关的 UAPI 更改允许转储程序或映射的附加信息,那么也应该扩展 bpftool 以支持转储它们。

问:我应该何时向 iproute2 的 BPF 加载器添加代码?

答:对于与 XDP 或 tc 层相关的 UAPI 更改(例如 cls_bpf),约定是将这些与控制路径相关的更改也从用户空间添加到 iproute2 的 BPF 加载器中。这不仅有助于使 UAPI 更改在设计上易于使用,而且还可以使这些更改可用于更广泛的下游发行版用户群。

问:你们也接受 iproute2 的 BPF 加载器的补丁吗?

答:iproute2 的 BPF 加载器的补丁必须发送到

虽然这些补丁不是由 BPF 内核维护者处理的,但也请将它们抄送给他们,以便他们可以进行审查。

iproute2 的官方 git 存储库由 Stephen Hemminger 运行,可以在以下位置找到:

补丁的主题前缀必须为 ‘[PATCH iproute2 master]’ 或 ‘[PATCH iproute2 net-next]’。‘master’ 或 ‘net-next’ 描述了补丁应该应用到的目标分支。这意味着,如果内核更改进入了 net-next 内核树,那么相关的 iproute2 更改需要进入 iproute2 net-next 分支,否则它们可以以 master 分支为目标。在发布 master 中的当前 iproute2 版本后,iproute2 net-next 分支将合并到 master 分支中。

与 BPF 类似,这些补丁最终会出现在 netdev 项目下的 patchwork 中,并被委托给 'shemminger' 进行进一步处理

问:提交 BPF 补丁的最低要求是什么?

答:提交补丁时,请始终花时间在提交 *之前* 正确测试您的补丁。永远不要匆忙!如果维护者发现您的补丁没有经过适当的测试,那么很容易让他们感到不快。测试补丁提交是一项硬性要求!

请注意,进入 bpf 树的修复 *必须* 包含 Fixes: 标签。对于以 bpf-next 为目标的修复也是如此,其中受影响的提交位于 net-next(或在某些情况下位于 bpf-next)中。Fixes: 标签对于识别后续提交至关重要,并且极大地帮助了需要进行反向移植的人员,因此它是必须的!

我们也不接受带有空提交消息的补丁。花点时间正确撰写高质量的提交消息,这至关重要!

这样想:一个月后查看您的代码的其他开发人员需要理解 *为什么* 以这种方式进行某些更改,以及原始作者的分析或假设中是否存在缺陷。因此,提供适当的理由并描述更改的用例是必须的。

包含 >1 个补丁的补丁提交必须包含一封涵盖系列高层描述的 cover letter。此高层摘要随后将由 BPF 维护者放入合并提交中,以便将来也可以从 git 日志中访问。

问:更改 BPF JIT 和/或 LLVM 的功能

问:当添加一个需要 BPF JIT 和/或 LLVM 集成的新指令或功能时,我需要考虑什么?

答:我们努力保持所有 BPF JIT 的更新,以确保在不同架构上运行 BPF 程序时,如果启用了内核中的 BPF JIT,可以保证相同的用户体验,而不会导致程序回退到效率较低的解释器。

如果您无法为某些架构实现或测试所需的 JIT 更改,请与相关的 BPF JIT 开发人员合作,以便及时实现该功能。请参考 git 日志 (arch/*/net/) 来找到可以提供帮助的必要人员。

同时,始终确保为新指令添加 BPF 测试用例(例如 test_bpf.c 和 test_verifier.c),以便它们可以获得广泛的测试覆盖,并帮助在运行时测试各种 BPF JIT。

对于新的 BPF 指令,一旦更改被 Linux 内核接受,请在 LLVM 的 BPF 后端实现支持。有关更多信息,请参阅下面的 LLVM 部分。

稳定提交

问:我需要在稳定内核中包含特定的 BPF 提交。我应该怎么做?

答:如果您需要在稳定内核中包含特定的修复程序,请首先检查该提交是否已应用于相关的 linux-*.y 分支

如果不是这种情况,请发送电子邮件给 BPF 维护人员,并将 netdev 内核邮件列表抄送,并要求将该修复程序排队

一般来说,该过程与 netdev 本身相同,另请参阅有关网络子系统的文档,网址为 网络子系统 (netdev)

问:你们还会向当前未作为稳定版维护的内核进行反向移植吗?

答:不会。如果您需要在当前未由稳定版维护人员维护的内核中包含特定的 BPF 提交,那么您只能自己处理。

当前的稳定版和长期稳定版内核都在此处列出

问:我即将提交的 BPF 补丁也需要进入稳定版

我应该怎么做?

答:与通常的 netdev 补丁提交适用相同的规则,请参阅 网络子系统 (netdev) 中的 netdev 文档。

永远不要在补丁描述中添加“Cc: [email protected]”,而是要求 BPF 维护人员将补丁排队。这可以通过在补丁的 --- 部分下的注释来完成,该注释不会进入 git 日志。或者,这也可以通过邮件以简单的请求来完成。

问:队列稳定版补丁

问:在哪里可以找到当前排队的 BPF 补丁,这些补丁将提交到稳定版?

答:一旦修复关键错误的补丁应用到 bpf 树中,它们就会排队等待在下面提交到稳定版

它们将在那里至少保持到相关的提交进入主线内核树为止。

在经过更广泛的暴露后,排队的补丁将由 BPF 维护人员提交给稳定版维护人员。

测试补丁

问:如何运行 BPF 自测

答:在您启动到新编译的内核后,请导航到 BPF 自测套件,以便测试 BPF 功能(当前工作目录指向克隆的 git 树的根目录)

$ cd tools/testing/selftests/bpf/
$ make

要运行验证器测试

$ sudo ./test_verifier

验证器测试会打印出当前正在执行的所有检查。运行所有测试结束时的摘要将转储测试成功和失败的信息

Summary: 418 PASSED, 0 FAILED

为了运行所有 BPF 自测,需要以下命令

$ sudo make run_tests

有关详细信息,请参阅 内核自测文档

为了最大限度地提高通过的测试数量,被测内核的 .config 应尽可能与 tools/testing/selftests/bpf 中的配置文件片段匹配。

最后,为了确保支持最新的 BPF 类型格式功能 - 在 BPF 类型格式 (BTF) 中讨论 - 对于使用 CONFIG_DEBUG_INFO_BTF=y 构建的内核,需要 pahole 版本 1.16。pahole 在 dwarves 软件包中提供,也可以从以下位置的源代码构建

https://github.com/acmel/dwarves

commit 21507cd3e97b(“pahole:将 libbpf 添加为 lib/bpf 下的子模块”)之后,pahole 开始使用 libbpf 定义和 API。它与 git 存储库配合良好,因为 libbpf 子模块将使用“git submodule update --init --recursive”进行更新。

不幸的是,默认的 github 发布源代码不包含 libbpf 子模块源代码,这会导致构建问题,来自 https://git.kernel.org/pub/scm/devel/pahole/pahole.git/ 的 tarball 与 github 相同,您可以从以下位置获取带有相应 libbpf 子模块代码的源代码 tarball

https://fedorapeople.org/~acme/dwarves

一些发行版已经打包了 pahole 版本 1.16,例如 Fedora、Gentoo。

问:我应该针对哪个版本的 BPF 内核自测运行我的内核?

答:如果您运行内核 xyz,则始终从该内核 xyz 运行 BPF 内核自测。不要期望来自最新主线树的 BPF 自测始终会通过。

特别是,test_bpf.c 和 test_verifier.c 有大量的测试用例,并且会不断更新新的 BPF 测试序列,或者现有的测试序列会适应验证器的更改,例如,由于验证器变得更智能并且能够更好地跟踪某些内容。

LLVM

问:在哪里可以找到支持 BPF 的 LLVM?

答:LLVM 的 BPF 后端自 3.7.1 版本起已在上游 LLVM 中。

现在所有主要发行版都提供了启用了 BPF 后端的 LLVM,因此对于大多数用例,不再需要手动编译 LLVM,只需安装发行版提供的软件包即可。

LLVM 的静态编译器通过 llc --version 列出支持的目标,请确保列出了 BPF 目标。例如

$ llc --version
LLVM (https://llvm.net.cn/):
  LLVM version 10.0.0
  Optimized build.
  Default target: x86_64-unknown-linux-gnu
  Host CPU: skylake

  Registered Targets:
    aarch64    - AArch64 (little endian)
    bpf        - BPF (host endian)
    bpfeb      - BPF (big endian)
    bpfel      - BPF (little endian)
    x86        - 32-bit X86: Pentium-Pro and above
    x86-64     - 64-bit X86: EM64T and AMD64

对于开发人员来说,为了利用添加到 LLVM 的 BPF 后端的最新功能,建议运行最新的 LLVM 版本。对新 BPF 内核功能(例如 BPF 指令集)的支持通常是共同开发的。

所有 LLVM 版本都可以在以下位置找到:http://releases.llvm.org/

问:明白了,那么无论如何我该如何手动构建 LLVM?

答:我们建议希望获得最快增量构建的开发人员使用 Ninja 构建系统,您可以在系统的软件包管理器中找到它,通常该软件包是 ninja 或 ninja-build。

您需要 ninja、cmake 和 gcc-c++ 作为 LLVM 的构建必备条件。一旦您设置好这些,请继续从 git 存储库构建最新的 LLVM 和 clang 版本

$ git clone https://github.com/llvm/llvm-project.git
$ mkdir -p llvm-project/llvm/build
$ cd llvm-project/llvm/build
$ cmake .. -G "Ninja" -DLLVM_TARGETS_TO_BUILD="BPF;X86" \
           -DLLVM_ENABLE_PROJECTS="clang"    \
           -DCMAKE_BUILD_TYPE=Release        \
           -DLLVM_BUILD_RUNTIME=OFF
$ ninja

然后可以在 build/bin/ 目录中找到构建的二进制文件,您可以在其中将 PATH 变量指向该目录。

-DLLVM_TARGETS_TO_BUILD 设置为等于您希望构建的目标,您将在 llvm-project/llvm/lib/Target 目录中找到完整的目标列表。

问:报告 LLVM BPF 问题

问:我应该将 LLVM 的 BPF 代码生成后端中的问题或验证器拒绝接受的 LLVM 生成的代码通知给 BPF 内核维护人员吗?

答:是的,请这样做!

LLVM 的 BPF 后端是整个 BPF 基础设施的关键部分,它与内核侧的程序验证紧密相关。因此,需要调查并修复任何一方的问题(如有必要)。

因此,请务必在 netdev 内核邮件列表中提出这些问题,并将 LLVM 和内核部分的 BPF 维护人员抄送。

LLVM 还有一个问题跟踪器,可以在其中找到与 BPF 相关的错误

但是,最好通过邮件列表联系,并将维护人员抄送。

问:内核和 LLVM 的新 BPF 指令

问:我在内核中添加了一条新的 BPF 指令,如何将其集成到 LLVM 中?

答:LLVM 为 BPF 后端提供了一个 -mcpu 选择器,以便选择 BPF 指令集扩展。默认情况下,使用 generic 处理器目标,它是 BPF 的基本指令集 (v1)。

LLVM 有一个选项可以选择 -mcpu=probe,它将探测主机内核以查找支持的 BPF 指令集扩展,并自动选择最佳集。

对于交叉编译,也可以手动选择特定版本

$ llc -march bpf -mcpu=help
Available CPUs for this target:

  generic - Select the generic processor.
  probe   - Select the probe processor.
  v1      - Select the v1 processor.
  v2      - Select the v2 processor.
[...]

新添加到 Linux 内核的 BPF 指令需要遵循相同的方案,增加指令集版本并实现对扩展的探测,以便 -mcpu=probe 用户在升级内核时可以透明地受益于优化。

如果您无法实现对新添加的 BPF 指令的支持,请联系 BPF 开发人员寻求帮助。

顺便说一下,BPF 内核自测以 -mcpu=probe 运行,以获得更好的测试覆盖率。

问:目标 bpf 的 clang 标志?

问:在某些情况下,使用了 clang 标志 --target=bpf,而在其他情况下,则使用了与底层架构匹配的默认 clang 目标。这两者之间有什么区别?我应该在什么情况下使用哪一个?

答:尽管 LLVM IR 的生成和优化力求保持架构独立,--target=<arch> 仍然会对生成的代码产生一些影响。

  • BPF 程序可能会递归包含带有文件作用域内联汇编代码的头文件。默认目标可以很好地处理这种情况,而如果 bpf 后端汇编器不理解这些汇编代码(在大多数情况下都是如此),则 bpf 目标可能会失败。

  • 当在不使用 -g 的情况下编译时,使用默认目标的对象文件中可能会存在额外的 elf 节,例如 .eh_frame 和 .rela.eh_frame,但使用 bpf 目标则不会。

  • 默认目标可能会将 C 的 switch 语句转换为 switch 表查找和跳转操作。由于 switch 表被放置在全局只读节中,bpf 程序将无法加载。bpf 目标不支持 switch 表优化。可以使用 clang 选项 -fno-jump-tables 来禁用 switch 表的生成。

  • 对于 clang --target=bpf,可以保证指针或 long / unsigned long 类型始终具有 64 位的宽度,无论底层 clang 二进制文件或默认目标(或内核)是 32 位的。但是,当使用本机 clang 目标时,它将根据底层架构的约定编译这些类型,这意味着在 32 位架构的情况下,例如在 BPF 上下文结构中的指针或 long / unsigned long 类型将具有 32 位的宽度,而 BPF LLVM 后端仍然以 64 位进行操作。在跟踪情况下,当需要遍历 pt_regs 或其他内核结构时,CPU 的寄存器宽度很重要,此时主要需要本机目标。否则,通常建议使用 clang --target=bpf

在以下情况下,你应该使用默认目标:

  • 你的程序包含一个头文件,例如 ptrace.h,它最终会引入一些包含文件作用域主机汇编代码的头文件。

  • 你可以添加 -fno-jump-tables 来解决 switch 表的问题。

否则,你可以使用 bpf 目标。此外,在以下情况下,你必须使用 bpf 目标:

  • 你的程序使用与 BPF 辅助函数或上下文数据结构交互的带有指针或 long / unsigned long 类型的数据结构。BPF 验证器会验证对这些结构的访问,如果本机架构与 BPF 架构(例如 64 位)不一致,则可能会导致验证失败。一个例子是 BPF_PROG_TYPE_SK_MSG 需要 --target=bpf

祝你 BPF 编程愉快!