15 puntos por GN⁺ 2024-09-01 | 2 comentarios | Compartir por WhatsApp
  • Creo que la gente no es suficientemente consciente de lo incompleta que es la documentación de la API del kernel de Linux, y de cómo Rust resuelve parte de ese problema
  • He escrito abstracciones de Rust para varios subsistemas, y en casi todos los casos tuve que leer el código fuente en C para entender por completo cómo usar la API de forma segura
  • Solo con las firmas de las funciones y los comentarios de documentación relacionados, o con la documentación explícita, es difícil entender por completo cómo usar la API de forma segura
    • si hay que mantener un bloqueo
    • si un argumento con conteo de referencias transfiere la referencia o toma su propia referencia
    • si el bloqueo se mantiene al invocar un callback o si hay que adquirirlo manualmente
    • si hay algo especial en el callback de free
    • cuál es el orden de bloqueo previsto
    • si hay situaciones especiales en las que algunas operaciones pueden usar bloqueos según el caso
    • si se permiten argumentos NULL y cuál es su uso válido
    • qué pasa con el conteo de referencias en caso de error
    • si un puntero con conteo de referencias devuelto ya viene incrementado o si es un préstamo implícito de la referencia del argumento pasado
    • si el valor de retorno es siempre un puntero válido, si puede ser NULL, o incluso un ERR_PTR
    • si un puntero devuelto a través de un argumento indirecto se limpia a NULL en caso de error o se deja como está
    • si es válido pasar NULL ** cuando no se necesita el puntero de retorno
  • A veces los requisitos eran razonables, pero no estaban documentados
    • a veces los requisitos eran tan flexibles o complejos que, al escribir la abstracción en Rust, tuve que tomar decisiones subjetivas para acotarlos a una forma de uso segura
    • a veces tuve que introducir bloqueos adicionales dentro de la abstracción para garantizar la seguridad
    • a veces hubo que hacer pequeños cambios al código en C para volverlo más ortogonal, lógico y fácil de usar (por ejemplo, exponer una función de desbloqueo para usarla mientras el bloqueo está tomado)
    • a veces el bloqueo era tan sutil que sí se podía escribir una abstracción segura en Rust, pero hacía falta un gran comentario de documentación advirtiendo que había que tener cuidado con el modo de uso y el orden de liberación para evitar deadlocks (Rust no evita deadlocks por sí mismo)
    • a veces era imposible resolverlo sin hacer que el código en C fuera más razonable (como en el caso de drm_sched)
  • Sin embargo, en la mayoría de los casos, los compromisos al escribir abstracciones en Rust sugieren problemas en el diseño del código en C y posibles direcciones de mejora
    • el enfoque general es: "primero escribir el código en Rust cambiando lo menos posible el código en C para evitar conflictos, y luego proponer cambios en el código en C basados en las lecciones aprendidas" (todavía no he llegado a la segunda parte)
  • Como resultado, en la mayoría de los casos basta con mirar la API de Rust para saber cuál es la forma correcta de usarla
    • no hace falta preocuparse por conteos de referencias, punteros NULL, olvidar comprobar resultados o liberar referencias en caso de error
    • no hace falta preocuparse por usar bien los bloqueos, por olvidar adquirir una referencia o por liberar algo dos veces
    • no hace falta preguntarse cómo está codificado un valor de retorno de error
    • porque si te equivocas en estas cosas, el código no compila
    • por supuesto, todavía se puede usar mal una API, pero en el peor de los casos eso solo provoca un retorno de error o un deadlock (los deadlocks se pueden depurar fácilmente con lockdep, y la integración con Arc<> puede detectar errores de bloqueo relacionados con liberación y decremento de referencias)
  • Incluso la API de OpenFirmware/DeviceTree, que está relativamente documentada de manera estricta, en C sigue siendo tediosa y propensa a errores al seguir todas las reglas
    • al ver el código OF de los drivers, es muy probable encontrar fugas de referencias
    • la mayoría de los sistemas no compilan el kernel con OF_DYNAMIC, así que el conteo de referencias se ignora y eso no se detecta ni se corrige
    • pero la abstracción en Rust para OF que escribí maneja automáticamente el conteo de referencias, así que no hace falta preocuparse por eso
  • Ventajas de programar el kernel en Rust frente a C
    • al programar el kernel en C, solo hay dos opciones
    • probar a ver qué pasa y esperar que el revisor lo detecte, o sufrir depurando
    • pasarte horas entendiendo el código antes de atreverte a usarlo, esperando detectar todo
    • esto también aumenta la carga de trabajo de revisores y mantenedores
      • tienen que revisar los envíos para verificar que cumplan todas las reglas ocultas no documentadas
      • a veces se pasan problemas por alto, y otras veces el problema es tan grande que hay que refactorizar el código a fondo
  • En Rust todo eso desaparece. Si compila, es seguro y no hay mal funcionamiento ni fugas de referencias (el código unsafe es la excepción, pero solo hay que revisar eso, y existe la regla de que debe estar bien documentado)
    • por supuesto, todavía hacen falta revisiones de código y la ayuda de expertos de subsistemas específicos. Rust no vuelve el código perfecto por arte de magia
    • pero elimina todos los problemas y errores tontos de bajo nivel, así que permite concentrarse en los problemas de alto nivel
  • Postura sobre los desarrolladores de Linux
    • no culpo a los desarrolladores de Linux por la documentación incompleta
    • el kernel de Linux es extremadamente complejo y tiene que lidiar con muchos problemas sutiles
    • la mayoría de las API de espacio de usuario tienen reglas mucho más simples para usarse de forma segura
    • el kernel es difícil
    • incluso los desarrolladores de kernel con experiencia se equivocan siempre en estas cosas
    • no es un problema técnico: es imposible que un ser humano mantenga en la cabeza todas estas reglas complejas y las ejecute correctamente cada vez
  • Solución
    • necesitamos herramientas
    • la solución es Rust. Una vez que todas las reglas se codifican una sola vez en el código y en el sistema de tipos, ya no hay que volver a preocuparse por ellas
    • es como cuando la solución a las discusiones sobre estilo de código es codificar todas las reglas en un formateador automático
    • entonces podemos dejar de preocuparnos por toda la seguridad de bajo nivel, la propiedad y los problemas de sincronización, y concentrarnos en cosas más importantes como los drivers de alto nivel y el diseño de subsistemas
  • Formato de código en el proyecto Rust for Linux
    • el proyecto Rust for Linux realmente aplica rustfmt a los envíos
    • al escribir Rust para el kernel, no hace falta preocuparse por el formato del código ni por quejas durante la revisión de código
    • solo hay que ejecutar make rustfmt

Opinión de GN⁺

  • Este texto señala bien los problemas de documentación de la API y de seguridad en el desarrollo del kernel de Linux. Muestra claramente las limitaciones de C y las ventajas de Rust
  • Sin embargo, la expresión "Rust es la única solución" parece algo exagerada. Otros métodos, como las herramientas de análisis estático, también podrían mejorar parte de esto
  • Aunque Rust resuelve muchos problemas, como la seguridad de memoria, siguen siendo necesarias revisiones de código cuidadosas y pruebas. No es una bala de plata
  • Cambiar a Rust puede traer varias dificultades, como la compatibilidad con el código C existente y la curva de aprendizaje para los desarrolladores. Una adopción gradual parece lo más deseable
  • Para mejorar las prácticas y la cultura antiguas del kernel de Linux, además de Rust probablemente hagan falta esfuerzos en varios frentes, como documentación, mentoría y comunicación
  • En general, este texto muestra bien el potencial y las ventajas de Rust en el desarrollo del kernel de Linux, al tiempo que advierte contra expectativas excesivas o fe ciega, ofreciendo una mirada equilibrada. Aunque adoptar Rust será difícil tanto técnica como culturalmente, a largo plazo podría contribuir a mejorar la seguridad y la mantenibilidad del código del kernel.

2 comentarios

 
aer0700 2024-09-01

Rust... en lo personal he intentado estudiarlo, pero todavía no lo estamos adoptando en nuestra empresa. Ya tenemos una montaña de cosas escritas en C++ y además está el problema de que el personal actual tendría que volver a aprender Rust... Escuché que ya hay empresas en Corea que lo están aplicando en producción, así que estaría bueno que se compartieran experiencias relacionadas o cosas por el estilo.

 
GN⁺ 2024-09-01
Comentario de Hacker News
  • Lenguajes como Rust y Swift tienen alta expresividad, por lo que el compilador puede indicar la seguridad de hilos de tipos de datos o métodos

    • Al revisar código, no hace falta verificar uno por uno la seguridad del uso de punteros, y al usar un lenguaje con seguridad de memoria se puede concentrar uno en implementar la lógica de negocio
  • Entre las bibliotecas de Rust hay muchos casos con documentación insuficiente

    • Rust no resuelve el problema de una documentación de API incompleta; lo resuelven los desarrolladores que documentan a fondo la API
  • Al intentar usar Rust como si fuera C, se encuentran dificultades por el borrow checker

    • Es importante leer la firma de la función y revisar &self o &mut self
    • Si hay &mut self, para compartir la instancia entre hilos hay que usar un mutex
  • Al ver una API de Rust, en la mayoría de los casos se puede saber cómo usarla correctamente

    • Sin embargo, en algunas API de Rust no se puede saber solo por la firma de la función cómo crear el tipo, así que hace falta buscar en Google
  • Como ejemplo concreto en Rust, existe la forma de usar locks para proteger datos

    • En Rust, el lock envuelve los datos protegidos, por lo que no se puede acceder a los datos sin liberar el lock
  • Incluso en otros lenguajes, implementar una API de forma redundante puede aumentar la claridad del código y la documentación

    • En una experiencia reciente, un cambio incorrecto en el stack de red pasó la revisión y fue incluido en una versión estable
    • Para evitar este tipo de problemas, es bueno usar herramientas
  • Al usar C en extensiones de Python, existe el problema de tener que conocer la convención de llamadas

    • Con Rust y PyO3, ese problema desaparece y baja la barrera de entrada
    • C++ ofrece funciones similares, pero no es tan seguro como Rust
  • Estas personas son héroes y están haciendo un trabajo excelente

  • Estamos un paso más cerca de implementar código completamente autodocumentado