Der erweiterte Berkeley-Paketfilter (eBPF) ist eine In-Kernel-VM, die von Nutzern bereitgestellte eBPF-Programme ausführt, um die Kernel-Funktionalität zu erweitern. Diese Programme können an Probes oder Ereignisse im Kernel angehängt werden und zum Erfassen nützlicher Kernelstatistiken, zur Überwachung und zum Debuggen verwendet werden. Ein Programm wird mit dem Systemaufruf bpf(2) in den Kernel geladen und vom Nutzer als binärer Blob mit eBPF-Maschinenanweisungen bereitgestellt. Das Android-Build-System unterstützt das Kompilieren von C-Programmen in eBPF mithilfe einer einfachen Build-Datei-Syntax, die in diesem Dokument beschrieben wird.
Weitere Informationen zu eBPF-Interna und -Architektur finden Sie auf Brendan Greggs eBPF-Seite.
Android enthält einen eBPF-Loader und eine Bibliothek, die eBPF-Programme beim Booten lädt.
Android-BPF-Ladeprogramm
Beim Booten von Android werden alle eBPF-Programme unter /system/etc/bpf/ geladen. Diese Programme sind binäre Objekte, die vom Android-Build-System aus C-Programmen erstellt werden. Sie werden von Android.bp-Dateien im Android-Quellbaum begleitet. Das Build-System speichert die generierten Objekte unter /system/etc/bpf. Diese Objekte werden Teil des System-Images.
Format eines Android-eBPF-C-Programms
Ein eBPF-C-Programm muss das folgende Format haben:
#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 also defines 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 licenseDabei gilt:
name_of_my_mapist der Name Ihrer Kartenvariablen. Der Name informiert den BPF-Loader über den Typ der zu erstellenden Karte und die zu verwendenden Parameter. Diese Strukturdefinition wird durch den enthaltenen Headerbpf_helpers.hbereitgestellt.PROGTYPE/PROGNAMEsteht für den Typ des Programms und den Programmnamen. Der Typ des Programms kann einer der in der folgenden Tabelle aufgeführten Typen sein. Wenn ein Programmtyp nicht aufgeführt ist, gibt es keine strenge Namenskonvention für das Programm. Der Name muss nur dem Prozess bekannt sein, der das Programm anhängt.PROGFUNCist eine Funktion, die nach der Kompilierung in einem Abschnitt der resultierenden Datei platziert wird.
| kprobe | Hooks PROGFUNC an einer Kernelanweisung mithilfe der kprobe-Infrastruktur. PROGNAME muss der Name der Kernel-Funktion sein, die mit kprobe untersucht wird. Weitere Informationen zu kprobes finden Sie in der Kernel-Dokumentation zu kprobe.
|
|---|---|
| Tracepoint | Hooks PROGFUNC an einem Tracepoint. PROGNAME muss das Format SUBSYSTEM/EVENT haben. Ein Beispiel für einen Tracepoint-Abschnitt zum Anhängen von Funktionen an Ereignisse für Kontextwechsel des Schedulers wäre SEC("tracepoint/sched/sched_switch"), wobei sched der Name des Trace-Subsystems und sched_switch der Name des Trace-Ereignisses ist. Weitere Informationen zu Tracepoints finden Sie in der Kernel-Dokumentation zu Trace-Ereignissen.
|
| skfilter | Programmfunktionen als Netzwerk-Socket-Filter. |
| schedcls | Programmfunktionen als Klassifikator für Netzwerkverkehr. |
| cgroupskb, cgroupsock | Das Programm wird ausgeführt, wenn Prozesse in einer CGroup einen AF_INET- oder AF_INET6-Socket erstellen. |
Zusätzliche Typen finden Sie im Quellcode des Loaders.
Das folgende myschedtp.c-Programm fügt beispielsweise Informationen zur letzten Aufgaben-PID hinzu, die auf einer bestimmten CPU ausgeführt wurde. Das Ziel dieses Programms wird erreicht, indem eine Karte erstellt und eine tp_sched_switch-Funktion definiert wird, die an das sched:sched_switch-Trace-Ereignis angehängt werden kann. Weitere Informationen finden Sie unter Programme an Tracepoints anhängen.
#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");
Das Makro LICENSE wird verwendet, um zu prüfen, ob das Programm mit der Lizenz des Kernels kompatibel ist, wenn das Programm BPF-Hilfsfunktionen verwendet, die vom Kernel bereitgestellt werden. Geben Sie den Namen der Lizenz Ihres Programms als String an, z. B. LICENSE("GPL") oder LICENSE("Apache 2.0").
Format der Datei „Android.bp“
Damit das Android-Build-System ein eBPF-.c-Programm erstellen kann, müssen Sie einen Eintrag in der Datei Android.bp des Projekts erstellen. Wenn Sie beispielsweise ein eBPF-C-Programm mit dem Namen bpf_test.c erstellen möchten, fügen Sie der Datei Android.bp Ihres Projekts den folgenden Eintrag hinzu:
bpf {
name: "bpf_test.o",
srcs: ["bpf_test.c"],
cflags: [
"-Wall",
"-Werror",
],
}In diesem Eintrag wird das C-Programm kompiliert, das zum Objekt /system/etc/bpf/bpf_test.o führt. Beim Booten lädt das Android-System automatisch das Programm bpf_test.o in den Kernel.
In sysfs verfügbare Dateien
Beim Booten lädt das Android-System automatisch alle eBPF-Objekte aus /system/etc/bpf/, erstellt die Karten, die das Programm benötigt, und pinnt das geladene Programm mit seinen Karten an das BPF-Dateisystem. Diese Dateien können dann für die weitere Interaktion mit dem eBPF-Programm oder zum Lesen von Maps verwendet werden. In diesem Abschnitt werden die Konventionen beschrieben, die für die Benennung dieser Dateien und ihrer Speicherorte in sysfs verwendet werden.
Die folgenden Dateien werden erstellt und angepinnt:
Für alle geladenen Programme, wobei
PROGNAMEder Name des Programms undFILENAMEder Name der eBPF-C-Datei ist, erstellt und pinnt der Android-Loader jedes Programm unter/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME.Für das vorherige
sched_switch-Tracepoint-Beispiel inmyschedtp.cwird beispielsweise eine Programmdatei erstellt und an/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switchangepinnt.Für alle erstellten Maps, wobei
MAPNAMEder Name der Map undFILENAMEder Name der eBPF-C-Datei ist, erstellt und pinnt der Android-Loader jede Map an/sys/fs/bpf/map_FILENAME_MAPNAME.Für das vorherige
sched_switch-Tracepoint-Beispiel inmyschedtp.cwird beispielsweise eine Map-Datei erstellt und an/sys/fs/bpf/map_myschedtp_cpu_pid_mapangepinnt.bpf_obj_get()in der Android-BPF-Bibliothek gibt einen Dateideskriptor aus der angepinnten Datei/sys/fs/bpfzurück. Dieser Dateideskriptor kann für weitere Vorgänge verwendet werden, z. B. zum Lesen von Maps oder zum Anhängen eines Programms an einen Tracepoint.
Android BPF-Bibliothek
Die Android-BPF-Bibliothek heißt libbpf_android.so und ist Teil des System-Images. Diese Bibliothek bietet dem Nutzer die Low-Level-eBPF-Funktionen, die zum Erstellen und Lesen von Maps sowie zum Erstellen von Probes, Tracepoints und Perf-Puffern erforderlich sind.
Programme an Tracepoints anhängen
Tracepoint-Programme werden beim Booten automatisch geladen. Nach dem Laden muss das Tracepoint-Programm so aktiviert werden:
- Rufen Sie
bpf_obj_get()auf, um das Programmfdaus dem Speicherort der angepinnten Datei abzurufen. Weitere Informationen finden Sie unter In sysfs verfügbare Dateien. - Rufen Sie
bpf_attach_tracepoint()in der BPF-Bibliothek auf und übergeben Sie das Programmfdund den Namen des Tracepoints.
Das folgende Codebeispiel zeigt, wie Sie den im vorherigen myschedtp.c-Quellcode definierten sched_switch-Tracepoint anhängen (die Fehlerprüfung wird nicht gezeigt):
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));
Karten lesen
BPF-Maps unterstützen beliebige komplexe Schlüssel- und Wertstrukturen oder -typen. Die Android BPF-Bibliothek enthält eine android::BpfMap-Klasse, die C++-Vorlagen verwendet, um BpfMap basierend auf dem Schlüssel- und Werttyp für die betreffende Map zu instanziieren. Im vorherigen Codebeispiel wird ein BpfMap mit Schlüssel und Wert als Ganzzahlen verwendet. Die Ganzzahlen können auch beliebige Strukturen sein.
Mit der templatisierten Klasse BpfMap können Sie also ein benutzerdefiniertes BpfMap-Objekt definieren, das für die jeweilige Karte geeignet ist. Auf die Map kann dann über die benutzerdefinierten Funktionen zugegriffen werden, die typbezogen sind, was zu saubererem Code führt.
Weitere Informationen zu BpfMap finden Sie unter Android-Quellen.
Den Code debuggen
Während des Bootvorgangs werden mehrere Meldungen zum BPF-Laden protokolliert. Wenn der Ladevorgang aus irgendeinem Grund fehlschlägt, wird in logcat eine detaillierte Logmeldung ausgegeben. Wenn Sie die logcat-Logs nach bpf filtern, werden alle Nachrichten und detaillierte Fehler während der Ladezeit ausgegeben, z. B. eBPF-Prüffehler.
Beispiele für eBPF in Android
Die folgenden Programme in AOSP enthalten weitere Beispiele für die Verwendung von eBPF:
Das
netdeBPF-C-Programm wird vom Netzwerk-Daemon (netd) in Android für verschiedene Zwecke verwendet, z. B. für die Socket-Filterung und die Erfassung von Statistiken. Informationen zur Verwendung dieses Programms finden Sie in den Quellen des eBPF-Traffic-Monitors.Das
time_in_stateeBPF-C-Programm berechnet die Zeit, die eine Android-App bei verschiedenen CPU-Frequenzen verbringt. Diese Daten werden zur Berechnung des Stromverbrauchs verwendet.In Android 12 wird mit dem
gpu_memeBPF-C-Programm die gesamte GPU-Arbeitsspeichernutzung für jeden Prozess und für das gesamte System erfasst. Dieses Programm wird für das Profiling des GPU-Arbeitsspeichers verwendet.