擴展伯克利資料包過濾器 (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 是追蹤事件的名稱。有關跟踪點的更多信息,請查看跟踪事件內核文檔。 |
斯克過濾器 | 程式充當網路套接字過濾器。 |
時間表 | 程式充當網路流量分類器。 |
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 的其他範例:
netd
eBPF C 程式由 Android 中的網路守護程式 (netd) 用於各種目的,例如套接字過濾和統計資訊收集。若要了解如何使用程序,請檢查eBPF 流量監控來源。time_in_state
eBPF C 程式計算 Android 應用程式在不同 CPU 頻率下花費的時間,用於計算功耗。在 Android 12 中,
gpu_mem
eBPF C 程式追蹤每個進程和整個系統的 GPU 記憶體總使用量。該程式用於 GPU 記憶體分析。