Extended Berkeley Packet Filter (eBPF) è una macchina virtuale in-kernel che esegue programmi eBPF forniti dall'utente per estendere la funzionalità del kernel. Questi programmi possono essere collegati a probe o eventi nel kernel e utilizzati per raccogliere statistiche utili del kernel, monitorare ed eseguire il debug. Un programma viene caricato nel kernel utilizzando la syscall bpf(2) e viene fornito dall'utente come blob binario di istruzioni macchina eBPF. Il sistema di build di Android supporta la compilazione di programmi C in eBPF utilizzando la sintassi semplice dei file di build descritta in questo documento.
Ulteriori informazioni sugli elementi interni e sull'architettura di eBPF sono disponibili nella pagina eBPF di Brendan Gregg.
Android include un caricatore e una libreria eBPF che carica i programmi eBPF al momento dell'avvio.
Caricatore BPF di Android
Durante l'avvio di Android, vengono caricati tutti i programmi eBPF che si trovano in /system/etc/bpf/. Questi programmi sono oggetti binari creati dal sistema di build di Android da programmi C e sono accompagnati da file Android.bp nell'albero di origine di Android. Il sistema di build archivia gli oggetti generati in /system/etc/bpf e questi oggetti diventano parte dell'immagine di sistema.
Formato di un programma C eBPF di Android
Un programma C eBPF deve avere il seguente formato:
#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 licenseDove:
name_of_my_mapè il nome della variabile della mappa. Questo nome informa il caricatore BPF del tipo di mappa da creare e con quali parametri. Questa definizione di struct è fornita dall'intestazionebpf_helpers.hinclusa.PROGTYPE/PROGNAMErappresenta il tipo di programma e il nome del programma. Il tipo di programma può essere uno di quelli elencati nella tabella seguente. Quando un tipo di programma non è elencato, non esiste una convenzione di denominazione rigorosa per il programma; il nome deve solo essere noto al processo che collega il programma.PROGFUNCè una funzione che, una volta compilata, viene inserita in una sezione del file risultante.
| kprobe | Collega PROGFUNC a un'istruzione del kernel utilizzando l'infrastruttura kprobe. PROGNAME deve essere il nome della funzione del kernel di cui viene eseguito il kprobe. Per ulteriori informazioni sui
kprobe, consulta la documentazione del kernel kprobe.
|
|---|---|
| tracepoint | Collega PROGFUNC a un punto di traccia. PROGNAME deve essere
nel formato SUBSYSTEM/EVENT. Ad esempio, una sezione di punti di traccia
per collegare le funzioni agli eventi di cambio di contesto dello scheduler sarebbe
SEC("tracepoint/sched/sched_switch"), dove sched è
il nome del sottosistema di traccia e sched_switch è il nome
dell'evento di traccia. Per ulteriori informazioni sui punti di traccia, consulta la documentazione del kernel degli eventi di traccia.
|
| skfilter | La funzione del programma funge da filtro di socket di rete. |
| schedcls | La funzione del programma funge da classificatore del traffico di rete. |
| cgroupskb, cgroupsock | Il programma viene eseguito ogni volta che i processi in un CGroup creano un socket AF_INET o AF_INET6. |
Altri tipi possono essere trovati nel codice sorgente del caricatore.
Ad esempio, il seguente programma myschedtp.c aggiunge informazioni sul PID dell'ultima attività eseguita su una determinata CPU. Questo programma raggiunge il suo obiettivo creando una mappa e definendo una funzione tp_sched_switch che può essere collegata all'evento di traccia sched:sched_switch. Per ulteriori informazioni, vedi
Collegare programmi a punti di traccia.
#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");
La macro LICENSE viene utilizzata per verificare se il programma è compatibile con la licenza del kernel quando il programma utilizza le funzioni helper BPF fornite dal kernel. Specifica il nome della licenza del programma in formato stringa, ad esempio
LICENSE("GPL") o LICENSE("Apache 2.0").
Formato del file Android.bp
Affinché il sistema di build di Android possa creare un programma .c eBPF, devi creare una voce nel file Android.bp del progetto. Ad esempio, per creare un programma C eBPF denominato bpf_test.c, inserisci la seguente voce nel file Android.bp del progetto:
bpf {
name: "bpf_test.o",
srcs: ["bpf_test.c"],
cflags: [
"-Wall",
"-Werror",
],
}Questa voce compila il programma C risultante nell'oggetto /system/etc/bpf/bpf_test.o. All'avvio, il sistema Android carica automaticamente il programma bpf_test.o nel kernel.
File disponibili in sysfs
Durante l'avvio, il sistema Android carica automaticamente tutti gli oggetti eBPF da /system/etc/bpf/, crea le mappe di cui il programma ha bisogno e blocca il programma caricato con le relative mappe nel file system BPF. Questi file possono essere utilizzati per ulteriori interazioni con il programma eBPF o per la lettura delle mappe. Questa sezione descrive le convenzioni utilizzate per la denominazione di questi file e le relative posizioni in sysfs.
Vengono creati e bloccati i seguenti file:
Per tutti i programmi caricati, supponendo che
PROGNAMEsia il nome del programma eFILENAMEsia il nome del file C eBPF, il caricatore Android crea e blocca ogni programma in/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME.Ad esempio, per l'esempio precedente del punto di traccia
sched_switchinmyschedtp.c, viene creato un file di programma e bloccato in/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch.Per tutte le mappe create, supponendo che
MAPNAMEsia il nome della mappa eFILENAMEsia il nome del file C eBPF, il caricatore Android crea e blocca ogni mappa in/sys/fs/bpf/map_FILENAME_MAPNAME.Ad esempio, per l'esempio precedente del punto di traccia
sched_switchinmyschedtp.c, viene creato un file di mappa e bloccato in/sys/fs/bpf/map_myschedtp_cpu_pid_map.bpf_obj_get()nella libreria BPF di Android restituisce un descrittore del file dal file/sys/fs/bpfbloccato. Questo descrittore del file può essere utilizzato per ulteriori operazioni, ad esempio la lettura delle mappe o il collegamento di un programma a un punto di traccia.
Libreria BPF di Android
La libreria BPF di Android si chiama libbpf_android.so e fa parte dell'immagine di sistema. Questa libreria fornisce all'utente le funzionalità eBPF di basso livello necessarie per creare e leggere mappe, creare probe, punti di traccia e buffer perf.
Collegare programmi a punti di traccia
I programmi dei punti di traccia vengono caricati automaticamente all'avvio. Dopo il caricamento, il programma del punto di traccia deve essere attivato seguendo questi passaggi:
- Chiama
bpf_obj_get()per ottenere il programmafddalla posizione del file bloccato. Per ulteriori informazioni, consulta la sezione File disponibili in sysfs. - Chiama
bpf_attach_tracepoint()nella libreria BPF, passandogli il programmafde il nome del punto di traccia.
Il seguente esempio di codice mostra come collegare il punto di traccia sched_switch definito nel file sorgente myschedtp.c precedente (il controllo degli errori non viene mostrato):
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));
Leggere dalle mappe
Le mappe BPF supportano strutture o tipi di chiavi e valori arbitrariamente complessi. La
libreria BPF di Android include una classe android::BpfMap che utilizza i modelli C++
per creare un'istanza di BpfMap in base al tipo di chiave e valore per la
mappa in questione. L'esempio di codice precedente mostra l'utilizzo di un BpfMap con chiave e valore come numeri interi. I numeri interi possono anche essere strutture arbitrarie.
Pertanto, la classe BpfMap con modelli consente di definire un oggetto BpfMap personalizzato adatto alla mappa specifica. È possibile accedere alla mappa utilizzando le funzioni generate personalizzate, che sono consapevoli del tipo, il che si traduce in un codice più pulito.
Per ulteriori informazioni su BpfMap, consulta le
origini Android.
Eseguire il debug di problemi
Durante l'avvio, vengono registrati diversi messaggi relativi al caricamento di BPF. Se il processo di caricamento non riesce per qualsiasi motivo, viene fornito un messaggio di log dettagliato in logcat. Il filtro dei log logcat per bpf stampa tutti i messaggi e gli errori dettagliati durante il caricamento, ad esempio gli errori del verificatore eBPF.
Esempi di eBPF in Android
I seguenti programmi in AOSP forniscono esempi aggiuntivi di utilizzo di eBPF:
Il
netdprogramma C eBPFviene utilizzato dal daemon di rete (netd) in Android per vari scopi, come il filtro dei socket e la raccolta di statistiche. Per vedere come viene utilizzato questo programma, consulta le origini del monitor del traffico eBPF.Il
time_in_stateprogramma C eBPF calcola la quantità di tempo che un'app per Android trascorre a diverse frequenze della CPU, che viene utilizzata per calcolare il consumo di energia.In Android 12, il
gpu_memprogramma C eBPF tiene traccia dell'utilizzo totale della memoria GPU per ogni processo e per l'intero sistema. Questo programma viene utilizzato per la profilazione della memoria GPU.
Per ulteriori informazioni sull'utilizzo di eBPF per raccogliere istogrammi di gestione della memoria del kernel a basso overhead
e di latenza di alimentazione per statsd, vedi Raccogliere la telemetria del kernel
con eBPF.