wandres.dev
TU PRIMER MÓDULO · LKM y ciclo de vida

Tu primer módulo del kernel

El 'hola mundo' del kernel: un módulo cargable con module_init y module_exit, printk, y las macros de metadatos. Tu primer código corriendo en anillo 0.

⏱ 13 min

Ha llegado el momento: tu propio código ejecutándose dentro del kernel, con privilegio total sobre la máquina. Un módulo cargable (LKM) es la forma de añadir código al kernel en caliente, sin recompilarlo ni reiniciar. Este es tu rito de paso.

🎯 Al terminar esta lección sabrás
  • La estructura de un módulo mínimo.
  • module_init y module_exit.
  • printk y los niveles de log.
  • Las macros de metadatos (LICENSE, AUTHOR).

El módulo mínimo

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

static int __init hola_init(void) {
    printk(KERN_INFO "hola: módulo cargado\n");
    return 0;            // 0 = éxito; negativo = fallo al cargar
}

static void __exit hola_exit(void) {
    printk(KERN_INFO "hola: módulo descargado\n");
}

module_init(hola_init);   // qué correr al cargar
module_exit(hola_exit);   // qué correr al descargar

MODULE_LICENSE("GPL");
MODULE_AUTHOR("William");
MODULE_DESCRIPTION("Mi primer módulo");

Compílalo con el Makefile del nivel 5.1 (make) y obtendrás hola.ko.

module_init y module_exit

Un módulo no tiene main. Tiene dos puntos de entrada que registras:

  • module_init(fn) — la función que corre al cargar el módulo (insmod). Aquí registras dispositivos, reservas recursos, arrancas tu funcionalidad. Devuelve 0 si todo fue bien, o un -errno (nivel 11) si algo falló.
  • module_exit(fn) — la función que corre al descargar (rmmod). Aquí deshaces exactamente lo que hizo init: liberas recursos, desregistras. La simetría init/exit es sagrada.

Las anotaciones __init y __exit le dicen al kernel que puede liberar la memoria de esas funciones tras usarlas (init) o descartarlas si el módulo va compilado dentro (exit) — una optimización del kernel.

Cargar y descargar simétricamente: el contrato del módulo

El principio que gobierna todo módulo del kernel: exit deshace lo que init hizo, en orden inverso. Si en init registraste un dispositivo, reservaste memoria y creaste una entrada en /proc, en exit las quitas al revés. ¿Por qué es tan crítico? Porque un módulo se carga y descarga en un sistema vivo: si tu exit olvida liberar algo, dejas una fuga o un recurso zombi en el kernel que no se limpia hasta reiniciar. Peor: si liberas algo que otro está usando, corrompes el kernel. Esta disciplina de “toda adquisición en init tiene su liberación en exit” es la versión kernel del ownership de memoria de C (nivel 13), pero con las apuestas más altas: aquí el que paga los platos rotos es la máquina entera. Escribe siempre init y exit a la vez, emparejando cada acción con su reverso.

printk y los niveles de log

printk es el printf del kernel, pero con un nivel de severidad al principio:

printk(KERN_ERR   "algo falló: %d\n", err);   // error
printk(KERN_WARNING "aviso\n");
printk(KERN_INFO  "información\n");
printk(KERN_DEBUG "detalle de depuración\n");

// atajos modernos, preferidos:
pr_info("cargado\n");
pr_err("falló: %d\n", err);

Los mensajes van al buffer del kernel, que lees con dmesg. El nivel controla si aparecen en la consola según la configuración del sistema.

Las macros de metadatos

MODULE_LICENSE("GPL") no es opcional: sin una licencia compatible, el kernel marca tu módulo como “tainted” y te niega el acceso a muchas APIs internas (símbolos exportados solo para GPL). MODULE_AUTHOR, MODULE_DESCRIPTION y MODULE_VERSION documentan el módulo (los ve modinfo, nivel 7.2).

⚔️ Tu código en el kernel
  1. Escribe hola.c y su Makefile; compílalo a hola.ko.
  2. En tu VM (nivel 6): sudo insmod hola.ko, y mira dmesg para ver tu mensaje.
  3. sudo rmmod hola y comprueba el mensaje de descarga.
  4. Quita MODULE_LICENSE("GPL") y observa el aviso de “tainted” al cargar.
  5. Añade un pr_err con un valor y confírmalo en dmesg.