Extensões de kernel rápidas, flexíveis e práticas
Título original: Fast, Flexible, and Practical Kernel Extensions
PublicadoProceedings of the ACM SIGOPS 30th Symposium on Operating Systems Principles
Saiu em uma revista, congresso ou livro, que normalmente avaliam o trabalho antes de aceitar. Mesmo assim, um único estudo nunca é a palavra final.
Resumo
Este paper não tem resumo disponível.
Análise de pesquisa
Escrita pelo Kurz, não pelos autores do paper. O resumo acima é dos próprios autores.
- Última revisão
- Versão
- 1.0
Visão geral
As extensões de kernel permitem que uma aplicação leve a própria lógica para dentro do SO, e o eBPF as tornou comuns no Linux e no Windows. Os autores observam que a Meta é conhecida por carregar de 50 a 150 extensões eBPF em cada servidor, e que Google, Netflix, Cloudflare, Apple, Alibaba e Dropbox relatam usos em produção. Eles citam quatro propriedades que um framework de extensões deveria ter: segurança, flexibilidade, desempenho e praticidade (sem linguagem nem toolchain novos). Cada abordagem anterior abre mão de uma delas. Kernels em linguagens seguras, como o SPIN, são flexíveis e rápidos, mas exigem linguagens novas e um SO novo. O software fault isolation (SFI), como no VINO, é flexível e fácil de adotar, mas paga por muitas verificações em tempo de execução. A verificação estática, como no eBPF, é rápida e prática, mas não escala para código arbitrário: as extensões não podem definir estruturas de dados (só os eBPF maps fornecidos pelo kernel), os loops precisam de limites calculáveis estaticamente e só se pode segurar um lock por vez.
Este artigo do SOSP 2024 (Austin), da EPFL e da UC Berkeley, defende que a segurança do kernel são na verdade duas propriedades que merecem mecanismos diferentes. O resultado é o KFlex, construído sobre o Linux e o framework eBPF.
Termos que vale manter separados: conformidade com a interface do kernel significa tocar a memória e as funções do kernel apenas como permitido. Correção da extensão significa acessar a própria memória com segurança e terminar. O heap de extensão é a memória que pertence à extensão. O Kie é o motor de instrumentação do KFlex, e um ponto de cancelamento (Cp) é uma instrução em que uma extensão pode ser parada à força.
Vale conhecer antes: como funcionam o verifier e o JIT do eBPF, o que é o hook XDP e a ideia básica do SFI (mascarar um ponteiro para dentro de um sandbox). Dois artigos da biblioteca seguem outros caminhos para estender o Linux: o ghOSt delega a política de escalonamento ao userspace e, do SOSP 2025, o cache_ext carrega políticas de despejo do page cache como eBPF. O PeeR trata do escalonamento de programas eBPF longos e inclui o Redis-KFlex entre as aplicações que estuda. O artigo do KFlex não cita nenhum desses três, então os links são meus.
Contribuição
- Dividir a segurança. O verifier do eBPF continua checando a conformidade com a interface do kernel, porque esses acessos carregam regras semânticas (por exemplo, invariantes de estruturas de dados do kernel). Verificações leves em tempo de execução garantem a correção da extensão, porque o kernel só precisa que os acessos da extensão à própria memória fiquem dentro dos limites e que ela termine. O KFlex mantém o conjunto de instruções e a interface do kernel do eBPF, então extensões eBPF existentes rodam sem modificação.
- Heaps de extensão. Uma extensão declara um heap com
kflex_heap(size)(tamanho em GB), aloca comkflex_mallocekflex_freee pode segurar várias instâncias de umkflex_spin_lockbaseado em fila ao mesmo tempo. O heap fica em uma parte própria do espaço de endereços virtual do kernel, alinhado ao seu tamanho, e por isso nunca faz alias com a memória do kernel. As páginas físicas são populadas sob demanda e cobradas no memory cgroup da aplicação. O backend do alocador é o jemalloc em userspace, e os caches por CPU que ele alimenta são reabastecidos por uma thread em userspace. - SFI leve. O Kie mascara cada ponteiro do heap para os bits que dão o deslocamento dentro do heap e soma a base do heap, o que no x86_64 vira um único AND mais endereçamento indexado (com os registradores R12 e R9 reservados para base e máscara). Ele reaproveita a análise de intervalos do verifier para dispensar guards em acessos comprovadamente seguros. Há guard pages de 32 KB dos dois lados porque os deslocamentos de load e store do eBPF são inteiros de 16 bits com sinal. Um modo de desempenho opcional deixa de sanitizar as leituras, o que abre mão da confidencialidade (as extensões podem ler memória arbitrária do kernel), enquanto as escritas continuam sanitizadas e o SMAP faz as leituras de endereços de usuário gerarem trap.
- Cancelamento de extensões. Os pontos de cancelamento são os back edges de loops cuja terminação não pode ser provada, mais os acessos ao heap. O Kie acrescenta um acesso ao heap (
*terminate) em cada back edge. Para cancelar, o runtime zera esse endereço, o acesso seguinte falha, e o runtime percorre uma tabela de objetos por ponto, calculada a partir da execução simbólica do verifier, chamando o destrutor de cada objeto do kernel que a extensão segura (por exemplobpf_sk_release). O preço é uma regra: os recursos do kernel adquiridos em uma iteração do loop devem ser liberados até o fim dela. A detecção é passiva, usando os watchdogs de soft e hard lockup do Linux em granularidade de segundos, e as extensões nunca são paradas dentro de helpers confiáveis. - Compartilhamento com o userspace. O userspace pode fazer
mmapdo heap. O Kie traduz os ponteiros armazenados para o mapeamento do userspace, de modo que os dois lados percorram as mesmas estruturas encadeadas. Uma tarefa em userspace que segura um lock de que uma extensão pode precisar pode pedir uma extensão de time slice de 50 µs, mantida como um contador na região rseq. Se ela ainda assim não soltar o lock, a extensão que gira esperando por ele acaba cancelada. - Tamanho. O KFlex tem cerca de 15 mil linhas no Linux v6.9: 2,5 mil no verifier e no JIT, 8 mil no runtime e 4,5 mil de testes unitários. Os autores relatam que ele passa em toda a suíte de testes do eBPF.
Metodologia
Duas máquinas idênticas, ligadas diretamente (no estilo da RFC 2544), têm cada uma um Intel Xeon Platinum 8468 de 96 cores, 512 GB de RAM e uma NIC Intel X710 de 10 Gbps, com Ubuntu 24.04 e o servidor rodando o Linux v6.9 modificado pelos autores. O cliente roda um gerador em malha fechada com 64 threads e 16 clientes por thread e mede no próprio cliente. As requisições seguem distribuição Zipf (s = 0,99), por padrão com chaves de 32 B e valores de 64 B, e cada execução dura 30 segundos, descartando os primeiros 10% das amostras.
O Memcached do KFlex (cerca de 2000 linhas, no hook XDP) é comparado com o Memcached em userspace e com o BMC, um cache look-aside em eBPF que só faz offload de GETs, com misturas de GET:SET de 90:10, 50:50 e 10:90, com 8 threads no servidor e de novo com 16. Nessas execuções o tamanho do valor cai para 32 B porque o BMC não aceita valores maiores que as chaves. O Redis do KFlex (cerca de 3100 linhas, no hook sk_skb) é comparado com o KeyDB, um Redis paralelo. Experimentos separados fazem offload de uma hash table, uma lista encadeada, uma árvore rubro-negra, uma skiplist e dois sketches de rede, comparados com uma thread contra o mesmo código como módulo de kernel sem verificações, com e sem modo de desempenho, e fazem offload do ZADD do Redis (um hashmap apontando para skiplists). Um experimento de co-design roda a coleta de lixo do Memcached a cada segundo em threads de userspace sobre o heap compartilhado, e o último conta os guards de SFI que o verifier permite ao Kie eliminar.
Resultados
Estes são os resultados relatados pelos autores.
- Memcached. Com 8 threads, o KFlex sustenta de 1,23× a 2,83× o throughput do BMC e de 2,33× a 3,01× o do Memcached em userspace, com latência p99 de 1,41× a 1,95× menor que a do BMC e de 1,95× a 9,35× menor que a do Memcached. A diferença sobre o BMC cresce com a fatia de SETs, que o BMC não descarrega. Os autores dizem que 16 threads dão benefícios parecidos.
- Redis. O throughput é de 1,61× a 2,14× o do KeyDB, com latência p99 de 0,97× a 2,96× menor (a ponta baixa lê-se como um pouco pior que o KeyDB em um caso, e os autores falam em ignorar um outlier). Os ganhos são menores que os do Memcached porque toda requisição do Redis atravessa a pilha TCP do Linux antes de a extensão enxergá-la.
- Offload do ZADD. O eBPF não tem skiplist, então isso não pode ser descarregado hoje. Com uma thread no servidor, o KFlex dá 1,65× o throughput do Redis e latência p99 52,8% menor.
- Estruturas de dados. Contra o módulo de kernel sem verificações, o overhead médio é de 9% no throughput e de 31,7% na latência sem o modo de desempenho. O modo de desempenho reduz o overhead de latência em 3 a 4 pontos na lista encadeada e na skiplist, que seguem muitos ponteiros, em 1 a 2 no hashmap e na árvore rubro-negra, e em nada nos sketches. Os autores atribuem parte do custo ao próprio eBPF, por exemplo a pressão sobre os registradores e um
memcpyruim. - Co-design com o userspace. Com a coleta de lixo em userspace, o Memcached do KFlex ainda dá de 2,2× a 2,9× o throughput e latência de cauda de 42,8% a 89,5% menor que a do Memcached em userspace. Isso fica abaixo dos 2,33× a 3,01× e 1,95× a 9,35× medidos sem coleta de lixo, o que os autores atribuem à contenção na hash table compartilhada.
- Verifier e SFI. A análise de intervalos do verifier elimina em média 76% dos guards em update, lookup e delete nas estruturas de dados, e 100% em várias operações. Os guards que sobram são em sua maioria aqueles em que o escalar somado ao ponteiro pode ser maior que o tamanho do heap.
Análise crítica
O artigo é franco sobre os limites do seu desenho. Ele admite o seguinte.
- A interface estreita do Linux. Como o eBPF foi adaptado depois, o Linux expõe menos às extensões do que um kernel projetado para extensibilidade. O exemplo clássico do SPIN, um servidor web no kernel, é inviável porque as extensões não podem ler arquivos nem fazer I/O bloqueante. Os autores argumentam que isso não é fundamental, já que a interface continua se ampliando.
- Travamentos demoram a ser cancelados. O cancelamento dirigido por watchdog funciona em granularidade de segundos, o que "pode não ser ideal", e um cancelamento causado por não terminação em uma CPU para a extensão em todas as CPUs.
- Escopo da segurança. O KFlex protege o kernel, não a extensão: bugs de memória dentro do heap e a correção funcional das extensões estão fora do escopo, e uma extensão cancelada não é reparada. O modo de desempenho abre mão da confidencialidade das leituras e é pensado para ambientes confiáveis.
- Limites de engenharia. Os heaps alinhados ao tamanho, mais as guard pages, fragmentam o espaço de endereços do kernel, então o número de heaps é limitado. Os arenas do eBPF no upstream têm teto de 4 GB. O eBPF também não tem SIMD nem um modelo de memória formal, e tem poucos registradores.
Os pontos a seguir são uma leitura das evidências, não afirmações do artigo.
- O cancelamento nunca é exercitado na avaliação. O overhead quase nulo do
*terminatese apoia na experiência dos autores de que a posição de*terminatecostuma estar no cache L1, e nenhum experimento trava uma extensão, a coloca em deadlock contra um processo em userspace que segura um lock ou informa o tempo de recuperação. O cancelamento é metade da nova história de segurança, então tem desenho, mas não tem medição. - A afirmação sobre o SFI é um proxy estático. A Tabela 3 conta instruções de guard emitidas, não executadas, e nenhuma execução desliga a eliminação para mostrar a velocidade ganha. Os 76% do texto batem com a média dos doze percentuais por operação, enquanto pela contagem bruta a tabela dá 79 de 99 guards, cerca de 80%. O lookup do hashmap elimina 0 de 2, e o delete da skiplist (a estrutura por trás do ZADD) só 44%. O texto não diz se o Memcached e o Redis rodaram no modo de desempenho, já que esse estudo é só das estruturas de dados. O overhead médio de latência de 31,7% também é muito maior que o de 9% no throughput, uma diferença que o texto não explica.
- Bases de comparação e formato da carga. As bases em userspace passam pela pilha de rede do Linux, então os ganhos comparam um fast path no kernel com um servidor comum em userspace. IX e Arrakis motivam o trabalho, mas não são bases de comparação. A carga é em malha fechada com 1.024 clientes fixos, então não há curvas de latência contra carga, o p99 vem de um único ponto de operação saturado, e o artigo não dá a dispersão entre execuções ("resultados parecidos" ao longo de vários minutos). A comparação com o BMC usa só valores de 32 B.
- Recurso novo no upstream, sem comparação. Os autores dizem que os arenas do eBPF alcançam paridade de recursos com os heaps (até 4 GB) e que estão migrando para eles. A avaliação não informa os tamanhos de heap nem compara com os arenas, então uma carga que precise de mais de 4 GB não é demonstrada.
- Base confiável. O próprio artigo cita bugs do verifier como custo de ampliá-lo, e ainda assim o KFlex soma 2,5 mil linhas ao verifier e ao JIT mais 8 mil de runtime. A evidência de correção relatada são testes unitários e a suíte de testes do eBPF, sem fuzzing nem argumento formal para a instrumentação do Kie.
- O que os praticantes podem usar. O offload dos SETs exigiu suporte próprio dos autores ao fast path do TCP no XDP, cujo tamanho não é informado. O resumo diz que vários mecanismos já estão no upstream, mas a Seção 4.5 lista como integrados apenas o suporte inicial a cancelamentos e o stack unwinding, com as tabelas de objetos e o modo de desempenho em revisão.
Pesquisas futuras
- Recuperação de travamentos em menos de um segundo, amostrando um relógio de hardware nos pontos de cancelamento dos back edges dos loops, e cancelamento restrito a uma invocação em uma CPU, ambos citados pelos autores.
- Empacotamento mais denso das regiões de heap, com striping de domínios de heap via Intel MPK e sem guard pages, para que os heaps possam ser numerosos e maiores que 4 GB, também planejado pelos autores. Na mesma linha entra portar as extensões para os arenas do upstream.
- Empilhar garantias mais fortes sobre a segurança do kernel do KFlex: extensões escritas em Rust ou verificadas com toolchains como o Dafny. O Atmosphere mostra o caminho oposto, um kernel em Rust verificado com o Verus (o artigo do KFlex não o cita).
- Lacunas que vejo: execuções com injeção de falhas para o cancelamento, uma comparação do cancelamento com a abordagem de preempção do PeeR e curvas de latência contra carga para o Memcached e o Redis descarregados.
Texto completo
Discussão · 0
Entre para participar da discussãoNenhum post ainda. Seja o primeiro a iniciar a discussão.