微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

eBPF - 加载、附加和链接之间的区别? 正在加载附加链接固定总结

如何解决eBPF - 加载、附加和链接之间的区别? 正在加载附加链接固定总结

我对 eBPF 系统调用bpf 函数中使用的 libbpf 术语感到非常困惑。有人可以打破我对使用 bpf 加载/附加进程的理解是错误的吗?


我的理解:

我一直在查看 cgroups代码,因此我将使用它作为示例。我知道 cgroup 的 bpf 程序存储在 cgroup_bpf->effective[bpf_attach_type] 中(参见 herehere)。我知道,当从调用它们的任何处理程序(例如,在本例中为函数 __cgroup_bpf_run_filter_sysctl()调用 proc_sys_call_handler()(在 cgroup.c 中)之类的函数时,这些程序会被访问和运行。但是这些程序是如何加载/存储/附加/链接的,每个术语之间有什么区别?

加载 - 显然,这需要发生 before attaching。我猜这是 cgroup 程序存储在通用位置的地方(即 notcgroup_bpf->effective[bpf_attach_type] 中),因此尚未附加/链接以在内核中执行。因此,加载与 bpf_attach_type 完全分开。如果我是对的,cgroup 程序类型的通用加载位置在哪里?

附加 - 也许这就是程序与 bpf_attach_type 相关联的地方。就像这样,现在我们采用通用存储的程序,现在我们可以使用我们想要的任何 cgroup_bpf->effective[bpf_attach_type] 将它放入 bpf_attach_type 数组中。然后,当到达内核中相应的附加点时,运行程序。

链接 - 这只是一种特殊类型的附件吗?这与正常的连接有何不同?我看到 link_create()cgroup_bpf_prog_attach() 在使用 cgroup 程序类型时最终调用cgroup_bpf_attach()

解决方法

让我们看看...

正在加载

加载程序包括通过 bpf(BPF_PROG_LOAD,...) 系统调用(对于大多数程序类型)将其指令注入内核。程序通过验证器,验证器运行许多检查并可能重写一些指令(特别是对于地图访问)。如果启用了 JIT 编译,则程序可能是 JIT 编译的。内核内存中定义的程序a struct bpf_prog object 包含(或指向)有关程序的信息,包括其 eBPF 字节码和(如果相关)JIT 编译的指令。

在这个过程结束时,程序位于内核内存中。它不依附于特定对象。它有一个引用计数器,内核会一直保持它直到计数器归零。引用可以由文件描述符保存到程序:例如,一个由 bpf() 系统调用返回到加载应用程序。可以通过附加、链接、固定程序或(如果我没记错的话)在 prog_array 映射中引用它来创建其他引用。如果没有引用保留(例如,加载应用程序在加载程序后立即退出,从而关闭其指向该程序的文件描述符),则将其从内核中删除。

“附加类型”的概念取决于程序类型。一些程序类型没有这个概念:XDP 程序只是附加到接口的 XDP 钩子上。附加到 cgroup 的程序确实有一个“附加类型”,它指明程序的确切位置。

加载程序大多与这些附加类型分开。但是,某些程序类型 - 不是全部 - do require the user to pass the expected attach type at load time,通过 expected_attach_type 对象的 union bpf_attr 字段传递给 bpf() 系统调用。验证程序和系统调用处理程序使用这种预期的附加类型来执行各种验证。

附加

您对附件步骤的理解听起来不错。根据它的附加和/或程序类型,程序被“附加”到它应该运行的钩子上。在您的情况下,相关的内核结构 cgroup_bpf->effective 将指向程序(不是 store 它 - 程序不会移动,cgroup_bpf->effective 只是指向 {{ 1}}),在这个钩子上发生的事件会触发程序。

请注意,对于某些程序类型,例如网络或 cgroup-attached 程序,附加程序会增加其引用计数器,以便加载应用程序可以退出,而不会将该程序从内核中删除。对于其他一些程序类型,例如kprobes,这不足以保持程序打开,因为附加是基于stuct bpf_prog *返回的文件描述符来保持程序附加,并且一个进程需要保持运行以保持此文件描述符打开。

链接

当加载应用程序关闭时,我们如何保持 eBPF 探测器运行?这就是 eBPF 链接发挥作用的地方。 eBPF 程序可以附加到链接而不是传统的钩子。链接本身附加到内核挂钩。这为操作程序提供了更好的界面。一个优点是可以pin这样的链接,以便在加载应用程序退出时保持 eBPF 探测器运行。另一个优点是更容易跟踪程序中保存的引用,并确保在加载应用程序意外退出时没有 eBPF 程序保持加载状态。

链接是“特殊类型的附件”吗?我不确定。查看代码,似乎跟踪钩子现在总是与新内核上的链接一起使用。对于其他程序类型,eBPF 链接提供的接口是后来添加的,似乎与传统钩子并驾齐驱。例如,对于 cgroup,您可以以旧方式附加程序(通过 perf_event_open() 您可以加载它们、创建 eBPF 链接并将您的程序附加到该链接(通过 {{1 }}) - 正如您所观察到的,在这两种情况下,您最终都会运行 cgroup_bpf_prog_attach()

我认为目前没有关于 eBPF 链接的好的文档,所以我们最好的可能是来自补丁集的求职信和提交日志:

eBPF 链接不要与用于在字节码加载到内核之前存储字节码的 ELF 目标文件的链接混淆。例如,libbpf 能够链接多个包含各种 eBPF 函数或子程序或其他对象的对象文件,并生成包含所有这些对象的单个输出 ELF 文件。这与“bpf_link”接口无关。

固定

Pinning 是一种保存对 eBPF 对象(程序、映射或链接)的引用的方法。它是通过 link_create() 系统调用完成的,它在 the eBPF virtual file system 中创建一个路径,并且稍后可以通过 cgroup_bpf_attach()-ing 该路径检索对象的文件描述符。只要一个对象被固定,它就会保留在内核中。无需固定程序或地图即可运行它。只要存在其他引用(文件描述符,或者程序附加到某些钩子;或者对于映射,它们被现有程序引用......),程序仍然加载在内核内存中,如果附加,它可以运行。

特别是,固定 eBPF 链接可确保附加到该链接的程序在其加载应用程序终止并关闭其文件描述符后仍保持加载状态。

总结

  • 加载: 将程序注入内核,验证器启动,它可能会重写一些指令并链接到相关的内部 eBPF 对象(BTF、映射等),JIT 编译可能会发生。字段bpf(BPF_OBJ_PIN,...) 可能是必要的。
  • 附加:程序附加到与其程序类型相关的钩子,使用提供的附加类型(如果相关)。
  • 链接:根据程序类型或如果需要,程序会附加到 eBPF 链接,而不是直接附加到其常规附加点。该链接附加到常规钩子上,并提供更灵活的界面来管理程序。
  • 固定:可以将程序或链接(或地图)固定到 bpff 以使其持久化(但不能在重新启动后保持不变)。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。