https://source.android.com/docs/core/architecture/kernel/bpf?hl=zh-cn
扩展型柏克莱封包过滤器 (eBPF) 是一个内核中的虚拟机,可运行用户提供的 eBPF 程序来扩展内核功能。这些程序可以挂接到内核中的探测点或事件,并用于收集有用的内核统计信息、监控和调试。程序使用 bpf(2) 系统调用加载到内核中,并作为 eBPF 机器指令的二进制 blob 由用户提供。Android 构建系统支持使用本文所述的简单 build 文件语法将 C 程序编译为 eBPF 程序。
如需详细了解 eBPF 内部构件和架构,请参阅 Brendan Gregg 的 eBPF 页面。
Android 包含一个 eBPF 加载器和库,它可在 Android 启动时加载 eBPF 程序。
Android BPF 加载器
在 Android 启动期间,系统会加载位于 /system/etc/bpf/ 的所有 eBPF 程序。这些程序是 Android 构建系统根据 C 程序构建而成的二进制对象,并附带了 Android 源代码树中的 Android.bp 文件。构建系统将生成的对象存储在 /system/etc/bpf 中,这些对象将成为系统映像的一部分。
Android eBPF C 程序的格式
eBPF C 程序必须采用以下格式:
#include <bpf_helpers.h>
/* Define one or more maps in the maps section, for example
* define a map of type array int -> uint32_t, with 10 entries
*/
DEFINE_BPF_MAP(name_of_my_map, ARRAY, int, uint32_t, 10);
/* this will also define type-safe accessors:
* value * bpf_name_of_my_map_lookup_elem(&key);
* int bpf_name_of_my_map_update_elem(&key, &value, flags);
* int bpf_name_of_my_map_delete_elem(&key);
* as such it is heavily suggested to use lowercase *_map names.
* Also note that due to compiler deficiencies you cannot use a type
* of 'struct foo' but must instead use just 'foo'. As such structs
* must not be defined as 'struct foo {}' and must instead be
* 'typedef struct {} foo'.
*/
DEFINE_BPF_PROG("PROGTYPE/PROGNAME", AID_*, AID_*, PROGFUNC)(..args..) {
<body-of-code
... read or write to MY_MAPNAME
... do other things
>
}
LICENSE("GPL"); // or other license
其中:
name_of_my_map是映射变量的名称。此名称可告知 BPF 加载器要使用哪些参数来创建哪种类型的映射。此结构体定义由包含的bpf_helpers.h头文件提供。-
PROGTYPE/PROGNAME表示程序类型和程序名称。程序类型可以是下表中列出的任意类型。如果程序类型未列出,则相应程序没有严格的命名惯例;只需让连接到程序的进程知道该名称即可。 -
PROGFUNC是一个函数,编译后将被放入所生成文件的一个区段中。
| kprobe | 使用 kprobe 基础架构将 PROGFUNC 挂接到某个内核指令。PROGNAME 必须是 kprobe 目标内核函数的名称。如需详细了解 kprobe,请参阅 kprobe 内核文档。 |
|---|---|
| tracepoint | 将 PROGFUNC 挂接到某个跟踪点。PROGNAME 必须采用 SUBSYSTEM/EVENT 格式。例如,用于将函数附加到调度程序上下文切换事件的跟踪点区段将为 SEC("tracepoint/sched/sched_switch"),其中 sched 是跟踪子系统的名称,sched_switch 是跟踪事件的名称。如需详细了解跟踪点,请参阅跟踪事件内核文档。 |
| skfilter | 程序将用作网络套接字过滤器。 |
| schedcls | 程序将用作网络流量分类器。 |
| cgroupskb 和 cgroupsock | 只要 CGroup 中的进程创建了 AF_INET 或 AF_INET6 套接字,程序就会运行。 |
您可以在加载器源代码中找到更多类型。
例如,以下 myschedtp.c 程序添加了与仍在特定 CPU 上运行的最新任务 PID 相关的信息。此程序通过创建映射并定义可附加到 sched:sched_switch 跟踪事件的 tp_sched_switch 函数来实现其目标。如需了解详情,请参阅将程序附加到跟踪点。
#include <linux/bpf.h>
#include <stdbool.h>
#include <stdint.h>
#include <bpf_helpers.h>
DEFINE_BPF_MAP(cpu_pid_map, ARRAY, int, uint32_t, 1024);
struct switch_args {
unsigned long long ignore;
char prev_comm[16];
int prev_pid;
int prev_prio;
long long prev_state;
char next_comm[16];
int next_pid;
int next_prio;
};
DEFINE_BPF_PROG("tracepoint/sched/sched_switch", AID_ROOT, AID_SYSTEM, tp_sched_switch)
(struct switch_args *args) {
int key;
uint32_t val;
key = bpf_get_smp_processor_id();
val = args->next_pid;
bpf_cpu_pid_map_update_elem(&key, &val, BPF_ANY);
return 1; // return 1 to avoid blocking simpleperf from receiving events
}
LICENSE("GPL");
当该程序使用由内核提供的 BPF 辅助函数时,系统会使用 LICENSE 宏来验证该程序是否与内核的许可证兼容。以字符串形式指定程序的许可证的名称,例如 LICENSE("GPL") 或 LICENSE("Apache 2.0")。
Android.bp 文件的格式
为了使 Android 构建系统能够构建 eBPF .c 程序,您必须在项目的 Android.bp 文件中输入相应的内容。例如,如需构建一个名为 bpf_test.c 的 eBPF C 程序,请在项目的 Android.bp 文件中输入以下内容:
bpf {
name: "bpf_test.o",
srcs: ["bpf_test.c"],
cflags: [
"-Wall",
"-Werror",
],
}
此内容会编译该 C 程序并生成对象 /system/etc/bpf/bpf_test.o。在启动时,Android 系统会自动将 bpf_test.o 程序加载到内核中。
注意:获得 GPL 许可证的 eBPF C 程序应采用 system/bpfprogs
sysfs 中的可用文件
在启动过程中,Android 系统会自动从 /system/etc/bpf/ 加载所有 eBPF 对象、创建程序所需的映射,并将加载的程序及其映射固定到 BPF 文件系统。这些文件随后可用于与 eBPF 程序进一步交互或读取映射。本部分介绍了这些文件的命名规范及它们在 sysfs 中的位置。
系统会创建并固定以下文件:
-
对于加载的任何程序,假设
PROGNAME是程序的名称,而FILENAME是 eBPF C 文件的名称,则 Android 加载器会创建每个程序并将其固定到/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME。例如,对于上述
myschedtp.c中的sched_switch跟踪点示例,系统会创建一个程序文件并将其固定到/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch。 -
对于创建的任何映射,假设
MAPNAME是映射的名称,而FILENAME是 eBPF C 文件的名称,则 Android 加载器会创建每个映射并将其固定到/sys/fs/bpf/map_FILENAME_MAPNAME。例如,对于上述
myschedtp.c中的sched_switch跟踪点示例,系统会创建一个映射文件并将其固定到/sys/fs/bpf/map_myschedtp_cpu_pid_map。 -
Android BPF 库中的
bpf_obj_get()可从已固定的/sys/fs/bpf文件返回文件描述符。此文件描述符可用于进一步的操作,例如读取映射或将程序附加到跟踪点。
Android BPF 库
Android BPF 库名为 libbpf_android.so,属于系统映像的一部分。该库向用户提供了执行以下操作所需的低级别 eBPF 功能:创建和读取映射,以及创建探测点、跟踪点和性能缓冲区。
将程序附加到跟踪点
跟踪点程序会在启动时自动加载。加载跟踪点程序后,必须按照以下步骤将其激活:
- 调用
bpf_obj_get()从固定文件的位置获取程序fd。如需了解详情,请参阅 sysfs 中的可用文件。 - 调用 BPF 库中的
bpf_attach_tracepoint(),并向其传递程序fd和跟踪点名称。
以下代码示例展示了如何附加上述 myschedtp.c 源文件中定义的 sched_switch 跟踪点(未显示错误检查):
char *tp_prog_path = "/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch";
char *tp_map_path = "/sys/fs/bpf/map_myschedtp_cpu_pid";
// Attach tracepoint and wait for 4 seconds
int mProgFd = bpf_obj_get(tp_prog_path);
int mMapFd = bpf_obj_get(tp_map_path);
int ret = bpf_attach_tracepoint(mProgFd, "sched", "sched_switch");
sleep(4);
// Read the map to find the last PID that ran on CPU 0
android::bpf::BpfMap<int, int> myMap(mMapFd);
printf("last PID running on CPU %d is %d\n", 0, myMap.readValue(0));
从映射中读取数据
BPF 映射支持任意复杂的键和值结构或类型。Android BPF 库包含一个 android::BpfMap 类,该类利用 C++ 模板根据相关映射的键和值类型来实例化 BpfMap。上述代码示例演示了如何使用键和值为整数的 BpfMap。整数也可以是任意结构。
因此,使用模板化的 BpfMap 类可让您轻松定义适合特定映射的自定义 BpfMap 对象。之后可以使用所生成的自定义函数来访问该映射,这些函数为类型感知型函数,可以令代码更简洁。
如需详细了解 BpfMap,请参阅 Android 源代码。
调试问题
在启动期间,系统将记录与 BPF 加载相关的多条消息。如果加载进程因任何原因失败,logcat 中会提供详细的日志消息。按照“bpf”过滤 logcat 日志时,会输出加载过程中的所有消息和错误详情,例如 eBPF 验证程序错误。
Android 中的 eBPF 示例
AOSP 中的以下程序提供使用 eBPF 的其他示例:

4660

被折叠的 条评论
为什么被折叠?



