15 pontos por GN⁺ 2024-09-01 | 2 comentários | Compartilhar no WhatsApp
  • Acho que as pessoas não têm noção suficiente de quão incompleta é a documentação da API do kernel Linux e de como Rust resolve parte desse problema
  • Escrevi abstrações Rust para vários subsistemas e, em quase todos os casos, precisei ler o código-fonte em C para entender completamente como usar a API com segurança
  • Só com a assinatura da função e os comentários de documentação relacionados, ou mesmo com documentação explícita, é difícil entender totalmente como usar a API com segurança
    • se é preciso manter locks ativos
    • se um argumento de contagem de referências transfere uma referência ou assume uma referência própria
    • se o lock é mantido ao chamar callbacks ou se é preciso adquiri-lo manualmente
    • se há algo especial no callback de free
    • qual é a ordem de locking pretendida
    • se existem situações especiais em que algumas operações podem usar locking dependendo do caso
    • se argumentos NULL são permitidos e qual é o uso válido
    • o que acontece com a contagem de referências em caso de erro
    • se o ponteiro com contagem de referências retornado já vem incrementado ou se é um empréstimo implícito da referência do argumento passado
    • se o valor retornado é sempre um ponteiro válido, se pode ser NULL ou até ERR_PTR
    • se o ponteiro retornado via argumento indireto é limpo para NULL em caso de erro ou permanece inalterado
    • se passar NULL ** é válido quando o ponteiro de retorno não é necessário
  • Às vezes, os requisitos eram razoáveis, mas não estavam documentados
    • às vezes, os requisitos eram flexíveis ou complexos demais, então foi preciso tomar decisões subjetivas ao escrever abstrações Rust para restringi-los a um uso seguro
    • às vezes, foi preciso introduzir locking adicional dentro da abstração para garantir segurança
    • às vezes, foi necessário fazer pequenas mudanças no código C para torná-lo mais ortogonal, lógico e fácil de usar (por exemplo, expor uma função de unlock para uso enquanto o lock está mantido)
    • às vezes, o locking era sutil o bastante para que desse para escrever uma abstração Rust segura, mas ainda assim fosse necessário um grande comentário de documentação avisando para tomar cuidado com a forma de uso e a ordem de liberação para evitar deadlocks (Rust não previne deadlocks por si só)
    • às vezes, era impossível resolver sem tornar o código C mais razoável (no caso de drm_sched)
  • Porém, na maioria dos casos, os compromissos feitos ao escrever abstrações Rust indicam problemas no design do código C e caminhos para melhorá-lo
    • a abordagem geral é "primeiro escrever código Rust mudando o mínimo possível do código C para evitar conflitos e, depois, com base nas lições aprendidas, propor mudanças no código C" (ainda não consegui chegar à segunda parte)
  • Como resultado, na maioria dos casos dá para saber o uso correto olhando apenas para a API Rust
    • não é preciso se preocupar com contagem de referências, ponteiros NULL, esquecer de checar resultados ou liberar referências em caso de erro
    • não é preciso se preocupar com uso correto de locking, esquecer de obter referências ou dar free duplo
    • não é preciso ficar se perguntando como os valores de retorno de erro são codificados
    • porque, se você errar essas coisas, o código simplesmente não compila
    • claro, ainda dá para usar a API de forma errada, mas no pior caso isso só causa retorno de erro ou deadlock (deadlock é fácil de depurar com lockdep, e a integração com Arc<> pode capturar erros de locking relacionados a free/desreferência)
  • Até mesmo a API de OpenFirmware/DeviceTree, que é relativamente bem documentada, é tediosa e propensa a erro em C quando é preciso seguir todas as regras
    • ao olhar o código OF de drivers, a chance de vazamento de referência é alta
    • a maioria dos sistemas não compila o kernel com OF_DYNAMIC, então a contagem de referências é ignorada e isso não é detectado nem corrigido
    • porém, a abstração Rust de OF que escrevi lida automaticamente com a contagem de referências, então não é preciso se preocupar com isso
  • Vantagens de programar para o kernel em Rust em comparação com C
    • ao programar para o kernel em C, só existem duas opções
    • tentar fazer e torcer para que o revisor pegue os problemas, ou sofrer com depuração
    • passar horas entendendo tudo antes mesmo de se atrever a usar o código, esperando encontrar todos os detalhes
    • isso também aumenta a carga de trabalho de revisores e mantenedores
      • eles precisam revisar envios para verificar se todas as regras ocultas e não documentadas estão sendo seguidas
      • às vezes deixam passar problemas, e às vezes os problemas são tão grandes que o código precisa ser amplamente refatorado
  • Em Rust, tudo isso desaparece. Se compila, é seguro e não há mau funcionamento nem vazamento de referências (código unsafe é a exceção, mas só ele precisa ser revisado, e há a regra de que deve ser bem documentado)
    • claro, ainda é necessária revisão de código e ajuda de especialistas no subsistema. Rust não deixa o código perfeito por mágica
    • porém, ele elimina todos os problemas e erros idiotas de baixo nível, então é possível focar nos problemas de alto nível
  • Posição sobre os desenvolvedores de Linux
    • não culpo os desenvolvedores de Linux pela documentação incompleta
    • o kernel Linux é muito complexo e precisa lidar com muitas sutilezas
    • a maioria das APIs de espaço de usuário tem regras muito mais simples para uso seguro
    • o kernel é difícil
    • até desenvolvedores experientes do kernel erram essas coisas o tempo todo
    • não é um problema de habilidade técnica, mas sim do fato de que é impossível para seres humanos manter todas essas regras complexas na cabeça e executá-las corretamente todas as vezes
  • Solução
    • precisamos de ferramentas
    • a solução é Rust. Depois de codificar todas as regras uma vez no código e no sistema de tipos, não é mais preciso se preocupar com isso
    • é como a solução para discussões sobre estilo de código: codificar todas as regras em um formatador automático
    • assim, podemos parar de nos preocupar com toda a segurança de baixo nível, ownership e problemas de sincronização, e passar a nos preocupar com coisas mais importantes, como o design de drivers e subsistemas em alto nível
  • Formatação de código no projeto Rust for Linux
    • o projeto Rust for Linux realmente aplica rustfmt aos envios
    • ao escrever Rust para o kernel, não é preciso se preocupar com formatação de código nem com reclamações em revisão por causa disso
    • basta rodar make rustfmt

Opinião do GN⁺

  • Este texto aponta bem os problemas de documentação de API e segurança no desenvolvimento do kernel Linux. Mostra claramente as limitações de C e as vantagens de Rust
  • Porém, a expressão "Rust é a única solução" parece um pouco exagerada. Parte das melhorias também poderia vir de outras abordagens, como ferramentas de análise estática
  • Rust resolve muitos problemas, como segurança de memória, mas revisão cuidadosa de código e testes ainda continuam necessários. Não é uma bala de prata
  • Migrar para Rust pode trazer várias dificuldades, como compatibilidade com código C existente e curva de aprendizado dos desenvolvedores. Uma adoção gradual parece mais desejável
  • Para melhorar práticas e a cultura antiga do kernel Linux, além de Rust também serão necessários esforços em várias frentes, como documentação, mentoria e comunicação
  • No geral, este texto mostra bem o potencial e as vantagens de Rust no desenvolvimento do kernel Linux, ao mesmo tempo em que alerta contra expectativas excessivas ou fé cega, apresentando uma visão equilibrada. A adoção de Rust terá dificuldades técnicas e culturais, mas, no longo prazo, pode contribuir para melhorar a segurança e a manutenibilidade do código do kernel.

2 comentários

 
aer0700 2024-09-01

Rust... pessoalmente até tentei estudar, mas ainda não estamos adotando aqui na empresa. Já temos uma montanha de coisas escritas em C++, e ainda existe a questão de a equipe atual ter que aprender Rust de novo... Ouvi dizer que já existem empresas na Coreia que estão usando Rust em produção; acho que seria ótimo se experiências desse tipo pudessem ser compartilhadas.

 
GN⁺ 2024-09-01
Comentários do Hacker News
  • Linguagens como Rust e Swift têm alta expressividade, então o compilador informa a segurança de threads de tipos de dados ou métodos

    • Em revisões de código, não é necessário verificar um por um a segurança do uso de ponteiros, e ao usar uma linguagem com segurança de memória é possível focar na implementação da lógica de negócio
  • Muitas bibliotecas Rust têm documentação insuficiente

    • Rust não resolve o problema de documentação incompleta de APIs; quem resolve isso são desenvolvedores que documentam APIs de forma rigorosa
  • Ao tentar usar Rust como se fosse C, há dificuldades por causa do borrow checker

    • É importante ler a assinatura da função e verificar &self ou &mut self
    • Quando há &mut self, é preciso usar um mutex para compartilhar a instância entre threads
  • Ao olhar uma API Rust, na maioria dos casos dá para entender como usá-la corretamente

    • Porém, em algumas APIs Rust, não dá para saber só pela assinatura da função como criar o tipo, então é preciso pesquisar no Google
  • Como exemplo concreto em Rust, há a forma de usar locks para proteger dados

    • Em Rust, o lock envolve os dados protegidos, então não é possível acessar os dados sem liberar o lock
  • Em outras linguagens também, implementar APIs de forma redundante pode aumentar a clareza do código e da documentação

    • Em uma experiência recente, uma alteração incorreta na pilha de rede passou pela revisão e foi incluída em uma versão estável
    • É bom usar ferramentas para evitar esse tipo de problema
  • Ao usar C em extensões de Python, há o problema de precisar conhecer a convenção de chamada

    • Com Rust e PyO3, esse problema desaparece e a barreira de entrada diminui
    • C++ também oferece funcionalidade semelhante, mas não é tão seguro quanto Rust
  • Essas pessoas são heróis e fazem um excelente trabalho

  • Estamos um passo mais perto de implementar código totalmente autoexplicativo