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

简介: 【2月更文挑战第4天】一个完整的 eBPF 程序,通常包含用户态和内核态两部分:用户态程序需要通过 BPF 系统调用跟内核进行交互,进而完成 eBPF 程序加载、事件挂载以及映射创建和更新等任务;而在内核态中,eBPF 程序也不能任意调用内核函数,而是需要通过 BPF 辅助函数完成所需的任务。尤其是在访问内存地址的时候,必须要借助 bpf_probe_read 系列函数读取内存数据,以确保内存的安全和高效访问。

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

image.png

对于用户态程序来说,它们与内核进行交互时必须要通过系统调用来完成。而对应到 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 命令是不同的。

image.png

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


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

image.png

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


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


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

image.png

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

image.png

除了创建之外,映射的删除也需要你特别注意。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 即可,不用再引入一大堆的内核头文件了。

image.png

同时,借助 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。

相关文章
|
JSON 安全 算法
API接口安全设计
API接口安全设计
9498 0
API接口安全设计
|
11月前
|
监控 安全 Ubuntu
从零开始学安全:服务器被入侵后的自救指南
在信息爆炸时代,服务器安全至关重要。本文针对黑客入侵问题,从应急处理、系统恢复到安全加固全面解析。发现入侵时应冷静隔离服务器,保存日志证据,深入排查痕迹;随后通过重装系统、恢复数据、更改密码完成清理;最后加强防火墙、更新软件、部署检测系统等措施防止二次入侵。服务器安全是一场持久战,需时刻警惕、不断优化防护策略。
1454 1
|
5月前
|
人工智能 自然语言处理 语音技术
2025年AI数字人公司哪家好?数字人厂商技术产品、核心优势、应用场景对比
AI数字人迈向规模化商用,2025年呈现“技术驱动、场景分化、生态协同”趋势。涵盖服务、身份、分身三类,广泛应用于政务、医疗、文旅等领域,实现效率提升与体验升级。企业格局多元:世优科技强在全栈自研与高拟真交互,百度依托大模型赋能媒体营销,中小厂商聚焦垂直场景创新。选型需综合技术、场景、成本与生态。
396 0
|
自然语言处理 JavaScript Java
《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》学习笔记——HarmonyOS架构介绍
HarmonyOS采用分层架构设计,从下至上分为内核层、系统服务层、框架层和应用层。内核层支持多内核设计与硬件驱动;系统服务层提供核心能力和服务;框架层支持多语言开发;应用层包括系统及第三方应用,支持跨设备调度,确保一致的用户体验。
1362 81
|
Linux 芯片
一篇文章讲明白Linux下控制GPIO的三种方法
一篇文章讲明白Linux下控制GPIO的三种方法
2777 3
|
机器学习/深度学习 传感器 边缘计算
基于深度学习的图像识别技术在自动驾驶中的应用####
随着人工智能技术的飞速发展,深度学习已成为推动自动驾驶技术突破的关键力量之一。本文深入探讨了深度学习算法,特别是卷积神经网络(CNN)在图像识别领域的创新应用,以及这些技术如何被集成到自动驾驶汽车的视觉系统中,实现对复杂道路环境的实时感知与理解,从而提升驾驶的安全性和效率。通过分析当前技术的最前沿进展、面临的挑战及未来趋势,本文旨在为读者提供一个全面而深入的视角,理解深度学习如何塑造自动驾驶的未来。 ####
682 1
|
资源调度 Linux 调度
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
360 0
Linux C/C++之线程基础
|
Ubuntu 安全 网络协议