Расширенный фильтр пакетов Беркли (eBPF) — это встроенная в ядро виртуальная машина, которая запускает предоставленные пользователем программы eBPF для расширения функциональности ядра. Эти программы можно подключить к зондам или событиям в ядре и использовать для сбора полезной статистики ядра, мониторинга и отладки. Программа загружается в ядро с помощью системного вызова bpf(2)
и предоставляется пользователем в виде двоичного объекта машинных инструкций eBPF. Система сборки Android поддерживает компиляцию программ C в eBPF с использованием простого синтаксиса файла сборки, описанного в этом документе.
Более подробную информацию о внутреннем устройстве и архитектуре eBPF можно найти на странице eBPF Брендана Грегга .
Android включает в себя загрузчик eBPF и библиотеку, которая загружает программы eBPF во время загрузки.
Загрузчик Android BPF
Во время загрузки Android загружаются все программы eBPF, расположенные в /system/etc/bpf/
. Эти программы представляют собой двоичные объекты, созданные системой сборки Android из программ C, и сопровождаются файлами Android.bp
в дереве исходного кода Android. Система сборки сохраняет сгенерированные объекты в /system/etc/bpf
, и эти объекты становятся частью образа системы.
Формат программы Android eBPF C
Программа eBPF C должна иметь следующий формат:
#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
Где:
-
name_of_my_map
— это имя переменной вашей карты. Это имя сообщает загрузчику BPF тип создаваемой карты и с какими параметрами. Это определение структуры предоставляется включенным заголовкомbpf_helpers.h
. PROGTYPE/PROGNAME
представляет тип программы и имя программы. Тип программы может быть любым из перечисленных в следующей таблице. Если тип программы не указан в списке, для программы не существует строгого соглашения об именовании; имя просто должно быть известно процессу, который присоединяет программу.PROGFUNC
— это функция, которая при компиляции помещается в раздел результирующего файла.
Кпробе | Подключает PROGFUNC к инструкции ядра, используя инфраструктуру kprobe. PROGNAME должно быть именем проверяемой функции ядра. Дополнительную информацию о kprobes можно найти в документации ядра kprobe . |
---|---|
точка трассировки | Подключает PROGFUNC к точке трассировки. PROGNAME должно иметь формат SUBSYSTEM/EVENT . Например, раздел точки трассировки для присоединения функций к событиям переключения контекста планировщика будет иметь вид SEC("tracepoint/sched/sched_switch") , где sched — имя подсистемы трассировки, а sched_switch — имя события трассировки. Дополнительные сведения о точках трассировки см. в документации ядра событий трассировки . |
скфильтр | Программа функционирует как фильтр сетевых сокетов. |
расписание | Программа функционирует как классификатор сетевого трафика. |
cgroupskb, cgroupsock | Программа запускается всякий раз, когда процессы в CGroup создают сокет AF_INET или AF_INET6. |
Дополнительные типы можно найти в исходном коде Loader .
Например, следующая программа myschedtp.c
добавляет информацию о PID последней задачи, которая выполнялась на определенном ЦП. Эта программа достигает своей цели, создавая карту и определяя функцию tp_sched_switch
, которую можно присоединить к событию трассировки sched:sched_switch
. Дополнительные сведения см. в разделе Прикрепление программ к точкам трассировки .
#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");
Макрос LICENSE используется для проверки совместимости программы с лицензией ядра, когда программа использует вспомогательные функции BPF, предоставляемые ядром. Укажите название лицензии вашей программы в строковой форме, например LICENSE("GPL")
или LICENSE("Apache 2.0")
.
Формат файла Android.bp
Чтобы система сборки Android могла создать программу eBPF .c
, необходимо создать запись в файле Android.bp
проекта. Например, чтобы создать программу eBPF C с именем bpf_test.c
, сделайте следующую запись в файле Android.bp
вашего проекта:
bpf { name: "bpf_test.o", srcs: ["bpf_test.c"], cflags: [ "-Wall", "-Werror", ], }
Эта запись компилирует программу C, в результате чего создается объект /system/etc/bpf/bpf_test.o
. При загрузке система Android автоматически загружает в ядро программу bpf_test.o
.
Файлы доступны в sysfs
Во время загрузки система Android автоматически загружает все объекты eBPF из /system/etc/bpf/
, создает карты, необходимые программе, и закрепляет загруженную программу с ее картами в файловой системе BPF. Эти файлы затем можно использовать для дальнейшего взаимодействия с программой eBPF или чтения карт. В этом разделе описываются соглашения, используемые для именования этих файлов и их расположения в sysfs.
Следующие файлы создаются и закрепляются:
Для любых загруженных программ, предполагая, что
PROGNAME
— это имя программы, аFILENAME
— это имя файла C eBPF, загрузчик Android создает и закрепляет каждую программу в/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME
.Например, для предыдущего примера точки трассировки
sched_switch
вmyschedtp.c
создается файл программы и закрепляется в/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch
.Для любых созданных карт, предполагая, что
MAPNAME
— это имя карты, аFILENAME
— это имя файла eBPF C, загрузчик Android создает и закрепляет каждую карту в/sys/fs/bpf/map_FILENAME_MAPNAME
.Например, для предыдущего примера точки трассировки
sched_switch
вmyschedtp.c
создается файл карты и закрепляется в/sys/fs/bpf/map_myschedtp_cpu_pid_map
.bpf_obj_get()
в библиотеке Android BPF возвращает дескриптор файла из закрепленного файла/sys/fs/bpf
. Этот файловый дескриптор можно использовать для дальнейших операций, таких как чтение карт или привязка программы к точке трассировки.
Библиотека Android BPF
Библиотека Android BPF называется libbpf_android.so
и является частью образа системы. Эта библиотека предоставляет пользователю низкоуровневые возможности eBPF, необходимые для создания и чтения карт, создания зондов, точек трассировки и буферов производительности.
Привязывайте программы к точкам трассировки
Программы Tracepoint загружаются автоматически при загрузке. После загрузки программу трассировки необходимо активировать, выполнив следующие действия:
- Вызовите
bpf_obj_get()
чтобы получить программуfd
из местоположения закрепленного файла. Для получения дополнительной информации обратитесь к файлам, доступным в sysfs . - Вызовите функцию
bpf_attach_tracepoint()
в библиотеке BPF, передав ейfd
программы и имя точки трассировки.
В следующем примере кода показано, как прикрепить точку трассировки sched_switch
, определенную в предыдущем исходном файле myschedtp.c
(проверка ошибок не показана):
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));
Читать по картам
Карты BPF поддерживают произвольные сложные структуры или типы ключей и значений. Библиотека Android BPF включает класс android::BpfMap
, который использует шаблоны C++ для создания экземпляра BpfMap
на основе типа ключа и значения для рассматриваемой карты. В предыдущем примере кода показано использование BpfMap
с ключом и значением в виде целых чисел. Целые числа также могут быть произвольными структурами.
Таким образом, шаблонизированный класс BpfMap
позволяет вам определить собственный объект BpfMap
подходящий для конкретной карты. Затем к карте можно будет получить доступ с помощью специально созданных функций, которые учитывают тип, что приводит к более чистому коду.
Дополнительную информацию о BpfMap
можно найти в исходниках Android .
Проблемы отладки
Во время загрузки регистрируется несколько сообщений, связанных с загрузкой BPF. Если процесс загрузки по какой-либо причине завершается неудачно, в logcat предоставляется подробное сообщение журнала. Фильтрация журналов logcat с помощью bpf
выводит все сообщения и все подробные ошибки во время загрузки, например ошибки проверки eBPF.
Примеры eBPF в Android
Следующие программы в AOSP предоставляют дополнительные примеры использования eBPF:
Программа
netd
eBPF C используется сетевым демоном (netd) в Android для различных целей, таких как фильтрация сокетов и сбор статистики. Чтобы увидеть, как используется эта программа, проверьте источники монитора трафика eBPF .Программа
time_in_state
eBPF C вычисляет количество времени, которое Android-приложение проводит на разных частотах процессора, что используется для расчета мощности.В Android 12 программа
gpu_mem
eBPF C отслеживает общее использование памяти графического процессора для каждого процесса и для всей системы. Эта программа используется для профилирования памяти графического процессора.