如何与BPF子系统交互

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

有关提交补丁的通用信息,请参阅提交补丁:将代码提交到内核的基本指南。本文档仅描述与 BPF 相关的额外细节。

报告错误

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

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

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

鉴于 netdev 流量很大,请同时将 BPF 维护者添加到抄送(Cc)列表(来自内核 MAINTAINERS 文件):

如果已经确定了有问题的提交,请务必在报告中也将实际的提交作者添加到抄送列表。通常可以通过内核的 Git 仓库识别他们。

请勿将 BPF 问题报告到 bugzilla.kernel.org,因为这几乎可以保证报告的问题会被忽视。

提交补丁

问:在发送更改进行审查之前,如何对我的更改运行 BPF CI?

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

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

  • 在您自己的账户中创建上述仓库的 fork(一次性操作)

  • 在本地克隆 fork,检出跟踪 bpf-next 或 bpf 分支的新分支,并在其之上应用您要测试的补丁。

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

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

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

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

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

如果您的补丁在多个不同的子系统(例如,网络、跟踪、安全等)中有更改,请务必抄送相关的内核邮件列表和维护者,以便他们能够审查这些更改并为补丁提供 Acked-by 签名。

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

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

那些针对 BPF 的补丁,被分配给“bpf”委托人,由 BPF 维护者进一步处理。当前正在审查的补丁队列可在以下链接找到:

一旦补丁经过整个 BPF 社区的审查并获得 BPF 维护者的批准,它们在 patchwork 中的状态将更改为“Accepted”(已接受),并且提交者将通过邮件收到通知。这意味着这些补丁从 BPF 的角度来看是好的,并且已被应用于两个 BPF 内核分支之一。

如果社区的反馈需要重新提交补丁,它们在 patchwork 中的状态将设置为“Changes Requested”(请求更改),并从当前审查队列中清除。同样适用于补丁被拒绝或不适用于 BPF 分支(但已分配给“bpf”委托人)的情况。

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

答:有两个 BPF 内核分支(Git 仓库)。一旦补丁被 BPF 维护者接受,它们将被应用于这两个 BPF 分支中的一个:

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

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-like”内容,以便维护者知道它是针对 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 拉取请求被 David S. Miller 接受,那么这些补丁将分别进入 net 或 net-next 分支,并从那里进一步进入主线。再次强调,请参阅 netdev 子系统的文档 网络子系统 (netdev) 以获取更多信息,例如它们多久合并到主线一次。

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

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

问:你们多久向 net 或 net-next 等主要内核分支发送拉取请求?

答:为了避免 bpf 或 bpf-next 中累积过多的补丁,拉取请求会相当频繁地发送。

通常来说,预计每个分支的拉取请求会在周末定期发送。在某些情况下,根据当前的补丁负载或紧急程度,拉取请求也可能在周中发出。

问:在合并窗口开放时,补丁会应用于 bpf-next 吗?

答:在合并窗口开放期间,bpf-next 将不会被处理。这大致类似于 net-next 的补丁处理方式,因此请随意查阅 netdev 文档 网络子系统 (netdev) 以获取更多细节。

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

对于未订阅内核邮件列表的用户,David S. Miller 在 net-next 上也运行了一个状态页面,提供指导:

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

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

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

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

问:samples/bpf 与 selftests 的选择?

问:何时应该将代码添加到 samples/bpf/,何时添加到 BPF 内核 selftests

答:通常,我们倾向于将代码添加到 BPF 内核 selftests,而不是 samples/bpf/。理由很简单:内核 selftests 会定期由各种机器人运行,以测试内核回归。

我们添加到 BPF selftests 的测试用例越多,覆盖率就越好,它们意外损坏的可能性就越小。这并不是说 BPF 内核 selftests 不能演示如何使用特定功能。

话虽如此,samples/bpf/ 可能是人们入门的好地方,因此建议将简单功能的演示代码放入 samples/bpf/,但高级功能和边缘情况测试则应放入内核 selftests。

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

问:何时应该将代码添加到 bpftool?

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

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

答:对于与 XDP 或 tc 层(例如 cls_bpf)相关的 UAPI 更改,约定是这些控制路径相关的更改也从用户空间侧添加到 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 分支。iproute2 net-next 分支将在 master 中当前的 iproute2 版本发布后合并到 master 分支。

与 BPF 类似,这些补丁最终会进入 netdev 项目下的 patchwork,并委托给“shemminger”进行进一步处理:

问:在提交 BPF 补丁之前,最低要求是什么?

答:提交补丁时,请务必花时间并在提交之前充分测试您的补丁。切勿急于提交!如果维护者发现您的补丁没有经过充分测试,这很容易让他们不高兴。测试补丁提交是一项硬性要求!

请注意,进入 bpf 分支的修复必须包含 Fixes: 标签。这同样适用于目标为 bpf-next 的修复,其中受影响的提交在 net-next(或某些情况下在 bpf-next)中。Fixes: 标签对于识别后续提交至关重要,并且对需要进行反向移植的人员有巨大帮助,因此它是必不可少的!

我们也不接受提交信息为空的补丁。请花时间认真编写高质量的提交信息,这至关重要!

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

包含多个补丁的提交必须附有封面信,其中包含对系列的高级描述。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_INTERNAL”符号命名空间的作用是什么?

答:导出为 BPF_INTERNAL 的符号只能由 BPF 基础设施使用,例如带有轻量骨架的预加载内核模块。BPF_INTERNAL 之外的大多数符号也不期望被 BPF 外部的代码使用。符号可能缺乏此指定,因为它们早于命名空间,或者由于疏忽。

稳定版提交

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

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

如果尚未应用,请发送电子邮件给 BPF 维护者,并将 netdev 内核邮件列表添加到抄送,请求将该修复排队。

总的来说,该过程与 netdev 本身相同,另请参阅网络子系统的文档:网络子系统 (netdev)

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

答:不。如果您需要在当前未由稳定版维护者维护的内核中包含某个特定的 BPF 提交,那么您需要自行处理。

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

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

我该怎么做?

答:与一般的 netdev 补丁提交规则相同,请参阅 netdev 文档 网络子系统 (netdev)

切勿在补丁描述中添加“Cc: stable@vger.kernel.org”,而是请求 BPF 维护者将补丁排队。这可以通过在补丁的 --- 部分下方添加注释来完成,该注释不会进入 Git 日志。或者,也可以通过简单的邮件请求来完成。

问:稳定版补丁队列

问:在哪里可以找到当前已排队等待提交到稳定版的 BPF 补丁?

答:一旦修复关键错误的补丁被应用到 bpf 分支,它们就会在以下链接排队等待提交到稳定版:

它们将至少在那里等待,直到相关提交进入主线内核分支。

在获得更广泛的关注后,排队的补丁将由 BPF 维护者提交给稳定版维护者。

测试补丁

问:如何运行 BPF selftests

答:启动到新编译的内核后,导航到 BPF selftests 套件以测试 BPF 功能(当前工作目录指向克隆的 Git 仓库的根目录):

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

要运行验证器测试:

$ sudo ./test_verifier

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

Summary: 418 PASSED, 0 FAILED

为了运行所有 BPF selftests,需要以下命令:

$ sudo make run_tests

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

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

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

https://github.com/acmel/dwarves

pahole 在 commit 21507cd3e97b(“pahole: add libbpf as submodule under lib/bpf”)之后,从 v1.13 版本开始使用 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 内核 selftests 版本?

答:如果您运行的是内核 xyz,那么也请始终运行来自该内核 xyz 的 BPF 内核 selftests。不要期望最新主线分支中的 BPF selftest 会一直通过。

特别是,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 问题

问:我是否应该通知 BPF 内核维护者关于 LLVM BPF 代码生成后端中的问题,或者验证器拒绝接受的 LLVM 生成代码的问题?

答:是的,请务必这样做!

LLVM 的 BPF 后端是整个 BPF 基础设施的关键部分,它与内核侧的程序验证紧密相连。因此,无论哪一方出现问题,都需要在必要时进行调查和修复。

因此,请务必在 netdev 内核邮件列表中提出这些问题,并抄送(Cc)BPF 维护者以处理 LLVM 和内核相关部分:

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 内核 selftests 使用 -mcpu=probe 运行以获得更好的测试覆盖率。

问:用于目标 bpf 的 clang 标志?

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

答:尽管 LLVM IR 的生成和优化试图保持与架构无关,但 --target=<arch> 仍然对生成的代码有一定影响:

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

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

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

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

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

  • 您的程序包含一个头文件,例如 ptrace.h,该头文件最终会引入一些包含文件范围主机汇编代码的头文件。

  • 您可以添加 -fno-jump-tables 来解决跳转表问题。

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

  • 您的程序使用指针或 long / unsigned long 类型的、与 BPF 助手或上下文数据结构交互的数据结构。对这些结构的访问由 BPF 验证器验证,如果原生架构与 BPF 架构(例如 64 位)不一致,可能会导致验证失败。例如,BPF_PROG_TYPE_SK_MSG 要求使用 --target=bpf

祝您 BPF 编程愉快!