inicio mail me! sindicaci;ón

Archive for Linux

Reserva de memoria en Linux

Ya sabemos que en C las reservas de memoria se hacen mediante la llamada al sistema malloc() y similares, pero cómo opera realmente el kernel es algo que puede no estar del todo claro.

Mediante una prueba de concepto voy a ir mostrando cómo se comporta un kernel Linux estándar, sin configuraciones especiales. Me voy a basar en el concepto de Tamaño del Conjunto Residente (Resident Set Size, RSS).

El Conjunto Residente en el conjunto de páginas de nuestro proceso que se encuentran en memoria física, que será igual o inferior al conjunto de páginas de nuestro proceso (cada página o bien está en memoria física, o bien en el archivo de intercambio).
Hay que recordar que para que puede accederse a una información o ejecutarse código, debe estar en memoria física, y que los algoritmos de gestión de memoria del S.O. tienden, cuando necesitan localizar un nuevo hueco de memoria libre, a enviar las páginas menos usadas al espacio de intercambio.

Inicialmente tenemos un programa que arranca:

#include
#include
#include
#include

void mi_pause(const char *str);

int main(int arg, char **argv)
{
int pagesize=sysconf(_SC_PAGE_SIZE);
int num_pag=10000,i;
char *buffer;
time_t t0;
short d_t=30;
float d_p=0.1;

/*Sin reservar memoria*/
mi_pause("El RSS deberia ser minimo");

Hasta aquí, si vemos su RSS:

pablo@linux-rt:# ps ax -o rss,ucmd | grep mem
352 mem

Hasta este punto, este programa ha sido cargado en memoria por el cargador del sistema operativo, junto con las librerías requeridas si es que no estaban ya cargadas.

Así que el proceso tiene asignadas al menos una serie de páginas donde está el código del programa y otras páginas donde está la pila (el kernel Linux por defecto asigna una pila de 8K a los procesos, y en la arquitectura i386 las páginas son de 4K, luego dos páginas para la pila). Ahora mismo desconozco si tendrá más secciones, aparte de código y pila, pero todas estas páginas suman 352k, tal y como nos indica ps.

Se sigue ejecutando el código:

/*Tras reservar memoria*/
buffer=(char *)malloc(num_pag*pagesize);
if(!buffer) {
perror("Error en malloc()");
exit(EXIT_FAILURE);
}
mi_pause("El RSS deberia seguir siendo minimo");

Ahora reservamos memoria, 10000 páginas en el heap (malloc() reserva en el heap o montículo, que es una tercera sección de memoria). En i386 eso son 40000k. Aparentemente, cabe esperar que el RSS haya subido de 352k que teníamos antes a 40352k, aproximadamente:

pablo@linux-rt:# ps ax -o rss,ucmd | grep mem
388 mem

Pues no, tenemos tan sólo 36k más, 9 páginas. Y las otras 9991, ¿dónde andan?

El comportamiento por defecto del kernel Linux es proporcionar las páginas bajo demanda, es decir, que mientras no vayas a usar la página, ¿para qué molestarse en hacer que esté disponible? (esto se llama deferred page allocation, y pareceser que AIX también disponía de un sistema como éste)

Si tú solicitas –malloc()– la memoria y no has superado el límite que tiene establecido tu usuario, etc., el sistema te dice que sí, que vale, que te da la memoria, pero en realidad aún no ha ido a buscarte una página libre.

Cuando por fin accedes a esa página que es legítimamente tuya, es cuando el S.O. se molesta en recorrer toda la lista de páginas que tiene, encontrar una libre –si la hay, y si no, ya se sabe: elegir una candidata y mandarla al espacio de intercambio para dejar un hueco libre–, marcarla como tuya, y devolver el control a tu proceso.

O resumidamente: que hasta que no accedes a las páginas no son tuyas del todo, y por tanto no pertenecen a tu conjunto residente.

Ventajas:

  • Ahorro de tiempo ante procesos que reservan mucho y usan poco.
  • Ahorro de tiempo en el arranque de un proceso (que es donde se suele reservar memoria), al contrario que durante su ejecución (donde se usa esa memoria y hay que localizarla, si es que llega a usarse).
  • Permite aprovechar al máximo el sistema, a expensas de tener una menor estabilidad. En sistemas de misión crítica esto puede no ser deseable, y de hecho creo que hay una opcion del kernel para desactivar este comportamiento.

Voy a tratar de explicar este último punto con el siguiente escenario:
Sea un sistema con 64M de RAM (y nada más de memoria), si el proceso A reserva 50M (aunque sólo usa 20M de esas 50M), y a continuación el proceso B reserva 30M y usa enteros esos 30M.

En total ambos procesos tratan de reservar 80M, de las cuales la máquina no dispone, así que en principio cabe pensar que no se pueden ejecutar a la vez ambos procesos.

En cambio, con el enfoque que hemos visto, el kernel le localiza 20M al proceso A (aunque le informe de que le ha reservado 50M) y 30M al proceso B, dando un total de 50M localizadas (abarcables por la máquina).

Si ahora el proceso A que sólo usaba 20M y que está conviviendo con B, poco a poco empieza a aumentar su consumo de memoria hasta usar por entero esos 50M, ¿qué ocurre? Pues sencillamente que no hay sitio para los dos procesos (situación Out-of-Memory) y al menos uno debe morir.

¿Quién se encarga de hacer justicia? El OOM Killer ¿Bajo qué política? Eso es más complicado, y en el enlace anterior se muestra un fragmento de código que explica un poco los fundamentos, aunque hay quien discrepa con este sistema.

¿Qué ocurre si mientras A engorda, a B le da tiempo a acabar? Pues todos felices y contentos y hemos podido ejecutar dos procesos que de otra manera no podríamos.

Sigamos ejecutando:

/*Tras acceder a la memoria*/
for(i=0;i
buffer[i*pagesize]=0;
}
mi_pause("Ahora deberia ser maximo");

El código ahora se dedica a poner el primer byte de cada página reservada a 0. Ahora sí que accedemos a las páginas que habíamos solicitado con malloc().

El resultado es que ahora nuestro RSS engorda estrepitosamente:

pablo@linux-rt:# ps ax -o rss,ucmd | grep mem
40384 mem

Ahora sí que hemos hecho la reserva efectiva.

Sigamos ejecutando:

/*Tras acceder sólo al 10% de las páginas durante 30s. mientras otra aplicación accede a mucha memoria*/
printf("Ahora se esta accediendo al %f%% de las paginas reservadas\n",d_p);
printf("– Comprueba el RSS mientras se accede (%hds.) –\n",d_t);
printf("– Correr paralelamente un devorador de memoria ayudara –\n");
t0=time(NULL);
while(time(NULL)
for(i=0;i
buffer[i*pagesize]=0;
}
}

Nos dedicamos, durante 30 segundos, a acceder al primer 10% de las páginas que reservamos, es decir, las primeras 1000 páginas.

¿Cuál es el objetivo de esto? Que si ahora otros procesos requieren grandes cantidades de memoria, podamos ver cómo el S.O. nos quita páginas del conjunto residente y las vaya enviando al espacio de intercambio.

El S.O. va a quitar primero las menos utilizadas, que son ese 90% que no estamos accediendo. Si los otros procesos no requieren demasiada memoria, lo que nos quite probablemente no llegue al 90%.
Si requieren muchísima, puede superarlo, dando lugar a que constantemente haya que estar metiendo y sacando páginas del espacio de intercambio, ya que parte de las que se han metido sí se usan con frecuencia (hiperpaginación o thrashing).

Para comer memoria he hecho devorador.c, que se reserva (y accede durante 20 segundos) 200M de memoria (estoy ejecutando todo esto en una máquina virtual con 256M de RAM):

#include
#include
#include
#include

int main(int argc, char **argv)
{
size_t size=200*1024*1024, offs;
char *buffer;
unsigned short pagesize=sysconf(_SC_PAGE_SIZE);
time_t t0, d_t=20;

buffer=(char *)malloc(size);
if(!buffer){
perror("Error en malloc()");
exit(EXIT_FAILURE);
}

offs=0;
t0=time(NULL);
while(time(NULL)
buffer[offs]=0;
offs=(offs+pagesize)%size;
}

free(buffer);

exit(EXIT_SUCCESS);
}

Si paralelamente a la ejecución del código anterior, ejecutamos el devorador en otra terminal, podemos ver como el RSS decae:

pablo@linux-rt:# ps ax -o rss,ucmd | grep mem
23268 mem
pablo@linux-rt:# ps ax -o rss,ucmd | grep mem
17484 mem

No llega hasta el 10% de los 40000k que (más o menos) teníamos, pero se ve cómo la mitad de las páginas se han ido al espacio de intercambio.

/*Tras liberar memoria*/
free(buffer);
mi_pause("Ahora deberia volver a ser minimo");

exit(EXIT_SUCCESS);
}

void mi_pause(const char *str)
{
if(str)
printf("%s\n",str);

printf("– Comprueba el RSS y dale a intro –\n");
getchar();
}

Y ya para acabar liberamos la memoria reservada, visualizamos cómo disminuye el RSS:

pablo@linux-rt:# ps ax -o rss,ucmd | grep mem
188 mem

y finalizamos el programa.

Espero que con esto se hayan refrescado o dado a conocer algunos conceptos sobre gestión de memoria de los sistemas operativos, y más particularmente sobre el deferred page allocation de Linux, y sus ventajas e inconvenientes.

Ficheros: mem.c y devorador.c

Configuración de servicios en CentOS 5

Como a veces no me acuerdo, dejo por aquí una chuleta para saber cuáles son los comandos que más habitualmente uso para gestionar los demonios y servicios de CentOS 5:

Por un lado está el comando /sbin/service, que permite arrancar, parar, reiniciar o ver el estado de un servicio:

/sbin/service <servicio> start
/sbin/service <servicio> stop
/sbin/service <servicio> restart
/sbin/service <servicio> status

Este comando lo que hace es ejecutar el script contenido en /etc/init.d/<servicio> con el argumento pasado a service. Tal comando al menos deberá tener implementadas las opciones start, stop y status.

Otras opciones son ejecutar todos los scripts en orden alfabético con la opción de status:

/sbin/service ––full-status

O bien primero todos con la opción stop, y a continuación otra vez todos con start:

/sbin/service ––full-restart

Y por otro lado está /sbin/chkconfig, para configurar cuáles arrancan con qué runlevel:

/sbin/chkconfig ––list

Para listar todos los servicios de todos los runlevels

/sbin/chkconfig ––level 345 <servicio> on

Para activar el demonio <servicio> en el arranque de los runlevels 3, 4 y 5.

/sbin/chkconfig ––level 345 <servicio> off

Para desactivar el demonio <servicio> en el arranque de los runlevels 3, 4 y 5.

Fuentes:

Comparando utilidades de compresión

Hoy ha tocado colocar un poco el disco duro, y en estas que me da por mirar cuánto ocupa el directorio

.thunderbird/

donde guardo todo el correo que recibo (desde hace años, incluído spam). 1.8G suman todos los correos. Casi nada, teniendo en cuenta que es texto plano.

Buceando en ese directorio llego al fichero

Inbox

de una de mis cuentas de correo. Todo lo que almacena está en formato mbox, que es lo que habitualmente usan los lectores de correo (o MUAs, Mail User Agent). Los MTAs (Mail Transport Agent) suelen usar el formato Maildir ya que evita el cuello de botella que impone mbox ante la concurrencia.

El caso es que me da por mirar a ver qué tal comprimen los diferentes algoritmos de compresión que tengo en Linux, que son básicamente cuatro: bzip2, LZMA (7zip y versiones nuevas del tar), el tradicional Zip (herramientas zip y gzip) y RAR.

Me he centrado sólo en el ratio de compresión de cada programa, no me he preocupado por cuánto tardan en comprimir, ni cuánto se tarda en listar el contenido de un fichero, o en descomprimir un sólo fichero. De hecho es que no he diferenciado entre formatos que comprimen y archivan a la vez, y los que sólo comprimen (p.ej.

bzip2

). He comprimido un sólo fichero, y era un fichero de texto (en formato

mbox

).

Los resultados para un solo fichero

mbox

de 118M:

Programa Algoritmo Tamaño ¿Libre?
gzip -9 LZ77 modificado 65M
zip -v9 LZ77 modificado 65M
bzip2 Burrous-Wheeler/Huffman 63M
rar LZSS 56M No
7-zip LZMA 54M
tar –lzma LZMA 54M

Con ¿Libre? me refiero a si yo conozco implementaciones libres del algoritmo tanto para comprimir como para descomprimir.

El LZ77 modificado también se conoce como DEFLATE.

Se concluye lo que más o menos ya habíamos observado a base bajarnos discos:

LZMA

es el que más comprime (y es libre),

RAR

no se queda muy lejos (pero no es libre, aunque es bastante multiplataforma),

bzip2

mejora un poco lo del tradicional

Zip

, pero éstos dos ya van teniendo que jubilarse.

Las únicas ventajas que tienen

zip/gzip/bzip2

son a) que aparentemente tardan menos en comprimir (lógico) y b) que están mucho más difundidos: es difícil encontrar un sistema que no tenga alguno de los tres.

LZMA

en cambio, es más difícil de encontrar.

Update (2009-02-17): Hoy en barrapunto.com han publicado Comparación entre diferentes algoritmos de compresión en Linux . Una noticia que enlaza a tres sitios donde han hecho comparativas similares, pero mucho más extensas:

Soporte para webcams en Linux (2)

Michel Xhaard hace algún tiempo se hizo famoso por haber escrito los drivers para Linux para más de 200 webcams.

Michel, que tiene algo más de 60 años, es un médico francés que está especializado en procesar imágenes de ultrasonidos. En cuestión de cuatro años fue capaz de desarrollar drivers para múltiples webcams, inicialmente empezó para el chipset Sunplus SPCA y ahora cubre muchos más, principalmente a base de reutilizar el código de unos drivers en otros.

Las cámaras con los chipsets soportados por la familia SPCA son de múltiples fabricantes y modelos.

El caso de Michel no es el único caso de un médico que tiene como pasatiempos la informática: Con Kolivas es anestesista y ha hecho importantes desarrollos para el kernel de Linux.

Soporte para webcams en Linux (1)

El soporte para webcams en Linux es una de las materias que aún quedan pendientes para que Linux esté tan cerca del usuario doméstico como lo están Windows o MacOS X.

Philips destaca por fabricar buenas cámaras con buenos sensores, siendo uno de los proveedores de Logitech — así que al comprar Logitech realmente gran parte de las piezas importantes son Philips en muchos modelos (que no en todos). Hasta la fecha en Linux gran parte de las cámaras Philips estaban soportadas por el driver PWC. Este driver tiene dos partes, el PWC propiamente dicho, que es código abierto y está dentro del kernel, y el PWCX que es una parte binaria que opera fuera del kernel (obviamente, por temas de licencia) e interactúa con el PWC a través de un hook.

El código del PWCX es algo que Philips quiere mantener escondido, por lo que al desarrollador inicial del PWC (Nemosoft Unv) Philips le obligó a firmar un NDA (Non-Disclosure Agreement) con caducidad a los tres años –ya vencidos, aunque aún no ha desvelado nada. La inclusión de esta parte binaria es lo que hizo que el equipo del kernel Linux se empeñara en no incluir el PWC como parte del kernel, por lo que Nemosoft Unv al final decidió dejar el desarrollo. En definitiva, este código lo que permitía era hacer una descompresión del flujo de datos recibido para así poder trabajar con resoluciones altas (640×480).

Actualmente Luc Saillard ha retomado el desarrollo principalmente para el kernel 2.6, y permite hacer esa descompresión para gran resolución sin necesidad de parte binaria.

Next entries »