eBPF 开发入门之 helloworld

我是 LEE,老李,一个在 IT 行业摸爬滚打 16 年的技术老兵。

事件背景

最近一直想写一个关于 ebpf 的文章,但是不知道从哪里开始写起。思考良久之后,决定站在一个研发的角度,求真务实的从入门的角度来介绍下 ebpf。 实际我真正的目的是能够用好的 cilium 的整个系统,但是站在 cilium 角度上看 ebpf,它确实一个黑盒子。emmm.... 这个不符合我的人设,决定今天整理一个文章往里尝试看看究竟。

废话不多说,我们从一个非常简单的 helloworld 的编写出发。

前置知识

这个作为 epbf 学习的第一章知识,我相信很多小伙伴跟我一样都是“白手起家,一穷二白”,害怕 ebpf 整个环境的复杂性。既然是前置知识,那一定是最简单的,能够让我们一下就懂的。

ebpf 是一个转发层的驱动模型,既然是模型,就一定有定义和抽象等概念,我们通过下图就可以有了第一印象。

ebpf

eBPF 分为用户空间程序和内核程序两部分:

  1. 用户空间程序负责加载 BPF 字节码至内核,如需要也会负责读取内核回传的统计信息或者事件详情
  2. 内核中的 BPF 字节码负责在内核中执行特定事件,如需要也会将执行的结果通过 maps 或者 perf-event 事件发送至用户空间
  3. 其中用户空间程序与内核 BPF 字节码程序可以使用 map 结构实现双向通信,这为内核中运行的 BPF 字节码程序提供了更加灵活的控制

上面的内容用大白话说:ebpf 包含用户层和内核层两层组成。用户层主要是负责业务逻辑处理和响应,同时也兼顾着内核中的 epbf 的逻辑 bytescode 生成(当然这里可以使用第三方生成),并将 bytescode 注入到内核中。内核层主要是接受 bytescode,然后在内核层内完成对 bytescode 执行。果真是妥妥的控制与转发分离的模型,要不然为什么 ebpf 这么高效呢?

上手开发

环境准备

我们使用 Ubuntu 20.04 这个系统作为整体开发环境,内核使用的是 5.4.0。

检查内核

root@ubuntu:/tmp# uname -a
Linux ubuntu 5.4.0-126-generic #142-Ubuntu SMP Fri Aug 26 12:12:57 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

安装编译工具和包

# apt install -y bison build-essential cmake flex git libedit-dev pkg-config libmnl-dev \
   python zlib1g-dev libssl-dev libelf-dev libcap-dev libfl-dev llvm clang pkg-config \
   gcc-multilib luajit libluajit-5.1-dev libncurses5-dev libclang-dev clang-tools

安装内核源码

# apt install linux-source-5.4.0

源码安装至 /usr/src/ 目录下, 使用下面的命里初始化编译环境。

# cd /usr/src/linux-source-5.4.0/
#
# tar xvf linux-source-5.4.0.tar.bz2
# cd linux-source-5.4.0
#
# cp -v /boot/config-$(uname -r) .config
# make headers_install && make modules_prepare

开发代码

ebpf 是使用内核层和用户两层,那么我们写代码也要实现两层代码。

内核层代码:hello_kern.c

#include <linux/bpf.h>
#include "bpf/bpf_helpers.h"

#define SEC(NAME) __attribute__((section(NAME), used))

SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(void *ctx)
{
    char msg[] = "Hello Shengyan.li, I'm in eBPF World\n";

    bpf_trace_printk(msg, sizeof(msg));  /* 把信息打印到 trace 队列中 */

    return 0;
}

char _license[] SEC("license") = "GPL"; /* 这里很重要,告诉 ebpf 当前的模块协议是 GPL,如果不是GPL,libpbf 报错 err = 22, 退出。*/

用户层代码:hello_user.c

#include <stdio.h>
#include "bpf_load.h"

int main(int argc, char **argv)
{
    if(load_bpf_file("hello_kern.o") != 0) /* 加载生成的 bytescode 到内核*/
    {
        printf("The kernel didn't load BPF program\n");
        return -1;
    }

    read_trace_pipe();  /* 从 trace 队列中读取信息,输出到 stdout */

    return 0;
}

编译测试

修改 Makefile
在 /usr/src/linux-source-5.4.0/linux-source-5.4.0/samples/bpf 目录下,修改 Makefile,然后在对应的位置添加对应内容。

hostprogs-y += helloworld
hello-objs := bpf_load.o hello_user.o
always += hello_kern.o

编译

# cd /usr/src/linux-source-5.4.0/linux-source-5.4.0
# make M=samples/bpf

测试

# cd /usr/src/linux-source-5.4.0/linux-source-5.4.0/samples/bpf
# ./helloworld

当你执行上面的 helloworld 命令后,发现没有反应,不要慌,这个是正常的。这个时候你只需要在打开一个新会话连接到编译环境的机器上,然后在这个新窗口执行如下命令:

# watch "ls -l"

之前执行 helloworld 的命令的窗口会有如下的输出:

helloworld

总结

我们通过上面的操作和使用,基本确认了 epbf 整体开发流程。 通过简单的开发,我们基本可以得出下面的几个结论:

  1. ebpf 开发不需要重新编译内核和重启服务器,能够非常快速的迭代开发。
  2. 和 DPDK 的逻辑很像,之前很多积累的知识这里可以无脑复用。
  3. 有很多开发框架和库都在使用 ebpf,代码风险可控。
  4. ebpf 开发模型没有那么复杂,还是很简单,且很有意思的。
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容