Rozszerzony filtr pakietów Berkeley (eBPF) to maszyna wirtualna 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ć 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 obiekt blob z instrukcjami maszynowymi eBPF. System kompilacji Androida obsługuje kompilowanie programów w języku C do formatu eBPF przy użyciu 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ł wczytujący eBPF i bibliotekę, które wczytują programy eBPF podczas uruchamiania.
Ładowarka BPF na Androida
Podczas uruchamiania Androida wczytywane są wszystkie programy eBPF znajdujące się w lokalizacji /system/etc/bpf/. Te programy to obiekty binarne tworzone 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 /system/etc/bpf, a obiekty te stają się częścią obrazu systemu.
Format programu C eBPF na Androida
Program C w eBPF 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ł wczytujący BPF o typie mapy do utworzenia i parametrach, z jakimi ma to zrobić. Ta definicja struktury jest podana w dołączonym pliku nagłówkowymbpf_helpers.h.PROGTYPE/PROGNAMEreprezentuje typ programu i jego nazwę. Typ programu może być dowolny z tych wymienionych w tabeli poniżej. Jeśli danego typu programu nie ma na liście, nie obowiązuje go ścisła konwencja nazewnictwa. Nazwa musi być tylko znana procesowi, który dołącza program.PROGFUNCto funkcja, która po skompilowaniu jest umieszczana w sekcji wynikowego pliku.
| kprobe | Podłącza się PROGFUNC do instrukcji jądra za pomocą infrastruktury kprobe. PROGNAME musi być nazwą funkcji jądra, która jest sondowana za pomocą narzędzia kprobe. Więcej informacji o kprobach znajdziesz w dokumentacji jądra dotyczącej kprobów.
|
|---|---|
| punkt śledzenia | Przyczepia się 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 zdarzeń śledzenia jądra.
|
| skfilter | Program działa jako filtr gniazd sieciowych. |
| schedcls | Program działa jako klasyfikator ruchu sieciowego. |
| cgroupskb, cgroupsock | Program jest uruchamiany, gdy procesy w grupie CGroup tworzą gniazdo AF_INET lub AF_INET6. |
Dodatkowe typy znajdziesz w kodzie źródłowym narzędzia do wczytywania.
Na przykład poniższy program myschedtp.c dodaje informacje o najnowszym identyfikatorze PID zadania, które zostało uruchomione na danym procesorze. Ten program osiąga swój cel, tworząc mapę i definiując tp_sched_switchfunkcję, którą można dołączyć do sched:sched_switchzdarzenia śledzenia. 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 korzysta z funkcji pomocniczych BPF udostępnianych przez jądro. Określ nazwę licencji programu w formie ciągu tekstowego, 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 w języku C eBPF o nazwie bpf_test.c, w pliku Android.bp projektu wpisz:
bpf {
name: "bpf_test.o",
srcs: ["bpf_test.c"],
cflags: [
"-Wall",
"-Werror",
],
}Ta pozycja kompiluje program w języku C, co daje obiekt /system/etc/bpf/bpf_test.o. Podczas uruchamiania system Android automatycznie wczytuje program bpf_test.o do jądra.
Pliki dostępne w sysfs
Podczas uruchamiania system Android automatycznie wczytuje wszystkie obiekty eBPF z /system/etc/bpf/, tworzy mapy potrzebne programowi i przypina wczytany program wraz z mapami do systemu plików BPF. Te pliki można następnie wykorzystać do dalszej interakcji z programem eBPF lub odczytywania map. W tej sekcji opisano konwencje używane do nazywania tych plików i ich lokalizacji w sysfs.
Utworzone i przypięte zostaną te pliki:
W przypadku wszystkich wczytanych programów, przy założeniu, że
PROGNAMEto nazwa programu, aFILENAMEto nazwa pliku C eBPF, moduł wczytujący Androida tworzy i przypina każdy program w lokalizacji/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME.Na przykład w przypadku poprzedniego przykładu punktu śledzenia
sched_switchwmyschedtp.ctworzony jest plik programu, który jest 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 C eBPF, program wczytujący Androida tworzy i przypina każdą mapę do/sys/fs/bpf/map_FILENAME_MAPNAME.Na przykład w przypadku poprzedniego przykładu punktu śledzenia
sched_switchwmyschedtp.ctworzony jest plik mapy, który jest przypinany do/sys/fs/bpf/map_myschedtp_cpu_pid_map.bpf_obj_get()w bibliotece Android BPF 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 na Androida
Biblioteka BPF na Androida ma nazwę libbpf_android.so i jest częścią obrazu systemu. Ta biblioteka udostępnia użytkownikowi funkcje 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ą wczytywane automatycznie podczas uruchamiania. Po wczytaniu programu punktu śledzenia należy go aktywować, wykonując te czynności:
- Zadzwoń pod numer
bpf_obj_get(), aby uzyskać programfdz 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 programfdi nazwę punktu śledzenia.
Poniższy przykładowy kod pokazuje, jak dołączyć sched_switchpunkt śledzeniamyschedtp.c zdefiniowany w poprzednim pliku źródłowym (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 Android BPF zawiera klasę android::BpfMap, która używa szablonów C++ do tworzenia instancji klasy BpfMap na podstawie typu klucza i wartości dla danej mapy. W poprzednim przykładzie kodu pokazano użycie BpfMap z kluczem i wartością jako liczbami całkowitymi. Liczby całkowite mogą też być dowolnymi strukturami.
Dlatego szablonowana klasa BpfMap umożliwia zdefiniowanie niestandardowego obiektu BpfMap
odpowiedniego dla danej mapy. Dostęp do mapy można uzyskać za pomocą wygenerowanych niestandardowo funkcji, które są świadome typu, co pozwala uzyskać bardziej przejrzysty kod.
Więcej informacji o BpfMap znajdziesz w źródłach Androida.
debugowanie problemów.
Podczas uruchamiania systemu rejestrowanych jest kilka komunikatów związanych z ładowaniem BPF. Jeśli proces wczytywania z jakiegoś powodu się nie powiedzie, w logcat pojawi się szczegółowy komunikat dziennika. Filtrowanie dzienników logcat według bpf powoduje wyświetlenie wszystkich wiadomości i szczegółowych błędów podczas ładowania, np. błędów weryfikatora eBPF.
Przykłady wykorzystania eBPF na Androidzie
Dodatkowe przykłady użycia eBPF znajdziesz w tych programach w AOSP:
netdProgram eBPF jest używany przez demona sieciowego (netd) w Androidzie do różnych celów, takich jak filtrowanie gniazd i zbieranie statystyk. Aby dowiedzieć się, jak ten program jest używany, sprawdź źródła monitora ruchu eBPF.time_in_stateProgram eBPF 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
gpu_memprogram eBPF C śledzi łączne wykorzystanie pamięci GPU w przypadku każdego procesu i całego systemu. Ten program służy do profilowania pamięci GPU.