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 信息)的分发交付。

相关文章
|
监控 调度 开发工具
IO神器blktrace使用介绍
## 前言 1. blktrace的作者正是block io的maintainer,开发此工具,可以更好的追踪IO的过程。 2. blktrace 结合btt可以统计一个IO是在调度队列停留的时间长,还是在硬件上消耗的时间长,利用这个工具可以协助分析和优化问题。 ## blktrace的原理 一个I/O请求的处理过程,可以梳理为这样一张简单的图: ![](http://image
19041 0
|
6月前
|
JSON NoSQL Java
从Redis到Tair:开源工具的最佳实践
《从Redis到Tair:开源工具的最佳实践》介绍了Redis闭源后Valkey社区的成立及其兼容性测试、性能测试、数据迁移与校验、客户端接入最佳实践,以及Tair的开源模块。内容涵盖Redis闭源背景、阿里云在Valkey社区中的贡献、Tair与Redis的兼容性测试工具(如resp-compatibility)、性能测试工具(如RESP-Benchmark)、数据迁移工具(如Redis Shake)及数据校验工具。此外,还详细介绍了TairHash和TairDoc两个开源模块的应用场景,帮助用户更好地理解和使用这些工具。
280 4
|
10月前
|
前端开发 Linux 调度
ftrace、perf、bcc、bpftrace、ply的使用
ftrace、perf、bcc、bpftrace、ply的使用
|
10月前
|
Linux Shell Android开发
用eadb在Android上搭建eBPF运行环境
用eadb在Android上搭建eBPF运行环境
|
11月前
|
前端开发 Linux C语言
BCC(可观测性)
BCC(可观测性)
133 0
|
监控 Kubernetes 网络协议
DoorDash 基于 eBPF 的监控实践
DoorDash 基于 eBPF 的监控实践
347 0
|
Ubuntu 网络协议 Python
|
监控 网络协议 Ubuntu
eBPF 深度探索: 高效 DNS 监控实现(上)
eBPF 深度探索: 高效 DNS 监控实现(上)
800 0
eBPF 深度探索: 高效 DNS 监控实现(上)
|
Rust 运维 监控
龙蜥社区开源 coolbpf,BPF 程序开发效率提升百倍 | 龙蜥技术
coolbpf,可以酷玩的BPF!来看看让BPF加了双翅膀的它究竟有多硬核?
龙蜥社区开源 coolbpf,BPF 程序开发效率提升百倍 | 龙蜥技术