Introducción a programación de módulos de kernel Linux

De Departamento de Informatica
Saltar a: navegación, buscar

Contenido

¿Qué es Linux?

Linus Torvalds, creador de Linux

Linux es el kernel de un sistema operativo de código abierto basado en Unix y es uno de los ejemplos de software libre más conocidos.

Se distribuye bajo licencia GPL v2 y actualmente es desarrollado por colaboradores alrededor de todo el mundo. Linux, inicialmente, fue desarrollado por Linus Torvalds, en 1991, y al ser de software libre, permitió que desarrolladores y usuarios de todo el mundo adaptaran códigos de otros proyectos para su uso en este nuevo sistema.

Actualmente el núcleo recibe contribuciones de miles de programadores y es normalmente empaquetado junto a distintos tipos de software y distribuido a través de una "distribución de Linux".

Introducción

Estructura de la arquitectura GNU/Linux

Un módulo es un archivo objeto ELF reubicable que resuelve sus símbolos cuando se carga en el kernel. Una vez que ya está cargado, es parte del kernel y su invocación tendrá, al igual que el kernel, interrupciones y/o excepciones. Los manejadores de dispositivos se desarrollan como módulos. Los módulos utilizan una interfaz clara entre el kernel y el dispositivo, lo que hace que el módulo sea fácil de escribir y a la vez mantiene el código libre de desorden.

El módulo debe ser compilado, y se carga en el kernel en ejecución. Esto significa que los módulos se escriben de forma muy similar a un programa de C, y que pueden utilizar funciones no definidas por nosotros. La diferencia es que sólo podemos utilizar un conjunto reducido de funciones externas, que son las funciones públicas definidas por el kernel. En teoría, podemos escribir cualquier cosa dentro de un módulo. En la práctica, debemos recordar que un módulo es código kernel y debe poseer una interfaz bien definida con el resto del sistema.

Recordar que Unix transfiere la ejecución desde modo usuario a modo kernel cuando se realiza una llamada al sistema o se produce una interrupción. El código kernel que ejecuta una llamada al sistema trabaja en el contexto del proceso actual, es decir, trabaja en beneficio del proceso actual y puede acceder a las estructuras de datos del espacio de direcciones del proceso. El código que maneja interrupciones, por otro lado, es asíncrono respecto de los procesos y no está relacionado con ningún proceso.

El objetivo de un módulo es extender la funcionalidad del kernel, es en otras palabras, un código modularizado se ejecuta en espacio del kernel. Normalmente un manejador realiza las dos tareas explicadas anteriormente: algunas funciones del módulo se ejecutan como parte de una llamada al sistema, y otras están a cargo del manejador de interrupciones.

Cómo el resto del kernel, los módulos y manejadores de dispositivos deben ser reentrantes, es decir, pueden ser ejecutados en más de un contexto a la vez. Las estructuras de datos deben ser diseñadas cuidadosamente para mantener separadas múltiples hebras de ejecución y el código debe cuidar el acceso a datos compartidos para evitar la corrupción de los datos.

Estructura de un módulo

A continuación se presenta la estructura principal de cada módulo del kernel

C

  #define MODULE
  #define __KERNEL__
  #include <linux/module.h>
  
  int init_module(void)
  {
  return 0;
  }
  
  void cleanup_module(void)
  {
  }
  
  MODULE_LICENSE("GPL");
  MODULE_AUTHOR(“nombre del autor <autor@correo.org>”);
  MODULE_DESCRIPTION(“Descripción del modulo”);

Estructura del código fuente del Kernel de Linux

Diagrama de la versión 2.4.0 del núcleo.

El código fuente del Kernel de Linux se estructura en varios directorios:

  • arch: Que contiene archivos referentes a arquitecturas específicas.
  • block: Contiene la implementación de algoritmos de planificación de E/S necesarios para el uso de dispositivos de almacenamiento por bloques
  • crypto: Contiene la implementación de operaciones de cifrado y la API criptológica
  • Documentation: Este directorio contiene la descripción de muchos subsistemas así como información valiosa sobre el funcionamiento del Kernel
  • drivers: Este directorio contiene multitud de subdirectorios con los controladores de numerosos dispositivos separados por clases de dispositivo.
  • fs: Contiene la implementación de los diferentes sistemas de archivos.
  • include: Los ficheros de cabecera del Kernel residen en este subdirectorio.
  • init: Contiene código de alto nivel de inicio y arranque
  • ipc: Contiene el código de soporte para la Comunicación entre Procesos (IPC)
  • Kernel: Las porciones del Kernel independientes de la arquitectura se encuentran en este directorio
  • lib: Contiene el código que implementa rutinas de librería
  • mm: En este directorio se encuentra la implementación de los mecanismos de gestión de memoria
  • net: Contiene la implementación de los protocolos de red.
  • scripts: Scripts usados durante la construcción del Kernel
  • sound: El subsistema de audio de Linux se encuentra en este subdirectorio
  • usr: Contiene la implementación del sistema initramfs

Cómo crear nuestros módulos

1. Instalar y preparar las cabeceras del kernel.

Debian y Ubuntu ambos proporcionan module-assistant, un conveniente paquete que contiene todo lo que necesitas para escribir tu propio LKM (Linux Kernel Module).

2. Crear el Código fuente y el Makefile para tu Módulo.

Ver Estructura del código fuente del Kernel de Linux

3. Compilar el Código.

Para compilar el código, ve a el directorio de tu proyecto (el que contiene archivo .c y el makefile) y luego tipear "make."

4. Inserte el módulo compilado en el Kernel en ejecución.

Para insertar su LKM en el kernel en ejecución, utilice insmod.

5. Retire el módulo cuando haya terminado.

Use rmmod para eliminar el LKM. Y para asegurarse de que el módulo ha sido retirado (revisar el syslog).

Con todos los pasos anteriores es posible crear y compilar Módulos.

Compilando el Kernel

Antes de Compilar

Primeramente se debe mencionar que a lo largo de esta sección todos los directorios que se mencionen hacen referencia al directorio principal, /usr/src/sys. Como ya fueron nombrados, existen diferentes directorios que representan diferentes partes del kernel, pero para la compilación de este, los más importantes son arch y conf.

Asumiremos en este caso, que se utilizará una arquitectura i386. Primeramente debemos dirigirnos al directorio arch/conf y copar el fichero de configuración GENERIC; como tradición el nombre del kernel es escrito en letras mayúsculas.

Una vez hecho esto procedemos a editar el fichero de configuración MIKERNEL. Es recomendable cambiar los comentarios al comienzo del fichero, por comentarios que expliquen los cambios hechos en el fichero. Se recomienda también, antes de actualizar el sistema, verificar el fichero /usr/src/UPDATING para así asegurarse de contar con la última versión de las fuentes.

Una vez hechas las modificaciones pertinentes al kernel, se puede proceder a compilar el nuevo núcleo.

Compilación del kernel

Vaya al directorio /usr/src:

  # cd /usr/src

Compile el kernel:

  # make buildkernel KERNCONF=MIKERNEL

Instale el nuevo kernel:

  # make installkernel KERNCONF=MIKERNEL

En Linux, por defecto, al compilar un kernel personalizado, se recompilarán todos los módulos de éste. Por eso, si se desea compilar solo los módulos modificados o personalizados se debe editar kernel modules will be rebuilt as well. /etc/make.conf antes de compilar.

Una vez realizada la compilacion, el kernel nuevo se copiará al directorio raíz con el nombre de /kernel y el antiguo, tomará el nombre de /kernel.old.

Carga y descarga de módulos

En Linux existen los siguientes comandos para trabajar con módulos:

Insmod

Permite cargar el módulo indicado que se busca en un subdirectorio de /lib/modules//<version>.

Rmmod

Se usa para quitar un módulo del kernel. Para quitarlo con éxito deben cumplirse dos condiciones:

  • Ningún proceso debe estar usando el módulo
  • Ningún otro módulo debe estar usándolo tampoco

Depmod

Este comando genera en el directorio /lib/modules/<version> el archivo modules.dep que registra la dependencia de los módulos entre sí.

Modprobe

Carga o descarga de un módulo considerando las dependencias con otros.

Lsmod

Este comando nos permite visualizar el estado actual de los módulos en el kernel y sus dependencias de Linux

Modinfo

Muestra información sobre un módulo.

Ejemplo

"Hello World" Loadable Kernel Module

Un módulo del kernel cargable (LKM) permite la modificación o ampliación de kernel de un sistema operativo tipo Unix, sin necesidad de recompilar o reiniciar la máquina. La funcionalidad LKM ha estado disponible para los usuarios de Linux desde 2,6, y una funcionalidad similar está disponible en BSD, OSX y más variantes de Unix.

Mostraremos en éste ejemplo el proceso de escribir y compilar insertando un "Hello World" LKM en el kernel de ejecución. Los pasos siguientes se realizaron en un equipo con Ubuntu 11.10, pero el mismo proceso debería funcionar (con modificaciones menores) en cualquier sistema Linux.


Instalar y preparar las cabeceras del kernel

$ sudo -i
# apt-get install module-assistant
# m-a prepare

Código Fuente para su LKM

// Definiendo __KERNEL__ and MODULE nos permite acceder a nivel de kernel.
#undef __KERNEL__
#define __KERNEL__
#undef MODULE
#define MODULE
// Linux Kernel/LKM headers: module.h es necesaria para todos los modulos y kernel.h es necesario   
para KERN_INFO.
#include <linux/module.h>    // Incluido para todos los modulos de kernel
#include <linux/kernel.h>    // Incluido para KERN_INFO
#include <linux/init.h>        // Incluido para __init y __exit macros
 static int __init hello_init(void)
 {
   printk(KERN_INFO "Hello world!\n");
   return 0;    // Non-zero return significa que el modulo no pudo ser cargado.
 }
 static void __exit hello_cleanup(void)
 {
   printk(KERN_INFO "Cleaning up module.\n");
 }
 module_init(hello_init);
 module_exit(hello_cleanup);

Makefile

obj-m := hello.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean

Compilar el código

$ make
make -C /lib/modules/3.0.0-17-generic/build M=/var/www/lkm modules
make[1]: Entering directory `/usr/src/linux-headers-3.0.0-17-generic'
CC [M] /var/www/lkm/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /var/www/lkm/hello.mod.o
LD [M] /var/www/lkm/hello.ko

Inserte el módulo compilado en el Kernel en ejecución.

$ sudo insmod hello.ko

Verificar syslog

$ tail /var/log/syslog
<snip>
Apr 20 16:27:39 laptop kernel: [19486.347191] Hello world!

Retire el módulo cuando haya terminado.

$ sudo rmmod hello

Verificar syslog Nuevamente

$ tail /var/log/syslog
<snip>
Apr 20 16:29:23 laptop kernel: [19486.347191] Cleaning up module.

Enlaces externos

Herramientas personales
Espacios de nombres
Variantes
Acciones
Navegación
Herramientas