Solaris: Buceando en el Kernel con mdb (III)

Kernel, OpenSolaris, mdb Dejar un comentario

En este artículo veremos cómo MDB nos puede ayudar a conocer el uso que está haciendo el kernel de la memoria del sistema, cuantas páginas de memoria está ocupando un proceso, el tamaño de los segmentos de una espacio de direcciones, las páginas de memoria asociadas a un vnodes, etc.


Antes de continuar, debemos aclarar algunos conceptos y nomenclaturas propias de Solaris, para poder interpretar toda la información que vamos a obtener con mdb.

Páginas

Como ya sabemos, todos los procesos, disponen de su propio espacio de direcciones de memoria, los espacios de direcciones están formados por segmentos de memoria, un espacio de direcciones tiene al menos 4 segmentos de memoria, el de datos, el de texto, el de heap y el de stack. Cada segmento de memoria está formado por un número determinado de páginas de memoria. La página la podemos definir como un bloque de información que puede ser almacenada en un marco de página en memoria física o en un almacenamiento secundario.

NOTA – Marco de página, se denomina así al bloque de memoria física en el que se almacena una página.

Las páginas tienen un tamaño fijo, suelen ser de 8K, con el comando pagesize podemos ver qué tamaños de página podemos utilizar en nuestro sistema y cual es el tamaño por defecto. Utilizando el ! podemos ejecutar comandos del sistema operativo dentro de mdb.

(root@huelva)# mdb -k
Loading modules: [ unix krtld genunix ip usba ipc random nfs ptm ]
>
> ! pagesize
8192
>
> ! pagesize -a
8192
65536
524288
4194304
>

El tamaño de página por defecto es 8K, aunque podemos disponer de páginas de 8K,64K, 512K y 4MB, el tamaño de página se define en el momento del arranque, por lo que no se puede cambiar en caliente, más adelante veremos una característica disponible desde Solaris 9, que permite que un proceso utilice páginas con distinto tamaño. La relación entre el tamaño de página y el rendimiento del sistema depende de la gestión que se realice de las caches, aunque entraremos más en detalle en otro artículo, podemos adelantar que algunas aplicaciones trabajan mejor con tamaños de página grandes, ya que los accesos a memoria provocarán menos fallos en las caches que si utilizasen páginas de menor tamaño. Siento dejar este tema tan interesante aquí, pero prometo que dentro de poco veremos como afecta el tamaño de las páginas en el rendimiento de las distintas caches del sistema.

En Solaris, si no realizamos ninguna modificación en el sistema, solo existen dos casos en los que las páginas son de tamaño distinto a 8K, uno son las páginas que se utilizan para mapear los segmentos de datos y texto del propio kernel, el otro caso es el de las páginas asignadas a los segmentos de memoria compartida.

Todas las páginas de memoria se representan mediante la estructura de datos page_t, que podemos encontrar en el fichero de cabecera /usr/include/vm/page.h, el kernel organiza las páginas físicas en varias listas, dependiendo de el uso que se les esté dando:

-Hash list, existe un array llamado page_hash el cual es utilizado para realizar búsquedas rápidas sobre las distintas lista.

-Free list, lista de todas las páginas que no están siendo utilizadas por ningún proceso, una página se denomina libre cuando tiene el bit p_free activo en el campo p_state de la estructura page_t.

-Cache list, esta lista la forma las páginas que podemos considerar libres, pero que aun están referenciadas por un vnode y por lo tanto aún contienen información, aunque el sistema las utilizará como páginas libre cuando las necesite, esta operación se conoce como reclamación de página.

-Vnode list, es la lista de todas las páginas que están asociadas a un vnode.

IMPORTANTE!! – En Solaris, la memoria libre del sistema la forman las página de la Free list y las páginas de la Cache list

Páginas anónimas

Todas las páginas de memoria están asociadas a un fichero del sistema de archivos mediante la tupla vnode:desplazamiento, la cual define el fichero al que hace referencia la página y el desplazamiento dentro del fichero, pero existe un tipo de página, las llamadas páginas anónimas, las cuales no tienen asociado un fichero sino que están asociadas al espacio de swap directamente. Las páginas anónimas corresponde a la memoria que se solicita de forma dinámica durante la vida de un proceso, por ejemplo cuando utilizamos la llamada de sistema malloc().

Page Cache

Podemos definir el Page Cache como las páginas que el kernel cachea en memoria cuando se solicita una página del sistema ficheros, es decir, cuando un proceso abre un fichero para leer datos de él, el kernel cachea dichos datos en páginas de memoria, de esta forma si se vuelve a solicitar datos del fichero que correspondan con páginas que se encuentran cacheadas en memoria, el kernel no necesita acceder al dispositivo físico, con la consiguiente perdida de rendimiento, sino que trae el dato de la página cacheada en memoria, disminuyendo considerablemente los tiempos de accesos.

Solaris procurará utilizar toda la memoria disponible en el sistema para el Page Cache liberando dicha memoria cuando la cantidad de memoria caiga por debajo de una serie de umbrales.

NOTA – El espacio ocupado por el contenido del directorio /tmp/ utiliza el Page Cache, por lo que tenemos que tener cuidado en no llenar dicho directorio con ficheros basura, ya que estos ficheros están ocupando memoria del sistema.

El comando de mdb ::memstat nos devuelve un sencillo informe sobre el uso que se está haciendo de la memoria del sistema.

(root@huelva)# mdb -k
Loading modules: [ unix krtld genunix ip usba ipc random nfs ptm ]
>
> ::memstat
Page Summary                Pages                MB  %Tot
------------     ----------------  ----------------  ----
Kernel                      32775               256   13%
Anon                        12230                95    5%
Exec and libs               12196                95    5%
Page cache                 153886              1202   62%
Free (cachelist)            31933               249   13%
Free (freelist)              6483                50    3%

Total                      249503              1949
>

De la información que hemos obtenido, podemos ver la cantidad de memoria que está utilizando el kernel, la cantidad de memoria asignada a páginas anónimas, la cantidad de memoria asignada a las páginas de los ficheros ejecutables y librerías, la memoria que está siendo utilizada por Page Cache y la memoria que conforman las dos listas de páginas libres.

Podemos hacer la prueba de modificar el tamaño el directorio /tmp, bien creando ficheros de gran tamaño, bien borrando ficheros que hayamos dejado y estén ocupando espacio, en el momento en el que estoy escribiendo este artículo, el /tmp de mi máquina está a un 32% de capacidad, como podemos ver en la siguiente salida.


(root@huelva)# df -k
Filesystem            kbytes    used   avail capacity  Mounted on
/dev/vx/dsk/bootdg/rootvol
                     10327372 4791380 5432719    47%    /
/proc                      0       0       0     0%    /proc
mnttab                     0       0       0     0%    /etc/mnttab
fd                         0       0       0     0%    /dev/fd
swap                 2292600     112 2292488     1%    /var/run
dmpfs                2292488       0 2292488     0%    /dev/vx/dmp
dmpfs                2292488       0 2292488     0%    /dev/vx/rdmp
swap                 3367080 1074592 2292488    32%    /tmp
...

Voy a borrar algunos de los ficheros que tengo en /tmp, una vez que los he borrado, vamos a comprobar con mdb cual es la distribución de memoria.

(root@huelva)# mdb -k
Loading modules: [ unix krtld genunix ip usba ipc random nfs ptm ]
>
> ::memstat
Page Summary                Pages                MB  %Tot
------------     ----------------  ----------------  ----
Kernel                      32833               256   13%
Anon                        15016               117    6%
Exec and libs               12207                95    5%
Page cache                  76079               594   30%
Free (cachelist)            31570               246   13%
Free (freelist)             81798               639   33%

Total                      249503              1949
>

Podemos comprobar como la cantidad de memoria asignada a Page cache se ha reducido hasta el 30% y la memoria Free (freelist) a aumentado hasta el 33%.

Segmentos de memoria

Como ya hemos comentado anteriormente los segmentos son grupos de páginas, todos los procesos disponen de al menos 4 segmentos de memoria.

Segmento Descripción
Texto Contiene el una imagen del programa que está ejecutando el proceso.
Datos Son los datos que han sido declarados en el programa.
Heap Este segmento de memoria se utilizará para almacenar los datos que se creen de forma dinámica, con llamadas de tipo malloc()
Pila Se utiliza para almacenar las llamadas a las distintas funciones.

Vamos a ver como mdb nos ayuda para conocer los distintos segmentos que están asignados al espacio de direcciones de un proceso. Para nuestro ejemplo crearemos un nuevo proceso lanzando el comando sleep 50000 en background.

(root@huelva)# sleep 50000 &
[1] 1788
(root@huelva)#
(root@huelva)# mdb -k
Loading modules: [ unix krtld genunix ip usba ipc random nfs ptm ]
>
>
> 0t1788::pid2proc |::print proc_t p_as |::walk seg |::seg
             SEG             BASE             SIZE             DATA OPS
     3007d839f80            10000             2000      3000863d598 segvn_ops
     300881d6d38            20000             2000      3578600afe8 segvn_ops
     3015579b128            22000             2000      3007d8469c0 segvn_ops
     3578db66828         ff280000            ac000      3578608a8a0 segvn_ops
     3578db4eb40         ff33c000             8000      36ccb9b5a18 segvn_ops
     3000360e240         ff380000             2000      357860796c0 segvn_ops
     300595aebd0         ff390000             2000      300db327938 segvn_ops
     300036af290         ff39a000             2000      362e5007db8 segvn_ops
     35785fea3a8         ff3a0000             2000      3578601aa38 segvn_ops
     3007d8661b0         ff3b0000            2e000      3578dae20e8 segvn_ops
     3007d842120         ff3ee000             2000      301b3697bb8 segvn_ops
     30008602678         ff3f0000             2000      3578da37e90 segvn_ops
     3578da9d5a8         ffbfe000             2000      3000369f590 segvn_ops
>

Hemos ejecutado el comando sleep en background y hemos obtenido el PID del proceso, para este ejemplo es 1788, después hemos arrancado mdb, una vez dentro del debugger hemos convertido el PID 1788 en la dirección del proceso, se le ha pasado la dirección al comando ::print para que nos imprima el contenido del campo p_as, el cual contiene la dirección del espacio de direcciones del proceso, utilizamos el walker seg, que nos devuelve la dirección de todos los segmento que pertenecen a un espacio de direcciones y por último pasamos esta salida al comando ::seg, el cual imprime cierta información del segmento, como su tamaño, su dirección base o el tipo de operaciones del segmento. Esta salida es parecida a la que podemos obtener con el comando del SO pmap.

Aunque no vayamos a profundizar mucho más, si vamos a continuar buceando algo en las estructuras de datos que mantienen la información sobre los distintos segmentos, para ello vamos a utilizar el comando ::print para que nos represente la estructura de datos struct seg de un segmento.

> 3007d839f80::print -t "struct seg"
{
    caddr_t s_base = 0x10000
    size_t s_size = 0x2000
    uint_t s_szc = 0
    struct as *s_as = 0x35785ff6578
    avl_node_t s_tree = {
        struct avl_node * [2] avl_child = [ 0, 0 ]
        uintptr_t avl_pcb = 0x300881d6d59
    }
    struct seg_ops *s_ops = segvn_ops
    void *s_data = 0x3000863d598
}
>

Esta es la información de un segmento, podemos destacar, el puntero s_as con la dirección del espacio de direcciones al que pertenece el segmento, el tamaño del segmento s_size, un puntero a las operaciones permitidas sobre este segmento s_ops, la información sobre el árbol AVL de los segmentos del espacio de direcciones s_tree y el campo s_data que contiene la información especifica para este tipo de segmento.

En el sistema existen varios tipos de segmentos de memoria, dependiendo de la funcionalidad para la que se crearon, así podemos encontrar segmentos de tipo seg_map, seg_kmem, seg_dev o seg_vn, entre otros. Todos los segmentos son de tipo struct seg y lo que los diferencia entre ellos, es el tipo de operaciones y el tipo de dato al que apunta el puntero del campo s_data, por esta razón, este campo es un puntero void. Los ficheros de cabecera donde se encuentran todas las definiciones de los distintos tipos de operaciones y datos en los segmentos, los podemos encontrar en el directorio /usr/include/vm/. Es recomendable ojear los ficheros de cabecera, para familiarizarnos con las distintas estructuras de datos.

En nuestro ejemplo, podemos identificar el tipo de segmento por el campo s_ops = segvn_ops, por lo tanto, el campo s_data a punta a una estructura de tipo struct segvn_data, la cual está definida en el fichero /usr/include/vm/seg_vn.h

> 0x3000863d598::print -t "struct segvn_data"
{
    krwlock_t lock = {
        void * [1] _opaque = [ 0 ]
    }
    kmutex_t segp_slock = {
        void * [1] _opaque = [ 0 ]
    }
    uchar_t pageprot = 0
    uchar_t prot = 0xd
    uchar_t maxprot = 0xf
    uchar_t type = 0x2
    u_offset_t offset = 0
    struct vnode *vp = 0x300055f76b0
    ulong_t anon_index = 0
    struct anon_map *amp = 0
    struct vpage *vpage = 0
    struct cred *cred = 0x362b6a64cd8
    size_t swresv = 0
    uchar_t advice = 0
    uchar_t pageadvice = 0
    ushort_t flags = 0x10
    ssize_t softlockcnt = 0
    lgrp_mem_policy_info_t policy_info = {
        int mem_policy = 0x1
        int mem_reserved = 0
    }
}
>

Uno de los campos más interesantes es vnode *vp = 0x300055f76b0, porque contiene la dirección del vnode al que está asociado la página de memoria.

> 0x3000863d598::print -t "struct segvn_data" vp |::print -t vnode_t v_pages
struct page *v_pages = 0x7000289c040
>
> 0x7000289c040::print -t page_t
{
    struct vnode *p_vnode = 0x300055f76b0
    struct page *p_hash = 0
    struct page *p_vpnext = 0x7000289c040
    struct page *p_vpprev = 0x7000289c040
    struct page *p_next = 0x7000289c040
    struct page *p_prev = 0x7000289c040
    u_offset_t p_offset = 0
    selock_t p_selock = 0
    ushort_t p_lckcnt = 0
    ushort_t p_cowcnt = 0
    kcondvar_t p_cv = {
        ushort_t _opaque = 0
    }
    kcondvar_t p_io_cv = {
        ushort_t _opaque = 0
    }
    uchar_t p_iolock_state = 0
    uchar_t p_szc = 0
    uchar_t p_fsdata = 0
    uchar_t p_state = 0
    void * [3] p_msresv = [ 0, 0, 0 ]
}

La salida de los comandos no ha devuelto la información sobre la página de memoria que está asociada al vnode.

El comando ::page

Con el comando ::page podemos obtener la lista de todas las páginas de memoria y a qué vnode está asociada cada página. La salida que obtendremos de la ejecución del comando será parecida a la siguiente.

>
> ::page
            PAGE            VNODE           OFFSET   SELOCK LCT COW IO FS ST
00000700017ce2e0      300cf3e4688                0        0   0   0  0  0  0
00000700023e1b40      300ce979690                0        0   0   0  0  0  0
0000070002b09d00      300cf06b690                0        0   0   0  0  0 80
0000070002f25650      30000283dd8      3005d0a8000        0   0   0  0  0 10
0000070001cd9348      35785fc0410      357987f8000        0   0   0  0  0 10
0000070002edd080      30059937300                0        0   0   0  0  0 80
00000700019c1de0      304bc34a628                0        0   0   0  0  0  0
0000070002cf0fd8      30000283b08      300dc188000        0   0   0  0  0 10
0000070002ed4098      304bc34a628             2000        0   0   0  0  0  0
0000070003019250      300cfc536d8                0        0   0   0  0  0  0
0000070001db2260      30155cf0b10                0        0   0   0  0  0  0
000007000268e0c8      35780f80c60             2000        0   0   0  0  0 80
0000070001ea9088      3578127ac70             2000        0   0   0  0  0 80
0000070001dd35f0      300a3a62590                0        0   0   0  0  0 80
000007000298f970      30135e75a28                0        0   0   0  0  0 80
0000070001776950      300a3e5a598                0        0   0   0  0  0  0
000007000296d1b8      35780f80c60             6000        0   0   0  0  0 80
0000070002f255d8      30000283dd8      3005d0c0000        0   0   0  0  0 10
0000070002a52178      3578127ac70             6000        0   0   0  0  0 80
...

Si no utilizamos el comando con una dirección, devolverá la lista de todas las páginas de memoria. La salida está formada por varias columnas, algunas de las cuales son autoexplicativas, como VNODE con las direcciones de los vnodes a los que están asociadas las distintas páginas, OFFSET define el desplazamiento dentro del vnode, LCT define el número de bloqueos de la página, COW contiene el número de bloqueos Copy-on-Write, ST define el estado de la página mediante una serie de flags. La siguiente tabla muestra los posibles flags que definen el estado de la página.

Flag Valor Descripción
P_FREE 0×80 La página está libre
P_NORELOC 0×40 La página no es relocable
P_MIGRATE 0×20 La página se puede migrar
P_SWAP 0×10 La página está en SWAP

Solaris: Buceando en el Kernel con mdb (II)<< | >>

Technorati Tag(s) :

Los comentarios están cerrados.

My name is null, /dev/null
Algunos derechos reservados. Licencia Creative Commons