O Filtro de Pacote Berkeley Estendido (eBPF, na sigla em inglês) é uma máquina virtual no kernel que executa programas eBPF fornecidos pelo usuário para estender a funcionalidade do kernel. Esses programas podem ser conectados a probes ou eventos no kernel e usados para coletar estatísticas úteis do kernel, monitorar e depurar. Um programa é carregado no kernel usando a syscall bpf(2) e é fornecido pelo usuário como um blob binário de instruções de máquina eBPF. O sistema de build do Android oferece suporte à compilação de programas C para eBPF usando a sintaxe simples de arquivo de build descrita neste documento.
Mais informações sobre os elementos internos e a arquitetura do eBPF podem ser encontradas na página eBPF de Brendan Gregg.
O Android inclui um carregador e uma biblioteca eBPF que carregam programas eBPF na inicialização.
Carregador BPF do Android
Durante a inicialização do Android, todos os programas eBPF localizados em /system/etc/bpf/ são carregados. Esses programas são objetos binários criados pelo sistema de build do Android a partir de programas C e são acompanhados por arquivos Android.bp na árvore de origem do Android. O sistema de build armazena os objetos gerados em /system/etc/bpf, e esses objetos se tornam parte da imagem do sistema.
Formato de um programa C eBPF do Android
Um programa C eBPF precisa ter o seguinte 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 licenseEm que:
name_of_my_mapé o nome da variável de mapa. Esse nome informa ao carregador BPF o tipo de mapa a ser criado e com quais parâmetros. Essa definição de struct é fornecida pelo cabeçalhobpf_helpers.hincluído.PROGTYPE/PROGNAMErepresenta o tipo do programa e o nome do programa. O tipo de programa pode ser qualquer um dos listados na tabela a seguir. Quando um tipo de programa não está listado, não há uma convenção de nomenclatura estrita para o programa. O nome só precisa ser conhecido pelo processo que anexa o programa.PROGFUNCé uma função que, quando compilada, é colocada em uma seção do arquivo resultante.
| kprobe | Conecta PROGFUNC a uma instrução do kernel usando a infraestrutura kprobe. PROGNAME precisa ser o nome da função do kernel que está sendo kprobed. Consulte a documentação do kernel kprobe (link em inglês) para mais informações sobre
kprobes.
|
|---|---|
| tracepoint | Conecta PROGFUNC a um tracepoint. PROGNAME precisa estar
no formato SUBSYSTEM/EVENT. Por exemplo, uma seção de tracepoint
para anexar funções a eventos de troca de contexto do agendador seria
SEC("tracepoint/sched/sched_switch"), em que sched é
o nome do subsistema de trace e sched_switch é o nome
do evento de trace. Consulte a documentação do kernel de eventos de trace
(link em inglês) para mais informações sobre tracepoints.
|
| skfilter | Funções de programa como um filtro de soquete de rede. |
| schedcls | Funções de programa como um classificador de tráfego de rede. |
| cgroupskb, cgroupsock | O programa é executado sempre que processos em um CGroup criam um soquete AF_INET ou AF_INET6. |
Outros tipos podem ser encontrados no código-fonte do carregador.
Por exemplo, o programa myschedtp.c a seguir adiciona informações sobre o PID da tarefa mais recente que foi executada em uma CPU específica. Esse programa atinge a meta criando um mapa e definindo uma função tp_sched_switch que pode ser anexada ao evento de trace sched:sched_switch. Para mais informações, consulte
Anexar programas a tracepoints.
#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");
A macro LICENSE é usada para verificar se o programa é compatível com a licença do kernel quando o programa usa funções auxiliares BPF fornecidas pelo kernel. Especifique o nome da licença do programa no formato de string, como
LICENSE("GPL") ou LICENSE("Apache 2.0").
Formato do arquivo Android.bp
Para que o sistema de build do Android crie um programa eBPF .c, é necessário criar uma entrada no arquivo Android.bp do projeto. Por exemplo, para criar um programa C eBPF chamado bpf_test.c, faça a seguinte entrada no arquivo Android.bp do projeto:
bpf {
name: "bpf_test.o",
srcs: ["bpf_test.c"],
cflags: [
"-Wall",
"-Werror",
],
}Essa entrada compila o programa C, resultando no objeto /system/etc/bpf/bpf_test.o. Na inicialização, o sistema Android carrega automaticamente o programa bpf_test.o no kernel.
Arquivos disponíveis no sysfs
Durante a inicialização, o sistema Android carrega automaticamente todos os objetos eBPF de /system/etc/bpf/, cria os mapas de que o programa precisa e fixa o programa carregado com os mapas no sistema de arquivos BPF. Esses arquivos podem ser usados para mais interações com o programa eBPF ou para leitura de mapas. Esta seção descreve as convenções usadas para nomear esses arquivos e os locais deles no sysfs.
Os arquivos a seguir são criados e fixados:
Para todos os programas carregados, supondo que
PROGNAMEseja o nome do programa eFILENAMEseja o nome do arquivo C eBPF, o carregador do Android cria e fixa cada programa em/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME.Por exemplo, para o exemplo anterior de tracepoint
sched_switchemmyschedtp.c, um arquivo de programa é criado e fixado em/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch.Para todos os mapas criados, supondo que
MAPNAMEseja o nome do mapa eFILENAMEseja o nome do arquivo C eBPF, o carregador do Android cria e fixa cada mapa em/sys/fs/bpf/map_FILENAME_MAPNAME.Por exemplo, para o exemplo anterior de tracepoint
sched_switchemmyschedtp.c, um arquivo de mapa é criado e fixado em/sys/fs/bpf/map_myschedtp_cpu_pid_map.bpf_obj_get()na biblioteca BPF do Android retorna um descritor de arquivo do arquivo/sys/fs/bpffixado. Esse descritor do arquivo pode ser usado para outras operações, como leitura de mapas ou anexação de um programa a um tracepoint.
Biblioteca BPF do Android
A biblioteca BPF do Android é chamada de libbpf_android.so e faz parte da imagem do sistema. Essa biblioteca fornece ao usuário recursos eBPF de baixo nível necessários para criar e ler mapas, criar probes, tracepoints e buffers de desempenho.
Anexar programas a tracepoints
Os programas de tracepoint são carregados automaticamente na inicialização. Após o carregamento, o programa de tracepoint precisa ser ativado seguindo estas etapas:
- Chame
bpf_obj_get()para receber o programafddo local do arquivo fixado. Para mais informações, consulte Arquivos disponíveis no sysfs. - Chame
bpf_attach_tracepoint()na biblioteca BPF, transmitindo o programafde o nome do tracepoint.
O exemplo de código a seguir mostra como anexar o tracepoint sched_switch definido no arquivo de origem myschedtp.c anterior (a verificação de erros não é mostrada):
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));
Ler dos mapas
Os mapas BPF oferecem suporte a estruturas ou tipos de chave e valor complexos arbitrários. A
biblioteca BPF do Android inclui uma classe android::BpfMap que usa modelos C++
para instanciar BpfMap com base no tipo de chave e valor do
mapa em questão. O exemplo de código anterior demonstra o uso de um BpfMap com chave e valor como números inteiros. Os números inteiros também podem ser estruturas arbitrárias.
Assim, a classe BpfMap com modelo permite definir um objeto BpfMap personalizado adequado para o mapa específico. O mapa pode ser acessado usando as funções geradas personalizadas, que são conscientes do tipo, resultando em um código mais limpo.
Para mais informações sobre BpfMap, consulte as
origens do Android.
Depurar problemas
Durante a inicialização, várias mensagens relacionadas ao carregamento do BPF são registradas. Se o processo de carregamento falhar por qualquer motivo, uma mensagem de registro detalhada será fornecida no logcat. A filtragem dos registros do logcat por bpf imprime todas as mensagens e erros detalhados durante o tempo de carregamento, como erros do verificador eBPF.
Exemplos de eBPF no Android
Os programas a seguir no AOSP fornecem outros exemplos de uso do eBPF:
O
netdprograma C eBPF é usado pelo daemon de rede (netd) no Android para várias finalidades, como filtragem de soquetes e coleta de estatísticas. Para saber como esse programa é usado, consulte as origens do monitor de tráfego eBPF.O
time_in_stateprograma C eBPF `time_in_state` calcula a quantidade de tempo que um app Android passa em diferentes frequências de CPU, que é usada para calcular a energia.No Android 12, o
gpu_memprograma C eBPF rastreia o uso total da memória da GPU para cada processo e para todo o sistema. Esse programa é usado para a criação de perfil de memória da GPU.
Para mais informações sobre como usar o eBPF para coletar o gerenciamento de memória do kernel de baixa sobrecarga
e histogramas de latência de energia para statsd, consulte Coletar telemetria do kernel
com eBPF.