Guía de seguimiento dinámico de Solaris

Capítulo 40 Traductores

En el Capítulo 39Estabilidad, aparece información sobre cómo DTrace computa y elabora informes sobre los atributos de estabilidad de los programas. Lo más correcto sería construir nuestros programas DTrace consumiendo sólo interfaces estables o evolutivas. Por desgracia, cuando se depuran problemas concretos o se evalúa el rendimiento de un sistema, es posible que se deban activar sondeos que estén asociados a rutinas internas del sistema operativo, como son las funciones del núcleo, en lugar de sondeos asociados a interfaces más estables, como es el caso de las llamadas de sistema. Los datos disponibles en las ubicaciones profundas de los sondeos en la pila de software son a menudo una colección de artefactos de implementación en lugar de estructuras de datos más estables, al igual que los asociados a las interfaces de llamada de sistema Solaris. Para asistirle a la hora de escribir programas en D estables, DTrace proporciona una utilidad para traducir artefactos de implementación en estructuras de datos estables accesibles desde las instrucciones del programa en D.

Declaraciones de los traductores

Un traductor es una colección de instrucciones de asignación en D proporcionada por el proveedor de una interfaz que se puede usar para traducir una expresión de entrada en un objeto de tipo de estructura. Para comprender la necesidad de los traductores, utilizaremos como ejemplo las rutinas de la biblioteca estándar ANSI-C definidas en stdio.h. Estas rutinas funcionan en una estructura de datos llamada FILE, cuyos artefactos de implementación se abstraen de los programadores en C. Una técnica estándar para crear una abstracción de estructura de datos es proporcionar sólo una declaración hacia adelante de una estructura de datos en los archivos de encabezado públicos, a la vez que se conserva la definición de estructura correspondiente en un archivo de encabezado privado separado.

Si está escribiendo un programa en C y desea conocer el descriptor de archivo correspondiente a una estructura FILE, podrá usar la función fileno(3C) para obtener el descriptor en lugar de que un miembro de la estructura FILE deje de hacer referencia directamente. Los archivos de encabezado Solaris fuerzan esta regla definiendo FILE como una etiqueta de declaración de avance opaca, por lo que no puede dejar de hacer referencia directamente mediante programas escritos en C que incluyan <stdio.h>. En la biblioteca libc.so.1, puede imaginarse que fileno() está implementado en C de una forma parecida a ésta:

int
fileno(FILE *fp)
{
	struct file_impl *ip = (struct file_impl *)fp;

	return (ip->fd);
}

La función hipotética fileno() considera el puntero FILE como un argumento y lo convierte en un puntero hacia la estructura libc interna correspondiente, struct file_impl, y, a continuación, devuelve el valor del miembro fd de la estructura de implementación. ¿Por qué implementa Solaris las interfaces de esta forma? Al abstraer los detalles de la implementación actual de libc con respecto a los programas cliente, Sun puede mantener un compromiso de compatibilidad binaria sólida a la vez que continúa evolucionando y cambiando los detalles de implementación internos de libc. En nuestro ejemplo, el miembro fd podría cambiar de tamaño o posición dentro de struct file_impl, incluso en un parche, y los binarios existentes que llaman a fileno(3C) no se verían afectados por este cambio porque no dependen de estos artefactos.

Desafortunadamente, el software de observación, como por ejemplo DTrace, necesita mirar en la implementación con objeto de proporcionar unos resultados satisfactorios y no tiene el lujo de llamar a funciones arbitrarias en C definidas en las bibliotecas de Solaris o en el núcleo. Puede declarar una copia de struct file_impl en el programa escrito en D con la intención de instrumentar las rutinas declaradas en stdio.h, pero entonces el programa escrito en D se basaría en artefactos de implementación privados de la biblioteca que podrían aparecer en una microversión futura, en una versión menor o incluso en un parche. Nuestro objetivo es proporcionar una construcción para usarla en programas escritos en D, que esté enlazada a la implementación de la biblioteca y que se actualice en función de ello, pero también proporcionar una capa adicional de abstracción asociada con una mayor estabilidad.

Un traductor nuevo se crea usando una declaración con la forma:

translator output-type < input-type input-identifier > {
	member-name = expression ;
	member-name = expression ;
	...
};	

output-type hace referencia a una estructura que será el tipo de resultado de la traducción. input-type hace referencia al tipo de la expresión de entrada; está rodeada de paréntesis angulares < > y seguida de input-identifier, que se puede usar en las expresiones del traductor como un alias para la expresión de entrada. El cuerpo del traductor está rodeado de llaves { } y termina con un punto y coma (;). Está formado por una lista de member-name e identificadores correspondientes a las expresiones de traducción. Cada una de las declaraciones miembro debe nombrar a un miembro único de output-type y debe estar asignado a una expresión de un tipo compatible con el tipo de miembro, según las reglas del operador de asignación de D (=).

Por ejemplo, podemos definir una estructura de información estable acerca de los archivos stdio basada en algunas de las interfaces libc disponibles:

struct file_info {
	int file_fd;   /* file descriptor from fileno(3C) */
	int file_eof;  /* eof flag from feof(3C) */
};

Un hipotético traductor de D de FILE a file_info se podría declarar en D de la siguiente forma:

translator struct file_info < FILE *F > {
	file_fd = ((struct file_impl *)F)->fd;
	file_eof = ((struct file_impl *)F)->eof;
};

En este traductor hipotético, la expresión de entrada es del tipo FILE * y está asignada a input-identifier F. El identificador F se puede usar en las expresiones que son miembros del traductor como una variable del tipo FILE * que está visible sólo en el cuerpo de la declaración del traductor. Para determinar el valor del miembro file_fd de salida, el traductor realiza una conversión y deja de hacer referencia, de forma similar a la implementación hipotética de fileno(3C) mostrada anteriormente. Una traducción parecida se realiza para obtener el valor del indicador EOF.

Sun proporciona un conjunto de traductores para usarlos con las interfaces de Solaris que se pueden ejecutar desde los programas escritos en D. Asimismo, Sun se compromete a mantener estos traductores de acuerdo con las reglas de estabilidad de las interfaces definidas anteriormente, como la implementación de los cambios de interfaz correspondientes. Más adelante en el capítulo, encontrará información acerca de estos traductores, después de aprender cómo se ejecutan los traductores desde D. La utilidad de los traductores en sí misma también se proporciona para que la usen los desarrolladores de bibliotecas y aplicaciones que deseen ofrecer sus propios traductores con objeto de que los programadores de D puedan usarlos para estudiar el estado de sus paquetes de software.

Operador de traducción

El operador de D xlate se usa para traducir una expresión de entrada en una de las estructuras de salida de traducción definidas. El operador xlate se usa en una expresión con la forma:

xlate < output-type > ( input-expression )

Por ejemplo, para ejecutar el traductor hipotético para la estructura FILE definida anteriormente y acceder al miembro file_fd, debería escribir la expresión:

xlate <struct file_info *>(f)->file_fd;

donde f es una variable de D del tipo FILE *. La expresión xlate en sí misma se asigna al tipo definido por output-type. Una vez que se define un traductor, se puede usar para traducir expresiones de entrada en un tipo de estructura de salida del traductor o en un puntero para la instrucción.

Si traduce una expresión de entrada en una estructura, podrá dejar de hacer referencia a un miembro concreto de la salida de forma inmediata usando el operador “.”. También puede asignar la estructura traducida entera a otra variable D para hacer una copia de los valores de todos los miembros. Si un único miembro deja de hacer referencia, el compilador D sólo generará código correspondiente a la expresión para dicho miembro. Si lo desea, puede no aplicar el operador & a una estructura traducida para obtener su dirección, puesto que el objeto de datos en sí no existe hasta que se copia o hasta que se hace referencia a uno de sus miembros.

Si traduce una expresión de entrada en un puntero en una estructura, podrá dejar de hacer referencia a un miembro concreto de la salida de forma inmediata usando el operador ->, o también puede hacer que el puntero deje de hacer referencia usando el operador unario *, en cuyo caso, el resultado actúa como si tradujera la expresión en una estructura. Si un único miembro deja de hacer referencia, el compilador D sólo generará código correspondiente a la expresión para dicho miembro. Si lo desea, puede dejar sin asignar un puntero traducido a otra variable D, dado que el objeto en sí no existe hasta que se copia o hasta que se hace referencia a uno de sus miembros y, en consecuencia, no se puede dirigir a él.

Una declaración de traductor puede omitir expresiones para uno o varios miembros del tipo de salida. Si se usa una expresión xlate para acceder a un miembro para el que no se ha definido una expresión de traducción, el compilador D generará un mensaje de error adecuado e interrumpirá la compilación del programa. Si se copia el tipo de salida entero mediante una asignación de estructura, cualquier miembro para el que no haya definidas expresiones de traducción se cumplimentará con ceros.

Para encontrar un traductor coincidente para una operación xlate, el compilador D examina el conjunto de traductores disponibles en el siguiente orden:

Si no se encuentra un traductor coincidente en función de estas reglas, el compilador D genera un mensaje de error adecuado y la compilación del programa falla.

Traductores del modelo de procesos

El archivo de la biblioteca de DTrace /usr/lib/dtrace/procfs.d proporciona un conjunto de traductores para usarlos en los programas escritos en D y traducir las estructuras de implementación del núcleo del sistema operativo para los procesos y subprocesos en estructuras proc(4) estables psinfo y lwpsinfo. Estas estructuras también se usan en los archivos de sistema de Solaris /proc/proc/pid/psinfo y /proc/pid/lwps/lwpid/lwpsinfo, y se definen en el archivo de encabezado de sistema /usr/include/sys/procfs.h. Estas estructuras definen información estable útil acerca de los procesos y los subprocesos, por ejemplo el proceso ID, LWP ID, los argumentos iniciales y otros datos que muestra el comando ps(1). Consulte proc(4) para ver una descripción completa de la semántica y los miembros de las estructuras.

Tabla 40–1 Traductores procfs.d

Tipo de entrada 

Atributos del tipo de entrada 

Tipo de salida 

Atributos del tipo de salida 

proc_t *

Private/Private/Common 

psinfo_t *

Stable/Stable/Common 

kthread_t *

Private/Private/Common 

lwpsinfo_t *

Stable/Stable/Common 

Traducciones Stable (estables)

Aunque un traductor proporciona la posibilidad de convertir información en una estructura de datos estable, no resuelve necesariamente todos los problemas de estabilidad que se pueden producir al traducir los datos. Por ejemplo, si la expresión de entrada para una operación de traducción en sí misma hace referencia a datos no estables, el programa escrito en D resultante tampoco será estable porque la estabilidad del programa siempre se computa como el mínimo de estabilidad de las instrucciones y expresiones acumuladas del programa escrito en D. Por lo tanto, en ocasiones es necesario definir una expresión de entrada estable para un traductor, con el objeto de poder construir programas estables. El mecanismo en línea de D se puede usar para facilitar este tipo de traducciones estables.

La biblioteca procfs.d de DTrace proporciona las variables curlwpsinfo y curpsinfo descritas anteriormente como traducciones estables. Por ejemplo, la variable curlwpsinfo es en realidad un elemento inline declarado de la siguiente manera:

inline lwpsinfo_t *curlwpsinfo = xlate <lwpsinfo_t *> (curthread);
#pragma D attributes Stable/Stable/Common curlwpsinfo

La variable curlwpsinfo se define como una traducción en línea de la variable curthread, un puntero a la estructura de datos Private (privada) del núcleo que representa a un subproceso para convertirlo en el tipo lwpsinfo_t Stable (estable). El compilador D procesa este archivo de biblioteca y almacena en la antememoria la declaración inline, lo que hace que curlwpsinfo aparezca como cualquier otra variable de D. La instrucción #pragma que sigue a la declaración se usa para restablecer explícitamente los atributos del identificador curlwpsinfo como Stable/Stable/Common (Estable/Estable/Común), enmascarando así la referencia a curthread en la expresión en línea. Esta combinación de funciones de D permite a los programadores de D usar curthread como origen de una traducción segura que los elementos coincidentes de Sun puedan actualizar con los cambios correspondientes en la implementación de Solaris.