viernes, 16 de marzo de 2012

Programación de Módulos para el Kernel de Linux



La programación de módulos para el Kernel de Linux es bastante parecida a la programación tradicional de aplicaciones en lenguaje C. Sin embargo, hay una gran cantidad de elementos a tener en cuenta, como por ejemplo que sólo se pueden utilizar un conjunto reducido de funciones públicas definidas en el kernel (no podemos utilizar la STL, por ejemplo), o que no se pueden realizar operaciones en punto flotante. Recomiendo leer "Understanding the Linux Kernel" de Daniel Bovet y Marco Cesati para profundizar en este tema.

Un módulo debe tener como mínimo dos funciones:
  • int init_module(): es la función que se ejecuta cuando se carga el módulo
  • void cleanup_module(): es la función que se ejecuta cuando se carga el módulo

El más básico de los módulos sería el siguiente:

#include <linux/module.h>  /* utilizada por todos los modulos */

int init_module()
{
    return 0;
}

void cleanup_module()
{

}

Este es un módulo perfectamente válido, pero que no haría absolutamente nada.

El valor de retorno de la función init_module() será cuando la carga del módulo sea exitosa. En caso de que la carga falle, el valor de retorno será -1 con errno igual a alguno de los siguientes valores:
  • EPERM: El usuario no es root
  • ENOENT: No existe ningún módulo con ese nombre
  • EINVAL: Argumentos no válidos
  • EBUSY: La rutina de inicialización del módulo falló
  • EFAULT: Tname o imagen fuera del espacio de direcciones accesible por el programa


Para personalizar un poco el módulo, podemos dar nombres a las funciones de carga/descarga, utilizando las macros module_init() y module_exit():

#include <linux/module.h>  /* utilizada por todos los modulos */
#include <linux/init.h>    /* utilizada para poder usar macros */

int init_func()
{
    return 0;
}

void exit_func()
{
}

module_init(init_func);
modue_exit(exit_func);

Para compilar nuestro módulo, podemos utilizar un Makefile como el siguiente (suponiendo que hallamos nombrado al archivo holamundo.c:

obj-m += holamundo.o
all:
 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

Luego, sólo ejecutamos
make
Y se generará un archivo holamundo.ko, correspondiente al código objeto del módulo, listo para ser cargado en el kernel. Nótese que se utiliza la opción -C para especificar que se genere código objeto.

Para cargar el módulo utilizamos el comando insmod (como root):
insmod ./holamundo.ko

Podemos ver la lista de módulos cargados en el kernel en /proc/modules o mediante el comando lsmod.

Para descargar el módulo, se utiliza el comando rmmod:
rmmod holamundo


La función printk()

Recordemos que un módulo del kernel no está hecho para interactuar con el usuario. Si queremos comunicar o dejar registro de lo que se hace dentro del módulo, podemos utilizar la función printk(), cuyo funcionamiento es similar al de printf(). printk() además permite especificar el tipo de mensaje que vamos a imprimir, el cual será uno de los siguientes:
  • KERN_EMERG
  • KERN_ALERT
  • KERN_CRIT
  • KERN_ERR
  • KERN_WARNING
  • KERN_NOTICE
  • KERN_INFO
  • KERN_DEBUG

Con printk() podemos mejorar nuestro primer módulo, para que deje un registro (ya veremos donde) al cargarse y descargarse el módulo:

#include <linux/module.h>  /* utilizada por todos los modulos */
#include <linux/init.h>    /* utilizada para poder usar macros */
#include <linux/kernel.h>  /* utilizada por printk */

int init_func()
{
    printk(KERN_INFO "holamundo: cargando...\n");
    return 0;
}

void exit_func()
{
    printk(KERN_INFO "holamundo: descargando...\n");
}

module_init(init_func);
modue_exit(exit_func);

Los logs generados por printk() se almacenan en /var/log/messages y en /var/log/syslog. En Ubuntu a partir de la versión 11.04 /var/log/messages se eliminó, por lo que en estos sistemas podemos ver los logs en syslog o modificando /etc/rsyslog.d/50-default.conf para activar /var/log/messages.



jueves, 15 de marzo de 2012

Administración de un LVS con ipvsadm


ipvsadm es una herramienta de línea de comando utilizada para la instalación, administración del LVS. La versión más reciente de ipvsadm fue liberada en febrero de 2011.

El comando tiene dos formas básicas de ejecución:

ipvsadm COMMAND [protocol] service-address
        [scheduling-method] [persistence options]

ipvsadm command [protocol] service-address
        server-address [packet-forwarding-method]
        [weight options]

La primera forma es para ser ejecutada en el nodo director y la segunda en los servidores reales. Una descripción detallada de los comandos y parámetros que acepta ipvsadm puede ser consultada aquí.


Comandos y parámetros

Entre las comandos más importantes, tenemos:
  • -A: permite agregar un servicio virtual
  • -C: limpia la tabla del lvs. Es importante un ipvsadm -C antes de iniciar una nueva configuración.
  • -a: permite agregar un servidor real a un servicio virtual.
  • -d: permite eliminar un servidor real de un servicio virtual.
En cuanto a los parámetros aceptados, están:
  • -t service-address: se utiliza para indicar que se agregará un servicio TCP. service-address debe ser especificada de la forma host[:port], siendo host una dirección IP o un hostname y port el número de puerto o el nombre del servicio (por ejemplo http).
  • -u service-address: igual que el anterior, pero indica que el servicio será UDP.
  • -s scheduling-method: se utiliza para especificar el algoritmo de planificación a utilizar. Los valores válidos para scheduling-method son:
    • rr (Round-Robin)
    • wrr (Weighted Round-Robin)
    • lc (Least-Connection)
    • wlc (Weighted Least-Connection)
    • lblc (Locality-Based Least-Connection)
    • lblcr (Locality-Based Least-Connection with Replication)
    • dh (Destination Hashing)
    • sh (Source Hashing)
    • sed (Shortest Expected Delay)
    • nq (Never Queue)
  • -r server-address: se utiliza para especificar la dirección ip de un servidor real, y opcionalmente puede agregarse un número de puerto.
  • -w weight: Se utiliza para especificar el peso de un servidor real. El peso es un valor numérico que indica la capacidad de procesamiento del servidor, relativa al conjunto de servidores. El rango de valores válidos de weight es [0, 65536]. El valor por omisión es 1. Un servidor con peso 5 indica que tiene mayor capacidad de procesamiento que un servidor con peso 2, y por tanto recibirá mayor número de peticiones.

Ejemplo práctico

Supongamos una red formada por un nodo director y 2 servidores reales con las siguientes direcciones IP:

La dirección IP que utilizaremos para el servicio virtual será: 192.168.122.110. Podemos configurar el LVS en el nodo director de la siguiente manera:

#clear ipvsadm table
ipvsadm -C

#especify the virtual service with Round-Robin algorithm
ipvsadm -A -t 192.168.122.110:http -s rr

#forward telnet to realserver 1 with weight 1
ipvsadm -a -t 192.168.122.110:http -r 192.168.122.225 -w 1

#forward telnet to realserver 2 with weight 3
ipvsadm -a -t 192.168.122.110:http -r 192.168.122.38 -w 3