Manuel de suivi dynamique Solaris

Chapitre 40 Translateurs

Vous avez appris dans le Chapitre39Stabilité comment DTrace calcule et signale les attributs de stabilité du programme. Nous aimerions, dans l'idéal, créer nos programmes DTrace en n'utilisant que des interfaces stables ou évolutives. Malheureusement, lors du débogage d'un problème de bas niveau ou de l'évaluation des performances du système, il est possible que vous souhaitiez activer des sondes associées à des routines du système d'exploitation interne comme les fonctions présentes dans le noyau, plutôt que des sondes associées à des interfaces plus stables comme les appels système. Les données disponibles à l'emplacement des sondes et figurant tout au bas de la pile logicielle constituent souvent un ensemble d'artefacts d'implémentation plutôt que des structures de données plus fiables comme celles associées aux interfaces d'appel système de Solaris. Afin de vous assister dans l'écriture de programmes en D stables, DTrace propose une fonction de conversion des artefacts d'implémentation en structures de données stables accessibles à partir des instructions des programmes en D.

Déclarations du translateur

Un translateur est un ensemble d'instructions d'affectation en D proposées par le fournisseur d'une interface permettant de convertir une expression d'entrée en un objet de type struct. Afin de comprendre la nécessité des translateurs et de leur utilisation, examinons les routines de bibliothèque standard ANSI-C définies dans stdio.h. Ces routines fonctionnent sur une structure de données nommée FILE dont les artefacts d'implémentation sont retirés par les programmeurs en langage C. Une technique standard de création d'une abstraction de structure de données ne consiste qu'à faire suivre une déclaration de structure de données dans des fichiers d'en-tête publics tout en conservant la définition struct correspondante dans un fichier d'en-tête privé.

Si vous écrivez un programme en C et souhaitez connaître le descripteur de fichier correspondant à une struct FILE, vous pouvez utiliser la fonction fileno(3C) pour obtenir le descripteur au lieu de déréférencer directement un membre de la struct FILE. Les fichiers d'en-tête Solaris appliquent cette règle en définissant FILE comme une balise de suivi de déclaration opaque de manière à empêcher son déréférencement direct par les programmes en C qui intègrent <stdio.h>. Dans la bibliothèque libc.so.1, vous pouvez imaginer que la fonction fileno() est implémentée dans C de la manière suivante :

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

	return (ip->fd);
}

Notre exemple de fonction fileno() utilise en argument un pointeur FILE et le transmet à un pointeur sur une structure libc interne correspondante, struct file_impl, puis retourne la valeur du membre fd de la structure d'implémentation. Pourquoi Solaris implémente-t-il des interfaces de cette manière ? La suppression des détails de l'implémentation actuelle de libc des programmes client permet à Sun de préserver une compatibilité binaire forte tout en continuant à évoluer et à modifier les détails d'implémentation interne de libc. Dans notre exemple, le membre fd peut changer de taille ou de position dans struct file_impl, y compris dans un correctif, alors que les codes binaires existants appelant fileno(3C) ne sont pas affectés par cette modification, car ils ne dépendent pas de ces artefacts.

Malheureusement, les logiciels d'observation comme DTrace doivent inspecter l'implémentation de l'intérieur pour fournir des résultats utiles sans se permettre le luxe d'appeler des fonctions arbitraires en C définies dans les bibliothèques de Solaris ou dans le noyau. Vous pouvez déclarer une copie de struct file_impl dans vos programmes en D pour instrumenter les routines déclarées dans stdio.h. Cependant, votre programme en D risque de reposer sur des artefacts d'implémentation privés de la bibliothèque qui ne seront peut-être plus valables dans une future micro-version ou version mineure, voire même dans un prochain correctif. Nous souhaitons, dans l'idéal, fournir une construction à utiliser dans les programmes en D, construction qui est liée à l'implémentation de la bibliothèque et mise à jour en conséquence tout en continuant de fournir une couche supplémentaire d'abstraction associée à une meilleure stabilité.

Un nouveau translateur est créé à l'aide d'une déclaration se présentant comme suit :

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

output-type nomme une struct qui sera le type de résultat de la conversion. input-type spécifie le type de l'expression d'entrée et figure entre crochets angulaires < >. Il est suivi de input-identifier que vous pouvez utiliser en tant qu'alias pour l'expression d'entrée dans les expressions du translateur. Le corps du translateur figure entre accolades { } et se termine par un point-virgule (;). Il consiste en une liste de member-name et d'identificateurs correspondant aux expressions de conversion. Chaque déclaration de membre doit nommer un membre unique de output-type et doit se voir affecter une expression dont le type est compatible avec celui du membre, conformément aux règles de l'opérateur d'affectation en D (=).

Par exemple, nous pouvons définir une struct d'informations stables sur les fichiers stdio en fonction de quelques-unes des interfaces libcdisponibles :

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

Il est ensuite possible de déclarer en langage D un translateur en D hypothétique de FILE vers file_info comme suit :

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

Dans notre translateur hypothétique, l'expression d'entrée est de type FILE * et input-identifier F lui est affecté. Il est ensuite possible d'utiliser l'identificateur F dans les expressions du membre du translateur comme une variable de type FILE * qui n'est visible que dans le corps de la déclaration du translateur. Pour déterminer la valeur du membre de sortie file_fd, le translateur exécute un forçage de type et un déréférencement identiques à l'implémentation hypothétique de fileno(3C), illustrée ci-dessus. Une conversion similaire est réalisée pour obtenir la valeur de l'indicateur EOF.

Sun fournit un ensemble de translateurs à utiliser avec les interfaces Solaris que vous pouvez invoquer à partir de vos programmes en D et promet de conserver ces translateurs conformément aux règles sur la stabilité des interfaces définies précédemment, au moment des changements dans l'implémentation de l'interface correspondante. Nous étudierons ces translateurs un peu plus loin dans ce chapitre, une fois que vous saurez invoquer des translateurs à partir du langage D. La fonction de conversion elle-même est également fournie par l'application et les développeurs de bibliothèque qui souhaitent proposer leurs propres translateurs que les programmeurs en C peuvent utiliser pour observer l'état de leurs packages logiciels.

Opérateur de conversion

L'opérateur D xlate permet de convertir une expression d'entrée en une expression des structures de sortie de conversion définies. L'opérateur xlate est utilisé dans une expression identique à l'exemple suivant :

xlate < output-type > ( input-expression )

Par exemple, pour invoquer un translateur hypothétique pour les structs FILE définies ci-dessus et accéder au membre file_fd, vous devriez utiliser l'expression suivante :

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

f correspondant à une variable en D du type FILE *. L'expression xlate elle-même se voit attribuer le type défini par output-type. Un translateur, après avoir été défini, permet de convertir des expressions d'entrée en type de struct de sortie du translateur ou en pointeur vers cette struct.

Si vous convertissez une expression d'entrée en struct, vous pouvez soit déréférencer un membre particulier de la sortie immédiatement à l'aide de l'opérateur “.” soit affecter l'intégralité de la struct convertie à une autre variable D pour réaliser une copie des valeurs de tous les membres. S vous déréférencez un seul membre, le compilateur D ne générera que le code correspondant à l'expression de ce membre. Vous ne pouvez pas appliquer l'opérateur & à une struct convertie pour obtenir son adresse étant donné que l'objet de données lui-même n'existe pas tant qu'il n'est pas copié ou qu'un de ses membres n'est pas référencé.

Si vous convertissez une expression d'entrée vers un pointeur en struct, vous pouvez soit déréférencer un membre particulier de la sortie immédiatement à l'aide de l'opérateur ->, soit déréférencer le pointeur à l'aide de l'opérateur unaire *, auquel cas le résultat se comporte comme si vous traduisiez l'expression en une struct. S vous déréférencez un seul membre, le compilateur D ne générera que le code correspondant à l'expression de ce membre. Vous ne pouvez pas affecter de pointeur converti en une autre variable en D étant donné que l'objet de donnée en lui-même n'existe pas tant qu'il n'a pas été copié ou que l'un de ses membres n'a pas été référencé car, de fait, son adressage est impossible.

Une déclaration du translateur peut omettre les expressions d'un ou de plusieurs membres du type de sortie. Si une expression xlate est utilisée pour accéder à un membre pour lequel aucune expression de conversion n'est définie, le compilateur D engendre un message d'erreur approprié et interrompt la compilation du programme. Si l'intégralité du type de sortie est copié au moyen d'une affectation de structure, tous les membres pour lesquels aucune expression de conversion n'a été définie sont remplis de zéros.

Afin de trouver un translateur correspondant à une opération xlate, le compilateur D examine l'ensemble des translateurs disponibles dans l'ordre suivant :

Si le compilateur D ne trouve aucun translateur conforme à ces règles, il produit un message d'erreur approprié et la compilation du programme échoue.

Translateurs du modèle de processus

Le fichier de bibliothèque de DTrace /usr/lib/dtrace/procfs.d fournit un ensemble de translateurs à utiliser avec vos programmes en D pour convertir à partir du noyau du système d'exploitation des structures d'implémentation des processus et des threads en structures proc(4) psinfo et lwpsinfo stables. Ces structures sont également utilisées dans les fichiers du système de fichiers /proc de Solaris, /proc/pid/psinfo et /proc/pid/lwps/lwpid/lwpsinfo, et sont définies dans le fichier d'en-tête du système /usr/include/sys/procfs.h. Ces structures définissent les informations stables utiles sur les processus et les threads comme l'ID de processus, l'ID LWP, les arguments initiaux et les autres données affichées par la commande ps(1). Reportez-vous à proc(4) pour obtenir une description complète des membres et de la sémantique des structs.

Tableau 40–1 Translateurs procfs.d

Type d'entrée 

Attributs du type d'entrée 

Type de sortie 

Attributs du type de sortie 

proc_t *

Privé/Privé/Commun 

psinfo_t *

Stable/Stable/Commun 

kthread_t *

Privé/Privé/Commun 

lwpsinfo_t *

Stable/Stable/Commun 

Conversions stables

Alors qu'un translateur permet de convertir des informations en structure de données stables, il ne résout pas nécessairement tous les problèmes de stabilité que les données en cours de conversion peuvent rencontrer. Par exemple, si l'expression d'entrée d'une opération xlate référence elle-même des données instables, le programme en D qui en résulte est également instable car la stabilité du programme est toujours calculée sur la base de la stabilité minimale des instructions et des expressions accumulées du programme en D. Il est, par conséquent, parfois nécessaire de définir pour un translateur une expression d'entrée stable spécifique afin de permettre la création de programmes stables. Il est possible d'utiliser le mécanisme intégré en langage D pour faciliter ces conversions stables.

La bibliothèque de DTrace procfs.d fournit les variables curlwpsinfo et curpsinfo décrites précédemment comme des conversions stables. Par exemple, la variable curlwpsinfo est en fait une déclaration inline se présentant comme suit :

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

La variable curlwpsinfo est définie comme une conversion intégrée de la variable curthread (un pointeur vers la structure de données privée du noyau) vers le type lwpsinfo_t stable. Le compilateur D traite ce fichier de bibliothèque et met en cache la déclaration inline, faisant ainsi apparaître curlwpsinfo comme une autre variable en D. L'instruction #pragma qui suit la déclaration est utilisée pour rétablir explicitement les attributs de l'identificateur curlwpsinfo sur Stable/Stable/Commun, masquant la référence à curthread dans l'expression intégrée. Grâce à cette combinaison de fonctionnalités en langage D, les programmeurs en D peuvent utiliser curthread comme source de conversion dans un mode sécurisé que Sun peut mettre à jour en fonction des changements correspondants dans l'implémentation de Solaris.