Desarrollo de módulos para el kernel Linux

Una de las primeras cosas que tiene que saber un hacker del kernel es cómo crear un módulo para el kernel Linux. Para continuar leyendo este artículo es conveniente haber leído antes el anterior Entorno para desarrollo del kernel Linux.
Linux es un kernel del tipo monolítico, lo cual quiere decir, grosso modo, que todo el kernel es un gran archivo ejecutable que se carga y toma el control al iniciar el ordenador. El problema de este tipo de kernels es que al compilarlo hay que añadir todos los controladores para el hardware que queremos soportar. Esto es un problema si queremos hacer un kernel genérico que soporte una gran variedad de dispositivos, ya que su tamaño será considerable, e incluso inviable.
Linux soluciona este problema a través de los módulos, por lo que podemos decir que Linux es un kernel modular que carga en memoria aquellos módulos que necesita en cada momento. Las distribuciones genéricas, como por ejemplo, Ubuntu, compilan un núcleo muy pequeño con lo mínimo para poder arrancar y en tiempo de ejecución carga en memoria aquellos módulos necesarios para dar servicio a los dispositivos que tiene nuestro equipo.
Los módulos son, pues, la manera más sencilla y cómoda de añadir código y funcionalidad al espacio del kernel sin tener que andar recompilándolo.
En este artículo vamos a crear un módulo muy simple (de hecho el más simple posible) y lo vamos a cargar en la máquina virtual Qemu que preparamos en el artículo anteriormente citado.



Una vez creado el módulo lo copiaremos desde nuestra máquina host a la máquina virtual usando scp. Por lo tanto, tenemos que asegurarnos de que nuestra máquina virtual tiene conectividad vía red con la máquina host. En mi caso, la configuración del kernel que compilamos en el anterior artículo tiene activado el soporte de la tarjeta de red Intel e1000 (la que emula Qemu) como módulo, pero en este caso, para facilitarnos la tarea nos va a interesar que el soporte a esta tarjeta esté compilado como parte integrante del kernel. Así que nos vamos a ir al directorio donde está el código fuente del kernel y nos vamos a asegurar de que el soporte para la tarjeta ethernet Intel e1000 está activado en el archivo .config (debe aparecer como CONFIG_E1000=y) y volvemos a compilar.

cd ~/kerneldev/src/linux-$(uname -r)
sed -i s/CONFIG_E1000=m/CONFIG_E1000=y/ .config
make -j4

También tenemos que asegurarnos de que tenemos instalado el servidor ssh en nuestra máquina host para poder copiar los ficheros de una máquina a otra.

sudo apt-get install openssh-server

Hechos los preparativos, podemos entrar en harina y comenzar a desarrollar nuestro módulo. Un módulo no es más que un programa escrito en C (aunque nada nos impide hacerlo directamente en ensamblador si nos sentimos fuertes) que cumple algunas condiciones. Veamos el código de nuestro primer módulo que pondremos en un archivos llamado hello_module.c y que estará en el directorio ~kerneldev/src/modulo1.

#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Modulo Hola mundo");
MODULE_AUTHOR("Alberto Garcia");

static int __init hello_init(void)
{
    printk(KERN_INFO "Hola mundo.\n");
    return (0);
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Hasta otra.\n");
}

module_init(hello_init);
module_exit(hello_exit);

De entrada nos encontramos con dos funciones de lo más normales salvo por que antes del nombre incluyen __init o __exit. Un módulo ha de tener al menos dos funciones: una que se invoca cuando se carga el módulo en memoria y que es la que lleva __init; y otra que es llamada cuando se descarga el módulo del kernel, y es la que lleva __exit. Esta función se encargará de limpiar y liberar todos los recursos que estemos usando. Si tienes curiosidad por saber como se definen __init y __exit puedes verlo en linux/init.h.
Las dos últimas líneas indican qué función es la que ha de llamarse al cargar y descargar el módulo usando module_init() y module_exit() respectivamente.
Las tres primeras líneas tras el #include son bastante autodescriptivas: en ellas indicamos el tipo de licencia del módulo (en este caso GPL), la descripción y el autor.
Otra función que no te resultará familiar es printk(). Recuerda que este código se ejecuta en el espacio del kernel y por lo tanto no tenemos acceso a la librería de C libc, es decir, no tenemos printf() así que tenemos que usar la función printk() que pertenece al código del kernel.

Para compilar el módulo vamos a usar el siguiente makefile.

ifneq ($(KERNELRELEASE),)
obj-m := hello_module.o 
else
KERNEL_VERSION = linux-3.5.0
KERNELDIR := ../$(KERNEL_VERSION)
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

clean:
rm -rf *.[oas] .*.flags *.ko .*.cmd .*.d .*.tmp *.mod.c \
.tmp_versions Module.symvers

Es importante poner la variable KERNEL_VERSION a su valor correcto. En mi caso particular el directorio donde está el código fuente de Linux es linux-3.5.0. Para compilar nuestro módulo sólo hemos de ejecutar la utilidad make y, si todo va bien, obtendremos varios archivos entre los que se encuentra el que vamos a usar nosotros, que es hello_module.ko.

Para probarlo iniciamos la máquina virtual Qemu que ya habíamos preparado en el artículo anterior con nuestro recién compilado kernel y una vez dentro copiamos el módulo hello_module.ko desde dentro de la máquina virtual.

scp alberto@10.0.2.2:kerneldev/src/modulo1/hello_module.ko .

La dirección IP 10.0.2.2 es la que Qemu le da por defecto a la máquina host, así que sólo tienes que cambiar alberto por el nombre el usuario de tu máquina host.
Una vez copiado, podemos cargar el módulo en memoria usando insmod:

insmod hello_module.ko

Si todo va bien verás aparecer la frase hola mundo. Para ver los módulos cargados en memoria usamos el comando lsmod. Finalmente, para descargar el módulo de memoria usamos el comando rmmod, que nos mostrará una frase de despedida.

rmmod hello_module


En este artículo llamado Linux Kernel Modules - Load, Unload, Configure puedes encontrar más información sobre la gestión de los módulos.
Por ahora nuestro módulo no es demasiado útil, pero nos ha permitido comprender todo el proceso de creación, carga y descarga. En próximos artículos iremos ampliándolo y empezaremos a hacer cosas más interesantes dentro de nuestro módulo.

No hay comentarios:

Publicar un comentario en la entrada