Rozszerzony filtr pakietów Berkeley (eBPF) to maszyna wirtualna w jądrze, która wykonuje dostarczane przez użytkownika programy eBPF w celu rozszerzenia funkcjonalności jądra. Te programy można podłączyć do sond lub zdarzeń w rdzeniu i wykorzystywać 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 binarna porcja instrukcji eBPF. System kompilacji Androida obsługuje kompilowanie programów C na eBPF za pomocą prostej składni pliku kompilacji opisanej w tym dokumencie.
Więcej informacji o elementach wewnętrznych i architekturze eBPF znajdziesz na stronie Brendan Strona Gregga eBPF.
Android zawiera ładowarkę eBPF i bibliotekę, które wczytują programy eBPF podczas uruchamiania.
Ładowarka BPF na Androida
Podczas uruchamiania Androida wszystkie programy eBPF znajdujące się na urządzeniu /system/etc/bpf/
są
wczytano. Są to obiekty binarne utworzone przez system kompilacji Androida na podstawie programów C. W drzewie źródeł Androida towarzyszą im pliki Android.bp
. System kompilacji przechowuje wygenerowane obiekty w /system/etc/bpf
,
stają się częścią obrazu systemu.
Format programu na Androida eBPF C
Program eBPF C musi mieć następujący 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 license
Gdzie:
name_of_my_map
to nazwa zmiennej mapy. Ten informuje program ładujący BPF o typie mapy do utworzenia i o tym, . Ta definicja struktury jest dostarczana przez dołączony nagłówekbpf_helpers.h
.PROGTYPE/PROGNAME
oznacza typ programu i jego nazwę. Program może należeć do dowolnego typu wymienionego w tabeli poniżej. Jeśli dany typ programu nie jest wymieniony, nie ma ścisłych zasad nazewnictwa programu. Nazwa musi być znana procesowi, który łączy program.PROGFUNC
to funkcja, która po skompilowaniu jest umieszczana w sekcji pliku wynikowego.
kprobe | Łączy usługę PROGFUNC z użyciem instrukcji jądra za pomocą metody
kprobe. PROGNAME musi być nazwą funkcji jądra, do której ma być zastosowany kprobe. Więcej informacji o kprobe znajdziesz w dokumentacji dotyczącej jądra kprobe.
|
---|---|
punkt kontrolny | Pociąga PROGFUNC w punktu śledzenia. PROGNAME musi mieć format SUBSYSTEM/EVENT . Na przykład sekcja punktów przełączania w celu dołączania funkcji do zdarzeń przełączania kontekstu przez planistę to: SEC("tracepoint/sched/sched_switch") , gdzie sched to nazwa podsystemu śledzenia, a sched_switch to nazwa zdarzenia śledzenia. Sprawdź jądro zdarzeń śledzenia
dokumentacji, aby dowiedzieć się więcej o Tracepoints.
|
skfilter | Program działa jako filtr gniazdka sieciowego. |
schedcls | Program działa jak klasyfikator ruchu sieciowego. |
cgroupskb, cgroupsock | Program jest uruchamiany za każdym razem, gdy procesy w grupie CGroup tworzą AF_INET lub AF_INET6 gniazda elektrycznego. |
Dodatkowe typy zostaną znalezione w module wczytywania kodu źródłowego.
Na przykład program myschedtp.c
dodaje informacje o
Identyfikator PID ostatniego zadania, który został uruchomiony na konkretnym procesorze. Ten program osiąga swój cel przez utworzenie mapy i zdefiniowanie funkcji tp_sched_switch
, którą można dołączyć do zdarzenia sched:sched_switch
ś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. Podaj nazwę licencji programu w postaci 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. Na przykład aby skompilować program eBPF C o nazwie bpf_test.c
, w pliku Android.bp
projektu wykonaj tę czynność:
bpf { name: "bpf_test.o", srcs: ["bpf_test.c"], cflags: [ "-Wall", "-Werror", ], }
Ten wpis kompiluje program C, który tworzy obiekt
/system/etc/bpf/bpf_test.o
Podczas uruchamiania system Android automatycznie się wczytuje
i zainstalować program bpf_test.o
w jądrze.
Pliki dostępne w sysfs
Podczas uruchamiania system Android automatycznie wczytuje wszystkie obiekty eBPF z
/system/etc/bpf/
, tworzy mapy potrzebne programowi i przypina wczytane
z mapami do systemu plików BPF. Pliki te będą następnie wykorzystywane do:
dalszą interakcję z programem eBPF lub czytaniem map. W tej sekcji opisano konwencje nazewnictwa tych plików oraz ich lokalizację w sysfs.
Tworzone i przypinane są te pliki:
Dla wszystkich wczytanych programów, zakładając, że
PROGNAME
to nazwa programu iFILENAME
to nazwa pliku eBPF C, program wczytujący Androida tworzy przypina każdy program na stronie/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME
.Na przykład dla poprzedniego przykładu punktu śledzenia
sched_switch
wmyschedtp.c
, zostaje utworzony plik programu i przypięty do/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch
W przypadku każdej utworzonej mapy, zakładając, że
MAPNAME
to nazwa mapy iFILENAME
to nazwa pliku eBPF C, program wczytujący Androida tworzy przypina każdą mapę do:/sys/fs/bpf/map_FILENAME_MAPNAME
.Na przykład dla poprzedniego przykładu punktu śledzenia
sched_switch
wmyschedtp.c
, zostaje utworzony plik mapy i przypięty do/sys/fs/bpf/map_myschedtp_cpu_pid_map
bpf_obj_get()
w bibliotece BPF Androida zwraca opis pliku z przypiętego pliku/sys/fs/bpf
. Tego deskryptora pliku można używać w przyszłości takie jak odczytywanie map czy podłączanie programu do punktu śledzenia.
Biblioteka BPF na Androida
Biblioteka BPF Androida ma nazwę libbpf_android.so
i jest częścią systemu
. Ta biblioteka zapewnia użytkownikowi niskopoziomowe funkcje eBPF, które są potrzebne
do tworzenia i odczytu map, tworzenia sond, punktów śledzenia i buforów wydajności.
Dołączanie programów do punktów śledzenia
Programy Tracepoint są ładowane automatycznie podczas uruchamiania. Po załadowaniu programu punktów kontrolnych należy go aktywować w ten sposób:
- Zadzwoń pod numer
bpf_obj_get()
, aby uzyskać dostęp do programufd
z lokalizacji przypiętego pliku. Więcej informacji znajdziesz w artykule Pliki dostępne w sysfs. - Wywołaj funkcję
bpf_attach_tracepoint()
w bibliotece BPF, zaliczając programfd
i nazwę punktu śledzenia.
Poniższy 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));
Czytanie 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 korzysta ze szablonów C++, aby utworzyć instancję BpfMap
na podstawie typu klucza i wartości mapy. Poprzedni przykładowy kod pokazuje używanie BpfMap
z kluczem
i wartości w postaci liczb całkowitych. Liczby całkowite mogą też być dowolnymi strukturami.
Dzięki temu szablonowa klasa BpfMap
umożliwia zdefiniowanie niestandardowego obiektu BpfMap
, który będzie odpowiedni dla danej mapy. Możesz ją wtedy wyświetlić za pomocą
niestandardowe funkcje, które rozpoznają typ, co pozwala uzyskać bardziej przejrzysty kod.
Więcej informacji o BpfMap
znajdziesz w źródłach Androida.
Rozwiązywanie problemów
Podczas uruchamiania rejestrowanych jest kilka komunikatów związanych z wczytywaniem BPF. Jeśli proces wczytywania nie powiedzie się z jakiegokolwiek powodu, w pliku logcat znajdziesz szczegółowy komunikat. Filtrowanie dzienników logcat według parametru bpf
powoduje wydrukowanie wszystkich komunikatów i
wszelkie szczegółowe błędy, takie jak błędy weryfikatora eBPF.
Przykłady eBPF na Androidzie
Dodatkowe przykłady korzystania z eBPF znajdziesz w tych programach w AOSP:
netd
eBPF C programu jest wykorzystywana przez demona sieci (netd) w Androidzie do różnych celów, jak filtrowanie gniazd i zbieranie statystyk. Aby sprawdzić, jak ten program jest używany, sprawdź źródła w monitorze ruchu eBPF.Program
time_in_state
eBPF C oblicza czas, jaki aplikacja na Androida spędza na różnych częstotliwościach procesora, co jest wykorzystywane do obliczania mocy.W Androidzie 12 moduł
gpu_mem
eBPF C programu śledzi całkowite wykorzystanie pamięci GPU w każdym procesie i w całym systemie. Ten program służy do profilowania pamięci GPU.