其他机制,如struct flavors,可以用于不同内核间类型不兼容的场景。这种场景下,无法使用一个通用的结构体定义来为多个内核提供相同的BPF程序。下面是一个人为构造的例子,看下struct flavors如何抽取fs/fsbase(已经重命名)来作一些线程本地数据的处理:
/* up-to-date thread_struct definition matching newer kernels */ struct thread_struct { ... u64 fsbase; ... }; /* legacy thread_struct definition for <= 4.6 kernels */ struct thread_struct___v46 { /* ___v46 is a "flavor" part */ ... u64 fs; ... }; extern int LINUX_KERNEL_VERSION __kconfig; ... struct thread_struct *thr = ...; u64 fsbase; if (LINUX_KERNEL_VERSION > KERNEL_VERSION(4, 6, 0)) fsbase = BPF_CORE_READ((struct thread_struct___v46 *)thr, fs); else fsbase = BPF_CORE_READ(thr, fsbase);本例中,BPF应用将<= 4.6内核的“旧版” thread_struct定义为struct thread_struct___v46。类型名称中的三个下划线以及其后的所有内容均被视为此结构的“flavor”。libbpf会忽略这个flavor部分,即在执行重定位时,该类型定义会匹配到实际运行的内核的struct thread_struct。这样的约定允许在一个C程序中具有可替代(且不兼容)的定义,并在运行时选择最合适的定义(例如,上面示例中的特定于内核版本的处理逻辑),然后使用类型强转为struct flavor来提取必要的字段。
如果没有structural flavors,则不能实现编译一次就可以在多个内核上运行的目标,否则就需要将#ifdef源代码编译成两个单独的BPF程序,并在运行时由控制应用程序手动选择适当的BPF程序,这些操作增加了复杂度和维护的成本。尽管不是透明的,但BPF CO-RE甚至可以使用这种高级方案,通过熟悉的C代码构造来解决此问题。
根据用户提供的配置变更行为有时候,在BPF程序了解内核版本和配置之后仍然无法决定如何从内核获取数据。这种情况下,用户空间的控制程序可能是唯一知道确切需要做什么的一方,以及需要启用或禁用那些特性。通常是通过某种配置数据进行通信,在用户空间和BPF程序之间共享数据。现今,一种不需要依赖BPF CO-RE的实现方式是使用BPF map作为配置数据的容器。BPF程序通过查找BPF map来抽取配置,并根据配置变更控制流,但这种方法有很多缺点:
BPF程序每次进行map查询配置值时都会造成运行时开销。这部分开销可能会快速增大,某些高性能BPF应用禁止这种方式。
配置值是不变的,且在BPF程序启动之后是只读的,但这部分数据仍然在BPF校验器在校验阶段仍然被认为是黑盒数据。意味着校验器无法清理无用代码以及执行其他高级代码分析,使得无法使用BPF程序逻辑的可配置部分(这部分功能是最前沿的功能,仅在新内核中支持,当运行在老内核上时不会破坏该程序)。由于BPF验证程序必须悲观地认为配置可以是任何东西,且有可能会使用该"未知"的功能(尽管用户明确配置不会发生这种情况)。
解决此类(公认复杂)场景的方法是使用只读全局数据。在BPF程序加载到内核之前由控制应用进行设置。从BPF程序侧看,这部分数据就像访问普通的全局变量。由于全局变量使用直接内存访问方式,因此不会产生BPF map查询的开销。控制语言侧需要在BPF程序加载之前设置初始的配置值,这样当BPF校验器进行程序校验时,会将配置值认为是只读的,这样BPF校验器会将这部分内容认为是已知的常量,并使用高级控制流分析来执行无用代码的删除。
上例中,在老版本的BPF校验器下,将不会使用未知的BPF辅助功能,且这部分代码会被移除。在新版本BPF校验器下,应用提供不同的配置后,允许使用新的BPF辅助功能,这部分逻辑会通过BPF校验器的校验。下面BPF代码例子很好地展示了这种行为:
/* global read-only variables, set up by control app */ const bool use_fancy_helper; const u32 fallback_value; ... u32 value; if (use_fancy_helper) value = bpf_fancy_helper(ctx); else value = bpf_default_helper(ctx) * fallback_value;