Rozszerzanie jądra za pomocą eBPF

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łówek bpf_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 i FILENAME 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 w myschedtp.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 i FILENAME 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 w myschedtp.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:

  1. Zadzwoń pod numer bpf_obj_get(), aby uzyskać dostęp do programu fd z lokalizacji przypiętego pliku. Więcej informacji znajdziesz w artykule Pliki dostępne w sysfs.
  2. Wywołaj funkcję bpf_attach_tracepoint() w bibliotece BPF, zaliczając program fd 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.