Guía de seguimiento dinámico de Solaris

Capítulo 3 Variables

D proporciona dos tipos básicos de variables para usarlas en los programas de seguimiento: variables escalables y matrices asociativas. El uso de estas variables se ilustró de manera sucinta en el capítulo 1. Este capítulo profundiza en las reglas de las variables de D y la forma en que las variables se pueden asociar con distintos ámbitos. Sobre una variable de matriz de un tipo especial, llamada adición, se habla en el Capítulo 9Adiciones.

Variables escalares

Las variables escalares se usan para representar objetos de datos individuales de tamaño fijo como, por ejemplo, los enteros y los punteros. Las variables escalares también se pueden usar para objetos de tamaño fijo que estén compuestos por una o varias primitivas o tipos compuestos. D proporciona la posibilidad de crear matrices de objetos, así como estructuras compuestas. DTrace también representa las cadenas como escalares de tamaño fijo y les permite crecer hasta una longitud máxima predefinida. El control de la longitud de las cadenas en un programa escrito en D se trata en profundidad en elCapítulo 6Cadenas.

Las variables escalares se crean automáticamente la primera vez que se asigna un valor a un identificador que previamente estaba sin definir en el programa en D. Por ejemplo, para crear una variable escalar llamada x del tipo int, puede asignarle simplemente un valor del tipo int en cualquier cláusula de sondeo:

BEGIN
{
	x = 123;
}

Las variables escalares creadas de esta manera son variables globales: sus nombres y la ubicación de almacenamiento de los datos se definen una vez y están visible en todas las cláusulas del programa escrito en D. En cualquier momento en que se haga referencia al identificador x, se hará referencia a una única ubicación de almacenamiento asociada a esta variable.

A diferencia de ANSI-C, D no requiere declaraciones con variables explícitas. Si desea declarar una variable global para asignarle nombre y su tipo de forma explícita antes de usarla, puede colocar una declaración fuera de las cláusulas del sondeo en el programa, tal y como se muestra en el siguiente ejemplo. Las declaraciones de variables explícitas no son necesarias en la mayoría de programas escritos en D, pero en ocasiones son útiles cuando se desea controlar cuidadosamente los tipos de variables o cuando usted desee comenzar un programa con un conjunto de declaraciones y comentarios que documenten las variables de los programas y sus significados.

int x; /* declare an integer x for later use */

BEGIN
{
	x = 123;
	...
}

A diferencia de las declaraciones ANSI-C, las de D es posible que no asignen valores iniciales. Debe usar una cláusula de sondeo BEGIN para asignar cualquier valor inicial. Todo el almacenamiento de variables globales lo cumplimenta DTrace con ceros antes de que se haga referencia por primera vez a la variable.

La definición del lenguaje D no establece límites en cuanto al tamaño y el número de variables de D, pero los límites los establece la implementación de DTrace y la memoria que esté disponible en el sistema. El compilador D forzará las limitaciones que se puedan aplicar en el momento de la compilación del programa. Puede obtener más información acerca del ajuste de las opciones relacionadas con los límites del programa en el Capítulo 16Opciones y optimizables.

Matrices asociativas

Las matrices asociativas se utilizan para representar conjuntos de elementos de datos que se pueden recuperar especificando un nombre llamado clave. Las matrices asociativas de D están formadas por una lista de valores de expresiones escalares llamadas tuplas. Puede considerar que una tupla de matriz es como una lista de parámetros imaginarios de una función que se ejecuta para recuperar el valor de matriz correspondiente cuando se hace referencia a la matriz. Cada matriz asociativa de D tiene una firma clave que incluye un número fijo de elementos de tupla, donde cada elemento tiene un tipo concreto fijo. Puede definir distintas firmas clave para cada matriz en los programas escritos en D.

Las matrices asociativas difieren de las matrices normales de tamaño fijo en que no tienen un límite predefinido en el número de elementos: los elementos se pueden indexar mediante cualquier tupla, a diferencia del uso exclusivo de enteros como claves, y los elementos no se almacenan en ubicaciones de almacenamiento consecutivas preasignadas. Estas matrices son útiles en situaciones en las que se necesita usar una tabla hash u otras estructuras de datos de diccionario simples escritas en lenguaje de programación C, C++ o Java. TM Las matrices asociativas le permiten crear un historial dinámico de los eventos y estados capturados en el programa escrito en D que se pueden usar para crear un flujo de control mucho más complejo.

Para definir una matriz asociativa, debe escribir una expresión de asignación del tipo:

nombre [ clave ] = expresión ;

donde nombre es cualquier identificador de D válido y clave es una lista separada por comas de una o varias expresiones. Por ejemplo, la siguiente declaración define una matriz asociativa a con una firma clave [ int, string ] y almacena el valor del entero 456 en una ubicación llamada mediante la tupla [ 123, "hello" ]:

a[123, "hello"] = 456;

El tipo de cada objeto incluido en la matriz también es fijo para todos los elementos de una matriz concreta. Dado que a se asignó en primer lugar usando el entero 456, cada valor subsiguiente almacenado en la matriz será también del tipo int. Puede usar cualquiera de los operadores de asignación definidos en el capítulo 2 para modificar los elementos de las matrices asociativas, en función de las reglas de los operandos establecidas para cada operador. El compilador D generará un mensaje de error adecuado si intenta realizar una asignación incompatible. Puede usar cualquier tipo con una clave de matriz asociativa o valor que pueda usar con una variable escalar. No se puede anidar una matriz asociativa en otra matriz asociativa como una clave o valor.

Puede hacer referencia a una matriz asociativa usando una tupla que sea compatible con la firma clave de la matriz. Las reglas para la compatibilidad de las tuplas son similares a las de las llamadas de funciones y asignaciones de variables: la tupla debe tener la misma longitud y cada tipo de la lista de parámetros reales debe ser compatible con el tipo correspondiente en la firma clave formal. Por ejemplo, si una matriz asociativa x se define de la siguiente forma:

x[123ull] = 0;

entonces la firma clave es del tipo unsigned long long y los valores son del tipo int. A esta matriz también se puede hacer referencia usando la expresión x['a'] porque la tupla consta del carácter constante 'a' del tipo int y la longitud uno es compatible con la firma clave unsigned long long en función de las reglas de conversión aritméticas descritas en conversiones de tipos.

Si necesita declarar explícitamente una matriz asociativa en D antes de usarla, puede crear una declaración del nombre de matriz y la firma clave fuera de las cláusulas del sondeo en el código fuente del programa:

int x[unsigned long long, char];

BEGIN
{
	x[123ull, 'a'] = 456;
}

Una vez que se define una matriz asociativa, se permiten las referencias a cualquier tupla de una firma clave compatible, incluso si la tupla en cuestión no se ha asignado previamente. El acceso a un elemento de matriz asociativa sin asignar está definido para devolver un objeto sin contenido. Una consecuencia de esta definición es que el almacenamiento subyacente no se asigna para un elemento de matriz asociativa hasta que un valor que no sea cero se asigne a dicho elemento. Por el contrario, al asignar un elemento de matriz asociativa a cero se consigue que DTrace elimine la asignación del almacenamiento subyacente. Este comportamiento es importante porque el espacio de variable dinámica fuera del que se asignan los elementos de matriz asociativa es finito; si se agota cuando se intenta una asignación, la asignación fallará y se generará un mensaje de error indicando una caída de variable dinámica. Asigne cero a los elementos de matrices asociativas que ya no estén en uso. Consulte el Capítulo 16Opciones y optimizables, para obtener información sobre otras técnicas para eliminar anulaciones de variables dinámicas.

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;
}

Variables de cláusulas locales

También puede definir variables de D cuyo almacenamiento se vuelva a utilizar para cada cláusula del programa escrito en D. Las variables de cláusulas locales son parecidas a las variables automáticas escritas en lenguaje de programación C, C++ o Java que están activas durante cada invocación de una función. Al igual que todas las variables de un programa escrito en D, las variables de cláusulas locales se crean en su primera asignación. A estas variables se les puede hacer referencia (y se pueden asignar) aplicando el operador -> al identificador especial this:

BEGIN
{
	this->secs = timestamp / 1000000000;
	...
}

Si desea declarar explícitamente variables de cláusulas locales antes de usarlas, puede hacerlo usando la palabra clave this:

this int x;   /* an integer clause-local variable */
this char c;  /* a character clause-local variable */

BEGIN
{
	this->x = 123;
	this->c = 'D';
}

Las variables de cláusulas locales están activas sólo mientras dure una cláusula de sondeo concreta. Después de que DTrace realice las acciones asociadas a las cláusulas para un sondeo determinado, el almacenamiento de todas las variables de cláusulas locales se reclama y se reutiliza para la siguiente cláusula. Por esta razón, las variables de cláusulas locales son las únicas variables de D que no se cumplimentan inicialmente con ceros. Tenga en cuenta que si un programa contiene varias cláusulas para un mismo sondeo, las variables de cláusulas locales permanecerán intactas mientras se ejecutan las cláusulas, tal y como se muestra en el siguiente ejemplo:


Ejemplo 3–2 clause.d: variables de cláusulas locales

int me;			/* an integer global variable */
this int foo;		/* an integer clause-local variable */

tick-1sec
{
	/*
	 * Set foo to be 10 if and only if this is the first clause executed.
	 */
	this->foo = (me % 3 == 0) ? 10 : this->foo;
	printf("Clause 1 is number %d; foo is %d\n", me++ % 3, this->foo++);
}

tick-1sec
{
	/*
	 * Set foo to be 20 if and only if this is the first clause executed. 
	 */
	this->foo = (me % 3 == 0) ? 20 : this->foo;
	printf("Clause 2 is number %d; foo is %d\n", me++ % 3, this->foo++);
}

tick-1sec
{
	/*
	 * Set foo to be 30 if and only if this is the first clause executed.
	 */
	this->foo = (me % 3 == 0) ? 30 : this->foo;
	printf("Clause 3 is number %d; foo is %d\n", me++ % 3, this->foo++);
}

Dado que las cláusulas siempre se ejecutan en el orden de los programas y como las variables de cláusulas locales son persistentes entre las diferentes cláusulas que activan el mismo sondeo, la ejecución de los programas anteriores generará siempre el mismo resultado:


# dtrace -q -s clause.d
Clause 1 is number 0; foo is 10
Clause 2 is number 1; foo is 11
Clause 3 is number 2; foo is 12
Clause 1 is number 0; foo is 10
Clause 2 is number 1; foo is 11
Clause 3 is number 2; foo is 12
Clause 1 is number 0; foo is 10
Clause 2 is number 1; foo is 11
Clause 3 is number 2; foo is 12
Clause 1 is number 0; foo is 10
Clause 2 is number 1; foo is 11
Clause 3 is number 2; foo is 12
^C

Mientras que las variables de cláusulas locales son persistentes entre las distintas cláusulas que activan el mismo sondeo, sus valores están sin definir en la primera cláusula ejecutada para un sondeo concreto. Asegúrese de asignar a la misma variable de cláusula local un valor adecuado antes de usarla; de lo contrario, el programa generará resultados inesperados.

Las variables de cláusulas locales se pueden definir usando un tipo de variable escalar, pero las matrices asociativas puede que no estén definidas usando un ámbito de cláusula local. El ámbito de las variables de cláusulas locales sólo se aplica a los datos de variables correspondientes, no al nombre y al tipo de identidad definido para la variable. Una vez que se define una variable de cláusula local, este nombre y firma de tipo puede usarse en cualquier cláusula de programa escrito en D posterior. No se puede esperar que la ubicación de almacenamiento sea la misma en cláusulas distintas.

Puede usar las variables de cláusulas locales para acumular resultados intermedios de cálculos o como copias temporales de otras variables. Acceder a una variable de cláusula local es mucho más rápido que acceder a una matriz asociativa. En consecuencia, si necesita hacer referencia a un valor de una matriz asociativa varias veces en una misma cláusula de programa escrito en D, es más eficaz copiarlo en una variable de cláusula local primero y luego hacer referencia varias veces a dicha variable local.

Variables integradas

En la tabla siguiente se incluye una lista completa de las variables integradas de D. Todas estas variables son variables globales escalares; actualmente D no define variables de cláusulas locales, variables de subproceso local ni matrices asociativas integradas.

Tabla 3–1 Variables integradas de DTrace

Tipo y nombre 

Descripción 

int64_t arg0, ..., arg9

Los primeros diez argumentos de entrada de un sondeo representados como enteros de 64 bits sin formato. Si se pasan menos de diez argumentos al sondeo actual, el resto de variables devuelve cero. 

args[]

Los argumentos escritos para el sondeo actual, en caso de que haya alguno. A la matriz args[] se accede usando un índice de enteros, pero cada elemento está definido para ser el tipo correspondiente al argumento del sondeo concreto. Por ejemplo, si un sondeo de llamada de sistema args[] ; hace referencia a read(2), args[0] es del tipo int, args[1] es del tipo void * y args[2] es del tipo size_t.

uintptr_t caller

La ubicación del contador de programa del subproceso actual justo antes de entrar en el sondeo actual. 

chipid_t chip

El identificador del chip de la CPU para el chip físico actual. Consulte el Capítulo 26Proveedor sched, para obtener más información.

processorid_t cpu

Identificador de la CPU para la CPU actual. Consulte el Capítulo 26Proveedor sched, para obtener más información.

cpuinfo_t *curcpu

Información de la CPU para la CPU actual. Consulte el Capítulo 26Proveedor sched, para obtener más información.

lwpsinfo_t *curlwpsinfo

Estado del proceso ligero (LWP) del LWP asociado al subproceso actual. Esta estructura se describe en detalle en la página de man proc(4) .

psinfo_t *curpsinfo

El estado del proceso asociado al subproceso actual. Esta estructura se describe en detalle en la página de man proc(4) .

kthread_t *curthread

La dirección de la estructura de datos interna del núcleo del sistema operativo para el subproceso actual (kthread_t). kthread_t se define en <sys/thread.h>. Consulte Solaris Internals (Funcionamiento de Solaris) para obtener más información sobre esta variable y otras estructuras de datos del sistema operativo.

string cwd

El nombre del directorio de trabajo actual del proceso asociado al subproceso actual. 

uint_t epid

El ID de sondeo activado (EPID) para el sondeo actual. Este entero identifica de forma exclusiva a un sondeo concreto que está activado con un predicado específico y un conjunto de acciones. 

int errno

Valor de error devuelto por la última llamada de sistema ejecutada por este subproceso. 

string execname

El nombre pasado a exec(2) para ejecutar el proceso actual.

gid_t gid

El ID del grupo real del proceso actual. 

uint_t id

El ID de sondeo correspondiente al sondeo actual. Este ID es el identificador único en todo el sistema para el sondeo, tal y como publica DTrace y aparece en la lista de resultados de dtrace -l.

uint_t ipl

El nivel de prioridad de interrupción (IPL) en la CPU actual en el momento de activar el sondeo. Consulte Solaris Internals (Funcionamiento de Solaris) para obtener más información acerca de los niveles de interrupción y la administración de las interrupciones en el núcleo del sistema operativo de Solaris.

lgrp_id_t lgrp

El ID de grupo de latencia correspondiente al grupo de latencia al que pertenece la CPU actual. Consulte el Capítulo 26Proveedor sched, para obtener más información.

pid_t pid

El ID de proceso del proceso actual. 

pid_t ppid

El ID de proceso principal del proceso actual. 

string probefunc

Parte del nombre de la función de la descripción del sondeo actual. 

string probemod

Parte del nombre del módulo de la descripción del sondeo actual. 

string probename

Parte del nombre de la descripción del sondeo actual. 

string probeprov

Parte del nombre del proveedor de la descripción del sondeo actual. 

psetid_t pset

ID de conjunto de procesadores correspondiente al conjunto de procesadores al que pertenece la CPU actual. Consulte el Capítulo 26Proveedor sched, para obtener más información.

string root

El nombre del directorio root del proceso asociado con el subproceso actual. 

uint_t stackdepth

Profundidad del marco de pila del subproceso actual en el momento de activar el sondeo. 

id_t tid

El ID de subproceso que identifica al subproceso actual. Para los subprocesos asociados a los procesos del usuario, este valor es igual al resultado de una llamada efectuada a pthread_self(3C).

uint64_t timestamp

El valor actual de un contador de marca de tiempo expresado en nanosegundos. Este contador aumenta a partir de un punto arbitrario en el pasado y sólo se debe usar para computaciones relativas. 

uid_t uid

El ID de usuario real del proceso actual. 

uint64_t uregs[]

Los valores de registro del modo de usuario guardados del subproceso actual en el momento de activar el sondeo. El uso de la matriz uregs[] se explica en el Capítulo 33Seguimiento de procesos de usuario.

uint64_t vtimestamp

El valor actual del contador de marcas de tiempo expresado en nanosegundos que se virtualiza hasta la cantidad de tiempo que el subproceso actual se ha estado ejecutando en la CPU menos el tiempo invertido en los predicados y las acciones de DTrace. Este contador aumenta a partir de un punto arbitrario en el pasado y sólo se debe usar para computaciones de tiempo relativas. 

uint64_t walltimestamp

El número actual de nanosegundos desde la hora coordinada universal 00:00 del 1 de enero de 1970. 

Las funciones integradas en el lenguaje D, por ejemplo trace(), se explican en el Capítulo 10Acciones y subrutinas.

Variables externas

D utiliza el carácter de comilla invertida (`) como un operador de ámbito especial para acceder a las variables que están definidas en el sistema operativo y no en el programa escrito en D. Por ejemplo, el núcleo de Solaris contiene una declaración escrita en C de un valor ajustable del sistema llamado kmem_flags para activar las funciones de depuración del asignador de memoria. Consulte Solaris Tunable Parameters Reference Manual para obtener más información sobre kmem_flags. Este valor ajustable se declara como una variable escrita en C en el código fuente del núcleo de la siguiente forma:

int kmem_flags;

Para acceder al valor de esta variable en un programa escrito en D, use la notación:

`kmem_flags

DTrace asocia cada símbolo del núcleo con el tipo usado para el símbolo en el código C del sistema operativo correspondiente, lo que proporciona un acceso sencillo basado en el código a las estructuras de datos nativas del sistema operativo. Para usar las variables externas del sistema operativo, necesitará acceder al código fuente del sistema operativo correspondiente.

Al acceder a las variables externas desde un programa escrito en D, se accede a los detalles de implementación internos de otro programa como, por ejemplo, el núcleo del sistema operativo o sus controladores de dispositivo. Estos detalles de implementación no constituyen una interfaz estable de la que haya que fiarse. Todos los programas en D que escriba y que dependan de estos detalles pueden dejar de funcionar la siguiente vez que actualice el software relacionado. Por esta razón, las variables externas las usan normalmente los desarrolladores de controladores de dispositivos y núcleos y el personal de servicio con objeto de depurar los problemas de rendimiento o de funcionalidad que se generan al usar DTrace. Para obtener más información acerca de la estabilidad de los programas escritos en D, consulte el Capítulo 39Estabilidad.

Los nombres de símbolo del núcleo se guardan en un espacio de nombre separado de la variable de D y los identificadores de función para que no tenga que preocuparse acerca de los conflictos de nombres con las variables escritas en D. Cuando se usa un acento grave como prefijo de una variable, el compilador D busca los símbolos del núcleo conocidos en orden usando la lista de módulos cargados para buscar una definición de variables coincidente. Dado que el núcleo de Solaris admite módulos cargados dinámicamente con espacios de nombre de símbolos separados, el mismo nombre de variable se puede usar más de una vez en el núcleo del sistema operativo activo. Puede resolver estos conflictos de nombres especificando el nombre del módulo del núcleo a cuya variable debe accederse antes de asignar un acento grave al nombre del símbolo. Por ejemplo, cada módulo del núcleo cargable proporciona habitualmente una función _fini(9E), por lo que para hacer referencia a la dirección de la función _fini proporcionada por el módulo del núcleo llamado foo, debería escribir:

foo`_fini

Puede aplicar cualquiera de los operadores D a variables externas, excepto aquellos que modifiquen valores, según las reglas habituales para los tipos de operando. Cuando se inicia DTrace, el compilador D carga el conjunto de nombres de variable correspondientes a los módulos del núcleo activo, por lo que las declaraciones de estas variables no se requieren. No se puede aplicar ningún operador a una variable externa que modifique su valor como = o +=. Por motivos de seguridad, DTrace impide que se dañe el estado del software que se está observando.