具体步骤依赖用户指定的配置和构建系统,此处不一一列出。一种方式是参考BCC’s libbpf-tools,它给出了一个通用的Makefile文件,可以通过该文件来检查环境配置。
当编译BPF代码并生成BPF skeleton后,需要在用户空间代码中包含libbpf和skeleton头文件:
#include <bpf/bpf.h> #include <bpf/libbpf.h> #include "path/to/your/skeleton.skel.h" Locked内存的限制BPF的BPF maps以及其他内容使用了locked类型的内存。默认的限制非常低,因此,除非增加该值,否则有可能连一个很小的BPF程序都无法加载。BCC会无条件地将限制设置为无限大,但libbpf不会自动进行设置。
生产环境中可能会有更好的方式来设置locked内存的限制。但为了快速实验或在没有更好的办法时,可以通过setrlimit(2)系统调用进行设置(在程序开始前调用)。
#include <sys/resource.h> rlimit rlim = { .rlim_cur = 512UL << 20, /* 512 MBs */ .rlim_max = 512UL << 20, /* 512 MBs */ }; err = setrlimit(RLIMIT_MEMLOCK, &rlim); if (err) /* handle error */ Libbpf 日志如果程序运行不正常,最好的方式是检查libbpf的日志输出。libbpf会以多种级别输出大量有用的日志。默认会输出error级别的日志。建议安装一个自定义的日志回调,这样就可以配置日志的输出级别:
int print_libbpf_log(enum libbpf_print_level lvl, const char *fmt, va_list args) { if (!FLAGS_bpf_libbpf_debug && lvl >= LIBBPF_DEBUG) return 0; return vfprintf(stderr, fmt, args); } /* ... */ libbpf_set_print(print_libbpf_log); /* set custom log handler */ BPF skeleton 和 BPF app 生命周期对BPF skeleton(以及libbpf API)的详细介绍和使用超出了本文档的范畴,内核selftests以及BCC提供的libbpf-tools 例子可以帮助熟悉这部分内容。查看runqslower 示例,它是一个使用skeleton的简单却真实的工具。
尽管如此,了解主要的libbpf概念和每个BPF应用经过的阶段是很有用的。BPF应用包含一组BPF程序(合作或完全独立),以及在所有的BPF程序间共享的BPF maps和全局变量(允许操作共同的数据)。BPF 也可以在用户空间(我们将用户空间中的程序称为"控制app")中访问maps和全局变量,允许控制app获取或设置必要的额外数据。BPF应用通常会经过如下阶段:
打开阶段:BPF对象文件的解析:发现但尚未创建的BPF maps,BPF程序和全局变量。在BPF app打开后,可以在所有的表项创建并加载前进行任何额外的调整(设置BPF类型;预设值全局变量的初始值等);
加载阶段:创建BPF maps并解决了符号重定位之后,BPF程序会被加载到内核进行校验。此时,BPF程序所有的部分都是有效且存在于内核中的,但此时的BPF并没有被执行。在加载阶段之后,可以配置BPF map状态的初始值,此时不会导致BPF程序代码竞争性地执行;
附加阶段:此阶段中,BPF程序会附加到各种BPF钩子上(如Tracepoints,kprobes,cgroup钩子,网络报文处理流水线等)。此时,BPF会开始执行有用的工作,并读取/更新BPF maps和全局变量;
清理阶段:分离并从内核卸载BPFBPF程序。销毁BPF maps,并释放所有的BPF使用的资源。
生成的BPF skeleton 使用如下函数触发相应的阶段:
<name>__open() – 创建并打开 BPF 应用(例如的runqslower的runqslower_bpf__open()函数);
<name>__load() – 初始化,加载和校验BPF 应用部分;
<name>__attach() – 附加所有可以自动附加的BPF程序 (可选,可以直接使用libbpf API作更多控制);
<name>__destroy() – 分离所有的 BPF 程序并使用其使用的所有资源.
BPF 代码转换本章节会检查常用的转换流,并概述BCC和libbpf/BPF CO-RE之间存在的典型的不匹配。通过本章节,希望可以使你的BPF代码能够同时兼容BCC和BPF CO-RE。
检测BCC与libbpf模式在需要同时支持BCC和libbpf模式的场景下,需要检测BPF程序代码能够编译为哪种模式。最简单的方式是依赖BCC中的宏BCC_SEC:
#ifdef BCC_SEC #define __BCC__ #endif之后,在整个BPF代码中,可以执行以下操作:
#ifdef __BCC__ /* BCC-specific code */ #else /* libbpf-specific code */ #endif这样就可以拥有通用的BPF源代码,并且只有必要的逻辑代码段才是BCC或libbpf特定的。
头文件包含使用 libbpf/BPF CO-RE时,不需要包含内核头文件(如#include <linux/whatever.h>),仅需要包含一个vmlinux.h和少量libbpf辅助功能的头文件:
#ifdef __BCC__ /* linux headers needed for BCC only */ #else /* __BCC__ */ #include "vmlinux.h" /* all kernel types */ #include <bpf/bpf_helpers.h> /* most used helpers: SEC, __always_inline, etc */ #include <bpf/bpf_core_read.h> /* for BPF CO-RE helpers */ #include <bpf/bpf_tracing.h> /* for getting kprobe arguments */ #endif /* __BCC__ */vmlinux.h可能不包含某些有用的内核#define定义的常量,此时需要重新声明这些变量。但bpf_helpers.h中提供了大部分常用的变量。
字段访问