El filtro de paquetes Berkeley extendido (eBPF) es una máquina virtual integrada en el kernel que ejecuta programas eBPF proporcionados por el usuario para extender la funcionalidad del kernel. Estos programas se pueden conectar a sondeos o eventos en el kernel y se pueden usar para recopilar estadísticas útiles del kernel, supervisar y depurar. Un programa se carga en el kernel con la llamada al sistema bpf(2)
y el usuario lo proporciona como un BLOB binario de instrucciones de la máquina eBPF. El sistema de compilación de Android admite la compilación de programas en C a eBPF con la sintaxis simple de archivos de compilación que se describe en este documento.
Puedes encontrar más información sobre el funcionamiento interno y la arquitectura de eBPF en la página de eBPF de Brendan Gregg.
Android incluye un cargador y una biblioteca de eBPF que cargan programas de eBPF durante el inicio.
Cargador de BPF de Android
Durante el inicio de Android, se cargan todos los programas eBPF ubicados en /system/etc/bpf/
. Estos programas son objetos binarios compilados por el sistema de compilación de Android a partir de programas en C y se acompañan de archivos Android.bp
en el árbol de origen de Android. El sistema de compilación almacena los objetos generados en /system/etc/bpf
, y esos objetos pasan a formar parte de la imagen del sistema.
Formato de un programa en C de eBPF para Android
Un programa en C de eBPF debe tener el siguiente formato:
#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
En la que:
name_of_my_map
es el nombre de tu variable de mapa. Este nombre informa al cargador de BPF sobre el tipo de mapa que se debe crear y con qué parámetros. El encabezadobpf_helpers.h
incluido proporciona esta definición de struct.PROGTYPE/PROGNAME
representa el tipo y el nombre del programa. El tipo de programa puede ser cualquiera de los que se enumeran en la siguiente tabla. Cuando no se indica un tipo de programa, no hay una convención de nomenclatura estricta para el programa; el nombre solo debe ser conocido por el proceso que adjunta el programa.PROGFUNC
es una función que, cuando se compila, se coloca en una sección del archivo resultante.
kprobe | Se conecta PROGFUNC a una instrucción del kernel con la infraestructura de kprobe. PROGNAME debe ser el nombre de la función del kernel que se está kprobeando. Consulta la documentación del kernel de kprobe para obtener más información sobre kprobes.
|
---|---|
punto de seguimiento | Conecta PROGFUNC a un punto de seguimiento. PROGNAME debe tener el formato SUBSYSTEM/EVENT . Por ejemplo, una sección de puntos de seguimiento para adjuntar funciones a eventos de cambio de contexto del programador sería SEC("tracepoint/sched/sched_switch") , en la que sched es el nombre del subsistema de seguimiento y sched_switch es el nombre del evento de seguimiento. Consulta la documentación del kernel de eventos de seguimiento para obtener más información sobre los puntos de seguimiento.
|
skfilter | El programa funciona como un filtro de sockets de redes. |
schedcls | El programa funciona como un clasificador de tráfico de redes. |
cgroupskb, cgroupsock | El programa se ejecuta cada vez que los procesos de un CGroup crean un socket AF_INET o AF_INET6. |
Puedes encontrar tipos adicionales en el código fuente de Loader.
Por ejemplo, el siguiente programa myschedtp.c
agrega información sobre el PID de la tarea más reciente que se ejecutó en una CPU en particular. Este programa logra su objetivo creando un mapa y definiendo una función tp_sched_switch
que se puede adjuntar al evento de seguimiento sched:sched_switch
. Para obtener más información, consulta Cómo adjuntar programas a puntos de seguimiento.
#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");
La macro LICENSE se usa para verificar si el programa es compatible con la licencia del kernel cuando el programa usa funciones auxiliares de BPF proporcionadas por el kernel. Especifica el nombre de la licencia de tu programa en formato de cadena, como LICENSE("GPL")
o LICENSE("Apache 2.0")
.
Formato del archivo Android.bp
Para que el sistema de compilación de Android compile un programa .c
de eBPF, debes crear una entrada en el archivo Android.bp
del proyecto. Por ejemplo, para compilar un programa en C de eBPF llamado bpf_test.c
, realiza la siguiente entrada en el archivo Android.bp
de tu proyecto:
bpf { name: "bpf_test.o", srcs: ["bpf_test.c"], cflags: [ "-Wall", "-Werror", ], }
Esta entrada compila el programa en C, lo que genera el objeto /system/etc/bpf/bpf_test.o
. Durante el inicio, el sistema Android carga automáticamente el programa bpf_test.o
en el kernel.
Archivos disponibles en sysfs
Durante el inicio, el sistema Android carga automáticamente todos los objetos eBPF desde /system/etc/bpf/
, crea los mapas que necesita el programa y fija el programa cargado con sus mapas en el sistema de archivos BPF. Luego, estos archivos se pueden usar para interactuar más con el programa eBPF o leer mapas. En esta sección, se describen las convenciones que se usan para nombrar estos archivos y sus ubicaciones en sysfs.
Se crean y fijan los siguientes archivos:
Para cualquier programa cargado, suponiendo que
PROGNAME
es el nombre del programa yFILENAME
es el nombre del archivo C de eBPF, el cargador de Android crea y fija cada programa en/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME
.Por ejemplo, para el ejemplo anterior de punto de seguimiento
sched_switch
enmyschedtp.c
, se crea un archivo de programa y se fija en/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch
.Para los mapas creados, suponiendo que
MAPNAME
es el nombre del mapa yFILENAME
es el nombre del archivo C de eBPF, el cargador de Android crea y fija cada mapa en/sys/fs/bpf/map_FILENAME_MAPNAME
.Por ejemplo, para el ejemplo anterior de punto de seguimiento
sched_switch
enmyschedtp.c
, se crea un archivo de mapa y se fija en/sys/fs/bpf/map_myschedtp_cpu_pid_map
.bpf_obj_get()
en la biblioteca de BPF de Android devuelve un descriptor de archivos del archivo/sys/fs/bpf
fijado. Este descriptor de archivo se puede usar para otras operaciones, como leer mapas o adjuntar un programa a un punto de seguimiento.
Biblioteca de BPF de Android
La biblioteca de BPF de Android se llama libbpf_android.so
y forma parte de la imagen del sistema. Esta biblioteca proporciona al usuario las capacidades de eBPF de bajo nivel necesarias para crear y leer mapas, crear sondas, puntos de seguimiento y búferes de perf.
Cómo adjuntar programas a puntos de seguimiento
Los programas de puntos de seguimiento se cargan automáticamente durante el arranque. Después de la carga, el programa de puntos de seguimiento se debe activar con estos pasos:
- Llama a
bpf_obj_get()
para obtener elfd
del programa desde la ubicación del archivo fijado. Para obtener más información, consulta Archivos disponibles en sysfs. - Llama a
bpf_attach_tracepoint()
en la biblioteca de BPF y pásale el programafd
y el nombre del punto de seguimiento.
En la siguiente muestra de código, se muestra cómo adjuntar el punto de seguimiento sched_switch
definido en el archivo fuente myschedtp.c
anterior (no se muestra la verificación de errores):
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));
Lectura de los mapas
Los mapas de BPF admiten estructuras o tipos de clave y valor arbitrariamente complejos. La biblioteca de BPF de Android incluye una clase android::BpfMap
que usa plantillas de C++ para crear instancias de BpfMap
según el tipo de clave y valor del mapa en cuestión. En el ejemplo de código anterior, se muestra el uso de un BpfMap
con clave y valor como números enteros. Los números enteros también pueden ser estructuras arbitrarias.
Por lo tanto, la clase BpfMap
con plantilla te permite definir un objeto BpfMap
personalizado adecuado para el mapa en particular. Luego, se puede acceder al mapa con las funciones generadas de forma personalizada, que son compatibles con los tipos, lo que genera un código más limpio.
Para obtener más información sobre BpfMap
, consulta las fuentes de Android.
Depure errores.
Durante el tiempo de arranque, se registran varios mensajes relacionados con la carga de BPF. Si el proceso de carga falla por algún motivo, se proporciona un mensaje de registro detallado en logcat. Si filtras los registros de logcat por bpf
, se imprimirán todos los mensajes y los errores detallados durante el tiempo de carga, como los errores del verificador de eBPF.
Ejemplos de eBPF en Android
Los siguientes programas en AOSP proporcionan ejemplos adicionales del uso de eBPF:
El daemon de redes (netd) en Android usa el programa en C de eBPF
netd
para diversos fines, como el filtrado de sockets y la recopilación de estadísticas. Para ver cómo se usa este programa, consulta las fuentes del monitor de tráfico de eBPF.El programa en C de eBPF
time_in_state
calcula la cantidad de tiempo que una app para Android dedica a diferentes frecuencias de CPU, lo que se usa para calcular la energía.En Android 12, el programa en C de eBPF
gpu_mem
hace un seguimiento del uso total de la memoria de la GPU para cada proceso y para todo el sistema. Este programa se usa para generar perfiles de la memoria de la GPU.