Guía de programación de módulos del kernel de Linux
(sysprog21.github.io)- Linux Kernel Module Programming Guide es una guía gratuita para crear módulos de kernel cargables en Linux v5.10 o superior, y recorre de forma integral desde el entorno de desarrollo hasta la compilación, carga, depuración y las principales interfaces del kernel
- Los ejemplos iniciales usan
hello-*.cpara aprendermodule_init(),module_exit(),kbuild,insmod,rmmodydmesg, y con devtools basadas en QEMU reducen el riesgo de dañar el sistema host - Como los módulos del kernel se ejecutan en el espacio de direcciones del kernel, punteros incorrectos, orden de descarga erróneo, concurrencia y fallos al copiar memoria de usuario pueden provocar corrupción de memoria del kernel o inestabilidad del sistema
- Se expande hacia dispositivos de caracteres,
/proc,seq_file, IRQ con hilos, input, PCI, USB, bloque, red, Device Model, Device Tree y static key, tratando repetidamente el orden de registro y liberación y la gestión del lifetime - Como las API internas del kernel cambian según la versión, se verifican condiciones como
LINUX_VERSION_CODE,KERNEL_VERSION,CONFIG_MODVERSIONS, firmas de SecureBoot y version magic, y los ejemplos también incluyen compilación condicional
Estructura de la guía y flujo básico
- Esta guía es material de aprendizaje sobre módulos del kernel que ofrece un repositorio en GitHub y un documento PDF, y puede copiarse, modificarse y distribuirse bajo los términos de Open Software License 3.0
- Actualmente la guía toma Linux v5.10 como requisito mínimo de soporte y busca mantener la compatibilidad de ejemplos e instrucciones a lo largo de los kernels con soporte de largo plazo
- El lector necesita experiencia con lenguaje C y con la escritura de programas para procesos normales, y los módulos del kernel se cargan y descargan dinámicamente para ampliar funciones del kernel sin reiniciar
- El flujo básico de desarrollo sigue con instalación de headers del kernel, compilación del
.koconmake, verificación conmodinfo, carga coninsmod, revisión de logs condmesgojournalctl -k, y descarga conrmmod devtools/compila el código fuente del kernel y un sistema de archivos raíz de BusyBox, arranca en QEMU, comparteexamples/mediante 9p virtfs y luego permite probar módulos dentro del guest- Para la inicialización y limpieza del módulo se prefiere usar
module_init()ymodule_exit(), y el método anterior coninit_module()ycleanup_module()puede causar fallos de compilación en ciertas condiciones a partir del kernel 6.15 con x86 IBT habilitado - Los módulos del kernel no usan
printf()ni libc y solo pueden usar símbolos exportados por el kernel; la salida no va a la terminal sino al ring buffer de logs del kernel - Para mover datos entre espacio de usuario y espacio de kernel se necesitan funciones dedicadas como
put_user,get_user,copy_to_userycopy_from_user - El ejemplo de dispositivo de caracteres muestra
register_chrdev,file_operations, major number dinámico, creación de nodos en/dev, apertura exclusiva, lectura basada enput_usery manejo de escritura no soportada - El ejemplo de
/proccubreproc_create,proc_ops, callbacks de lectura y escritura, y la APIseq_file, reflejando el cambio introducido desde Linux v5.6 dondeproc_opsreemplaza afile_operationsen los handlers de/proc - Las IRQ con hilos usan
request_threaded_irq()para dividir top-half y bottom-half; el top-half realiza solo trabajo mínimo en contexto de interrupción y despierta el bottom-half basado en hilo conIRQ_WAKE_THREAD - Los capítulos posteriores se expanden a áreas reales de drivers como input, PCI, USB, bloque, red, Device Model y Device Tree, centrados en el método de registro de cada subsistema y la elección de ABI de userspace
- La parte de optimización y seguridad trata
likelyyunlikely, static key, stacks pequeños del kernel, prohibición de usar FPU, fuga de padding no inicializado y precauciones al usar API internas con doble guion bajo
Restricciones con las que uno tropieza primero al compilar y cargar
- Un módulo compilado para un kernel puede no cargarse en otro, y si version magic o
CONFIG_MODVERSIONSno coinciden apareceránInvalid module formato errores de versión de símbolos - En la mayoría de los kernels de distribuciones Linux comunes puede estar activado modversioning, así que si los ejemplos no funcionan de inmediato conviene considerar un kernel con modversioning desactivado o un entorno QEMU
- En sistemas con SecureBoot activado, la carga de módulos sin firma puede estar restringida, y si aparece
Lockdown: insmod: unsigned module loading is restricted, será necesario desactivar SecureBoot o seguir el proceso de firmado del módulo
Entorno de práctica basado en QEMU
devtools/setup.shdescarga y compila el tarball del kernel y BusyBox, y empaqueta el initramfsdevtools/build-modules.shcompila módulos para el kernel de QEMU,devtools/boot.shproporciona un shell del guest, ydevtools/test-modules.shejecuta pruebas automáticas deinsmodyrmmodpor módulo- La depuración con GDB se realiza compilando
vmlinuxconLKMPG_NO_PREBUILT=1 devtools/setup.sh, y luego usandodevtools/boot.sh --gdbcon una conexión remota de GDB
Reglas para escribir código de kernel
- En la función init pueden fallar el registro y la asignación, por lo que los recursos adquiridos deben liberarse en orden inverso dentro de rutas de error basadas en
goto - Si se registra una estructura de callbacks en el kernel, userspace puede invocarlos incluso antes de que init retorne, por lo que es importante la regla de registrar al final y desregistrar primero: terminar la inicialización interna antes de registrar y hacer la liberación en el orden inverso
- El process context, softirq/tasklet context y hardirq context difieren en si pueden dormir, acceder a memoria de usuario, usar
GFP_KERNELo mutex; entender mal esa distinción lleva a bugs comunes del kernel
Puntos de atención por dispositivo y subsistema
- Los dispositivos de caracteres identifican al driver con el major number y distinguen varios dispositivos internos con el minor number; en el enfoque moderno se recomienda la interfaz
cdevpor encima deregister_chrdev() - Los drivers PCI no asumen direcciones fijas, sino que mapean los recursos BAR enumerados por el core PCI; en código para Linux 5.10 o posterior,
pcim_enable_device()y la API de recursos administrados por el dispositivo ayudan a reducir bugs de teardown - Los drivers USB deben tratar hotplug y disconnect como eventos normales, y diseñarse asumiendo que la finalización de URB puede competir con disconnect, timeout, suspend y apagado desde userspace
- Los drivers de bloque funcionan alrededor de
blk-mq,request,gendisk, límites de cola y semántica de flush/FUA, y participan en un modelo de finalización asíncrona de requests en lugar de simples callbacks de lectura/escritura - Los drivers de red se integran con
struct net_device,net_device_ops,sk_buff, NAPI, banderas de offload y reporte de estado del enlace, y declarar mal una capacidad de offload puede corromper el tráfico
Adaptación a cambios de versión del kernel
- Los ejemplos manejan con compilación condicional cambios como la firma de
class_create()en Linux 6.4,proc_opsen Linux v5.6, el cambio del tipo de retorno deremove()en Linux 6.11 y cambios de helpers deblk-mqentre Linux 5.15 y 6.9 - Como las interfaces internas del kernel cambian con más frecuencia que las llamadas al sistema, en módulos que soportan varios kernels es difícil evitar comparaciones entre
LINUX_VERSION_CODEyKERNEL_VERSION
Puntos de control de seguridad
- El stack del kernel es mucho más pequeño que el stack de userspace y en muchos sistemas puede ser de 8 KiB o 16 KiB, por lo que los arreglos grandes deben usar
kmalloc()okzalloc() - Al enviar datos a userspace con
copy_to_user(), todos los bytes, incluido el padding, deben estar inicializados; de lo contrario puede producirse una filtración de información de memoria del kernel - API que empiezan con doble guion bajo como
__kmalloc()o__list_add()pueden asumir precondiciones internas, por lo que salvo que la documentación lo exija, conviene usar primero el wrapper público
Alcance omitido
- Se indica que algunos fragmentos originales fueron omitidos durante el procesamiento de entrada por límites de longitud o costo, por lo que este resumen no cubre exhaustivamente todos los capítulos, ejemplos ni rutas de código de la guía completa
1 comentarios
Opiniones en Hacker News
QEMU es una buena forma de experimentar con hacking del kernel.
Sería bueno que alguien actualizara LDD (Linux Device Drivers) y los libros sobre el kernel de Linux; como este tipo de libros técnicos difícilmente genera ganancias, quizá la Linux Foundation podría patrocinarlos.
Incluso esta semana, con QEMU + GDB reproduje en v6.8 un problema en el que el kernel se detenía de inmediato y en silencio si el parámetro de línea de comandos del kernel arm64 tenía 146 caracteres o más; emulé la compilación de un kernel arm64 en un host amd64 con Debian 12 Bookworm y fui siguiendo línea por línea el código problemático hasta encontrar la causa.
El flujo consiste en compilar una imagen de kernel arm64 y scripts para GDB en un entorno con las dependencias de compilación y herramientas de cross-compilation listas; instalar en el host
gdb, si hace faltagdb-multiarch, yqemu-system-arm; luego iniciarqemu-system-aarch64detenido con-S -gdb tcp::1234y conectarse desde otra terminal congdb-multiarch ./vmlinux.Después, en GDB, al ejecutar
target remote :1234,break __parse_cmdlineycontinue, se pueden usar funciones normales de GDB como revisar memoria, variables y stack, y ejecutar paso a paso.Para depuración del kernel con GDB y los scripts
lx-*, consultar https://www.kernel.org/doc/html/latest/dev-tools/gdb-kernel-...Para que GDB use los scripts Python
lx-*, normalmente también hace falta permitir la ruta, por ejemplo conecho "add-auto-load-safe-path ${SRC_DIR}/scripts/gdb/vmlinux-gdb.py" > ~/.gdbinit.Hilos relacionados en HN: https://news.ycombinator.com/item?id=35782630, https://news.ycombinator.com/item?id=28283030
The Linux Memory Manager también vale la pena como referencia: https://linuxmemory.org/chapters
Según la última actualización que envió el autor a principios de julio, terminó el primer borrador y ahora entró en la etapa de edición junto con la editorial.
Algunos ejemplos parecen difíciles de ejecutar directamente.
Por ejemplo, “Detecting button presses” asume que se puede compilar un módulo para RPi, pero eso en sí puede requerir tareas como cross-compilation, así que quizá no sea tan simple.
Es excelente: detallado, práctico y orientado a un tutorial donde de inmediato se compila un módulo del kernel.
Material para ver junto con esto: https://0xax.gitbooks.io/linux-insides/content/index.html
Me pregunto dónde conviene mirar para aprender sobre programación del kernel de Linux en general, como sistemas de archivos o administración de memoria.
Hace mucho estaba “Linux Kernel Development” de Robert Love, pero parece que ya no se actualiza.
Leí esto por primera vez hace unos 22 años :)