Guia de programação de módulos do kernel Linux
(sysprog21.github.io)- O Linux Kernel Module Programming Guide é um guia gratuito para criar módulos carregáveis do kernel no Linux v5.10 ou superior, abordando em um único fluxo desde o ambiente de desenvolvimento até build, carregamento, depuração e as principais interfaces do kernel
- Os exemplos iniciais usam
hello-*.cpara ensinarmodule_init(),module_exit(),kbuild,insmod,rmmodedmesg, e reduzem o risco de danificar o sistema host com devtools baseadas em QEMU - Como módulos do kernel executam no espaço de endereçamento do kernel, ponteiros incorretos, ordem de unload, concorrência e erros ao copiar memória de usuário podem levar a corrupção da memória do kernel ou instabilidade do sistema
- O guia avança para dispositivos de caractere,
/proc,seq_file, threaded IRQ, input, PCI, USB, bloco, rede, Device Model, Device Tree e static key, tratando repetidamente da ordem de registro/liberação e do gerenciamento de lifetime - Como APIs internas do kernel mudam conforme a versão, ele verifica condições como
LINUX_VERSION_CODE,KERNEL_VERSION,CONFIG_MODVERSIONS, assinatura para SecureBoot e version magic, e os exemplos também incluem compilação condicional
Estrutura do guia e fluxo básico
- Este guia é um material de estudo sobre módulos do kernel que oferece um repositório no GitHub e um documento em PDF, podendo ser copiado, modificado e distribuído sob os termos da Open Software License 3.0
- Atualmente, o guia adota Linux v5.10 como requisito mínimo de suporte e busca manter a compatibilidade dos exemplos e instruções em toda a família de kernels de suporte de longo prazo
- O estudante precisa ter experiência com a linguagem C e com a escrita de programas para processos comuns; módulos do kernel são carregados e descarregados dinamicamente para estender funcionalidades do kernel sem reinicialização
- O fluxo básico de desenvolvimento passa por instalar os headers do kernel, compilar o
.kocommake, verificar commodinfo, carregar cominsmod, consultar logs comdmesgoujournalctl -ke descarregar comrmmod devtools/compila o código-fonte do kernel e um sistema de arquivos raiz com BusyBox, faz boot no QEMU, compartilhaexamples/via 9p virtfs e permite testar os módulos no guest- Para inicialização e limpeza de módulos, prefere-se usar
module_init()emodule_exit(); a forma antiga cominit_module()ecleanup_module()pode causar falhas de build em algumas condições a partir do kernel 6.15 com x86 IBT habilitado - Módulos do kernel não usam
printf()nem libc, apenas símbolos exportados pelo kernel, e a saída vai para o buffer circular de logs do kernel, não para o terminal - A movimentação de dados entre o espaço de usuário e o espaço do kernel exige funções específicas como
put_user,get_user,copy_to_userecopy_from_user - O exemplo de dispositivo de caractere mostra
register_chrdev,file_operations, major number dinâmico, criação de nó em/dev, open exclusivo, read baseado emput_usere tratamento de write não suportado - Os exemplos de
/proccobremproc_create,proc_ops, callbacks de read/write e a APIseq_file, refletindo a mudança introduzida a partir do Linux v5.6, quandoproc_opssubstituiufile_operationspara handlers de/proc - Threaded IRQ separa top-half e bottom-half com
request_threaded_irq(); o top-half executa apenas o mínimo necessário em interrupt context e acorda o bottom-half baseado em thread comIRQ_WAKE_THREAD - Os capítulos seguintes avançam para áreas de drivers reais como input, PCI, USB, bloco, rede, Device Model e Device Tree, seguindo com foco nos métodos de registro de cada subsystem e na escolha da ABI de userspace
- A parte de otimização e segurança trata de
likely/unlikely, static key, pilha pequena do kernel, proibição de uso da FPU, vazamento de padding não inicializado e cuidado ao usar APIs internas com double underscore
Restrições encontradas primeiro no build e no carregamento
- Um módulo compilado para um kernel pode não carregar em outro; se version magic e
CONFIG_MODVERSIONSnão coincidirem, podem ocorrerInvalid module formatou incompatibilidade de versão de símbolos - A maioria dos kernels de distribuições Linux comuns pode ter modversioning habilitado; se os exemplos não funcionarem de imediato, deve-se considerar um kernel com modversioning desativado ou um ambiente QEMU
- Em sistemas com SecureBoot ativado, o carregamento de módulos não assinados pode ser restrito; se aparecer
Lockdown: insmod: unsigned module loading is restricted, é necessário desativar o SecureBoot ou seguir o procedimento de assinatura de módulos
Ambiente de prática baseado em QEMU
devtools/setup.shbaixa e compila o tarball do kernel e o BusyBox, e empacota o initramfsdevtools/build-modules.shcompila os módulos destinados ao kernel do QEMU,devtools/boot.shfornece um shell no guest, edevtools/test-modules.shexecuta testes automatizados deinsmodermmodpara cada módulo- A depuração com GDB é feita compilando
vmlinuxcomLKMPG_NO_PREBUILT=1 devtools/setup.sh, depois usandodevtools/boot.sh --gdbe conectando-se via GDB remoto
Regras para escrever código de kernel
- Como registros e alocações podem falhar na função init, os recursos obtidos devem ser liberados na ordem inversa nos caminhos de erro baseados em
goto - Ao registrar uma estrutura de callbacks no kernel, o espaço de usuário pode chamar callbacks até mesmo antes do retorno de init; por isso, é importante a regra registrar por último, desregistrar primeiro, fazendo o registro apenas após terminar a inicialização interna e a liberação antes de tudo
- Process context, softirq/tasklet context e hardirq context diferem quanto à possibilidade de sleep, acesso à memória de usuário, uso de
GFP_KERNELe uso de mutex; entender mal essa distinção leva a bugs comuns no kernel
Cuidados por dispositivo e subsystem
- Dispositivos de caractere identificam o driver pelo major number e distinguem vários dispositivos internos pelo minor number; na abordagem moderna, a interface
cdevé recomendada em vez deregister_chrdev() - Drivers PCI não assumem endereços fixos e mapeiam recursos BAR enumerados pelo PCI core; em código para Linux 5.10 ou posterior,
pcim_enable_device()e APIs de recursos gerenciados pelo dispositivo ajudam a reduzir bugs de teardown - Drivers USB devem tratar hotplug e disconnect como eventos normais, e seu design deve partir da premissa de que URB completion pode concorrer com disconnect, timeout, suspend e shutdown iniciado por userspace
- Drivers de bloco operam em torno de
blk-mq,request,gendisk, limites de fila e semântica de flush/FUA, participando de um modelo de conclusão assíncrona de requests, não de simples callbacks de read/write - Drivers de rede combinam
struct net_device,net_device_ops,sk_buff, NAPI, flags de recursos de offload e reporte de link-state; declarações incorretas de offload podem corromper tráfego
Como lidar com mudanças de versão do kernel
- Os exemplos tratam por compilação condicional mudanças como a assinatura de
class_create()no Linux 6.4,proc_opsno Linux v5.6, a alteração do tipo de retorno deremove()no Linux 6.11 e mudanças em helpers deblk-mqentre Linux 5.15 e 6.9 - Interfaces internas do kernel mudam com mais frequência do que chamadas de sistema, então módulos que dão suporte a vários kernels dificilmente conseguem evitar comparações com
LINUX_VERSION_CODEeKERNEL_VERSION
Checkpoints de segurança
- A pilha do kernel é muito menor que a pilha do espaço de usuário e, em muitos sistemas, pode ser de 8 KiB ou 16 KiB; portanto, arrays grandes devem usar
kmalloc()oukzalloc() - Ao enviar dados ao espaço de usuário com
copy_to_user(), todos os bytes, incluindo padding, precisam estar inicializados; caso contrário, pode ocorrer vazamento de informações da memória do kernel - APIs que começam com double underscore, como
__kmalloc()e__list_add(), podem assumir pré-condições internas; se a documentação não exigir seu uso, deve-se priorizar o wrapper público
Escopo omitido
- Como está indicado que alguns chunks do original foram omitidos por limites de tamanho e custo no processo de entrada, este resumo não cobre exaustivamente todos os capítulos, exemplos e caminhos de código do guia
1 comentários
Opiniões no Hacker News
QEMU é uma boa forma de experimentar hacking no kernel
Seria ótimo se alguém atualizasse o LDD (Linux Device Drivers) e os livros sobre o kernel Linux; como é difícil ganhar dinheiro com livros técnicos desse tipo, talvez fizesse sentido a Linux Foundation patrocinar
Ainda nesta semana, em v6.8, reproduziram com QEMU + GDB um problema em que o kernel parava imediatamente e em silêncio se o parâmetro de linha de comando do kernel arm64 tivesse 146 caracteres ou mais; em um host Debian 12 Bookworm amd64, emularam o build de um kernel arm64 e foram acompanhando o código problemático linha por linha para encontrar a causa
O fluxo consiste em, em um ambiente com dependências de build e ferramentas de cross-compilação prontas, compilar a imagem do kernel arm64 e os scripts para GDB; instalar no host
gdb, se necessáriogdb-multiarch, eqemu-system-arm; depois iniciarqemu-system-aarch64pausado com-S -gdb tcp::1234e, em outro terminal, conectar comgdb-multiarch ./vmlinuxEm seguida, no GDB, ao executar
target remote :1234,break __parse_cmdlineecontinue, é possível usar recursos comuns do GDB, como inspecionar memória, variáveis e pilha, além de executar passo a passoPara depuração do kernel com GDB e scripts
lx-*, consulte https://www.kernel.org/doc/html/latest/dev-tools/gdb-kernel-...Para que o GDB use os scripts Python
lx-*, em geral também é preciso permitir o caminho, por exemplo comecho "add-auto-load-safe-path ${SRC_DIR}/scripts/gdb/vmlinux-gdb.py" > ~/.gdbinitThreads relacionadas no HN: https://news.ycombinator.com/item?id=35782630, https://news.ycombinator.com/item?id=28283030
The Linux Memory Manager também vale como referência: https://linuxmemory.org/chapters
Segundo a última atualização enviada pelo autor no começo de julho, ele concluiu o primeiro rascunho e agora entrou na fase de edição com a editora
Alguns exemplos parecem difíceis de executar por conta própria
Por exemplo, “Detecting button presses” parte do pressuposto de que é possível compilar um módulo para RPi, mas isso por si só pode exigir tarefas como cross-compilação, então talvez não seja simples
É excelente: detalhado, focado na prática e um tutorial que já leva a compilar módulos de kernel na hora
Material para ver junto: https://0xax.gitbooks.io/linux-insides/content/index.html
Fico curioso sobre onde seria bom aprender sobre programação do kernel Linux em geral, como sistemas de arquivos ou gerenciamento de memória
Antigamente havia “Linux Kernel Development”, de Robert Love, mas parece que ele não é mais atualizado
Li isso pela primeira vez há cerca de 22 anos :)