Extended Berkeley Packet Filter (eBPF) – это виртуальная машина в ядре, которая запускает предоставленные пользователем программы eBPF для расширения функциональности ядра. Эти программы можно привязать к зондам или событиям в ядре и использовать для сбора полезной статистики ядра, мониторинга и отладки. Программа загружается в ядро с помощью системного вызова bpf(2) и предоставляется пользователем в виде двоичного объекта с машинным кодом eBPF. Система сборки Android поддерживает компиляцию программ на языке C в eBPF с помощью простого синтаксиса файла сборки, описанного в этом документе.
Дополнительную информацию о внутренней структуре и архитектуре eBPF можно найти на странице Брендана Грегга (Brendan Gregg), посвященной eBPF.
В Android есть загрузчик и библиотека eBPF, которые загружают программы eBPF во время загрузки.
Загрузчик BPF для Android
При загрузке Android загружаются все программы eBPF, расположенные в /system/etc/bpf/. Эти программы представляют собой двоичные объекты, созданные системой сборки Android на основе программ на языке C. В дереве исходного кода Android они сопровождаются файлами Android.bp. Система сборки сохраняет сгенерированные объекты в /system/etc/bpf, и они становятся частью образа системы.
Формат программы eBPF на языке C для Android
Программа 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– это функция, которая при компиляции помещается в раздел полученного файла.
| kprobe | Подключается к PROGFUNC в инструкции ядра с помощью инфраструктуры kprobe. PROGNAME – название функции ядра, которая проверяется с помощью kprobe. Дополнительную информацию о kprobes можно найти в документации по ядру kprobe.
|
|---|---|
| точка трассировки | Подключает PROGFUNC к точке трассировки. PROGNAME должен быть в формате SUBSYSTEM/EVENT. Например, раздел tracepoint для прикрепления функций к событиям переключения контекста планировщика будет выглядеть так: SEC("tracepoint/sched/sched_switch"), где sched – это название подсистемы трассировки, а sched_switch – название события трассировки. Дополнительную информацию о точках трассировки можно найти в документации по ядру событий трассировки.
|
| skfilter | Программа работает как фильтр сетевых сокетов. |
| schedcls | Программа работает как классификатор сетевого трафика. |
| cgroupskb, cgroupsock | Программа запускается, когда процессы в CGroup создают сокет AF_INET или AF_INET6. |
Дополнительные типы можно найти в исходном коде загрузчика.
Например, следующая программа 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. Этот дескриптор файла можно использовать для дальнейших операций, например чтения карт или подключения программы к точке трассировки.
Библиотека BPF для Android
Библиотека Android BPF называется libbpf_android.so и является частью образа системы. Эта библиотека предоставляет пользователю низкоуровневые возможности eBPF, необходимые для создания и чтения карт, создания зондов, точек трассировки и буферов perf.
Прикрепление программ к точкам трассировки
Программы точек трассировки загружаются автоматически при запуске. После загрузки программу точек трассировки необходимо активировать, выполнив следующие действия:
- Вызовите
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 используется сетевым демоном (netd) в Android для различных целей, таких как фильтрация сокетов и сбор статистики. Чтобы узнать, как используется эта программа, ознакомьтесь с исходным кодом eBPF traffic monitor.time_in_stateПрограмма eBPF C рассчитывает, сколько времени приложение Android работает на разных частотах ЦП. Эти данные используются для расчета энергопотребления.В Android 12
gpu_memпрограмма eBPF C отслеживает общее использование памяти графического процессора для каждого процесса и всей системы. Эта программа используется для профилирования памяти графического процессора.
Подробнее о том, как использовать eBPF для сбора гистограмм управления
памятью ядра и задержки питания для statsd…