首页 > 编程语言 >eBPF程序如何跟内核进行交互

eBPF程序如何跟内核进行交互

时间:2024-02-04 22:31:43浏览次数:27  
标签:bpf BPF eBPF 程序 内核 数据结构 交互

一个完整的 eBPF 程序通常包含用户态和内核态两部分。其中,用户态负责 eBPF 程序的加载、事件绑定以及 eBPF 程序运行结果的汇总输出;内核态运行在 eBPF 虚拟机中,负责定制和控制系统的运行状态。

eBPF程序如何跟内核进行交互_eBPF

对于用户态程序来说,它们与内核进行交互时必须要通过系统调用来完成。而对应到 eBPF 程序中,我们最常用到的就是 bpf 系统调用。

在命令行中输入 man bpf ,就可以查询到 BPF 系统调用的调用格式:

#include <linux/bpf.h>

int bpf(int cmd, union bpf_attr *attr, unsigned int size);

BPF 系统调用接受三个参数:

  • 第一个,cmd ,代表操作命令,比如 BPF_PROG_LOAD 就是加载 eBPF 程序;
  • 第二个,attr,代表 bpf_attr 类型的 eBPF 属性指针,不同类型的操作命令需要传入不同的属性参数;
  • 第三个,size ,代表属性的大小。

不同版本的内核所支持的 BPF 命令是不同的。

eBPF程序如何跟内核进行交互_eBPF_02

eBPF 程序并不能随意调用内核函数,因此,内核定义了一系列的辅助函数,用于 eBPF 程序与内核其他模块进行交互。比如,上一讲的 Hello World 示例中使用的 bpf_trace_printk() 就是最常用的一个辅助函数,用于向调试文件系统(/sys/kernel/debug/tracing/trace_pipe)写入调试信息。

需要注意的是,并不是所有的辅助函数都可以在 eBPF 程序中随意使用,不同类型的 eBPF 程序所支持的辅助函数是不同的。

eBPF程序如何跟内核进行交互_eBPF_03

这其中,需要你特别注意的是以bpf_probe_read  开头的一系列函数。我在上一讲中已经提到,eBPF 内部的内存空间只有寄存器和栈。所以,要访问其他的内核空间或用户空间地址,就需要借助  bpf_probe_read  这一系列的辅助函数。这些函数会进行安全性检查,并禁止缺页中断的发生。

而在 eBPF 程序需要大块存储时,就不能像常规的内核代码那样去直接分配内存了,而是必须通过 BPF 映射(BPF Map)来完成。

BPF 映射用于提供大块的键值存储,这些存储可被用户空间程序访问,进而获取 eBPF 程序的运行状态。eBPF 程序最多可以访问 64 个不同的 BPF 映射,并且不同的 eBPF 程序也可以通过相同的 BPF 映射来共享它们的状态。

eBPF程序如何跟内核进行交互_eBPF_04

几种最常用的映射类型及其功能和使用场景:

eBPF程序如何跟内核进行交互_eBPF_05

除了创建之外,映射的删除也需要你特别注意。BPF 系统调用中并没有删除映射的命令,这是因为 BPF 映射会在用户态程序关闭文件描述符的时候自动删除(即close(fd) )。 如果你想在程序退出后还保留映射,就需要调用  BPF_OBJ_PIN 命令,将映射挂载到 /sys/fs/bpf 中。

在安装 BCC 工具的时候,内核头文件 linux-headers-$(uname -r) 也是必须要安装的一个依赖项。这是因为 BCC 在编译 eBPF 程序时,需要从内核头文件中找到相应的内核数据结构定义。这样,你在调用 bpf_probe_read 时,才能从内存地址中提取到正确的数据类型。

编译时依赖内核头文件也会带来很多问题。主要有这三个方面:

  • 首先,在开发 eBPF 程序时,为了获得内核数据结构的定义,就需要引入一大堆的内核头文件;
  • 其次,内核头文件的路径和数据结构定义在不同内核版本中很可能不同。因此,你在升级内核版本时,就会遇到找不到头文件和数据结构定义错误的问题;
  • 最后,在很多生产环境的机器中,出于安全考虑,并不允许安装内核头文件,这时就无法得到内核数据结构的定义。在程序中重定义数据结构虽然可以暂时解决这个问题,但也很容易把使用着错误数据结构的 eBPF 程序带入新版本内核中运行。

从内核 5.2 开始,只要开启了 CONFIG_DEBUG_INFO_BTF,在编译内核时,内核数据结构的定义就会自动内嵌在内核二进制文件 vmlinux 中。并且,你还可以借助下面的命令,把这些数据结构的定义导出到一个头文件中(通常命名为 vmlinux.h):

bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

有了内核数据结构的定义,你在开发 eBPF 程序时只需要引入一个 vmlinux.h 即可,不用再引入一大堆的内核头文件了。

eBPF程序如何跟内核进行交互_eBPF_06

同时,借助 BTF、bpftool 等工具,我们也可以更好地了解 BPF 程序的内部信息,这也会让调试变得更加方便。比如,在查看 BPF 映射的内容时,你可以直接看到结构化的数据,而不只是十六进制数值:

# bpftool map dump id 386
[
  {
      "key": 0,
      "value": {
          "eth0": {
              "value": 0,
              "ifindex": 0,
              "mac": []
          }
      }
  }
]

解决了内核数据结构的定义问题,接下来的问题就是,如何让 eBPF 程序在内核升级之后,不需要重新编译就可以直接运行。eBPF 的一次编译到处执行(Compile Once Run Everywhere,简称 CO-RE)项目借助了 BTF 提供的调试信息,再通过下面的两个步骤,使得 eBPF 程序可以适配不同版本的内核:

  • 第一,通过对 BPF 代码中的访问偏移量进行重写,解决了不同内核版本中数据结构偏移量不同的问题;
  • 第二,在 libbpf 中预定义不同内核版本中的数据结构的修改,解决了不同内核中数据结构不兼容的问题。

它们都要求比较新的内核版本(>=5.2),并且需要非常新的发行版(如 Ubuntu 20.10+、RHEL 8.2+ 等)才会默认打开内核配置 CONFIG_DEBUG_INFO_BTF。

标签:bpf,BPF,eBPF,程序,内核,数据结构,交互
From: https://blog.51cto.com/key3feng/9595461

相关文章

  • usb相关的内核线程占用cpu较高
    1.在ls1028的平台上,升级内核版本,从5.4-->5.10,出现usb相关的内核线程占用cpu较高问题。/sys/bus/usb/devices/usbx/power/''其中,x为设备id文件:-power/wakeup--enable/disabled,代表是否支持remotewakeup功能,即系统唤醒功能。常见如鼠标,键盘等HID设......
  • "与事件处理程序不同,事件处理程序只在每次交互时运行一次,而 Effect 则在需要进行同步
    "与事件处理程序不同,事件处理程序只在每次交互时运行一次,而Effect则在需要进行同步时运行。"但是交互往往会同时触发事件处理,从而引起值变化,进而导致同步,从而运行Effect,不是吗?那么如何确定方法应该写在事件处理里还是Effect里面??事件处理程序(EventHandler)和React中的Effect(......
  • Centos降内核版本
    #查看当前yum源中kernel版本yum--showduplicateslistkernelInstalledPackageskernel.x86_643.10.0-1160.el7@anacondakernel.x86_64......
  • Edge换内核了?与Chrome孰高孰低?
    新版EdgevsChrome:浏览器两大巨头孰高孰低?自从edge换了Chromium内核以后,它具有了与chrome平起平坐的能力,那么,它与chrome究竟孰高孰低呢?特性对比虽然chrome的简洁程度、速度与功能强大程度不可否认,但是,只能说,chrome有的优点和功能edge都有。并且,众所周知,chrome吃内存方面十分......
  • Windows内核开发-[6]、内核编程基础(3)
    内存分配在应用层编程时,系统提供了GlobalAlloc/HeapAlloc/LocalAlloc等函数。C/C++库提供了malloc函数,以及new操作符在堆上分配内存。在我前面一个关于Windows页交换文件的博客中,介绍了虚拟内存,虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的......
  • eBPF是如何工作的
    eBPF全称“扩展的伯克利数据包过滤器(ExtendedBerkeleyPacketFilter)”来看,它是一种数据包过滤技术,是从BPF(BerkeleyPacketFilter)技术扩展而来的。BPF提供了一种在内核事件和用户程序事件发生时安全注入代码的机制,这就让非内核开发人员也可以对内核进行控制。随着内核......
  • 鸿蒙开发丨设备内 UIAbility 的几种交互方式
    UIAbility组件间交互(设备内)在设备内,UIAbility(用户界面能力)是系统调度的最小单元,它们负责展示用户界面和执行相关的业务逻辑。设备内的不同功能模块之间的交互是应用程序开发中的重要部分。本文将探讨设备内UIAbility之间的交互方式,包括启动应用内的UIAbility、启动其他应用的U......
  • linux内核-3.Linux 内核启动流程
    1链接脚本vmlinux.lds先编译一下Linux源码,因为有很多文件是需要编译才会生成的。首先分析Linux内核的连接脚本文件arch/arm/kernel/vmlinux.lds,通过链接脚本可以找到Linux内核的第一行程序是从哪里执行的。vmlinux.lds中有如下代码:492OUTPUT_ARCH(arm)493ENTRY(ste......
  • RK3568驱动指南|驱动基础进阶篇-进阶8 内核运行ko文件总结
    瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和MaliG522EE图形处理器。RK3568支持4K解码和1080P编码,支持SATA/PCIE/USB3.0外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568支持安卓11和linux系统,主要面向......
  • Windows内核开发-[5]、内核编程基础(2)
    上下文环境应用层应用程序工作在用户模式,内核驱动程序工作在内核模式。这里的用户模式和内核模式是基于CPU的特权环来定义的,CPU提供了0环~3环(ring0~ring3)共四个特权环,Windows操作系统使用了其中的0环和3环,0环为内核模式,3环为用户模式。不同环之间的代码特权不同,访问地址空......