Normalmente cuando leemos un manual de tunning en Solaris, en el apartado de memoria siempre se menciona que observes la columna sr de la salida del vmstat, si esta es mayor que 0 indica que el sistema tiene escasez de memoria física. Esta columna (scan rate) indica el número de veces que se ha ejecutado el Page Scanner en el intervalo de tiempo que hayamos especificado al vmstat. Pero como funciona ? en este articulo lo trataremos con algo más de profundidad.
El Page Scanner se encarga de robar páginas de memoria dejándolas libres para ser usadas y enviando a la swap su contenido. Si estas páginas son requeridas más adelante se producirá un page fault y volverán a ser copiadas a la memoria física. Lógicamente esto afecta al rendimiento de las aplicaciones que las estaban usando por ello se trata de robar aquellas que hace más tiempo que no han sido accedidas.
El Page Scanner controla que páginas han sido accedidas mediante la lectura de dos bits en el hardware de la memoria . Estos indican si la página ha sido leída o modificada desde la última vez que se pusieron a cero. Entra en acción cuando la cantidad de memoria libre es menor que lostfree que se calcula en el arranque del sistema y suele ser 1/64 de la memoria física disponible. Esta comprobación se realiza mediante la función schedpaging(). Existe una entrada en la tabla callout que la ejecuta 4 veces por segundo. También puede ser llamada por el page_allocator.
Para explicar su funcionamiento pensaremos en el como si se tratara de un reloj, las dos manillas son dos threads del kernel y las horas las distintas páginas de la memoria. A diferencia de un reloj normal, las dos manillas del nuestro giran a la misma velocidad manteniendo entre ellas un espacio y nunca se llegan a superponer.
El thread A pone a 0 los bits del hardware que indican si la memoria a sido modificada/leída, el thread B comprueba si estos bits han sido modificados, en caso negativo envía a la swap el contenido de la página y la marca como libre. Ambos threads corresponden al pid 2.
La velocidad de "rotación" los threads es inversamente proporcional a la cantidad de memoria libre del sistema, y el espacio entre ellos es determinado por el parámetro handsoreadpages que se calcula de forma dinámica. El parámetro pageout_new_spread indica el número máximo de páginas que pueden ser escaneadas en un segundo o dicho de otra forma fija la velocidad máxima de escaneo, es calculado la primera vez que el scanner se ejecuta y se almacena en la variable fastscan.
Algunas peculiaridades:
Existe una limitación para que el scanner no robe páginas de librerías compartidas, el número de mapeos que debe existir para no ser robada es almacenado en el parámetro po_share, inicialmente tiene un valor de 8 pero puede decrementarse en caso de que el scanner no encuentre páginas para liberar.
También existe una limitación del tiempo de cpu que puede ser consumido por los threads, hay dos parámetros el min_precent_cpu y el max_percent_cpu, cuando la memoria libre es igual al parámetro lostfree su consumo es el valor mínimo, cuando no hay memoria libre su valor es el máximo. Por defecto son el 4% y el 80% de una cpu respectivamente.
Viendo el código fuente.
Podemos echar un vistazo al código fuente en la web de opensolaris.org, concretamente en el fichero vm_pageout.c, voy a copiar algunos extractos para ilustrar el articulo pero esta sobradamente documentado por lo que su lectura completa puede resultar interesante.
Cálculo del parámetro lostfree:252 /* 253 * Lotsfree is threshold where paging daemon turns on. 254 */ 255 if (init_lfree == 0 || init_lfree >= looppages) 256 lotsfree = MAX(looppages / 64, btop(512 * 1024)); 257 else 258 lotsfree = init_lfree;
Inicialización de fastscan:
416 if (init_mfscan == 0) {
417 if (pageout_new_spread != 0)
418 maxfastscan = pageout_new_spread;
419 else
420 maxfastscan = MAXHANDSPREADPAGES;
421 } else {
422 maxfastscan = init_mfscan;
423 }
424 if (init_fscan == 0)
425 fastscan =MIN(looppages / loopfraction,maxfastscan);
426 else
427 fastscan =init_fscan;
428 if (fastscan >looppages / loopfraction)
429 fastscan = looppages /loopfraction
La función pageout_scanner es el scanner propiamente dicho, copio solo el
comienzo debido a su longitud, visitad el enlace al fichero completo para
verla entera:
731 pageout_scann(void)
732 {
733 struct page *fronthand, *backhand;
734 uint_t count;
735 callb_cpr_t cprinfo;
736 pgcnt_t nscan_limit;
737 pgcnt_t pcount;
738
739 CALLB_CPR_INIT(&cprinfo,&pageout_mutex, callb_generic_cpr,"poscan");
740 mutex_enter(&pageout_mutex);
[...]
Rastreando la actividad del Page Scanner en nuestro sistema
Como decía al principio del artículo la columna sr de la salida del vmstat es el rastro más evidente que deja el scanner al ejecutarse y la mayoría de manuales de tunning recomiendan revisarla para verificar si hay escasez de memoria física.
Sin embargo podemos obtener algo más de información del scanner en nuestro sistema. El comando "kstat -n system_pages" nos da los valores de algunos de los parámetros antes comentados.
module: unix instance: 0
name: system_pages class: pages
availrmem 5851
crtime 0
desfree 239
desscan 3825
econtig 4274888704
fastscan 15302
freemem 230
kernelbase 3556769792
lotsfree 478
minfree 119
nalloc 18895816
nalloc_calls 12648
nfree 17047790
nfree_calls 7993
nscan 3825
pagesfree 230
pageslocked 24754
pagestotal 30605
physmem 30606
pp_kernel 26396
slowscan 100
snaptime 5882.612731441
Podemos ver el lotsfree, el fastscan, slowscan, ... otro dato interesante es el nscan que indica el número de páginas que ha comprobado el scanner desde la última vez que se activó.
Con dtrace también podemos hacer varias comprobaciones, por ejemplo podemos ver el número de veces que se llama a la función pageout_scann():
dtrace -n 'vminfo::pageout_scanner: { @num[probefunc] = count
(); }'
dtrace: description 'vminfo::pageout_scanner: ' matched 3 probes
pageout_scanner 60997
Palabras finales
Como hemos podido ver la actividad del Page Scanner no es nunca lineal sino que su rendimiento es inversamente proporcional a la escasez de memoria detectada, sin embargo existen una serie de parámetros que limitad su actividad de manera que no pueda convertirse el mismo en el cuello de botella del sistema ocupando demasiados recursos.