Últimamente estuve experimentando algunos problemas en mi workstation con FreeBSD 10.1. En escenarios de mucha carga ocurría que alguna aplicación se cerraba inesperadamente (notablemente máquinas virtuales de VirtualBox). Se me ocurrió que, a causa de un elevado consumo de algún recurso, el kernel de FreeBSD estuviera matando al proceso en cuestión por seguridad o porque no tuviese otra alternativa. Pero como aún no conozco bien el funcionamiento del kernel de FreeBSD, y no he tenido tiempo para debuggear errores en mi workstation, lo dejé de lado.

Recién hoy descubrí la causa del problema. Un lector me preguntó, en los comentarios de un artículo de este blog, cómo determinar qué procesos están utilizando la swap en FreeBSD, y en ese momento descubrí que mi workstation no tiene swap. ¡Ouch! Fue entonces cuando recordé que durante la instalación de FreeBSD no definí ninguna swap. Muy desprolijo de mi parte, lo reconozco. Pero fue por "ahorrar disco".

Lo que sucede en un sistema sin swap (memoria de intercambio) es que, cuando la memoria principal (RAM) se agota (y en mi workstation sólo tengo 4 GB), al no tener un espacio de almacenamiento secundario el kernel no tiene otra alternativa que matar procesos. Porque, lógicamente, no todos los procesos caben en memoria principal, y al momento de intentar lanzar un nuevo proceso, uno o más de los que se están ejecutando actualmente deben ser "sacrificados".

¿Cómo descubrí que no tenía swap? Simple, al ejecutar el comando top, la salida era la siguiente:

last pid:  1536;  load averages:  0.29,  0.26,  0.21     up 0+02:20:09  09:56:21
93 processes:  1 running, 92 sleeping
CPU:  8.5% user,  0.0% nice,  1.5% system,  0.1% interrupt, 89.9% idle
Mem: 677M Active, 1519M Inact, 632M Wired, 964K Cache, 429M Buf, 1032M Free
Swap:

  PID USERNAME    THR PRI NICE   SIZE    RES STATE   C   TIME    WCPU COMMAND
 1229 emi          66  20    0  1342M   902M uwait   0  11:36  13.92% firefox
  933 root          1  21    0   170M 58944K select  0   1:33   1.46% Xorg
 1360 emi           4  22    0   512M 92236K select  1   0:36   0.98% kdeinit4

Dado que no tenía la intención de redimensionar sistemas de archivos y particiones para hacer espacio para una swap, se me ocurrió utilizar un archivo como memoria de intercambio, al mejor estilo Windows. La desventaja de este esquema es que el rendimiento de la swap es más lento por dos razones. Primero, porque se debe atravesar una capa de abstracción adicional para acceder a los datos en swap: el sistema de archivos huésped del archivo que funciona como swap (por ejemplo es necesario acceder e interpretar inodos, lo que involucra verificar permisos, indexar bloques, actualizar metadatos, etc.) Segundo, porque la swap queda presumiblemente fragmentada: los bloques en disco pertenecientes al archivo pueden estar desparramados en diferentes áreas (en cambio cuando se trata de una partición, los bloques de disco de la swap ocupan un espacio contiguo, lo cual acelera los accesos.)

El primer paso consiste en crear el archivo que será utilizado como swap. En mi caso decidí crear una swap de 2 GB. El nombre de archivo que utilicé fue /swap ya que tenía mucho espacio disponible en el sistema de archivos raíz:

root@hal9000:~ # dd if=/dev/zero of=/swap bs=4096 count=524288
524288+0 records in
524288+0 records out
2147483648 bytes transferred in 16.804642 secs (127791098 bytes/sec)
root@hal9000:~ # ll -h /swap
-rw-r--r--  1 root  wheel   2.0G Apr 15 10:04 /swap

En FreeBSD, la herramienta swapon permite utilizar cualquier dispositivo por bloques como área de intercambio. Entonces es necesario convertir de alguna forma al archivo que deseamos utilizar en un dispositivo de acceso por bloques. Personalmente se me ocurrió utilizar un ramdisk, es decir un disco o dispositivo en RAM, implementado utilizando el driver "md". Pero, como no tiene mucho sentido definir una swap en RAM, lo que hice es definir un ramdisk soportado por un archivo. Es decir, en lugar de estar alojado en RAM, el ramdisk queda alojado en un archivo. Se trata de un ramdisk de tipo "vnode".

root@hal9000:~ # mdconfig -a -t vnode -f /swap -s 2g
md0
root@hal9000:~ # mdconfig -lv
md0     vnode    2048M  /swap
root@hal9000:~ # ll /dev/md0 
crw-r-----  1 root  operator  0x9e Apr 15 10:09 /dev/md0

Con el ramdisk creado bajo el nombre de dispositivo /dev/md0, ya es posible utilizarlo como swap:

root@hal9000:~ # swapon /dev/md0
root@hal9000:~ # swapctl -l
Device:       1024-blocks     Used:
/dev/md0        2097152         0

Ahora, al abrir top, se observa que el sistema dispone de 2 GB de memoria swap:

last pid:  1722;  load averages:  0.52,  0.37,  0.25     up 0+02:37:11  10:13:23
98 processes:  1 running, 97 sleeping
CPU:  3.8% user,  0.0% nice,  0.7% system,  0.0% interrupt, 95.5% idle
Mem: 1408M Active, 1723M Inact, 682M Wired, 21M Cache, 419M Buf, 28M Free
Swap: 2048M Total, 2048M Free

  PID USERNAME    THR PRI NICE   SIZE    RES STATE   C   TIME    WCPU COMMAND
 1229 emi          66  20    0  1334M   896M uwait   0  11:54  13.62% firefox
  933 root          1  21    0   170M 59024K select  3   1:35   1.66% Xorg
 1360 emi           4  22    0   512M 92236K select  1   0:38   0.78% kdeinit4

Para que esta configuración sea persistente, es necesario realizar algunos cambios en la configuración del sistema. Primero es necesario habilitar y configurar el servicio mdconfig, para que el ramdisk sea creado automáticamente al iniciar el sistema. Editar el archivo de configuración del sistema /etc/rc.conf:

root@hal9000:~ # nano /etc/rc.conf

Y agregar las siguientes líneas:

mdconfig_enable="YES"
mdconfig_md0="-a -t vnode -f /swap"

Luego es necesario que la swap sea agregada automáticamente al iniciar el sistema. Para ello, agregar las siguientes líneas dentro del script de inicio de demonios y servicios locales /etc/rc.local. Es necesario crearlo si no existe, incluyendo el hashbang por supuesto. Por ejemplo:

#!/bin/sh

/sbin/swapon /dev/md0

¿Por qué no definir la swap directamente dentro del archivo de información estática de sistemas de archivos /etc/fstab? Porque el servicio mdconfig requiere que la swap sea habilitada antes de poder ser iniciado. Esto se debe a que el driver md ofrece la posibilidad de definir discos en swap, por ello requiere que haya una swap antes. Lo que ocurre, por supuesto, es que los sistemas de archivos definidos dentro del archivo /etc/fstab se montan antes de comenzar a iniciar los demonios y servicios. Lo mismo ocurre con la activación de la swap, si ésta se define dentro de /etc/fstab.

Pero lo que yo quiero justamente es utilizar un ramdisk como swap. En este caso, debo activar la swap luego de que se haya levantado el servicio mdconfig. Por ello es que incluyo tal comando dentro del script de inicio de demonios locales y tareas de startup /etc/rc.local, ya que es el último script en ejecutarse durante el inicio en un sistema con init System V.

Al reiniciar el sistema, la swap es activada automáticamente utilizando el archivo /swap. De yapa se solucionaron mis problemas con VirtualBox. Era claro que, al iniciar una máquina virtual esta consume presumiblemente mucha RAM, y al quedarse sin memoria el host, el kernel terminaba "sacrificando" a la misma.

Espero que les haya gustado. Para más información ejecutar:

man 8 swapon
man 2 swapon
man 1 dd
man 4 md
man 8 mdconfig


Tal vez pueda interesarte


Compartí este artículo