BCC 是如何兼容eBPF多内核版本的

简介: 【2月更文挑战第6天】

在理想情况下,开发测试环境和生产环境应该都是一致的,包括使用相同的内核版本。如果你已经满足了这个条件,那么自然也就不需要考虑内核兼容的问题。但注意这只是理想情况,实际情况下内核版本不一致的问题是不可避免的,比如:


  • 为了获取更好的稳定性和社区支持,内核版本(甚至是 Linux  发行版版本)需要持续跟随上游社区进行升级;
  • 为了采纳新技术,新的产品架构可能一开始就会采纳较新的内核,而使用旧内核的遗留系统还需要很长时间的迭代过程;
  • 为了获得更广的用户,很多商业或开源项目不仅要支持最新的内核版本,还需要兼容各种各样的用户环境,而这些用户所使用的内核版本也是千差万别的。


由于这些兼容性问题都是由内核版本不同而导致的,所以我们很容易想到的一个笨方法就是给所有不兼容的内核版本分别开发不同的 eBPF 程序。但这种方法的缺点太明显了,维护大量功能重复的 eBPF 程序成本太高,所以并不推荐你使用这种方法。

BCC 和 bpftrace 作为使用最广泛的 eBPF 项目,自然也最容易碰到内核兼容性的问题。那么,它们是怎么解决这些兼容性问题的呢?其实也很简单,主要就是下面两个方法:


  • 第一,在运行 eBPF 程序的时候使用当前系统安装的内核头文件进行就地编译,这样就可以确保 eBPF 程序中所引用的内核数据结构和函数签名等,跟运行中的内核是完全匹配的。
  • 第二,在 eBPF 程序编译前事先探测内核支持的函数签名和数据结构,进而为 eBPF 程序生成适配当前内核的版本。比如,在块设备 I/O 延迟跟踪程序 biolantecy 中,BCC 借助库函数 BPF.get_kprobe_functions() 来判断内核是不是支持特定的探针函数,进而再根据结果去选择挂载点:
if BPF.get_kprobe_functions(b'__blk_account_io_start'):
    b.attach_kprobe(event="__blk_account_io_start", fn_name="trace_req_start")
  else:
    b.attach_kprobe(event="blk_account_io_start", fn_name="trace_req_start")
  
  if BPF.get_kprobe_functions(b'__blk_account_io_done'):
      b.attach_kprobe(event="__blk_account_io_done", fn_name="trace_req_done")
  else:
      b.attach_kprobe(event="blk_account_io_done", fn_name="trace_req_done")

当然了,BCC 采用的这些方法虽然解决了内核版本兼容的问题,但同时也存在很多的缺点,包括需要在所有目标机器安装开发工具和内核头文件、编译消耗额外资源、eBPF 程序启动慢以及编译时错误难以排查等。


Linux 内核维护者提供了一个更好的方案,那就是一次编译到处执行(Compile Once Run Everywhere,简称 CO-RE)。


eBPF 的一次编译到处执行(简称 CO-RE)项目借助了 BPF 类型格式(BPF Type Format, 简称 BTF)提供的调试信息,再通过下面的四个步骤,使得 eBPF 程序可以适配不同版本的内核:

  • 第一,在 bpftool 工具中提供了从 BTF 生成头文件的工具,从而摆脱了对内核头文件的依赖。
  • 第二,通过对 BPF 代码中的访问偏移量进行重写,解决了不同内核版本中数据结构偏移量不同的问题。
  • 第三,在 libbpf 中预定义不同内核版本中数据结构的修改,解决了不同内核中数据结构不兼容的问题。
  • 第四,在 libbpf 中提供一系列的内核特性探测库函数,解决了 eBPF 程序在不同内核内版本中需要执行不同行为的问题。比如,你可以用 bpf_core_type_exists() 和bpf_core_field_exists() 分别检查内核数据类型和成员变量是否存在,也可以用类似 extern int LINUX_KERNEL_VERSION __kconfig 的方式查询内核的配置选项。


采用这些方法之后,CO-RE 就使得 eBPF 程序可以在开发环境编译完成之后,分发到不同版本内核的机器中运行,并且也不再需要目标机器安装各种开发工具和内核头文件。所以,Linux 内核社区更推荐所有开发者使用 CO-RE 和 libbpf 来构建 eBPF 程序。实际上,如果你看过 BCC 的源代码,你会发现 BCC 已经把很多工具都迁移到了 CO-RE。


需要注意的是,CO-RE 需要比较新的内核版本(大于等于 5.2)并且需要打开 CONFIG_DEBUG_INFO_BTF 配置选项。所以,实际上采用 CO-RE 技术的 eBPF 程序还是只能运行在满足这两个条件的内核版本中。那么,不支持 BTF 的内核怎么办呢?根据开源社区的实践经验,有两种不同的解决办法。


第一种,采用条件编译的方式,根据是否支持 CO-RE,生成两个不同的 eBPF 字节码文件。而到程序运行时,再根据内核是否支持 CO-RE 选择对应的字节码文件加载运行。


第二种,采用 Aqua Security 开源的 btfhub ,为目标机器匹配的内核版本下载独立的 BTF 信息库,最后再通过如下的方法借助 libbpf 进行加载:

struct bpf_object_open_opts openopts = {
    .sz = sizeof(struct bpf_object_open_opts),
         // 从BPF_CUSTOM_BTF环境变量读取BTF文件路径
    .btf_custom_path = getenv("BPF_CUSTOM_BTF"),
  };
  obj = hello_btf_bpf__open_opts(&openopts);
  if (!obj) {
    fprintf(stderr, "failed to open and/or load BPF object\n");
    return 1;
  }


除此之外,eBPF 程序在运行时一般不需要内核的所有 BTF 信息,而只是访问其中的几个少数类型。因而,从内核 5.18 开始,bpftool 还新增了 bpftool gen min_core_btf 命令,帮你精简 BTF 信息,进一步减轻了 eBPF 程序(包括 BTF 信息)的分发交付。

相关文章
|
存储 网络协议 Linux
高效调试与分析:利用ftrace进行Linux内核追踪(下)
高效调试与分析:利用ftrace进行Linux内核追踪
|
Linux 编译器 Shell
eBPF动手实践系列三:基于原生libbpf库的eBPF编程改进方案
为了简化 eBPF程序的开发流程,降低开发者在使用 libbpf 库时的入门难度,libbpf-bootstrap 框架应运而生。本文详细介绍基于原生libbpf库的eBPF编程改进方案。
|
机器学习/深度学习 弹性计算 人工智能
阿里云服务器架构有啥区别?X86计算、Arm、GPU异构、裸金属和高性能计算对比
阿里云ECS涵盖x86、ARM、GPU/FPGA/ASIC、弹性裸金属及高性能计算等多种架构。x86架构采用Intel/AMD处理器,适用于广泛企业级应用;ARM架构低功耗,适合容器与微服务;GPU/FPGA/ASIC专为AI、图形处理设计;弹性裸金属提供物理机性能;高性能计算则针对大规模并行计算优化。
1367 7
|
数据库 开发者 Python
"揭秘FastAPI异步编程魔法:解锁高性能Web应用的终极奥义,让你的并发处理能力飙升,秒杀同行就靠这一招!"
【8月更文挑战第31天】FastAPI是一款基于Python的现代化Web框架,内置异步编程支持,可充分利用多核CPU的并行处理能力,大幅提升Web应用的性能。本文探讨FastAPI的异步编程特性,通过示例代码展示其在处理并发请求时的优势。异步编程不仅提高了并发处理能力,还降低了资源消耗,使代码更简洁易读。无论对于初创企业还是大型企业级应用,FastAPI都是构建高性能Web服务的理想选择。
755 0
|
监控 Unix Linux
|
数据采集 机器学习/深度学习 人工智能
[大语言模型-论文精读] 利用多样性进行大型语言模型预训练中重要数据的选择
[大语言模型-论文精读] 利用多样性进行大型语言模型预训练中重要数据的选择
|
Shell 开发工具 Android开发
ADB 下载、安装及使用教程:让你更好地管理 Android 设备
ADB 下载、安装及使用教程:让你更好地管理 Android 设备
|
存储 运维 监控
SRE方法论之监控设计
监控系统的四个黄金指标是:延迟(Latency)、流量(Traffic)、错误(Errors)、饱和度(Saturation)
|
存储 缓存 算法

热门文章

最新文章