首页 > 系统相关 >ebpf-使用内核编译开发一个程序(ubuntu20.04)

ebpf-使用内核编译开发一个程序(ubuntu20.04)

时间:2023-09-09 15:46:24浏览次数:68  
标签:ubuntu20.04 bpf ebpf eBPF BPF 内核 linux hello

前不久正好工作中使用到了这个方面的知识,这里写一下我的总结

我对ebpf的理解

ebpf(extended Berkeley Packet Filter)是一种虚拟机,通常我们使用的vmware是一种大型的虚拟机,vmware里面可以模拟cpu、显卡、网卡、硬盘等硬件,而ebpf这种的虚拟机是只模拟栈的小型的虚拟机,jvm也是一种栈虚拟机,栈虚拟机的优点就是可以跨平台,代码简单,但是不好优化,与其相比寄存器虚拟机就很好优化,但是代码会复杂一点,跨平台不方便。
ebpf的使用方式和java有点类似,只是ebpf需要两个文件,一个在用户态运行,一个在内核态运行,两者可以相互通信,运行时由用户态将内核态程序载入内核中,然后再和内核态程序通信。

发展的历史

Ebpf(extended Berkeley Packet Filter)起源于bpf,最初的目的是用于网络报文的过滤,而ebpf不再仅仅用于网络报文,而是可以用于多种场景,1992 年,Steven McCanne 和 Van Jacobson 写了一篇名为 The BSD Packet Filter: A New Architecture for User-level Packet Capture的论文。在文中,作者描述了他们如何在 Unix 内核实现网络数据包过滤,这种新的技术比当时最先进的数据包过滤技术快 20 倍。2014 年初,Alexei Starovoitov 实现了 eBPF(extended Berkeley Packet Filter)。经过重新设计,eBPF 演进为一个通用执行引擎,可基于此开发性能分析工具、软件定义网络等诸多场景。eBPF 最早出现在 3.18 内核中,此后原来的 BPF 就被称为经典 BPF,缩写 cBPF(classic BPF),cBPF 现在已经基本废弃。现在,Linux 内核只运行 eBPF,内核会将加载的 cBPF 字节码透明地转换成 eBPF 再执行。2014 年 6 月,eBPF 扩展到用户空间,这也成为了 BPF 技术的转折点。 正如 Alexei 在提交补丁的注释中写到:“这个补丁展示了 eBPF 的潜力”。当前,eBPF 不再局限于网络栈,已经成为内核顶级的子系统。

ebpf编程的实现方式

  • 基于 bcc 开发:bcc 提供了对 eBPF 开发,前段提供 Python API,后端 eBPF 程序通过 C 实现。特点是简单易用,但是性能较差。
  • 基于 libebpf-bootstrap 开发:libebpf-bootstrap 提供了一个方便的脚手架
  • 基于内核源码开发:内核源码开发门槛较高,但是也更加切合 eBPF 底层原理。

一个hello程序的编译运行过程

这里使用基于内核源码开发,实现一个简单的hello程序来演示编译和运行过程。
步骤是:

  1. 创建一个linux操作环境(Ubuntu 20.04.2-Ubuntu)
    这里用的操作系统镜像我是在国内mirror找的,我试过centos8,rocky linux,ubuntu22版本,都没成功,最后这个版本试了一下就可以了,

进入root状态,输入

apt-cache search linux-source #会显示一系列内核版本,尽量选择和本系统靠近的内核版本
apt install linux-source-5.4.0
cd /usr/src
tar -jxvf linux-source-5.4.0.tar.bz2#解压内核代码
cd linux-source-5.4.0
make script
cp -v /boot/config-$(uname -r) .configs 
make headers_install
make M=samples/bpf #这里不出现error即可

执行命令后的情况

root@ubuntu:/usr/src/linux-source-5.4.0# make M=samples/bpf
make -C /usr/src/linux-source-5.4.0/samples/bpf/../../tools/lib/bpf/ RM='rm -rf' LDFLAGS= srctree=/usr/src/linux-source-5.4.0/samples/bpf/../../ O=
Warning: Kernel ABI header at 'tools/include/uapi/linux/netlink.h' differs from latest version at 'include/uapi/linux/netlink.h'
Warning: Kernel ABI header at 'tools/include/uapi/linux/if_link.h' differs from latest version at 'include/uapi/linux/if_link.h'

  WARNING: Symbol version dump ./Module.symvers
           is missing; modules will have no dependencies and modversions.

  Building modules, stage 2.
  MODPOST 0 modules

会有一些警告,只要没有红色的error显示,就可以了,有时会出现这样一些红色的error,这些错误的解决方法可以在常见error解决方法里面找到
2. 编写内核态代码和用户态代码
这里用户态代码和内核态代码我参考知乎上的,这里给出链接 https://zhuanlan.zhihu.com/p/492185920

cd /samples/bpf
vim hello_kern.c
#include <linux/bpf.h>
#include "bpf_helpers.h"

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

SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(void *ctx)
{
	char msg[] = "Hello BPF!\n";
	bpf_trace_printk(msg, sizeof(msg));
	return 0;
}

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

execve是用于运行用户程序(a.out)或shell脚本的函数,是linux编程中常用的一个系统调用类函数。在linux命令行下运行用户程序本质其实就是执行execve系统调用,这里是将整个函数挂载到tracepoint/syscalls/sys_enter_execve这个section,这里的入口函数使用的是*ctx,bpf_trace_printk() 是一个最常用的 BPF 辅助函数,它的作用是输出一段字符串,这个程序的意义就是系统调用用户程序时打印字符串 Hello BPF

然后再写一个user_hello.c文件

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

int main(int argc, char **argv)
{
	if( load_bpf_file("hello_kern.o") != 0)
	{
		printf("The kernel didn't load BPF program\n");
		return -1;
	}

	read_trace_pipe();
	return 0;
}

再修改Makefile文件,在对应位置加上
这里要仿照不同内核版本下的格式,可能会有一点不同,主要要和其他内容对应上,

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

hostprogs-y += hello 的含义是指定生成一个名为hello的可执行文件
hello-objs :=bpf_load.o hello_user.o 来指明生成可执行文件hello所需的文件清单就是bpf_load.o和hello_user.o
always-y += hello_kern.o这个要和Makefile文件中的always := $(hostprogs-y)结合起来看,意义是告诉kbuild需要的依赖有hello_kern.o这个文件

  1. 运行

回到内核根目录下执行 make M=samples/bpf,这个命令的作用就是将xxx_kern.c、xxx_user.c等ebpf源文件进行编译链接,最后变成可以在内核执行的字节码文件和用户态运行的可执行文件。

root@ubuntu:/usr/src/linux-source-5.4.0#cd /samples/bpf
root@ubuntu:/usr/src/linux-source-5.4.0/samples/bpf# ./hello

运行./hello,加载 bpf 程序实质上是加载 ELF 格式文件,Linux 加载普通 ELF 格式的文件在通过 load_elf_binary 来实现,而 Linux 加载 bpf elf 其实在用户态实现的,使用的是开源的 libelf 库实现的,调用过程不太一样,而且只是把 ELF 格式的指令 dump 出来,接下来还需要 JIT 编译器翻译出机器汇编码才能执行,这个调用过程比 Linux 加载普通 ELF 格式文件简单。通过 load_bpf_file 加载 .o 文件。这个时候界面上是没有任何输出的。
我们打开另外一个终端,输入ls,终端上面出现字符。

root@ubuntu:/usr/src/linux-source-5.4.0/samples/bpf# ./hello 
            bash-8586    [003] d...1 37473.713482: bpf_trace_printk: Hello BPF from houmin!


       (install)-8588    [002] d...1 37476.396070: bpf_trace_printk: Hello BPF from houmin!


          (find)-8590    [002] d...1 37476.400707: bpf_trace_printk: Hello BPF from houmin!


           <...>-8587    [001] d...1 37476.406077: bpf_trace_printk: Hello BPF from houmin!



当ls程序运行时,会调用syscall,那么被JIT编译后的挂载在tracepoint中的程序int bpf_prog(void *ctx)会运行,通过bpf helper函数bpf_trace_printk打印字符。

标签:ubuntu20.04,bpf,ebpf,eBPF,BPF,内核,linux,hello
From: https://www.cnblogs.com/ruoxingruocheng/p/17665451.html

相关文章

  • 67.Oracle之内核参数
    net.ipv4.tcp_rmem=4096873804194304net.ipv4.tcp_wmem=4096163844194304fs.aio-max-nr=1048576fs.file-max=6815744kernel.shmall=2097152kernel.shmmax=4294967295kernel.shmmni=4096kernel.sem=25032000100128net.ipv4.ip_local_port_range......
  • PHP7内核实现原理-启动过程
    FPM启动和初始化worker的过程代码在源码/sapi/fpm/fpm/fpm_main.c中fpm_conf_init_main()函数解析php-fpm.conf配置文件,分配workerpool的内存空间。每个workerpool用结构体fpm_worker_pool_s表示,每个pool中的有一个fpm_scoreboard_s结构体,用来管理具体一个......
  • PHP7内核实现原理-基本环境和C基础
    编译安装PHP7.1.0下载7.1.0源码压缩包:www.php.net/releases/./configure--prefix=/Users/lisong/Documents/workspace/php-src/output--enable-fpm编译,报错:configure:error:Pleasespecifytheinstallprefixoficonvwith--with-iconv=iconv是个国际化扩展,暂时用......
  • PHP7内核实现原理-基本架构
    发展史PHP最早是由Lerdorf于1995年,使用Perl语言,以PersonalHomePageTools(PHPTools)的形式创建的,目的是为了方便记录个人网站的访客记录和支持留言本等功能,此时称为PHP1。后来越来越多的网站开始使用PHP并希望能提供更多的功能,之后Lerdorf将PHP开源,此时称为......
  • 内核升级
    一、内核升级在企业用过的内核升级#centos7.6内核升级4.19前提条件已配置好基础yum源,参考yum源配置1、获取内核的rpm包http://193.49.22.109/elrepo/kernel/el7/x86_64/RPMS/从上述页面上下载4.19.12的rpm包kernel-ml-4.19.12-1.el7.elrepo.x86_64.rpm然后拷贝到主机上......
  • RDMA-内核接口-rxe_map_mr_sg
    描述:映射mr的sg调用链:此处多处调用staticintnvme_rdma_map_sg_fr(structnvme_rdma_queue*queue,    structnvme_rdma_request*req,structnvme_command*c,    intcount){   req->mr=ib_mr_pool_get(queue->qp,&queue->qp->rdma_mrs);  /*  ......
  • ubuntu20.04 链路聚合的shell脚本
    链路聚合,就是将两个网口的功能合并,比如eth0和eth1两个网口,合并之后,本来只有使用eth0才能实现的功能,使用eth1后也可以,他们的网速也会变成eth0的网速+eth1的网速;#!/bin/bashname=/etc/netplan/01-network-manager-all.yaml#ip_netmask=$(grepaddresses$name|grep/)#gateway4=......
  • 视频监控汇聚平台EasyNVR安防视频内核启动正常,但视频无法播放是什么原因?
    EasyNVR是基于RTSP/Onvif协议的安防视频云服务平台,可实现设备接入、实时直播、录像、检索与回放、云存储、视频分发、级联等视频能力服务,可覆盖全终端平台(电脑、手机、平板等终端),在智慧工厂、智慧工地、智慧社区、智慧校园等场景中有大量落地应用。 近期有用户向我们求助,EasyNV......
  • DeeTune:基于 eBPF 的百度网络框架设计与应用
    作者|百度APP云原生技术研发组导读随着云计算的技术的不断迭代演进,百度内部服务逐渐搬迁到云环境中,部署成本和效率取得明显收益,但一些可观测能力的短板和缺失逐渐显露,传统的方式往往通过植入代码进行修改来实现,但在业务形态和技术栈多样性的背景下,面临业务被侵入、沟通协调、性能......
  • RDMA-MR内核接口-rxe_get_dma_mr
    描述:注册物理内存,获取具有数据传输所需密钥的内存区域结构.get_dma_mr=rxe_get_dma_mr,内核的PD下面会注册一个内部的特殊MR,这个MR的范围包含了所有的系统内存。然后如果内核态的RDMA用户创建PD的时候传入了这个flag,就会把这个MR的R_Key挂在PD结构体的unsafe_global_rkey里面返回......