【eBPF-04】进阶:BCC 框架中 BPF 映射的应用 v2.0——尾调用

这篇具有很好参考价值的文章主要介绍了【eBPF-04】进阶:BCC 框架中 BPF 映射的应用 v2.0——尾调用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

这两天有空,继续更新一篇有关 eBPF BCC 框架尾调用的内容。

eBPF 技术很新,能够参考的中文资料很少,而对于 BCC 框架而言,优秀的中文介绍和教程更是凤毛麟角。我尝试去网上检索有关尾调用的中文资料,BCC 框架的几乎没有。即使找到了,这些资料也难以给出可供参考和正确运行的例子。

BCC 框架的中文资料也就图一乐,真正有指导意义的,还得去看 Brendan Gregg 大神的博客和 bcc 项目。

既然如此,我来抛砖引玉,就简单介绍一下 eBPF 尾调用在 BCC 框架中是如何应用的吧。

1 何为尾调用?

引用 ebpf.io 网站的一句介绍:“尾调用允许 eBPF 调用和执行另一个 eBPF 并替换执行上下文,类似于一个进程执行 execve() 系统调用的方式。”

也就是说,尾调用之后,函数不会再返回给调用者了。

那么,eBPF 为什么要使用尾调用呢?这是因为,eBPF 的运行栈太有限了(仅有 512 字节),在递归调用函数时(实际上是向运行栈中一节一节地添加栈帧),很容易导致栈溢出。而尾调用恰恰允许在不增加堆栈的情况下,调用一系列函数。这是非常有效且实用的。

你可以使用下面的辅助函数来增加一个尾调用:

long bpf_tail_call(void *ctx, struct bpf_map *prog_array_map, u32 index)

其三个参数的含义分别是:

  • ctx 向被调用者传递当前 eBPF 程序的上下文信息。
  • prog_array_map 是一个程序数组(BPF_MAP_TYPE_PROG_ARRAY)类型的 eBPF map,用于记录一组 eBPF 程序的文件描述符。
  • index 为程序数组中需要调用的 eBPF 程序索引。

2 如何使用尾调用?

关于 BCC 框架,reference_guide.md 给出了一个例子。见 27.map.call()

内核态程序:

// example.c
BPF_PROG_ARRAY(prog_array, 10);			// A)定义程序数组

int tail_call(void *ctx) {
    bpf_trace_printk("Tail-call\n");
    return 0;
}

int do_tail_call(void *ctx) {
    bpf_trace_printk("Original program\n");
    prog_array.call(ctx, 2);			// B)调用 ID 为 2 的函数
    return 0;
}

用户态程序:

b = BPF(src_file="example.c")
tail_fn = b.load_func("tail_call", BPF.KPROBE)		# C)尾调用函数定义
prog_array = b.get_table("prog_array")
prog_array[c_int(2)] = c_int(tail_fn.fd)		# D)绑定尾调用函数
b.attach_kprobe(event="some_kprobe_event", fn_name="do_tail_call")

代码解释:

A)尾调用的实现,基于 程序数组(BPF_PROG_ARRAY) 这一映射结构。程序数组也是一个键值对结构(废话,它也是 BPF_MAP 类型之一)。其 key 为自定义索引,用于寻找对应的调用程序;其 value 为尾调用函数的文件描述符 fd

B)调用尾调用函数需要执行 call() 方法,传入程序数组(BPF_PROG_ARRAY)中的 key,用来查找对应的函数 fd。

C)尾调用函数的定义在用户态完成。注意这里有一个易错点:尾调用需要和父调用保持相同的程序类型(这里是 BPF.KPROBE)。

D)绑定尾调用函数到程序数组中。不再赘述。

尾调用示意图如下图:
【eBPF-04】进阶:BCC 框架中 BPF 映射的应用 v2.0——尾调用

3 实现一个尾调用程序

明白尾调用则怎么玩之后,接下来,我们一起实现一个稍微复杂一点的尾调用,用来监视系统调用。

例子改编自《Learning eBPF》一书。目前该书还没有中文版本。

内核态程序:

// tail_hello.c
BPF_PROG_ARRAY(syscall, 300);					// A

int hello(struct bpf_raw_tracepoint_args *ctx) {		// B
    int opcode = ctx->args[1];					// C
    syscall.call((void *)ctx, opcode);				// D
    return 0;
}

int hello_exec(void *ctx) {					// E
    bpf_trace_printk("Executing a program\n");
    return 0;
}

int hello_timer(struct bpf_raw_tracepoint_args *ctx) {		// F
    int opcode = ctx->args[1];
    switch (opcode) {
        case 222:
            bpf_trace_printk("Creating a timer\n");
            break;
        case 226:
            bpf_trace_printk("Deleting a timer\n");
            break;
        default:
            bpf_trace_printk("Some other timer operation\n");
            break;
    }
    return 0;
}

代码解释:

【A】BPF_PROG_ARRAY 宏定义,对应映射类型 BPF_MAP_TYPE_PROG_ARRAY。在这里,命名为 syscall,容量为 300。

【B】即将被用户态代码绑定在 sys_enter 类别的 Tracepoint 上,即当有任何系统调用被执行时,都会触发这个函数。bpf_raw_tracepoint_args 类型的结构体 ctx 存放上下文信息。

译者注:sys_enterraw_syscalls 类型的 Tracepoint;同族还有 sys_exit

详细信息可查看文件:/sys/kernel/debug/tracing/events/raw_syscalls/sys_enter/format

【C】对于 sys_enter 类型的追踪点,其参数第 2 项为操作码,即指代即将执行的系统调用号。这里赋值给变量 opcode

【D】这一步,我们把 opcode 作为索引,进行尾调用,执行下一个 eBPF 程序。

再次提醒,这里的写法是 BCC 优化,在真正编译前,BCC 最终会将其重写为 bpf_tail_call 辅助函数。

【E】hello_execve(),程序数组的一项,对应 execve()系统调用。经由尾调用触发。

【F】hello_timer(),程序数组的一项,对应计时器相关的系统调用。经由尾调用触发。

现在,我们来看一下用户态的程序。

#!/usr/bin/python3
from bcc import BPF
import ctypes as ct

b = BPF(src_file="tail_hello.c")
b.attach_raw_tracepoint(tp="sys_enter", fn_name="hello")		# A

exec_fn = b.load_func("hello_exec", BPF.RAW_TRACEPOINT)			# B
timer_fn = b.load_func("hello_timer", BPF.RAW_TRACEPOINT)

prog_array = b.get_table("syscall")					# C
prog_array[ct.c_int(59)] = ct.c_int(exec_fn.fd)
prog_array[ct.c_int(222)] = ct.c_int(timer_fn.fd)
prog_array[ct.c_int(223)] = ct.c_int(timer_fn.fd)
prog_array[ct.c_int(224)] = ct.c_int(timer_fn.fd)
prog_array[ct.c_int(225)] = ct.c_int(timer_fn.fd)
prog_array[ct.c_int(226)] = ct.c_int(timer_fn.fd)

b.trace_print()								# D

代码解释:

【A】与前文绑定到 kprobe 不同,这次用户态将 hello() 主 eBPF 程序绑定到 sys_enter 追踪点(Tracepoint)上.

【B】这些 load_func() 方法用来将每个尾调用函数载入内核,并返回尾调用函数的文件描述符。尾调用需要和父调用保持相同的程序类型(这里是 BPF.RAW_TRACEPOINT)。

一定不要混淆,每个尾调用程序本身就是一个 eBPF 程序。

【C】接下来,向我们创建好的 syscall 程序数组中添充条目。大可不必全部填满,如果执行时遇到空的,那也没啥影响。同样的,将多个 index 指向同一个尾调用也是可以的(事实上这段程序就是这样做的,将计时器相关的系统调用指向同一个 eBPF 尾调用)。

译者注:这里的 ct.c_int() 来自 Python 的 ctypes 库,用于 Python 到 C 的类型转换。

【D】不断打印输出,直到用户终止程序。

4 运行这个尾调用程序

运行这个 Python 程序,恭喜你,你可能会得到一段报错:

/virtual/main.c:22:20: error: incompatible pointer to integer conversion passing 'void *' to parameter of type 'u64' (aka 'unsigned long long') [-Wint-conversion]
    bpf_tail_call_((void *)bpf_pseudo_fd(1, -1), ctx, opcode);
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/virtual/include/bcc/helpers.h:552:25: note: passing argument to parameter 'map_fd' here
void bpf_tail_call_(u64 map_fd, void *ctx, int index) {

出现这个问题,说明你的系统上 clang 版本为 15。不信你可以看一下:

clang -v

我们可以在这个 issue 中找到问题描述(https://github.com/iovisor/bcc/issues/4642 )。
可以在这个 issue 中找到解决方案(https://github.com/iovisor/bcc/issues/4467 )。

大致意思就是,你尝试将一个 u64 类型的值转换成 void *。这在 clang-14 中仅仅是一个 warning,但是在 clang-15 中就会被认定为一个 error

你可以选择降低 clang 版本来解决这个问题。

运行截图如下所示:

【eBPF-04】进阶:BCC 框架中 BPF 映射的应用 v2.0——尾调用

5 总结

尾调用的适当应用,能够使 eBPF 如虎添翼。

然而,内核 4.2 版本才开始支持尾调用,在很长的一段时间内,尾调用和 BPF 的编译过程不太兼容(尾调用需要 JIT 编译器的支持)。直到 5.10 版本才解決了这个问题。

你可以最多链接 33 个尾调用(而每个 eBPF 程序的指令复杂度最大支持 100w)。这样一来,eBPF 终于能够真正发挥出巨大潜力来了。

至此,BCC 框架中 BPF 映射就先告一段落了,后面看经历是否在增加其他 BPF 映射结构的应用。

如果您有问题,欢迎留言讨论!如果您觉得这篇文章还不错,请点一个推荐吧~文章来源地址https://www.toymoban.com/news/detail-790873.html

到了这里,关于【eBPF-04】进阶:BCC 框架中 BPF 映射的应用 v2.0——尾调用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java 新的生态型应用开发框架,Solon v2.2.13 发布

    Java 新的生态型应用开发框架,Solon :更快、更小、更简单。从零开始构建,有自己的标准规范与开放生态: 150多个生态插件,可以满足各种场景开发 大量的国产框架适配,可以为应用软件国产化提供更好支持,助力信创建设 相对于 Spring Boot 和 Spring Cloud 的项目: 启动快

    2023年04月25日
    浏览(27)
  • Solon v2.2.17 发布,Java 新的生态型应用开发框架

    一个, Java 新的生态型应用开发框架 。它从零开始构建,有自己的标准规范与开放生态。与其他框架相比, 它解决了一个重要的痛点:启动慢,费资源。 由于Solon Bean容器的独特设计, Solon 不会因为扩展依赖变多而启动很慢(开发调试时,爽快)!!! 以开源项目“小诺”

    2024年02月05日
    浏览(31)
  • eBPF 入门开发实践教程十一:在 eBPF 中使用 libbpf 开发用户态程序并跟踪 exec() 和 exit() 系统调用

    eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具。它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。 在本教程中,我们将了解内核态和用户态的 eBPF 程序是如何协同工作的。我们还将学习如何使用原生的 libbpf 开发用户态程序,

    2024年02月07日
    浏览(36)
  • Caretta 利用 eBPF 实现 Kubernetes 应用网络拓扑

    Caretta 是一种轻量级的独立工具,快速展示集群中运行的服务可视化网络图。 Caretta 利用 eBPF 有效地展示 K8s 集群中的服务网络交互图,并利用 Grafana 查询和可视化收集的数据。科学家们早就知道,海龟和许多动物一样,通过感应磁场中看不见的线在海上航行,类似于水手使用

    2024年02月10日
    浏览(25)
  • 【前端进阶】-TypeScript高级类型 | 交叉类型、索引签名类型、映射类型

    前言 博主主页👉🏻蜡笔雏田学代码 专栏链接👉🏻【TypeScript专栏】 上篇文章讲解了TypeScript部分高级类型 详细内容请阅读如下:🔽 【前端进阶】-TypeScript高级类型 | 类的初始化、构造函数、继承、成员可见性 今天来学习TypeScript另外一些高级类型! 感兴趣的小伙伴一起来

    2023年04月08日
    浏览(25)
  • 边缘计算框架 Baetyl v2.4.3 正式发布

    导读 Baetyl v2.4.3 版本已经发布,对 v2.3.0 版本的部分功能进行了升级优化。公告称,这些新功能继续遵循云原生理念,构建了一个开放、安全、可扩展、可控制的智能边缘计算平台。 Baetyl 项目由百度发起,基于百度天工 AIoT 智能边缘进行开源,是国内首个加入 LF Edge 的边缘计

    2024年02月14日
    浏览(36)
  • opencv-26 图像几何变换04- 重映射-函数 cv2.remap()

    重映射(Remapping)是图像处理中的一种操作,用于将图像中的像素从一个位置映射到另一个位置。重映射可以实现图像的平移、旋转、缩放和透视变换等效果。它是一种基于像素级的图像变换技术,可以通过定义映射关系来改变图像的几何形状和外观。 在重映射中,我们需要

    2024年02月15日
    浏览(37)
  • Fastadmin框架 聚合数字生活抵扣卡系统v2.8.6

    【2.8.6更新公告】 1.【优化】优化已知问题。 2.【新增 】新增区县影院。

    2024年02月11日
    浏览(23)
  • 《cuda c编程权威指南》04 - 使用块和线程索引映射矩阵索引

    目录 1. 解决的问题 2. 分析 3. 方法 4. 代码示例 利用块和线程索引,从全局内存中访问指定的数据。 通常情况下, 矩阵 是用行优先的方法在 全局内存 中线性存储的。如下。 8列6行矩阵(nx,ny)=(8,6)。 这里建立二维网格(2,3)+二维块(4,2)为例,使用其块和线程索引映射矩阵

    2024年02月14日
    浏览(30)
  • MFC 简单的SendMessage子窗口调用主窗口函数(消息映射)的实现

    只说实现,不讲原理 环境:VS2022 community版 0.先建立一个全局调用的主对话框的指针g_pMainThis; 1.建立一个基于对话框的MFC工程; 2.在预编译头文件“phc.h”或“stdafx.h”定义消息调用的宏,其值要“WM_USER+100”以上; 3.在主对话框类建立消息映射的功能实现函数; 4.建立一个子

    2024年02月11日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包