Rozszerzony filtr pakietów Berkeley (eBPF) to wirtualna maszyna w jądrze, która uruchamia dostarczone przez użytkownika programy eBPF, aby rozszerzyć funkcjonalność jądra. Te programy można podłączyć do sond lub zdarzeń w jądrze i używać ich do zbierania przydatnych statystyk jądra, monitorowania i debugowania. Program jest ładowany do jądra za pomocą wywołania systemowego bpf(2) i jest dostarczany przez użytkownika jako binarny blob instrukcji maszyny eBPF. System kompilacji Androida obsługuje kompilowanie programów w języku C do eBPF za pomocą prostej składni pliku kompilacji opisanej w tym dokumencie.
Więcej informacji o wewnętrznej strukturze i architekturze eBPF znajdziesz na stronie Brendana Gregga poświęconej eBPF.
Android zawiera moduł ładujący eBPF i bibliotekę, która ładuje programy eBPF podczas uruchamiania systemu.
Moduł ładujący BPF w Androidzie
Podczas uruchamiania Androida ładowane są wszystkie programy eBPF znajdujące się w katalogu /system/etc/bpf/. Te programy to obiekty binarne utworzone przez system kompilacji Androida z programów w języku C. Towarzyszą im pliki Android.bp w drzewie źródłowym Androida. System kompilacji przechowuje wygenerowane obiekty w katalogu /system/etc/bpf, a te obiekty stają się częścią obrazu systemu.
Format programu eBPF w języku C w Androidzie
Program eBPF w języku C musi mieć ten format:
#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 licenseGdzie:
name_of_my_mapto nazwa zmiennej mapy. Ta nazwa informuje moduł ładujący BPF o typie mapy do utworzenia i o parametrach. Ta definicja struktury jest dostarczana przez dołączony plik nagłówkowybpf_helpers.h.PROGTYPE/PROGNAMEto typ programu i jego nazwa. Typ programu może być dowolnym typem wymienionym w tabeli poniżej. Jeśli typ programu nie jest wymieniony, nie obowiązuje ścisła konwencja nazewnictwa programu. Nazwa musi być tylko znana procesowi, który dołącza program.PROGFUNCto funkcja, która po skompilowaniu jest umieszczana w sekcji wynikowego pliku.
| kprobe | Dołącza PROGFUNC do instrukcji jądra za pomocą infrastruktury kprobe. PROGNAME musi być nazwą funkcji jądra, która jest kprobed. Więcej informacji o
kprobes znajdziesz w dokumentacji jądra kprobe.
|
|---|---|
| tracepoint | Dołącza PROGFUNC do punktu śledzenia. PROGNAME musi mieć
format SUBSYSTEM/EVENT. Na przykład sekcja punktu śledzenia
do dołączania funkcji do zdarzeń przełączania kontekstu harmonogramu to
SEC("tracepoint/sched/sched_switch"), gdzie sched to
nazwa podsystemu śledzenia, a sched_switch to nazwa
zdarzenia śledzenia. Więcej informacji o punktach śledzenia znajdziesz w dokumentacji
jądra dotyczącej zdarzeń śledzenia.
|
| skfilter | Funkcje programu jako filtr gniazd sieciowych. |
| schedcls | Program działa jako klasyfikator ruchu w sieci. |
| cgroupskb, cgroupsock | Program jest uruchamiany, gdy procesy w CGroup tworzą gniazdo AF_INET lub AF_INET6. |
Dodatkowe typy znajdziesz w kodzie źródłowym modułu ładującego.
Na przykład ten program myschedtp.c dodaje informacje o najnowszym identyfikatorze PID zadania, które zostało uruchomione na danym procesorze. Program ten osiąga swój cel, tworząc mapę i definiując funkcję tp_sched_switch, którą można dołączyć do zdarzenia śledzenia sched:sched_switch. Więcej informacji znajdziesz w artykule
Dołączanie programów do punktów śledzenia.
#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");
Makro LICENSE służy do sprawdzania, czy program jest zgodny z licencją jądra, gdy program korzysta z funkcji pomocniczych BPF dostarczanych przez jądro. Określ nazwę licencji programu w postaci ciągu znaków, np.
LICENSE("GPL") lub LICENSE("Apache 2.0").
Format pliku Android.bp
Aby system kompilacji Androida mógł skompilować program eBPF .c, musisz utworzyć wpis w pliku Android.bp projektu. Aby na przykład skompilować program eBPF w języku C o nazwie bpf_test.c, dodaj ten wpis do pliku Android.bp projektu:
bpf {
name: "bpf_test.o",
srcs: ["bpf_test.c"],
cflags: [
"-Wall",
"-Werror",
],
}Ten wpis kompiluje program w języku C, tworząc obiekt /system/etc/bpf/bpf_test.o. Podczas uruchamiania system Android automatycznie ładuje program bpf_test.o do jądra.
Pliki dostępne w sysfs
Podczas uruchamiania system Android automatycznie ładuje wszystkie obiekty eBPF z katalogu /system/etc/bpf/, tworzy mapy potrzebne programowi i przypina załadowany program z jego mapami do systemu plików BPF. Te pliki można następnie wykorzystać do dalszej interakcji z programem eBPF lub do odczytywania map. W tej sekcji opisujemy konwencje używane do nazywania tych plików i ich lokalizacji w sysfs.
Tworzone i przypinane są te pliki:
W przypadku wszystkich załadowanych programów, przy założeniu, że
PROGNAMEto nazwa programu, aFILENAMEto nazwa pliku eBPF w języku C, moduł ładujący Androida tworzy i przypina każdy program w katalogu/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME.Na przykład w przypadku poprzedniego
sched_switchprzykładu punktu śledzenia wmyschedtp.ctworzony jest plik programu i przypinany do/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch.W przypadku wszystkich utworzonych map, przy założeniu, że
MAPNAMEto nazwa mapy, aFILENAMEto nazwa pliku eBPF w języku C, moduł ładujący Androida tworzy i przypina każdą mapę w katalogu/sys/fs/bpf/map_FILENAME_MAPNAME.Na przykład w przypadku poprzedniego przykładu punktu śledzenia
sched_switchw plikumyschedtp.ctworzony jest plik mapy i przypinany do katalogu/sys/fs/bpf/map_myschedtp_cpu_pid_map.Funkcja
bpf_obj_get()w bibliotece BPF Androida zwraca deskryptor pliku z przypiętego pliku/sys/fs/bpf. Ten deskryptor pliku może być używany do dalszych operacji, takich jak odczytywanie map lub dołączanie programu do punktu śledzenia.
Biblioteka BPF Androida
Biblioteka BPF Androida ma nazwę libbpf_android.so i jest częścią obrazu systemu. Ta biblioteka zapewnia użytkownikowi możliwości eBPF niskiego poziomu potrzebne do tworzenia i odczytywania map, tworzenia sond, punktów śledzenia i buforów wydajności.
Dołączanie programów do punktów śledzenia
Programy punktów śledzenia są ładowane automatycznie podczas uruchamiania. Po załadowaniu program punktu śledzenia musi zostać aktywowany, wykonując te czynności:
- Wywołaj funkcję
bpf_obj_get(), aby uzyskać deskryptor pliku programufdz lokalizacji przypiętego pliku. Więcej informacji znajdziesz w sekcji Pliki dostępne w sysfs. - Wywołaj funkcję
bpf_attach_tracepoint()w bibliotece BPF, przekazując jej deskryptor pliku programufdi nazwę punktu śledzenia.
Ten przykładowy kod pokazuje, jak dołączyć punkt śledzenia sched_switch zdefiniowany w poprzednim pliku źródłowym myschedtp.c (sprawdzanie błędów nie jest pokazane):
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));
Odczytywanie z map
Mapy BPF obsługują dowolne złożone struktury lub typy kluczy i wartości. Biblioteka BPF
Androida zawiera klasę android::BpfMap, która używa szablonów C++
do tworzenia instancji BpfMap na podstawie typu klucza i wartości dla danej
mapy. Poprzedni przykładowy kod pokazuje, jak używać BpfMap z kluczem i wartością jako liczbami całkowitymi. Liczby całkowite mogą być też dowolnymi strukturami.
Dzięki temu klasa BpfMap z szablonami umożliwia zdefiniowanie niestandardowego obiektu BpfMap odpowiedniego dla danej mapy. Dostęp do mapy można następnie uzyskać za pomocą niestandardowych wygenerowanych funkcji, które są świadome typu, co skutkuje czystszym kodem.
Więcej informacji o BpfMap znajdziesz w
źródłach Androida.
Debugowanie problemów
Podczas uruchamiania rejestrowanych jest kilka komunikatów związanych z ładowaniem BPF. Jeśli proces ładowania z jakiegoś powodu się nie powiedzie, w Logcat pojawi się szczegółowy komunikat logu. Filtrowanie logów logcat według bpf powoduje wyświetlenie wszystkich komunikatów i szczegółowych błędów podczas ładowania, takich jak błędy weryfikatora eBPF.
Przykłady eBPF w Androidzie
Te programy w AOSP zawierają dodatkowe przykłady użycia eBPF:
Program
netdeBPF w języku C jest używany przez demona sieciowego (netd) w Androidzie do różnych celów, takich jak filtrowanie gniazd i zbieranie statystyk. Aby zobaczyć, jak ten program jest używany, sprawdź źródła monitora ruchu eBPF.Program
time_in_stateeBPF w języku C oblicza czas, jaki aplikacja na Androida spędza przy różnych częstotliwościach procesora, co jest wykorzystywane do obliczania zużycia energii.W Androidzie 12 program
gpu_memeBPF w języku C śledzi całkowite wykorzystanie pamięci GPU przez każdy proces i cały system. Ten program służy do profilowania pamięci GPU.
Więcej informacji o używaniu eBPF do zbierania histogramów zarządzania pamięcią jądra
i opóźnień związanych z energią o niskim obciążeniu dla statsd znajdziesz w artykule Zbieranie telemetrii jądra
za pomocą eBPF.