La estructura de un módulo del kernel en Rust
La macro module! con sus metadatos y licencia, el tipo que implementa el trait kernel::Module y su init fallible, y Drop como el module_exit que no puedes olvidar. La misma anatomía del módulo C del nivel 7, ahora verificada por el compilador.
En el nivel 7 escribiste tu primer módulo en C: module_init, module_exit, MODULE_LICENSE. Rust for Linux —fusionado en el árbol oficial desde 6.1 y ya infraestructura de primera clase en los kernels de 2026— no reinventa ese ciclo de vida: lo reviste. La misma anatomía de metadatos, un punto de entrada y un punto de salida, pero expresada con tipos que hacen que el contrato del módulo lo verifique el compilador y no tu disciplina.
- Declarar un módulo con la macro
module!y sus metadatos. - Implementar el trait
kernel::Moduley su métodoinitfallible. - Entender
Dropcomo elmodule_exitque el compilador no te deja olvidar. - Contrastar la anatomía, línea a línea, con el módulo C del nivel 7.
La macro module!
El hola.c del nivel 7 esparcía la identidad del módulo en macros sueltas —MODULE_LICENSE, MODULE_AUTHOR, MODULE_DESCRIPTION— y registraba las funciones de entrada y salida con module_init y module_exit. En Rust todo eso se declara en un solo lugar, la macro module!, que en tiempo de compilación genera la sección .modinfo, el registro de la licencia y el pegamento con el cargador de módulos:
// SPDX-License-Identifier: GPL-2.0
//! Un modulo minimo del kernel en Rust.
use kernel::prelude::*;
module! {
type: ModuloMinimo,
name: "modulo_minimo",
author: "William",
description: "Mi primer modulo del kernel en Rust",
license: "GPL",
}
El campo license no es decorativo, igual que en C: si no declaras una licencia compatible con GPL, el kernel marca el módulo como tainted y te niega los símbolos exportados solo para GPL. La diferencia es que aquí license es un campo obligatorio de la macro —olvidarlo es un error de compilación, no un aviso en tiempo de carga— y el campo type nombra al tipo que encarna el módulo. Ese type es la novedad conceptual: tu módulo no es un puñado de funciones sueltas, es un objeto.
Un módulo en Rust necesita un árbol con CONFIG_RUST=y, que a su vez exige una versión soportada de rustc, el bindgen que genera los enlaces a las cabeceras C, y el core y alloc de Rust compilados para el kernel. El sistema Kbuild del nivel 5 ya sabe compilar .rs a .ko junto a los .c: en el Kbuild pones obj-m += modulo_minimo.o igual que con C. El resultado es un .ko indistinguible desde insmod, lsmod o modinfo.
El tipo que implementa kernel::Module
Donde C tenía dos funciones independientes conectadas por variables globales static, Rust tiene un struct que posee el estado del módulo y un trait, kernel::Module, cuyo método init lo construye:
struct ModuloMinimo {
numeros: KVec<i32>,
}
impl kernel::Module for ModuloMinimo {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("modulo_minimo: cargado\n");
let mut numeros = KVec::new();
numeros.push(72, GFP_KERNEL)?;
numeros.push(108, GFP_KERNEL)?;
Ok(ModuloMinimo { numeros })
}
}
init devuelve Result<Self>, no un int. Si todo va bien, construye y devuelve el struct del módulo dentro de Ok; el kernel guarda ese valor y lo mantiene vivo mientras el módulo esté cargado. Si algo falla —una asignación, un registro— el ? corta y devuelve el error, y el módulo no se carga. Fíjate en numeros.push(72, GFP_KERNEL)?: hasta reservar espacio en un vector es una operación fallible y explícita, que recibe el flag GFP del nivel 20 y devuelve un Result. No hay asignación silenciosa que pueda devolver un puntero nulo sin que lo notes.
pr_info! es el printk y el pr_info del nivel 7, ahora una macro de Rust con formato verificado en compilación. ThisModule es el equivalente tipado del THIS_MODULE de C: la referencia a la propia instancia cargada.
Drop: el module_exit que no se olvida
¿Dónde está el module_exit? No hace falta escribirlo como función aparte. Cuando el módulo se descarga, el kernel destruye el objeto que init devolvió, y destruir un valor en Rust ejecuta su Drop:
impl Drop for ModuloMinimo {
fn drop(&mut self) {
pr_info!("modulo_minimo: numeros = {:?}\n", self.numeros);
pr_info!("modulo_minimo: descargado\n");
}
}
El KVec que reservaste en init se libera solo, sin que escribas nada, porque el vector tiene su propio Drop que corre al destruirse el struct que lo contiene. La “simetría init/exit sagrada” del nivel 7 —cada adquisición en init tiene su liberación en exit, en orden inverso— deja de ser una disciplina que recuerdas y pasa a ser una propiedad del lenguaje: el orden de destrucción de los campos es el inverso al de su construcción, garantizado.
Con init y Drop escritos, ya puedes compilar y cargar el módulo. Kbuild compila el .rs a un objeto y lo enlaza en un .ko idéntico en forma a uno de C, con el mismo flujo del nivel 7 pero con Rust en medio:
# en el Kbuild del directorio del modulo: obj-m += modulo_minimo.o
make -C /lib/modules/$(uname -r)/build M=$PWD # construye modulo_minimo.ko
sudo insmod modulo_minimo.ko # init corre; mira dmesg
lsmod | grep modulo_minimo # cargado, como cualquier .ko
sudo rmmod modulo_minimo # Drop corre; mira dmesg
Para el kernel, modulo_minimo.ko es un módulo más: insmod, lsmod, modinfo y rmmod funcionan exactamente igual que con C, porque el .ko resultante tiene la misma estructura ELF y los mismos metadatos. La diferencia vive en el .rs, no en el binario que carga el sistema.
La misma anatomía, otras garantías
Puestos lado a lado, el módulo C del nivel 7 y este comparten la forma exacta; cambia quién vigila el contrato:
| Nivel 7 en C | Nivel 55 en Rust |
|---|---|
module_init(fn) |
método init del trait kernel::Module |
module_exit(fn) |
impl Drop del tipo del módulo |
MODULE_LICENSE("GPL") |
campo license de module! |
return -ENOMEM a mano |
? que propaga el error |
estado en static globales |
campos del struct del módulo |
| liberar en exit por disciplina | liberar por Drop, garantizado |
Tres piezas encarnan esa anatomía, y las tres reaparecen en cada lección de este nivel:
module!
Declara metadatos, licencia y el tipo del módulo. Sustituye a las macros sueltas y a module_init/module_exit de C.
init
El constructor del módulo. Devuelve Result<Self>: construye el estado o falla con un errno, sin cargar nada a medias.
Drop
El destructor. Corre al descargar y libera cada campo en orden inverso, sin un module_exit que puedas olvidar.
flowchart LR INSMOD[insmod] --> INIT[init construye el objeto] INIT --> VIVE[modulo cargado y vivo] VIVE --> RMMOD[rmmod] RMMOD --> DROP[Drop destruye y libera] style INIT fill:#89b4fa,color:#11111b style DROP fill:#a6e3a1,color:#11111b
Aquí está el giro que reorganiza todo lo que aprendiste en el nivel 7. En C, un módulo es un archivo con funciones y estado global suelto, y el contrato “todo lo que init adquiere, exit lo libera en orden inverso” es una convención que tú sostienes con cuidado, sabiendo que un olvido deja una fuga o un recurso zombi en un sistema vivo. Rust for Linux toma ese mismo contrato y lo hace estructural: el módulo es un objeto, init es su constructor, Drop es su destructor, y su tiempo de vida es exactamente el intervalo entre insmod y rmmod. Todo recurso que el módulo posee vive como un campo de ese objeto, y desaparece cuando el objeto muere, en el orden inverso al que nació, sin que tú escribas la liberación. No es que Rust te ayude a recordar la simetría: es que la simetría ya no depende de tu memoria. El mismo principio que en C era la mejor práctica —acoplar la vida de un recurso a la vida de un objeto, el RAII que el nivel 29 emulaba con devm— aquí es el comportamiento por defecto e ineludible del lenguaje. Y esa es la tesis entera del track: el kernel no cambia de reglas al pasar a Rust; cambia quién las hace cumplir, del programador al compilador.
- Escribe
modulo_minimo.rscon sumodule!, elimpl kernel::Moduley unDrop, y añádelo alKbuilddel directorio conobj-m. - Compila con un árbol que tenga
CONFIG_RUST=yy carga el.koconinsmod; confirma endmesgel mensaje deinit. - Descárgalo con
rmmody observa que el mensaje deDropimprime el contenido delKVecantes de liberarlo. - Compara este archivo con el
hola.cdel nivel 7 y localiza el equivalente de cada macro y cada función. - Provoca un fallo en
initdevolviendoErr(ENOMEM)a mitad y comprueba que el módulo no se carga y no deja nada a medias.