Guía de seguimiento dinámico de Solaris

Uniones

Las uniones son otra clase de tipo compuesto compatibles con ANSI-C y D, y están relacionadas con las estructuras. Una unión es un tipo compuesto en el que se define un conjunto de miembros de distintos tipos y los objetos de los miembros ocupan la misma región de almacenamiento. Por tanto, una unión es un objeto de tipo variable, donde un miembro sólo es válido en un momento dado en función de la forma en la que se haya asignado la unión. Normalmente se utiliza otra variable o parte de estado para indicar el miembro de unión que es válido en la actualidad. El tamaño de una unión es el tamaño del miembro más grande, y la alineación de memoria utilizada para la unión es la alineación máxima requerida por los miembros de la unión.

La estructura kstat de Solaris define una estructura que contiene una unión que se utiliza en el siguiente ejemplo para ilustrar y observar las uniones de C y D. La estructura kstat se utiliza para exportar un conjunto de contadores con nombre que representan las estadísticas del núcleo, como la utilización de la memoria y el rendimiento de E/S. La estructura se utiliza para implementar utilidades como mpstat(1M) e iostat(1M). Esta estructura utiliza struct kstat_named para representar un contador con nombre y su valor y se define de la siguiente manera:

struct kstat_named {
	char name[KSTAT_STRLEN]; /* name of counter */
	uchar_t data_type;	/* data type */
	union {
		char c[16];
		int32_t i32;
		uint32_t ui32;
		long l;
		ulong_t ul;
		...
	} value;	/* value of counter */
};	

La declaración que se examina se abrevia con fines ilustrativos. Puede encontrar una definición completa de la estructura en el archivo de cabecera <sys/kstat.h> y se describe en kstat_named(9S). La declaración anterior es válida en ANSI-C y D, y define una estructura que contiene un valor de unión como uno de sus miembros con miembros de varios tipos, en función del tipo del contador. Tenga en cuenta que dado que la propia unión se declara dentro de otro tipo, struct kstat_named, se omite un nombre formal para el tipo de unión. Este estilo de declaración se conoce como una unión anónima. El miembro que se denomina value es de un tipo de unión descrito en la declaración precedente, pero este tipo de unión en sí no tiene nombre, ya que no necesita utilizarse en ningún otro lugar. El miembro de la estructura data_type tiene asignado un valor que indica el miembro de la unión que es válido para cada objeto de tipo struct kstat_named. Un conjunto de símbolos de preprocesador de lenguaje C se define para los valores de data_type. Por ejemplo, el símbolo KSTAT_DATA_CHAR es igual a cero e indica que el miembro value.c es donde está guardado el valor en la actualidad.

Ejemplo 7–3 demuestra el acceso a la unión kstat_named.value realizando un seguimiento del proceso del usuario. Los contadores kstat se pueden tomar de un proceso de usuario con la función kstat_data_lookup(3KSTAT), que devuelve un puntero a struct kstat_named. La utilidad mpstat(1M) llama a esta función repetidamente cuando se ejecuta para tomar una muestra de los últimos valores de contador. Vaya a la shell e intente ejecutar mpstat 1 y observe el resultado. Pulse Control-C en la shell para cancelar mpstat tras unos pocos segundos. Para observar un muestreo del contador, nos gustaría habilitar un sondeo que se active cada vez que el comando mpstat llame a la función kstat_data_lookup(3KSTAT) en libkstat. Para ello, vamos a utilizar un nuevo proveedor de DTrace: pid. El proveedor pid permite crear dinámicamente sondeos en los procesos de usuario en las ubicaciones del símbolo C, como los puntos de entrada de función. Puede solicitar al proveedor de pid que cree un sondeo en una entrada de función de usuario y que devuelva sitios escribiendo descripciones de sondeo con el formato:

pidID_proceso:nombre_objeto:nombre_función:entry
pidID_proceso:nombre_objeto:nombre_función:return

Por ejemplo, si desease crear un sondeo en el Id. de proceso 12345 que se active al entrar en kstat_data_lookup(3KSTAT), escribiría la siguiente descripción de sondeo:

pid12345:libkstat:kstat_data_lookup:entry

El proveedor pid inserta una instrumentación dinámica en un proceso de usuario especificado en la ubicación del programa correspondiente a la descripción del sondeo. La implementación del sondeo fuerza a cada subproceso de usuario que llega al programa instrumentado a que se capture en el núcleo del sistema operativo y acceda a DTrace, activando el sondeo correspondiente. Por ello, aunque la ubicación de la instrumentación está asociada con un proceso de usuario, las acciones y predicados de DTrace que especifique se ejecutarán en el contexto del núcleo del sistema operativo. El proveedor de pid se describe más detalladamente en el Capítulo 30Proveedor pid.

en vez de tener que editar el código fuente del programa D cada vez que desea aplicar el programa a distintos procesos, puede insertar en el programa identificadores llamados variables de macro que se evalúan en el momento en que se compila el programa y se sustituyen con los argumentos de línea de comandos dtrace adicionales. Las variables de macro se especifican utilizando un signo de dólar $ seguido de un identificador o número. Si ejecuta el comando dtrace -s script foo bar baz, el compilador de D definirá automáticamente las variables de macro $1, $2 y $3 para que sean los símbolos foo, bar y baz respectivamente. Puede utilizar las variables de macro en expresiones de programa D o en descripciones de sondeos. Por ejemplo, las siguientes descripciones de sondeos muestran que cualquier Id. de proceso se especifica como un argumento adicional a dtrace:

pid$1:libkstat:kstat_data_lookup:entry
{
	self->ksname = arg1;
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 != NULL/
{
	this->ksp = (kstat_named_t *)copyin(arg1, sizeof (kstat_named_t));
	printf("%s has ui64 value %u\n", copyinstr(self->ksname),
	    this->ksp->value.ui64);
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 == NULL/
{
	self->ksname = NULL;
}

Las variables de macro y las secuencias de comando reutilizables se describen con más detalle en el Capítulo 15Secuencias de comandos. Ahora que conocemos cómo instrumentar los procesos de usuario utilizando su Id. de proceso, volvamos a las uniones de muestra. Acceda al editor y escriba el código fuente de nuestro ejemplo completo y guárdelo en un archivo que se llame kstat.d:


Ejemplo 7–3 kstat.d: realizar seguimiento de llamadas a kstat_data_lookup(3KSTAT )

pid$1:libkstat:kstat_data_lookup:entry
{
	self->ksname = arg1;
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 != NULL/
{
	this->ksp = (kstat_named_t *) copyin(arg1, sizeof (kstat_named_t));
	printf("%s has ui64 value %u\n",
	    copyinstr(self->ksname), this->ksp->value.ui64);
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 == NULL/
{
	self->ksname = NULL;
}

Ahora vaya a una de las shells y ejecute el comando mpstat 1 para iniciarmpstat(1M) ejecutándose en un modo donde tome muestras de las estadísticas y genere informes de ellas una vez por segundo. Una vez que se esté ejecutando mpstat, ejecute el comando dtrace -q -s kstat.d `pgrep mpstat` en la otra shell. Verá el resultado correspondiente a las estadísticas a las que se está accediendo. Pulse Control-C para cancelar dtrace y volver al indicador del shell.


# dtrace -q -s kstat.d `pgrep mpstat`
cpu_ticks_idle has ui64 value 41154176
cpu_ticks_user has ui64 value 1137
cpu_ticks_kernel has ui64 value 12310
cpu_ticks_wait has ui64 value 903
hat_fault has ui64 value 0
as_fault has ui64 value 48053
maj_fault has ui64 value 1144
xcalls has ui64 value 123832170
intr has ui64 value 165264090
intrthread has ui64 value 124094974
pswitch has ui64 value 840625
inv_swtch has ui64 value 1484
cpumigrate has ui64 value 36284
mutex_adenters has ui64 value 35574
rw_rdfails has ui64 value 2
rw_wrfails has ui64 value 2
...
^C
#

Si captura el resultado en cada ventana del terminal y resta cada valor del valor notificado por la iteración anterior mediante las estadísticas, debería poder correlacionar el resultado de dtrace con el resultado de mpstat. El programa de ejemplo registra el puntero del nombre del contador en la entrada a la función de búsqueda y a continuación, realiza la mayoría del trabajo de seguimiento sobre la respuesta de kstat_data_lookup(3KSTAT). Las funciones incorporadas de D copyinstr() y copyin() copian los resultados de la función desde el proceso del usuario en DTrace si arg1 (el valor devuelto) no es NULL. Una vez que se hayan copiado los datos de kstat, el ejemplo notifica el valor de contador ui64 desde la unión. Este ejemplo simplificado asume que mpstat toma muestras de contadores que utilizan el miembro value.ui64. Como ejercicio, intente grabar kstat.d para utilizar varios predicados e imprimir el miembro de unión correspondiente al miembro data_type. También puede intentar crear una versión de kstat.d que calcule la diferencia entre los valores de datos sucesivos y que genere un resultado similar al de mpstat.