当前位置: 首页 > news >正文

eBPF调研-附上参考资源

文章目录

    • 0. resource
    • 1. abstract
      • 1.1 Native XDP
      • 1.2 Offloaded XDP
      • 1.3 Generic XDP
    • 2. introduce
    • 3, implement 在用户态和内核态间使用 ring buffer 传送数据
    • 4. make 编译和运行代码

本文参考自https://eunomia.dev/zh/tutorials/35-user-ringbuf/

0. resource

在调研xdp/ebpf技术过程中,找到了一些有用的资料,对自己理解xdp,编写xdp用户态代码有很大帮助。特此备份,以便不时之需

  1. 比较多的ebpf用例-供学习参考用–> https://github.com/eunomia-bpf/bpf-developer-tutorial
  2. libbpf库的下载-github仓库–>https://github.com/libbpf/libbpf
  3. bpftool工具的下载与使用–>https://github.com/libbpf/bpftool
  4. xdp-redirect例程-github仓库–>https://github.com/zhao-kun/xdp-redirect
  5. xdp-official提供的xdp用例–>https://github.com/xdp-project/xdp-tutorial
  6. 关于一个xdp用例的详解(icmp6包重定向到用户空间)–>https://forsworns.github.io/zh/blogs/20210715/
  7. 关于bpftool工具使用详细文档–>https://man.archlinux.org/man/bpftool-prog.8.en
  8. 个人觉得讲解bpftool工具最详细的博文–>https://thegraynode.io/posts/bpftool_introduction/
  9. 知乎上关于xdp讲解的万字长文(很详细)–>https://zhuanlan.zhihu.com/p/492185920
  10. Linux内核文档:https://www.kernel.org/doc/html/latest/bpf/index.html
  11. ebpf教程:https://cilium.io/docs/ebpf/
  12. ebpf实践指南:https://www.oreilly.com/library/view/ebpf-practitioners-guide/9781492044295/
  13. ebpf入门教程:https://www.cncf.io/blog/2018/04/17/an-introduction-to-ebpf/
  14. ebpf专题讲座:https://www.youtube.com/watch?v=B8LfVpUbCmE
  15. ebpf项目实战:https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md
  16. ebpf_helper_function_reference–>https://docs.ebpf.io/linux/helper-function/bpf_redirect_map/
  17. ebpf map分析(全面)–>https://yanjingang.com/blog/?p=8040
  18. ebpf map分析 2 --> https://tonybai.com/2022/07/25/bidirectional-data-exchange-between-kernel-and-user-states-of-ebpf-programs-using-go/
  19. linux official doc for ebpf functions–>https://man7.org/linux/man-pages/man2/bpf.2.html

1. abstract

鄙人彼时正在学习ebpf的通信方式,也就是如何让用户态程序和内核态用户程序进行通信,刚开始以为是ebpf提供的kernel ringbuffer用于内核态向用户态程序通信,而用户态程序向内核态程序通信需要用到下面的user ringbuffer,虽然确实可以,但是有更好的技术可供选择,那就是AF_XDP。关于linux官方提供的文档,你可以去参考这个链接 https://www.kernel.org/doc/html/latest/networking/af_xdp.html

也有第三方公司提供的关于xdp的文档,讲解的都挺详细的 https://docs.cilium.io/en/latest/reference-guides/bpf/progtypes/#xdp
有一说一cilium提供的文档看起来更舒服一点,更有看下去的欲望,当然论信息含量肯定是linux官方提供的高!
想要查看关于ebpf/xdp更多用例的话,可以去看看这篇文章 https://eunomia.dev/zh/tutorials/
它将大部分的用例都放在了github里面

关于XDP operation modes这三种操作模式还是有必要了解一下

XDP has three operation modes where ‘native’ XDP is the default mode. When talked about XDP this mode is typically implied.

1.1 Native XDP

This is the default mode where the XDP BPF program is run directly out of the networking driver’s early receive path. Most widespread used NICs for 10G and higher support native XDP already.

1.2 Offloaded XDP

In the offloaded XDP mode the XDP BPF program is directly offloaded into the NIC instead of being executed on the host CPU. Thus, the already extremely low per-packet cost is pushed off the host CPU entirely and executed on the NIC, providing even higher performance than running in native XDP. This offload is typically implemented by SmartNICs containing multi-threaded, multicore flow processors where an in-kernel JIT compiler translates BPF into native instructions for the latter. Drivers supporting offloaded XDP usually also support native XDP for cases where some BPF helpers may not yet or only be available for the native mode.

1.3 Generic XDP

For drivers not implementing native or offloaded XDP yet, the kernel provides an option for generic XDP which does not require any driver changes since run at a much later point out of the networking stack. This setting is primarily targeted at developers who want to write and test programs against the kernel’s XDP API, and will not operate at the performance rate of the native or offloaded modes. For XDP usage in a production environment either the native or offloaded mode is better suited and the recommended way to run XDP.

2. introduce

eBPF,即扩展的Berkeley包过滤器(Extended Berkeley Packet Filter),是Linux内核中的一种革命性技术,它允许开发者在内核态中运行自定义的“微程序”,从而在不修改内核代码的情况下改变系统行为或收集系统细粒度的性能数据。

eBPF的一个独特之处是它不仅可以在内核态运行程序,从而访问系统底层的状态和资源,同时也可以通过特殊的数据结构与用户态程序进行通信。关于这方面的一个重要概念就是内核态和用户态之间的环形队列——ring buffer。在许多实时或高性能要求的应用中,环形队列是一种常用的数据结构。由于它的FIFO(先进先出)特性,使得数据在生产者和消费者之间可以持续、线性地流动,从而避免了频繁的IO操作和不必要的内存 reallocation开销。

在eBPF中,分别提供了两种环形队列: user ring buffer 和 kernel ring buffer,以实现用户态和内核态之间的高效数据通信。本文是 eBPF 开发者教程的一部分,更详细的内容可以在这里找到:https://eunomia.dev/tutorials/ 源代码在 GitHub 仓库 中开源。

用户态和内核态环形队列—user ring buffer和kernel ring buffer
围绕内核态和用户态这两个主要运行级别,eBPF提供了两种相应的环形队列数据结构:用户态环形队列——User ring buffer和内核态环形队列——Kernel ring buffer。

Kernel ring buffer 则由 eBPF实现,专为Linux内核设计,用于追踪和记录内核日志、性能统计信息等,它的能力是内核态和用户态数据传输的核心,可以从内核态向用户态传送数据。Kernel ring buffer 在 5.7 版本的内核中被引入,目前已经被广泛应用于内核日志系统、性能分析工具等。

对于内核态往用户态发送应用场景,如内核监控事件的发送、异步通知、状态更新通知等,ring buffer 数据结构都能够胜任。比如,当我们需要监听网络服务程序的大量端口状态时,这些端口的开启、关闭、错误等状态更新就需由内核实时传递到用户空间进行处理。而Linux 内核的日志系统、性能分析工具等,也需要频繁地将大量数据发送到用户空间,以支持用户人性化地展示和分析这些数据。在这些场景中,ring buffer在内核态往用户态发送数据中表现出了极高的效率。

User ring buffer 是基于环形缓冲器的一种新型 Map 类型,它提供了单用户空间生产者/单内核消费者的语义。这种环形队列的优点是对异步消息传递提供了优秀的支持,避免了不必要的同步操作,使得内核到用户空间的数据传输可以被优化,并且降低了系统调用的系统开销。User ring buffer 在 6.1 版本的内核中被引入,目前的使用场景相对较少。

bpftime 是一个用户空间 eBPF 运行时,允许现有 eBPF 应用程序在非特权用户空间使用相同的库和工具链运行。它为 eBPF 提供了 Uprobe 和 Syscall 跟踪点,与内核 Uprobe 相比,性能有了显著提高,而且无需手动检测代码或重启进程。运行时支持用户空间共享内存中的进程间 eBPF 映射,也兼容内核 eBPF 映射,允许与内核 eBPF 基础架构无缝运行。它包括一个适用于各种架构的高性能 LLVM JIT,以及一个适用于 x86 的轻量级 JIT 和一个解释器。GitHub 地址:https://github.com/eunomia-bpf/bpftime

在 bpftime 中,我们使用 user ring buffer 来实现用户态 eBPF 往内核态 eBPF 发送数据,并更新内核态 eBPF 对应的 maps,让内核态和用户态的 eBPF 一起协同工作。user ring buffer 的异步特性,可以避免系统调用不必要的同步操作,从而提高了内核态和用户态之间的数据传输效率。

eBPF 的双向环形队列也和 io_uring 在某些方面有相似之处,但它们的设计初衷和应用场景有所不同:

设计焦点:io_uring主要专注于提高异步I/O操作的性能和效率,而eBPF的环形队列更多关注于内核和用户空间之间的数据通信和事件传输。
应用范围:io_uring主要用于文件I/O和网络I/O的场景,而eBPF的环形队列则更广泛,不限于I/O操作,还包括系统调用跟踪、网络数据包处理等。
灵活性和扩展性:eBPF提供了更高的灵活性和扩展性,允许用户定义复杂的数据处理逻辑,并在内核态执行。
下面,我们将通过一段代码示例,详细展示如何利用 user ring buffer,实现从用户态向内核传送数据,并以 kernel ring buffer 相应地从内核态向用户态传送数据。

3, implement 在用户态和内核态间使用 ring buffer 传送数据

借助新的 BPF MAP,我们可以实现在用户态和内核态间通过环形缓冲区传送数据。在这个示例中,我们将详细说明如何在用户空间创建一个 “用户环形缓冲区” (user ring buffer) 并向其写入数据,然后在内核空间中通过 bpf_user_ringbuf_drain 函数来消费这些数据。同时,我们也会使用 “内核环形缓冲区” (kernel ring buffer) 来从内核空间反馈数据到用户空间。为此,我们需要在用户空间和内核空间分别创建并操作这两个环形缓冲区。

完整的代码可以在 https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/35-user-ringbuf 中找到。

创建环形缓冲区
在内核空间,我们创建了一个类型为 BPF_MAP_TYPE_USER_RINGBUF 的 user_ringbuf,以及一个类型为 BPF_MAP_TYPE_RINGBUF 的 kernel_ringbuf。在用户空间,我们创建了一个 struct ring_buffer_user 结构体的实例,并通过 ring_buffer_user__new 函数和对应的操作来管理这个用户环形缓冲区。

    /* Set up ring buffer polling */
    rb = ring_buffer__new(bpf_map__fd(skel->maps.kernel_ringbuf), handle_event, NULL, NULL);
    if (!rb)
    {
        err = -1;
        fprintf(stderr, "Failed to create ring buffer\n");
        goto cleanup;
    }
    user_ringbuf = user_ring_buffer__new(bpf_map__fd(skel->maps.user_ringbuf), NULL);
 

编写内核态程序
我们定义一个 kill_exit 的 tracepoint 程序,每当有进程退出时,它会通过 bpf_user_ringbuf_drain 函数读取 user_ringbuf 中的用户数据,然后通过 bpf_ringbuf_reserve 函数在 kernel_ringbuf 中创建一个新的记录,并写入相关信息。最后,通过 bpf_ringbuf_submit 函数将这个记录提交,使得该记录能够被用户空间读取。

// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "user_ringbuf.h"

char _license[] SEC("license") = "GPL";

struct
{
    __uint(type, BPF_MAP_TYPE_USER_RINGBUF);
    __uint(max_entries, 256 * 1024);
} user_ringbuf SEC(".maps");

struct
{
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 256 * 1024);
} kernel_ringbuf SEC(".maps");

int read = 0;

static long
do_nothing_cb(struct bpf_dynptr *dynptr, void *context)
{
    struct event *e;
    pid_t pid;
    /* get PID and TID of exiting thread/process */
    pid = bpf_get_current_pid_tgid() >> 32;

    /* reserve sample from BPF ringbuf */
    e = bpf_ringbuf_reserve(&kernel_ringbuf, sizeof(*e), 0);
    if (!e)
        return 0;

    e->pid = pid;
    bpf_get_current_comm(&e->comm, sizeof(e->comm));

    /* send data to user-space for post-processing */
    bpf_ringbuf_submit(e, 0);
    __sync_fetch_and_add(&read, 1);
    return 0;
}

SEC("tracepoint/syscalls/sys_exit_kill")
int kill_exit(struct trace_event_raw_sys_exit *ctx)
{
    long num_samples;
    int err = 0;

    // receive data from userspace
    num_samples = bpf_user_ringbuf_drain(&user_ringbuf, do_nothing_cb, NULL, 0);

    return 0;
}

编写用户态程序
在用户空间,我们通过 ring_buffer_user__reserve 函数在 ring buffer 中预留出一段空间,这段空间用于写入我们希望传递给内核的信息。然后,通过 ring_buffer_user__submit 函数提交数据,之后这些数据就可以在内核态被读取。

static int write_samples(struct user_ring_buffer *ringbuf)
{
    int i, err = 0;
    struct user_sample *entry;

    entry = user_ring_buffer__reserve(ringbuf, sizeof(*entry));
    if (!entry)
    {
        err = -errno;
        goto done;
    }

    entry->i = getpid();
    strcpy(entry->comm, "hello");

    int read = snprintf(entry->comm, sizeof(entry->comm), "%u", i);
    if (read <= 0)
    {
        /* Assert on the error path to avoid spamming logs with
         * mostly success messages.
         */
        err = read;
        user_ring_buffer__discard(ringbuf, entry);
        goto done;
    }

    user_ring_buffer__submit(ringbuf, entry);

done:
    drain_current_samples();

    return err;
}

初始化环形缓冲区并轮询
最后,对 ring buffer 进行初始化并定时轮询,这样我们就可以实时得知内核态的数据消费情况,我们还可以在用户空间对 user_ringbuf 进行写入操作,然后在内核态对其进行读取和处理。

    write_samples(user_ringbuf);

    printf("%-8s %-5s %-16s %-7s %-7s %s\n",
           "TIME", "EVENT", "COMM", "PID", "PPID", "FILENAME/EXIT CODE");
    while (!exiting)
    {
        err = ring_buffer__poll(rb, 100 /* timeout, ms */);
        if (err == -EINTR)
        {
            err = 0;
            break;
        }
        if (err < 0)
        {
            printf("Error polling perf buffer: %d\n", err);
            break;
        }
    }

通过以上步骤,我们实现了用户态与内核态间环形缓冲区的双向数据传输。

4. make 编译和运行代码

为了编译和运行以上代码,我们可以通过以下命令来实现:

make

关于如何安装依赖,请参考:https://eunomia.dev/tutorials/11-bootstrap/

运行结果将展示如何使用 user ring buffer 和 kernel ringbuffer 在用户态和内核态间进行高效的数据传输:

$ sudo ./user_ringbuf
Draining current samples...
TIME     EVENT COMM             PID   
16:31:37 SIGN  node             1707   
Draining current samples...
16:31:38 SIGN  node             1981   
Draining current samples...
16:31:38 SIGN  node             1707   
Draining current samples...
16:31:38 SIGN  node             1707   
Draining current samples...

总结
在本篇文章中,我们介绍了如何使用eBPF的user ring buffer和kernel ring buffer在用户态和内核态之间进行数据传输。通过这种方式,我们可以有效地将用户态的数据传送给内核,或者将内核生成的数据反馈给用户,从而实现了内核态和用户态的双向通信。

如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 或网站 https://eunomia.dev/zh/tutorials/ 以获取更多示例和完整的教程。

想看一下dpdk的内容:https://cloud.tencent.com/developer/article/1198333

相关文章:

  • 人工智能之数学基础:齐次方程组和非齐次方程组的区别
  • java+selenium(资源全备,打开已使用浏览器信息,保留用户信息)
  • Day21:二叉树的深度
  • 知行之桥EDI系统应用程序目录切换指南(Windows与跨平台版)
  • Java-SpringBootWeb入门、Spring官方脚手架连接不上解决方法
  • 使用Three.js渲染器创建炫酷3D场景
  • 74HC04(反相器)和74HC14(反相器、施密特触发器)的区别
  • 【项目】幸运抽奖 测试报告
  • 怎么查看linux是Ubuntu还是centos
  • Compose 实践与探索十五 —— 自定义触摸
  • Python 应用部署云端实战指南 —— AWS、Google Cloud 与 Azure 全解析
  • 学习threejs,使用TextGeometry文本几何体
  • MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 高级篇 part11
  • Springboot各版本与Java JDK的对应关系及JDK商用版本
  • 【JavaWeb学习Day27】
  • 洛谷 P2157 [SDOI2009] 学校食堂
  • C++从入门到实战(六)类和对象(第二部分)C++成员对象及其实例化,对象大小与this详解
  • pytorch 网络结构可视化Netron安装使用方法(已解决)
  • 人力资源管理基于SpringBootSSM框架
  • 基于NSGA2算法的无人机航迹规划算法
  • 乌称苏梅州一公共汽车遭俄军袭击,致9死4伤
  • 俄乌直接谈判结束
  • 国家统计局:2024年城镇单位就业人员工资平稳增长
  • 昆明警方重拳打击经济领域违法犯罪:去年抓获905名嫌犯
  • 央媒评网红质疑胖东来玉石定价暴利:对碰瓷式维权不能姑息
  • 科普|揭秘女性压力性尿失禁的真相