Manuel de suivi dynamique Solaris

Chapitre 3 Variables

Le langage D fournit deux types de variable à utiliser dans vos programmes de suivi : les variables scalaires et les tableaux associatifs. Les exemples du chapitre 1 illustraient brièvement l'utilisation de ces variables. Ce chapitre présente de façon approfondie les règles relatives aux variables en D et explique comment associer ces variables à diverses étendues. Un type spécial de variable de tableau, appelé groupement, est présenté dans le Chapitre9Groupements.

Variables scalaires

Les variables scalaires servent à représenter les objets de données individuels de taille fixe, comme les nombres entiers et les pointeurs. Vous pouvez également utiliser des variables scalaires pour les objets à taille fixe constitués d'un ou de plusieurs types de primitive ou composites. Le langage D permet de créer des tableaux et des objets, ainsi que des structures composites. DTrace représente aussi des chaînes sous forme de données scalaires de taille fixe en leur permettant de grossir jusqu'à une longueur maximale prédéfinie. Le contrôle de la longueur des chaînes dans votre programme en D est abordé plus en détails dans le Chapitre6Chaînes de caractères.

Les variables scalaires sont créées automatiquement la première fois que vous attribuez une valeur à un identificateur précédemment non défini dans votre programme en D. Par exemple, pour créer la variable scalaire x de type int, vous pouvez simplement affecter une valeur de type int dans la clause d'une sonde :

BEGIN
{
	x = 123;
}

Les variables scalaires créées de cette manière sont des variables globales : leur nom et leurs données de stockage sont définis une fois et sont visibles dans chaque clause de votre programme en D. Chaque fois que vous référencez l'identificateur x, vous faites référence à un emplacement de stockage unique lié à cette variable.

Contrairement au langage ANSI-C, le langage D ne demande pas de déclarations de variables explicites. Si vous souhaitez déclarer une variable globale pour affecter son nom et son type explicitement avant de l'utiliser, vous pouvez placer une déclaration à l'extérieur des clauses de la sonde dans votre programme, comme illustré dans l'exemple suivant. Les déclarations de variables explicites ne sont pas requises dans la plupart des programmes en D mais peuvent s'avérer utiles lorsque vous souhaitez contrôler soigneusement vos types de variable ou lorsque vous souhaitez commencer votre programme par un ensemble de déclarations et de commentaires documentant les variables de votre programme et leur signification.

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

BEGIN
{
	x = 123;
	...
}

Contrairement au langage ANSI-C, la déclaration de variables en langage D peut ne pas affecter de valeurs initiales. Vous devez utiliser une clause de sonde BEGIN pour affecter n'importe quelle valeur initiale. Le stockage de variable globale est rempli de zéros par DTrace avant que vous ne référenciez la variable.

La définition du langage D n'impose aucune limite de taille ni de nombre de variables en D. Par contre, une limite est définie par l'implémentation de DTrace et par la mémoire disponible sur votre système. Le compilateur D mettra en œuvre toutes les restrictions qu'il est possible d'appliquer au moment de la compilation de votre programme. Vous découvrirez de plus amples informations sur l'ajustement des options en rapport avec les limites du programme dans le Chapitre16Options et paramètres réglables.

Tableaux associatifs

Les tableaux associatifs servent à représenter les ensembles d'éléments de données que vous pouvez récupérer en spécifiant un nom appelé une clé. Les clés de tableau associatif en D sont constituées de valeurs d'expression scalaires appelées un tuple. Vous pouvez imaginer le tuple de tableau lui-même comme une liste de paramètres imaginaires vers une fonction appelée pour récupérer la valeur de tableau correspondante lors du référencement du tableau. Chaque tableau associatif en D comporte une signature de clé fixe consistant en un nombre fixe d'éléments de tuple, chaque élément ayant un type fixe donné. Vous pouvez définir différentes signatures de clé par tableau dans votre programme en D.

Les tableaux associatifs diffèrent des tableaux normaux de taille fixe en ce qu'ils n'ont aucune limite prédéfinie en matière de nombre d'éléments. Les éléments peuvent être indexés par un tuple par opposition à la simple utilisation de nombres entiers comme clés. Par ailleurs, les éléments ne sont pas enregistrés dans des emplacements de stockage consécutifs pré-alloués. Les tableaux associatifs sont utiles dans les situations dans lesquelles vous devez utiliser une table de hachage ou une autre structure de données de dictionnaire simple dans un programme en langage C, C++ ou Java. TM Les tableaux associatifs vous permettent de créer un historique dynamique des événements et de l'état capturé dans votre programme en D que vous pouvez utiliser pour créer des flux de contrôle plus complexes.

Pour définir un tableau associatif, vous écrivez une expression d'affectation du formulaire :

name [ key ] = expression ;

name correspond à un identificateur D valide et key à une liste séparée par des virgules d'une ou plusieurs expressions. Par exemple, l'instruction suivante définit un tableau associatif a avec la signature de clé [ int, string ] et enregistre la valeur entière 456 à l'emplacement nommé par le tuple [ 123, "hello" ]:

a[123, "hello"] = 456;

Le type de chaque objet contenu dans le tableau est également fixe pour tous les éléments d'un tableau donné. Comme a a été affecté en premier à l'aide du nombre entier 456, chaque valeur ultérieure enregistrée dans le tableau aura également comme type int. Vous pouvez utiliser les opérateurs d'affectation définis dans le chapitre 2 pour modifier les éléments de tableau associatif en fonction des règles d'opérande définies par l'opérateur. Le compilateur D crée un message d'erreur approprié si vous tentez une affectation incompatible. Vous pouvez utiliser les mêmes types avec une clé ou une valeur de tableau associatif que ceux utilisés avec une variable scalaire. Vous ne pouvez pas imbriquer un tableau associatif dans un autre sous forme de clé ou de valeur.

Vous pouvez référencer un tableau associatif à l'aide d'un tuple compatible avec la signature de la clé du tableau. Les règles de compatibilité du tuple sont similaires à celles des appels de fonction et des affectations de variable : La longueur du tuple doit être la même et chaque type de la liste de paramètres réels doit être compatible avec le type correspondant dans la signature de clé formelle. Par exemple, si un tableau associatif x est défini comme suit :

x[123ull] = 0;

la signature de clé est de type unsigned long long et les valeurs sont de type int. Vous pouvez également référencer ce tableau à l'aide de l'expression x['a'], car le tuple consistant en une constante caractère 'a' de type int et en une longueur de 1 est compatible avec la signature de clé unsigned long long conformément aux règles de conversion arithmétique décrites à la section Conversions de types.

Si vous devez déclarer de manière explicite un tableau associatif en D avant de l'utiliser, vous pouvez créer une déclaration du nom du tableau et de la signature de clé en dehors des clauses de la sonde dans le code source de votre programme :

int x[unsigned long long, char];

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

Une fois un tableau associatif défini, les références à un tuple d'une signature de clé compatible sont autorisées même si le tuple en question n'a pas été affecté précédemment. L'accès à un élément de tableau associatif non affecté est défini pour revenir sur un objet rempli de zéros. Cette définition a notamment pour conséquence que le stockage sous-jacent n'est pas alloué à un élément de tableau associatif tant qu'une valeur différente de zéro est affectée à cet élément. À l'inverse, l'affectation de la valeur zéro à cet élément de tableau associatif pousse DTrace à supprimer l'allocation de stockage sous-jacent. Ce comportement est important car l'espace d'adressage dynamique en dehors duquel les éléments du tableau sont alloués est fini ; si cet espace est épuisé lors d'une tentative d'allocation, cette dernière échoue et un message d'erreur est généré indiquant un abandon de variable dynamique. Affectez toujours des zéros aux éléments de tableaux associatifs qui ne sont plus utilisés. Pour connaître d'autres techniques visant à supprimer les abandons de variables dynamiques, reportez-vous au Chapitre16Options et paramètres réglables.

Variables locales de thread

DTrace permet de déclarer le stockage des variables locales sur chaque thread du système d'exploitation, par opposition aux variables globales comme indiqué précédemment dans ce chapitre. Les variables locales de thread sont utiles lorsque vous souhaitez activer une sonde et marquer chaque thread qui déclenche la sonde avec une balise ou d'autres données. Il est aisé de créer un programme pour résoudre ce problème en langage D car les variables locales de thread partagent un nom commun dans votre code en D mais font référence à un stockage de données distinct associé à chaque thread. Les variables locales de thread sont référencées en appliquant l'opérateur -> à un identificateur self spécial :

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

Cet exemple en langage D active la sonde sur l'appel système read(2) et associe la variable locale de thread read à chaque thread qui déclenche la sonde. De la même manière que les variables globales, les variables locales de thread sont créées automatiquement sur leur première affectation et adoptent le type utilisé à droite de la première instruction d'affectation (dans cet exemple, int).

Chaque fois que la variable self->read est référencée dans votre programme en D, l'objet de données référencé correspond à l'objet associé au thread du système d'exploitation en cours d'exécution au déclenchement de la sonde de DTrace correspondante. Vous pouvez imaginer une variable locale de thread comme un tableau associatif implicitement indexé par un tuple qui décrit l'identité du thread dans le système. L'identité d'un thread est unique tout au long de la durée de vie du système : si le thread est fermé et que la même structure de données du système d'exploitation est utilisée pour créer un nouveau thread, ce dernier ne réutilise pas la même identité de stockage local de thread de DTrace.

Une fois que vous avez défini une variable locale de thread, vous pouvez la déréférencer pour n'importe quel thread dans le système même si la variable en question n'a pas été précédemment affectée à ce thread particulier. Si la copie d'un thread d'une variable locale de thread n'a pas encore été affectée, le stockage des données dédié à la copie est défini pour être rempli de zéros. De même qu'avec les éléments de tableau associatif, le stockage sous-jacent n'est pas alloué à une variable locale de thread tant qu'une valeur différente de zéro lui est affectée. De la même manière, l'affectation de zéro à une variable locale de thread entraîne la suppression de l'allocation de stockage sous-jacent au niveau de DTrace. Affectez toujours zéro aux variables locales de thread que vous n'utilisez plus. Pour découvrir les autres techniques de réglage de l'espace des variables dynamiques à partir duquel les variables locales de thread sont allouées, reportez-vous au Chapitre16Options et paramètres réglables.

Vous pouvez définir les variables locales de thread, quel que soit leur type, dans vos programmes en D, y compris les tableaux associatifs. Voici quelques exemples de définitions de variables locales de thread :

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

De même que les variables en langage D, vous ne pouvez pas déclarer explicitement les variables locales de thread avant de les utiliser. Si vous souhaitez malgré tout créer une déclaration, vous pouvez en placer une en dehors des clauses de votre programme en ajoutant initialement le mot-clé self :

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

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

Les variables locales de thread sont conservées dans un espace de noms distinct des variables globales. Vous pouvez donc réutiliser les noms. N'oubliez pas que x et self->x sont deux variables différentes si vous chargez des noms dans votre programme ! L'exemple suivant présente la méthode d'utilisation des variables locales de thread. Dans un éditeur de texte, entrez le programme suivant et enregistrez-le dans le fichier rtime.d :


Exemple 3–1 rtime.d : calcul du temps passé dans 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;
}

Accédez à présent au shell et lancez le programme. Patientez quelques secondes avant de voir apparaître une sortie. En l'absence de sortie, essayez d'exécuter quelques commandes.


# 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 utilise la variable locale de thread t pour capturer un horodatage à l'entrée de read(2) par n'importe quel thread. Dans la clause de retour, le programme imprime ensuite le temps passé dans read(2) en retirant self->t de l'horodatage actuel. Les variables en D intégrées pid et tid indiquent l'ID de processus et l'ID de thread exécutant read(2). Comme self->t n'est plus requis une fois ces informations reportées, la valeur 0 lui est affectée pour permettre à DTrace de réutiliser le stockage sous-jacent associé à t pour le thread actuel.

En règle générale, vous voyez s'afficher de nombreuses lignes de sortie sans rien faire car, en coulisses, les processus serveur et les démons exécutent read(2) en permanence et ce, même lorsque vous ne faites rien. Essayez de modifier la seconde clause de rtime.d afin d'utiliser la variable execname servant à imprimer le nom du processus exécutant read(2) dans le but d'en apprendre davantage à son sujet :

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

Si vous découvrez un processus particulièrement intéressant, ajoutez un prédicat pour en découvrir davantage sur son comportement read(2) :

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

Variables locales de clause

Vous pouvez également définir les variables en D dont le stockage est réutilisé pour chaque clause du programme en D. Les variables locales de clause sont similaires aux variables automatiques des programmes en langage C, C++ ou Java actifs pendant chaque invocation d'une fonction. Comme toutes les variables de programme en D, les variables locales de clause sont créées à leur première affectation. Vous pouvez référencer et affecter ces variables en appliquant l'opérateur -> à l'identificateur spécial this :

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

Si vous souhaitez déclarer de manière explicite une variable locale de clause avant de l'utiliser, vous pouvez le faire à l'aide du mot-clé this :

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

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

Les variables locales de clause ne sont actives que pendant la durée de vie d'une clause de sonde donnée. Une fois que DTrace a exécuté les actions liées à ces clauses pour une sonde donnée, le stockage de toutes les variables locales de clause est récupéré et réutilisé pour la clause suivante. C'est pourquoi les variables locales de clause constituent les seules variables en D à ne pas contenir initialement que des zéros. Notez que si votre programme contient plusieurs clauses pour une seule sonde, toutes les variables locales de clause resteront intactes lors de l'exécution des clauses, comme illustré dans l'exemple suivant :


Exemple 3–2 clause.d : variables locales de clause

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

Comme les clauses sont toujours exécutées dans l'ordre du programme et que les variables locales de clause sont persistantes à travers les différentes clauses activant la même sonde, l'exécution du programme ci-dessus entraîne toujours la même sortie :


# 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

Alors que les variables locales de clause sont persistantes à travers les clauses activant la même sonde, leurs valeurs restent indéfinies dans la première clause exécutée pour une sonde donnée. Veillez à affecter chaque variable locale de clause à une valeur appropriée avant de l'utiliser ou votre programme risque de produire un résultat inattendu.

Vous pouvez définir les variables locales de clause à l'aide de n'importe quel type de variable scalaire mais pas les tableaux associatifs au moyen d'une étendue locale de clause. L'étendue des variables locales de clause ne s'applique qu'aux données de variable correspondantes et non à l'identité du nom et du type définie dans la variable. Après avoir défini une variable locale de clause, vous pouvez utiliser le nom et la signature de type correspondants dans n'importe quelle clause de programme en D ultérieure. Vous ne pouvez pas espérer que l'emplacement de stockage soit le même entre les différentes clauses.

Vous pouvez utiliser des variables locales de clause pour accumuler des résultats intermédiaires de calculs ou comme copies temporaires d'autres variables. Il est plus rapide d'accéder à une variable locale de clause qu'à un tableau associatif. Par conséquent, si vous devez référencer à plusieurs reprises une valeur de tableau associatif dans la même clause d'un programme en D, vous gagnerez en efficacité en la copiant dans une variable locale de clause, puis en référençant cette dernière à plusieurs reprises.

Variables intégrées

La table suivante contient la liste complète des variables en D intégrées. Toutes ces variables sont des variables globales scalaires ; les variables locales de thread ou de clause ou les tableaux associatifs ne sont pas définis en langage D pour le moment.

Tableau 3–1 Variables intégrées de DTrace

Type et nom 

Description 

int64_t arg0, ..., arg9

Dix premiers arguments d'entrée vers une sonde représentés comme des nombres entiers bruts de 64 bits. Si moins de dix arguments sont transmis à la sonde actuelle, les variables restantes retournent à zéro. 

args[]

Arguments type de la sonde actuelle, le cas échéant. Le tableau args[] est accessible au moyen d'un index de nombres entiers mais chaque élément est défini pour représenter le type correspondant à l'argument de sonde donné. Par exemple, si args[] est référencé par une sonde d'appel système read(2), args[0] appartient au type int, args[1] au type void * et args[2] au type size_t.

uintptr_t caller

Emplacement du compteur du programme du thread actuel juste avant d'entrer dans la sonde actuelle. 

chipid_t chip

Identificateur de puce de CPU de la puce physique actuelle. Pour plus d'informations, reportez-vous au Chapitre26Fournisseur sched.

processorid_t cpu

Identificateur de la CPU actuelle. Pour plus d'informations, reportez-vous au Chapitre26Fournisseur sched.

cpuinfo_t *curcpu

Informations sur la CPU actuelle. Pour plus d'informations, reportez-vous au Chapitre26Fournisseur sched.

lwpsinfo_t *curlwpsinfo

État du processus léger (LWP) de LWP associé au thread actuel. Cette structure est décrite plus en détails dans la page de manuel proc(4).

psinfo_t *curpsinfo

État du processus lié au thread actuel. Cette structure est décrite plus en détails dans la page de manuel proc(4).

kthread_t *curthread

Adresse de la structure des données internes du noyau du système d'exploitation du thread actuel, kthread_t. kthread_t est défini dans <sys/thread.h>. Reportez-vous aux Données internes sur Solaris pour plus d'informations sur cette variable et les autres structures de données du système d'exploitation.

string cwd

Nom du répertoire de travail actuel du processus associé au thread actuel. 

uint_t epid

ID de sonde activée (EPID) de la sonde actuelle. Ce nombre entier n'identifie qu'une sonde particulière qui est activée par un prédicat et un ensemble d'actions spécifiques. 

int errno

Valeur d'erreur retournée par le dernier appel système exécuté par ce thread. 

string execname

Nom transmis à exec(2) pour exécuter le processus actuel.

gid_t gid

ID de groupe réel du processus actuel. 

uint_t id

ID de sonde de la sonde actuelle. Cet ID constitue l'identificateur unique à l'échelle du système de la sonde tel qu'émis par DTrace et répertorié dans la sortie de dtrace -l.

uint_t ipl

Niveau de priorité d'interruption (IPL) sur la CPU actuelle au moment du déclenchement de la sonde. Reportez-vous aux Données internes sur Solaris pour plus d'informations sur les niveaux d'interruption et la gestion des interruptions dans le noyau du système d'exploitation de Solaris.

lgrp_id_t lgrp

ID du groupe de latence auquel la CPU actuelle appartient. Pour plus d'informations, reportez-vous au Chapitre26Fournisseur sched.

pid_t pid

ID de processus du processus actuel. 

pid_t ppid

ID de processus parent du processus actuel. 

string probefunc

Partie du nom de fonction de la description de la sonde actuelle. 

string probemod

Partie du nom de module de la description de la sonde actuelle. 

string probename

Partie du nom de la description de la sonde actuelle. 

string probeprov

Partie du nom du fournisseur de la description de la sonde actuelle. 

psetid_t pset

ID de l'ensemble de processeurs contenant la CPU actuelle. Pour plus d'informations, reportez-vous au Chapitre26Fournisseur sched.

string root

Nom du répertoire root du processus associé au thread actuel. 

uint_t stackdepth

Profondeur du cadre de la pile du thread actuel au moment du déclenchement de la sonde. 

id_t tid

ID du thread actuel. Pour les threads liés aux processus utilisateur, cette valeur équivaut au résultat d'un appel vers pthread_self(3C).

uint64_t timestamp

Valeur actuelle d'un compteur d'horodatage en nanosecondes. Ce compteur procède à l'incrémentation à partir d'un point arbitraire antérieur et ne doit être utilisé que pour la réalisation de calculs relatifs. 

uid_t uid

ID de l'utilisateur réel du processus actuel. 

uint64_t uregs[]

Valeurs d'enregistrement en mode utilisateur de sauvegarde du thread actuel au moment du déclenchement de la sonde. L'utilisation du tableau uregs[] est présentée dans le Chapitre33Suivi des processus utilisateur.

uint64_t vtimestamp

Valeur actuelle d'un compteur d'horodatage en nanosecondes, virtualisée par la durée d'exécution du thread actuel sur la CPU moins le temps passé dans les prédicats et actions de DTrace. Ce compteur procède à l'incrémentation à partir d'un point arbitraire antérieur et ne doit être utilisé que pour la réalisation de calculs temporels relatifs. 

uint64_t walltimestamp

Nombre actuel de nanosecondes depuis 00:00 (heure coordonnée universelle) le 1er janvier 1970. 

Les fonctions intégrées au langage D, comme trace() sont présentées dans le Chapitre10Actions et sous-routines.

Variables externes

Le langage D utilise le caractère ` comme opérateur d'étendue spécial pour accéder aux variables définies dans le système d'exploitation et non dans votre programme en D. Par exemple, le noyau de Solaris contient une déclaration en C du réglage système kmem_flags pour activer les fonctions de débogage du programme d'allocation de mémoire. Reportez-vous au manuel Solaris Tunable Parameters Reference Manual pour plus d'informations sur kmem_flags. Ce réglage est déclaré sous forme de variable en C dans la source du noyau comme suit :

int kmem_flags;

Pour accéder à la valeur de cette variable dans un programme en D, utilisez la notation en D suivante :

`kmem_flags

DTrace associe chaque symbole de noyau au type utilisé pour le symbole dans le code en C correspondant du système d'exploitation, simplifiant ainsi l'accès basé sur la source aux structures de données du système d'exploitation natif. Pour utiliser les variables externes du système d'exploitation, vous devez accéder au code source correspondant du système d'exploitation.

Lorsque vous accédez à des variables externes à partir d'un programme en D, vous accédez aux détails d'implémentation interne d'un autre programme comme le noyau du système d'exploitation ou ses pilotes de périphérique. Ces détails d'implémentation ne constituent pas une interface stable sur laquelle vous pouvez compter ! Tout programme en D que vous écrivez et qui repose sur ces détails peut être interrompu lors de la prochaine mise à jour de la partie logicielle correspondante. C'est pourquoi, les variables externes sont généralement utilisées par les développeurs de noyaux et de pilotes de périphérique, ainsi que par les techniciens de maintenance, pour déboguer les problèmes de performances ou de fonctionnalités à l'aide de DTrace. Pour plus d'informations sur la stabilité de vos programmes en D, reportez-vous au Chapitre39Stabilité.

Les noms de symbole de noyau sont conservés dans un espace de noms distinct des identificateurs de fonctions et de variables en D de sorte que vous n'ayiez jamais à vous préoccuper des conflits de noms avec vos variables en D. Lorsque vous ajoutez le préfixe ` à une variable, le compilateur D recherche les symboles de noyau connus dans la liste de modules chargés pour trouver une définition de variable correspondante. Comme le noyau de Solaris prend en charge les modules chargés de manière dynamique avec les espaces de noms distincts, le même nom de variable peut être utilisé plusieurs fois dans le noyau actif du système d'exploitation. Vous pouvez résoudre ces conflits de noms en spécifiant le nom du module de noyau dont l'accès à la variable doit être exécuté avant l'accès au symbole ` dans le nom du symbole. Par exemple, chaque module de noyau chargeable fournit généralement une fonction _fini(9E). Pour renvoyer à l'adresse de la fonction _fini fournie par le module de noyau foo, vous devez écrire :

foo`_fini

Vous pouvez appliquer des opérateurs en D aux variables externes, à l'exception de ceux qui modifient des valeurs, en fonction des règles usuelles des types d'opérande. Lorsque vous lancez DTrace, le compilateur en D charge l'ensemble des noms de variable correspondant aux modules de noyau actifs, par conséquent, les déclarations de ces variables ne sont pas requises. Vous ne pouvez pas appliquer d'opérateur modifiant sa valeur à une variable externe, comme = ou +=. À des fins de sécurité, DTrace vous empêche d'endommager ou de corrompre l'état du logiciel que vous observez.