Der erweiterte Berkeley-Paketfilter (eBPF) ist eine virtuelle Maschine im Kernel, die von Nutzern bereitgestellte eBPF-Programme ausführt, um die Kernelfunktionen zu erweitern. Diese Programme können an Probes oder Ereignisse im Kernel angehängt und verwendet werden, um nützliche Kernelstatistiken zu erfassen, zu überwachen und zu debuggen. Ein Programm wird über den Systemaufruf bpf(2)
in den Kernel geladen und vom Nutzer als Binär-Blob mit eBPF-Maschinenanweisungen bereitgestellt. Das Android-Build-System unterstützt die Kompilierung von C-Programmen in eBPF mithilfe der in diesem Dokument beschriebenen einfachen Build-Dateisyntax.
Weitere Informationen zur internen Struktur und Architektur von eBPF finden Sie unter Brendan Greggs eBPF-Seite
Android enthält einen eBPF-Ladeprogramm und eine Bibliothek, die eBPF-Programme beim Starten lädt.
Android BPF-Ladeprogramm
Während des Android-Startvorgangs werden alle eBPF-Programme unter /system/etc/bpf/
ausgeführt
geladen. Diese Programme sind Binärobjekte, die vom Android-Build-System aus C-Programmen erstellt wurden. Sie werden im Android-Quellbaum von Android.bp
-Dateien begleitet. Das Build-System speichert die generierten Objekte unter /system/etc/bpf
und
werden diese Objekte 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 license
Dabei gilt:
name_of_my_map
ist der Name der Zuordnungsvariablen. Dieses wird dem BPF-Ladeprogramm mitgeteilt, welche Art von Karte erstellt werden soll und mit welchem Parameter. Diese Strukturdefinition wird durch den enthaltenenbpf_helpers.h
-Header bereitgestellt.PROGTYPE/PROGNAME
steht für den Programmtyp und den Programmnamen. Bei der Art des Programms kann es sich um einen Programmtyp handeln, der in der folgenden Tabelle. Ist ein Programmtyp nicht aufgeführt, gibt es keine strikte Benennung Konvention für das Programm; muss der Name nur dem Prozess bekannt sein, das Programm anhängt.PROGFUNC
ist eine Funktion, die beim Kompilieren in einem Abschnitt der resultierenden Datei.
Kprobe | Hooks PROGFUNC an einer Kernelanweisung mithilfe der kprobe-Infrastruktur an. PROGNAME muss der Name der Kernelfunktion sein, die kprobed wird. Weitere Informationen zu Kprobes finden Sie in der Kernel-Dokumentation zu Kprobe.
|
---|---|
tracepoint | Verbindet PROGFUNC mit einem Tracepoint. PROGNAME muss
des Formats SUBSYSTEM/EVENT . Ein Tracepoint-Abschnitt zum Anhängen von Funktionen an Ereignisse des Kontextwechsels des Schedulers wäre beispielsweise SEC("tracepoint/sched/sched_switch") , wobei sched der Name des Trace-Subsystems und sched_switch der Name des Trace-Ereignisses ist. Prüfen Sie den Kernel für Trace-Ereignisse
Dokumentation finden Sie weitere Informationen zu Tracepoints.
|
skfilter | Das Programm dient als Netzwerk-Socket-Filter. |
Schedcls | Das Programm dient als Netzwerkverkehrsklassifikator. |
cgroupskb, cgroupsock | Das Programm wird ausgeführt, wenn Prozesse in einer CGroup einen AF_INET- oder AF_INET6-Socket erstellen. |
Weitere Typen finden Sie im Quellcode des Laders.
Das folgende myschedtp.c
-Programm fügt beispielsweise Informationen zur letzten Aufgabe-PID hinzu, die auf einer bestimmten CPU ausgeführt wurde. Dieses Programm erreicht sein Ziel
indem Sie eine Karte erstellen und eine tp_sched_switch
-Funktion definieren,
an das Trace-Ereignis sched:sched_switch
angehängt. 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");
Mit dem LICENSE-Makro wird geprüft, ob das Programm mit der Lizenz des Kernels kompatibel ist, wenn es BPF-Hilfsfunktionen des Kernels verwendet. Geben Sie den Namen der Lizenz Ihres Programms in Stringform an, z. B. LICENSE("GPL")
oder LICENSE("Apache 2.0")
.
Format der Android.bp-Datei
Damit das Android-Build-System ein eBPF-.c
-Programm erstellen kann, musst du
Erstellen Sie einen Eintrag in der Datei Android.bp
des Projekts. 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", ], }
Mit diesem Eintrag wird das C-Programm kompiliert, was zum Objekt /system/etc/bpf/bpf_test.o
führt. Beim Starten lädt das Android-System das bpf_test.o
-Programm automatisch in den Kernel.
In sysfs verfügbare Dateien
Beim Starten lädt das Android-System automatisch alle eBPF-Objekte aus /system/etc/bpf/
, erstellt die vom Programm benötigten Maps und ordnet das geladene Programm mit seinen Maps dem BPF-Dateisystem zu. Diese Dateien können dann für
weitere Interaktion mit dem eBPF-Programm oder das Lesen von Karten. Dieser Abschnitt
beschreibt die Konventionen für die Benennung dieser Dateien und ihre Speicherorte in
sysfs.
Die folgenden Dateien werden erstellt und angepinnt:
Angenommen,
PROGNAME
ist der Name des Programms undFILENAME
der Name der eBPF-C-Datei. Der Android-Ladeprogramm erstellt und verankert jedes Programm unter/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME
.Für das vorherige Tracepoint-Beispiel
sched_switch
inmyschedtp.c
, wird eine Programmdatei erstellt und an die/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch
Für alle erstellten Karten unter der Annahme,
MAPNAME
ist der Name der Karte undFILENAME
ist der Name der eBPF-C-Datei, die vom Android-Ladeprogramm erstellt und pinnen jede Karte an/sys/fs/bpf/map_FILENAME_MAPNAME
an.Für das vorherige
sched_switch
-Tracepoint-Beispiel inmyschedtp.c
wird beispielsweise eine Kartendatei erstellt und an/sys/fs/bpf/map_myschedtp_cpu_pid_map
angepinnt.bpf_obj_get()
in der Android-BPF-Bibliothek gibt einen Dateideskriptor aus der angepinnten/sys/fs/bpf
-Datei zurück. Dieser Dateideskriptor kann für weitere Vorgänge verwendet werden, z. B. zum Lesen von Karten 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 Systems
Bild. Diese Bibliothek bietet dem Nutzer Low-Level-eBPF-Funktionen, die zum Erstellen und Lesen von Maps, zum Erstellen von Probes, Tracepoints und Perf-Buffers 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 Programmfd
aus dem Speicherort der angepinnten Datei abzurufen. Weitere Informationen finden Sie in der In sysfs verfügbare Dateien - Rufen Sie
bpf_attach_tracepoint()
in der BPF-Bibliothek auf und übergeben Sie ihr das Programmfd
und den Namen des Tracepoints.
Das folgende Codebeispiel zeigt, wie der in der vorherigen myschedtp.c
-Quelldatei definierte sched_switch
-Tracepoint angehängt wird. Die Fehlerprüfung wird nicht angezeigt:
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));
Aus den Karten lesen
BPF-Zuordnungen unterstützen beliebig 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 die Verwendung eines BpfMap
mit Schlüssel veranschaulicht
und Wert als Ganzzahlen. Die Ganzzahlen können auch beliebige Strukturen sein.
So können Sie mit der BpfMap
-Klasse mit Vorlage ein benutzerdefiniertes BpfMap
-Objekt für die jeweilige Karte definieren. Auf die Karte kann dann über die
benutzerdefinierten Funktionen erstellt werden, die typbewusst sind, was zu einem saubereren Code führt.
Weitere Informationen zu BpfMap
finden Sie in den Android-Quellen.
Probleme beheben
Während des Startvorgangs werden mehrere Meldungen im Zusammenhang mit dem Laden von BPF protokolliert. Wenn die
wenn der Ladevorgang aus irgendeinem Grund fehlschlägt, wird eine detaillierte Protokollmeldung angezeigt.
in Logcat ein. Wenn Sie die Logcat-Protokolle nach bpf
filtern, werden alle Nachrichten und alle detaillierten Fehler während der Ladezeit ausgegeben, z. B. eBPF-Verifiziererfehler.
Beispiele für eBPF in Android
Die folgenden Programme in AOSP bieten weitere Beispiele für die Verwendung von eBPF:
Das
netd
-eBPF-C-Programm wird vom Netzwerk-Daemon (netd) in Android für verschiedene Zwecke wie Socket-Filterung und Statistikerhebung verwendet. Um zu sehen, wie dieses Programm verwendet wird, den eBPF-Traffic überprüfen Monitor.Das
time_in_state
eBPF-C-Programm berechnet die Zeit, die eine Android-App mit verschiedenen CPU-Frequenzen verbringt. Dieser Wert wird zur Berechnung des Energieverbrauchs verwendet.In Android 12 wird mit dem
gpu_mem
eBPF-C-Programm die gesamte GPU-Speichernutzung für jeden Prozess und für das gesamte System erfasst. Dieses Programm wird für das GPU-Arbeitsspeicher-Profiling verwendet.