Guía de seguimiento dinámico de Solaris

Variables de subproceso local

DTrace proporciona la posibilidad de declarar un almacenamiento de variables que sea local para cada subproceso del sistema operativo, en contraposición a las variables locales de las que se habló en este capítulo anteriormente. Las variables de subproceso local son útiles en situaciones en las que se desea activar un sondeo y marcar cada subproceso que active el sondeo con alguna etiqueta u otros datos. Crear un programa para resolver este problema es fácil en D porque las variables de subproceso local comparten un nombre común en el código D, pero hacen referencia a almacenamientos de datos separados asociados a cada subproceso. A las variables de subproceso local se hace referencia aplicando el operador -> al identificador especial self:

syscall::read:entry
{
	self->read = 1;
}

Este fragmento de ejemplo escrito en D activa el sondeo en la llamada de sistema read(2) y asocia una variable de subproceso local llamada read a cada subproceso que activa el sondeo. Al igual que las variables globales, las variables de subproceso local se crean automáticamente en su primera asignación y asumen el tipo usado en el lado derecho de la primera declaración de asignación (en este ejemplo, int).

Cada vez que se hace referencia a la variable self->read en el programa escrito en D, el objeto de datos al que se hace referencia es el asociado al subproceso del sistema operativo que se estaba ejecutando cuando se activó el sondeo de DTrace correspondiente. Puede considerar que una variable de subproceso local es una matriz asociativa que está indexada de forma implícita por tuplas que describen la identidad del subproceso en el sistema. La identidad de un subproceso es exclusiva durante el tiempo de vida del sistema: si se cierra el subproceso y se usa la misma estructura de datos del sistema operativo para crear un nuevo subproceso, este subproceso no reutiliza la misma identidad de almacenamiento de subproceso local de DTrace.

Una vez que se haya definido una variable de subproceso local, puede hacer referencia a ella para cualquier subproceso del sistema, incluso si la variable en cuestión no se ha asignado previamente para este subproceso concreto. Si aún no se ha asignado una copia de un subproceso de la variable de subproceso local, el almacenamiento de datos para la copia se define cumplimentándolo con ceros. Al igual que con los elementos de matriz asociativa, el almacenamiento subyacente no se asigna para una variable de subproceso local hasta que se le haya asignado un valor que no sea cero. También, del mismo modo que los elementos de matriz asociativa, la asignación de cero a una variable de subproceso local provoca que DTrace anule la asignación del almacenamiento subyacente. Asigne siempre cero a las variables de subproceso local que ya no estén en uso. Consulte Capítulo 16Opciones y optimizables, para conocer otras técnicas para ajustar el espacio de las variables dinámicas desde las que se asignan las variables de subproceso local.

Las variables de subproceso local de cualquier tipo se pueden definir en un programa escrito en D, incluidas las matrices asociativas. Algunos ejemplos de definiciones de variables de subproceso local son:

self->x = 123;              /* integer value */
self->s = "hello";	          /* string value */
self->a[123, 'a'] = 456;    /* associative array */

Del mismo modo que con cualquier variable de D, no es necesario declarar explícitamente las variables de subproceso local antes de usarlas. Si desea crear una declaración de todas formas, puede colocar una fuera de las cláusulas del programa anteponiendo la palabra clave self:

self int x;    /* declare int x as a thread-local variable */

syscall::read:entry
{
	self->x = 123;
}

Las variables de subproceso local se guardan en un espacio de nombre separado de las variables globales para que pueda reutilizar los nombres. Recuerde que x y self->x no son la misma variable si sobrecarga de nombres el programa. El siguiente ejemplo muestra cómo se usan las variables de subproceso local. En un editor de texto, escriba el siguiente programa y guárdelo en un archivo llamado rtime.d:


Ejemplo 3–1 rtime.d: calcula el tiempo invertido en read(2)

syscall::read:entry
{
	self->t = timestamp;
}

syscall::read:return
/self->t != 0/
{
	printf("%d/%d spent %d nsecs in read(2)\n",
	    pid, tid, timestamp - self->t);
	
	/*
	 * We're done with this thread-local variable; assign zero to it to
	 * allow the DTrace runtime to reclaim the underlying storage.
	 */
	self->t = 0;
}

Ahora acceda a la shell e inicie el programa. En unos segundos debería comenzar a ver algunos resultados. Si no aparece ninguna salida, intente ejecutar algunos comandos.


# dtrace -q -s rtime.d
100480/1 spent 11898 nsecs in read(2)
100441/1 spent 6742 nsecs in read(2)
100480/1 spent 4619 nsecs in read(2)
100452/1 spent 19560 nsecs in read(2)
100452/1 spent 3648 nsecs in read(2)
100441/1 spent 6645 nsecs in read(2)
100452/1 spent 5168 nsecs in read(2)
100452/1 spent 20329 nsecs in read(2)
100452/1 spent 3596 nsecs in read(2)
...
^C
#

rtime.d usa una variable de subproceso local llamada t para capturar una marca de tiempo en la entrada a read(2) mediante cualquier subproceso. Entonces, en la cláusula de devolución, el programa imprime la cantidad de tiempo que ha invertido en read(2) restando self->t a la marca de tiempo actual. Las variables de D integradas pid y tid registran el ID de proceso y el de subproceso correspondientes al subproceso que ejecuta read(2). Como self->t ya no se necesita una vez que se ha registrado la información, se le asigna 0 para permitir que DTrace reutilice el almacenamiento subyacente asociado a t para el subproceso actual.

Normalmente verá varias líneas de salida, aunque no realice ninguna acción porque en segundo plano los procesos del servidor y de los daemons ejecutan read(2) todo el tiempo, incluso aunque no se esté haciendo nada. Intente cambiar la segunda cláusula de rtime.d para usar la variable execname con objeto de imprimir el nombre del proceso que ejecuta read(2) para obtener más información:

printf("%s/%d spent %d nsecs in read(2)\n",
    execname, tid, timestamp - self->t);

Si encuentra algún proceso que le interese especialmente, agregue un predicado para conocer más aspectos sobre el comportamiento de read(2):

syscall::read:entry
/execname == "Xsun"/
{
	self->t = timestamp;
}