Extended Berkeley Packet Filter (eBPF) 是一個內核虛擬機,它運行用戶提供的 eBPF 程序來擴展內核功能。這些程序可以與內核中的探測器或事件掛鉤,並用於收集有用的內核統計信息、監控和調試。程序使用bpf(2)系統調用加載到內核中,並由用戶作為 eBPF 機器指令的二進制 blob 提供。 Android 構建系統支持使用本文檔中描述的簡單構建文件語法將 C 程序編譯為 eBPF。
有關 eBPF 內部結構和架構的更多信息,請訪問Brendan Gregg 的 eBPF 頁面。
Android 包含一個 eBPF 加載器和庫,可在啟動時加載 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 基礎結構將PROGFUNC掛鉤到內核指令。 PROGNAME必須是被 kprobed 的內核函數的名稱。有關 kprobe 的更多信息,請參閱kprobe 內核文檔。 |
|---|---|
| 跟踪點 | 將PROGFUNC掛鉤到跟踪點。 PROGNAME必須採用SUBSYSTEM/EVENT格式。例如,用於將函數附加到調度程序上下文切換事件的跟踪點部分將是SEC("tracepoint/sched/sched_switch") ,其中sched是跟踪子系統的名稱, sched_switch是跟踪事件的名稱。查看跟踪事件內核文檔以獲取有關跟踪點的更多信息。 |
| skfilter | 程序用作網絡套接字過濾器。 |
| 時間表 | 程序用作網絡流量分類器。 |
| cgroupskb, cgroupsock | 每當 CGroup 中的進程創建 AF_INET 或 AF_INET6 套接字時,程序就會運行。 |
其他類型可以在Loader 源代碼中找到。
例如,以下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程序加載到內核中。
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 功能。
將程序附加到跟踪點
Tracepoint 程序會在啟動時自動加載。加載後,必須使用以下步驟激活跟踪點程序:
- 調用
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 的其他示例: