sábado, 30 de junio de 2012

Recuperar GRUB después de instalar Windows

Es sumamente común: tenemos una PC con varios sistemas operativos, utilizamos GRUB como gestor de arranque; todo bien hasta que (re)instalamos algún Windows y GRUB desaparece.

Existen diversas soluciones al problema, unas más complicadas que otras, algunas inviables por algun motivo particular. En mi caso, para que Ubuntu pueda utilizar la tarjeta Wi-Fi, debo utilizar ndiswrapper con un  controlador para Windows, por lo que utilizando un Live-CD de entrada no tengo Internet. Además estoy lejos del AP, como para conectarme por cable. En fin, opciones que requieran el uso de Internet no me sirven.

A continuación pongo los pasos que me sirvieron en este caso, repito, en el que sólo dispongo de un Live-CD de Ubuntu, sin conexión a Internet.

0. Arrancar el Live-CD

1. Determinar la partición que tiene Ubuntu.
sudo fdisk -l

2. Montar la partición.
sudo mount /dev/sdXY /mnt

En donde X corresponde a la letra del disco duro y Y a la partición, que determinamos en el paso anterior. En mi caso X=a, Y=5, es decir, que mi comando fue sudo mount /dev/sda5 /mnt

3. Instalar GRUB
sudo grub-install --root-directory=/mnt /dev/sdX

En donde X corresponde a la letra del disco duro (a en mi caso)

4. Reiniciar la pc. Si todo salió bien, no debería aparecer ningún error y arrancar GRUB.

5. Actualizar GRUB.
sudo update-grub2

Y eso es todo. Espero que haya sido de utilidad

miércoles, 16 de mayo de 2012

Stress para sobrecargar un sistema Linux

Stress (de Amos Waterland) es una sencilla herramienta que nos permite estresar o sobrecargar sistemas Unix/Linux en 4 aspectos: CPU, Memoria, uso de disco y Entrada/Salida.


Instalación

La instalación es muy sencilla. Es cuestión de descargar y descomprimir el paquete, luego navegar hasta el directorio y ejecutar:
./configure
make
sudo make install


Si se cuenta con Debian/Ubuntu, es inclusive más sencillo, ya que se encuentra en los repositorios, así que en este caso sólo sería cuestión de hacer un
sudo aptitude install stress



Uso

Su uso también es muy simple. Basta con indicarle algunos de los siguientes parámetros:
  • -c, --cpu N: genera N workers realizando operaciones de tipo sqrt()
  • -i, --io N: genera N workers realizando operaciones de tipo sync()
  • -m, --vm N: genera N workers realizando operaciones malloc()/free()
  • -d, --hdd N: genera N workers realizando operaciones write()/unlink()
Adicionalmente también podemos especificar si queremos que opere en modo verbose (-v, --verbose) o en modo quiet (-q, --quiet) y especificar algunas otras opciones que por supuesto podemos revisar en la documentación o en la ayuda.

Un ejemplo de uso puede ser el siguiente:
stress --cpu 2 --io 1 --vm 1 --timeout 10s --verbose


Aquí estaríamos especificando que cargaremos al sistema con: dos procesos relacionados con el procesador, un proceso relacionado con operaciones de entrada/salida y un proceso de asignación de memoria, que todo esto durará 10 segundos y que muestre salidas por cónsola.

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

martes, 31 de enero de 2012

Estructura de un Módulo de balanceo para LVS

De momento, en el LVS existen 10 algoritmos de balanceo de carga, de los cuales ya comenté brevemente. Voy a volver a listarlos, agregando el archivo en el cual se encuentran definidos:
  • Planificación por Round-Robin (Round-Robin Scheduling). Definido en: ip_vs_rr.c
  • Planificación por Round-Robin ponderado (Weighted Round-Robin  Scheduling). Definido en: ip_vs_wrr.c
  • Planificación por menor número de conexiones (Least-Connection Scheduling). Definido en: ip_vs_lc.c
  • Planificación por menor número de conexiones ponderado (Weighted Least-Connection Scheduling). Definido en: ip_vs_wlc.c
  • Planificación por menor número de conexiones local (Locality-Based Least-Connection Scheduling). Definido en: ip_vs_lblc.c
  • Planificación por menor número de conexiones local con réplicas (Locality-Based Least-Connection with Replication Scheduling). Definido en: ip_vs_lblcr.c
  • Planificación por hashing de destino (Destination Hashing Scheduling). Definido en: ip_vs_dh.c
  • Planificación por hashing de origen (Source Hashing Scheduling). Definido en: ip_vs_sh.c
  • Planificación por menor retardo esperado (Shortest Expected Delay Scheduling). Definido en: ip_vs_sed.c
  • Planificación por servidores sin peticiones en espera (Never Queue Scheduling). Definido en: ip_vs_nq.c


Cualquiera de estos algoritmos se puede escoger para realizar el balanceo en un sistema basado en LVS, a través de ipvsadm, la herramienta de administración para cónsola del LVS. Probablemente los más comunes sean el algoritmo de Round-Robin o el de Round-Robin ponderado (aunque no necesariamente sean los mejores para todas las situaciones).

Si queremos realizar nuestro propio algoritmo de balanceo, o queremos modificar alguno de los ya existentes, debemos empezar por estudiar la estructura que tienen o deben tener.

Recordemos que estos programas están diseñados para ser utilizados como módulos del kernel de Linux, por ello, todos deben tener al menos dos funciones:

static int __init_ip_vs_XXX_init(void);
Es la función que se ejecuta cuando se carga el módulo. Entre otras cosas, acá se registran los módulos.

static int __exit_ip_vs_XXX_cleanup(void);
Es la función que se ejecuta cuando se descarga el módulo.

Sobra decir que en las definiciones anteriores, XXX se sustituye por el nombre del módulo (rr, wrr, wlc, etc). Adicionalmente, en todo algoritmo de balanceo, se define un struct de tipo ip_vs_scheduler, en el cual se asignan las propiedades (atributos y funciones) que definen el comportamiento del módulo. La estructura de estos, es la siguiente:

struct ip_vs_scheduler {
 struct list_head n_list;  /* d-linked list head */
 char   *name;  /* scheduler name */
 atomic_t  refcnt;  /* reference counter */
 struct module  *module; /* THIS_MODULE/NULL */

 /* scheduler initializing service */
 int (*init_service)(struct ip_vs_service *svc);
 /* scheduling service finish */
 int (*done_service)(struct ip_vs_service *svc);
 /* scheduler updating service */
 int (*update_service)(struct ip_vs_service *svc);

 /* selecting a server from the given service */
 struct ip_vs_dest* (*schedule)(struct ip_vs_service *svc,
           const struct sk_buff *skb);
};

Las funciones que definen el comportamiento del algoritmo son:
  • init_service: se ejecuta en el momento en que se asocia un servicio con el algoritmo de planificación. Es opcional, no todos los algoritmos requieren realizar alguna tarea especial al iniciar el servicio. Ejemplo de uso: el algoritmo de round-robin, utiliza la función init_service para inicializar la data que utilizará para el balanceo (sched_data), con la lista de servidores reales actuales (destinations).
  • update_service: se ejecuta al actualizar o eliminar alguno de los destinos. Es opcional. Ejemplo de uso: el algoritmo de round-robin utiliza esta función para actualizar la data que utiliza para el balanceo (sched_data), con la lista de servidores reales actuales (destinations).
  • done_service: se ejecuta al finalizar el servicio. Es opcional. Ejemplo de uso: el algoritmo de round-robin ponderado utiliza la función done_service para liberar la memoria ocupada por la data utilizada para el balanceo (sched_data).
  • schedule: esta es la función en donde se define el algoritmo de balanceo per se. Es la parte más importante del módulo, y por supuesto no es opcional. Devuelve un apuntador a un objeto de tipo ip_vs_dest, que indica cuál es el servidor que el algoritmo escogió para responder la petición realizada por el cliente.
Por supuesto, las funciones que definamos para estas 4 opciones debemos especificarlas en el mismo archivo, o incluir el archivo de cabecera que las contenga. Lo mismo si estas funciones a su vez utilizan otras funciones.

Y bien, estos son todos los elementos que necesitamos conocer para escribir un algoritmo de balanceo o modificar alguno de los existentes. Recomiendo comenzar estudiando los algoritmos de balanceo por menor número de conexiones y por round-robin, pues son los más cortos (no pasa de 120 líneas ninguno de los dos) y sencillos de entender.


Linux Virtual Server


El Servidor Virtual de Linux (Linux Virtual Server o LVS) es una solución de balanceo de carga de alto rendimiento y alta disponibilidad, basada en software, que funciona sobre un cluster de servidores bajo el sistema operativo Linux. El funcionamiento del LVS es transparente al usuario, y además provee tolerancia a fallos, alta escalabilidad y confiabilidad.

La arquitectura del LVS está formada por un conjunto de servidores que se encargan de atender las peticiones de los visitantes, denominados servidores reales, y uno o más servidores, que se encargan de distribuir la carga entre los primeros, denominados directores. La conexión entre el(los) nodo(s) director(es) y los servidores reales puede ser a través de una red de área local (LAN) o una red de área amplia (WAN). En la siguiente figura se muestra esta arquitectura.


El LVS se puede implementar mediante dos mecanismos, ambos programados como un conjunto de módulos para el núcleo de Linux:

  • IP Virtual Server (IPVS): implementa el balanceo de carga a nivel de capa 4 del modelo OSI (capa de transporte). El IPVS se distribuye incorporado al Kernel de Linux, a partir de la versión 2.6.10, liberada el 24 de diciembre de 2004.
  • Kernel TCP Virtual Server (KTCPVS): implementa el balanceo de carga a nivel de la capa 7 del modelo OSI (capa de aplicación). La ventaja de realizar el balanceo en esta capa, es que se puede realizar la asignación de tareas a los servidores reales en base al contexto de las peticiones, sin embargo, por esta misma razón, resulta menos escalable que el IPVS.


Algoritmos nativos de planificación de tareas del LVS

Para distribuir la carga entre los servidores reales, el LVS implementa los siguientes algoritmos de planificación:

  • Planificación por Round-Robin (Round-Robin Scheduling).
  • Planificación por Round-Robin ponderado (Weighted Round-Robin  Scheduling).
  • Planificación por menor número de conexiones (Least-Connection Scheduling).
  • Planificación por menor número de conexiones ponderado (Weighted Least-Connection Scheduling).
  • Planificación por menor número de conexiones local (Locality-Based Least-Connection Scheduling).
  • Planificación por menor número de conexiones local con réplicas (Locality-Based Least-Connection with Replication Scheduling).
  • Planificación por hashing de destino (Destination Hashing Scheduling).
  • Planificación por hashing de origen (Source Hashing Scheduling).
  • Planificación por menor retardo esperado (Shortest Expected Delay Scheduling).
  • Planificación por servidores sin peticiones en espera (Never Queue Scheduling).


Los algoritmos de planificación por Round-Robin y Round-Robin ponderado son los más sencillos. El primero se basa en un esquema de Round-Robin tradicional: se mantiene una lista con los servidores reales, y las peticiones son asignadas, a medida que van llegando, de acuerdo al orden de la lista. En el algoritmo de Round-Robin ponderado, a cada servidor real se le asigna un peso (un valor entero que representa su capacidad de procesamiento), y las tareas son asignadas por el(los) nodo(s) director(es) de acuerdo a ese peso.

Los algoritmos de planificación Least-Connection y Weighted Least-Connection se basan en el número de conexiones activas. En el primero se entrega la petición al servidor con el menor número de conexiones activas establecidas. El segundo algoritmo se basa en el primero, pero pondera el número de conexiones activas de cada servidor real, por un peso asignado estáticamente, que busca representar la capacidad de procesamiento del servidor.

El algoritmo Locality-Based Least-Connection funciona de manera similar al algoritmo Least-Connection, pero mantiene una tabla caché por dirección IP destino; si el servidor solicitado se encuentra en la tabla caché y además no se encuentra sobrecargado (donde la sobrecarga significa que el servidor tenga más conexiones activas que el valor de su peso), le asignará la petición, en caso contrario, utilizará el algoritmo Weighted Least-Connection para seleccionar el servidor. Locality-Based Least-Connection with Replication, es muy parecido al anterior, pero en vez de tener un servidor por destino, mantiene un conjunto de servidores. Si ninguno de los servidores del conjunto está disponible, utiliza el algoritmo Weighted Least-Connection para seleccionar el servidor.

Los algoritmos por Hashing (destino y origen) designan el servidor que responderá la petición entrante, mediante una búsqueda en una tabla hash estática, por su IP destino o IP origen, según sea el caso. Si el servidor real se encuentra caído o sobrecargado (donde la sobrecarga significa que su número de conexiones activas es al menos el doble que el valor de su peso), entonces el director no asignará la tarea a ningún servidor y el usuario no obtendrá una respuesta satisfactoria.

El algoritmo por menor retardo esperado, asigna la petición al servidor que en ese momento tenga la menor demora esperada, definida esta para el i-ésimo servidor como $(Ci+1)/Ui$, donde Ci es el número de conexiones activas y Ui es el peso de ese servidor. El algoritmo por servidores sin peticiones en espera, si encuentra a un servidor inactivo (sin conexiones activas), le asignará la carga, en caso contrario, responderá como el algoritmo por menor retardo esperado.


martes, 3 de enero de 2012

Instaladores multiplataforma con InstallJammer

Tengo un pequeño proyecto multiplataforma escrito en C++ al que necesitaba hacer un instalador para ambas plataformas. Mi proyecto consiste en un archivo ejecutable, un directorio de documentación en HTML y un par de archivos XML para idiomas (español e inglés).

Por recomendación llegué a InstallJammer. Lo he probado y ha ido muy bien. Aunque el tutorial que dejan en su página va perfectamente bien, dejo esta entrada a manera de tutorial personal :)

1) Descargar la última versión de IntallJammer. En este caso la palabra "última" no es usada como "más reciente", sino como "última", pues al parecer el proyecto ha sido recientemente abandonado. Pero bueno, yo he hecho la prueba en Windows 7 y funciona, así que va a servir un buen tiempo más, tal como está.

Yo al principio me bajé la que según es la última versión (1.2.15) en .tar.gz, pero no me funcionó en Windows 7, así que bajé un Snapshot de la versión 1.3 que si funcionó perfectamente.

2) Una vez descargado y descomprimido se puede correr directamente, no necesita instalación. Se mostrará una ventana como la siguiente.


3) Al hacer click en el botón New Project Wizard se abrirá el asistente para la creación de nuestro instalador. Acá nos comenzará a pedir datos sobre el proyecto de instalador: Nombre, Directorio del proyecto de instalación.


4) Al presionar Next, mostrará el siguiente paso donde pedirá detalles sobre la aplicación:



5) Luego más datos de la aplicación...




6) Luego InstallJammer nos preguntará el directorio en el cual están (o estarán) los archivos que va a utilizar para hacer el instalador. Es decir, este es el directorio donde debemos colocar nuestros ejecutables y todo archivo/directorio que queremos que quede en el directorio donde se instale la aplicación. Por ejemplo, como comenté al principio, yo necesito además del programa principal, 2 archivos XML en el mismo directorio que éste.




7) Luego nos preguntará el estilo del Wizard de instalación. Yo escogí Modern Wizard.


8) Luego especificaremos las plataformas para las cuales generaremos instaladores. Si nuestra aplicación no es multiplataforma, lógicamente sólo seleccionamos la plataforma para la cual está hecha.


9) Finalmente nos permitirá seleccionar algunas otras opciones, como crear shortcuts, ejecutar la aplicación después de instalación, etc.


10) Si todo salió bien, hemos terminado de configurar nuestro instalador.


11) Al presionar Finish en la ventana anterior, volveremos a la interfaz principal de la aplicación en la cual nos mostrará detalles del proyecto que acabamos de crear.


12) Al presionar Build Install podremos generar nuestro instalador para las plataformas seleccionadas. Lógicamente, antes de hacer esto, debemos asegurarnos de que todos los archivos necesarios se encuentran en el directorio que especificamos al principio.


Y eso es todo. Parecen muchos pasos, pero es por el nivel de detalle que puse. Luego de generado el instalador lo probé y todo funcionó bien, colocó los archivos donde debía, creó los shortcuts que especifiqué, el desinstalador, etc.