Handbuch zur dynamischen Ablaufverfolgung in Solaris

Kapitel 40 Übersetzer

In Kapitel 39Stabilität haben Sie erfahren, wie DTrace die Stabilitätsattribute von Programmen berechnet und meldet. Idealerweise möchten wir DTrace-Programme erstellen, die ausschließlich Schnittstellen der Stabilität „Stable“ oder „Evolving“ verbrauchen. Leider kommt man zum Debuggen von Problemen auf einer tiefen Programmebene oder beim Messen der Systemleistung manchmal nicht umhin, Prüfpunkte für interne Betriebssystemroutinen wie etwa Funktionen im Kernel zu aktivieren, anstatt Prüfpunkte für eher stabilere Schnittstellen wie beispielsweise Systemaufrufe. Bei den Daten an den tief im Software-Stack liegenden Prüfpunkten handelt es sich oft nicht um stabile Datenstrukturen wie etwa die Solaris-Systemaufrufschnittstellen, sondern um eine Sammlung von Artefakten der Implementierung. DTrace unterstützt Sie beim Schreiben stabiler D-Programme mit einer Einrichtung zum Übersetzen von Implementierungsartefakten in stabile Datenstrukturen, auf die über die D-Programmanweisungen zugegriffen werden kann.

Übersetzerdeklarationen

Ein Übersetzer ist eine Sammlung von D-Zuweisungsanweisungen, die der Hersteller einer Schnittstelle bereitstellt und die dazu dient, einen Eingangsausdruck in ein Objekt des Typs struct zu übersetzen. Zur Erklärung des Nutzens und der Verwendung von Übersetzern ziehen wir die in stdio.h definierten ANSI-C-Standardbibliotheksroutinen als Beispiel heran. Diese Routinen wirken auf eine Datenstruktur namens FILE, deren Implementierungsartefakte fern von C-Programmierern abstrahiert sind. Eine Standardtechnik zum Erzeugen einer Datenstrukturabstraktion besteht darin, in öffentlichen Definitionsdateien nur eine Vorausdeklaration einer Datenstruktur bereitzustellen und die zugehörige struct-Definition in einer separaten, privaten Definitionsdatei zu führen.

Wenn Sie ein C-Programm schreiben und den Dateibezeichner für eine FILE-Struktur kennen möchten, können Sie, anstatt eine Komponente der FILE-Struktur direkt zu dereferenzieren, mit der Funktion fileno(3C) den Bezeichner abrufen. Die Solaris-Definitionsdateien setzen diese Regel um, indem sie FILE als ein „undurchsichtiges“ Vordeklarations-Tag definieren, das von C-Programmen, die <stdio.h> enthalten, nicht direkt dereferenziert werden kann. Man kann sich die Implementierung von fileno() innerhalb der Bibliothek libc.so.1 in C ungefähr wie folgt vorstellen:

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

	return (ip->fd);
}

Unser hypothetisches fileno() übernimmt einen FILE-Zeiger als Argument, wandelt ihn in den der internen libc-Struktur entsprechenden Zeiger struct file_impl um und gibt anschließend den Wert der fd-Komponente der Implementierungsstruktur zurück. Weshalb implementiert Solaris Schnittstellen auf diese Weise? Indem die Details der aktuellen libc-Implementierung weit entfernt von den Client-Programmen abstrahiert werden, ist Sun in der Lage, so weit wie möglich eine starke Binärkompatibilität zu sichern, aber gleichzeitig die internen Implementierungsdetails von libc weiterzuentwickeln und zu ändern. In unserem Beispiel könnte die Komponente fd ihre Größe oder ihre Position innerhalb von struct file_impl selbst in einem Patch ändern, ohne dass bereits vorhandene Binärdateien, die fileno(3C) aufrufen, durch diese Änderungen beeinträchtigt würden, da sie nicht von diesen Artefakten abhängig sind.

Leider genießt jedoch Beobachtungssoftware wie DTrace nicht den Luxus, beliebige in Solaris-Bibliotheken oder im Kernel definierte C-Funktionen aufrufen zu können. Sie muss in die Implementierung blicken, um nützliche Resultate zu erbringen. Sie könnten nun zum Instrumentieren der in stdio.h deklarierten Routinen eine Kopie von struct file_impl in Ihrem D-Programm deklarieren. Dann würde das D-Programm aber von privaten Implementierungsartefakten der Bibliothek abhängen, die in einer künftigen Micro- oder Unterversion oder gar in einem Patch nicht mehr vorhanden sein könnten. Unsere Idealvorstellung ist es, ein Konstrukt für die Verwendung in D-Programmen bereitzustellen, das an die Implementierung der Bibliothek gebunden und entsprechend aktualisiert wird, aber trotzdem eine zusätzliche Abstraktionsebene mit höherer Stabilität bietet.

Zum Erstellen eines neuen Übersetzers verwenden Sie eine Deklaration der Form:

translator Ausgangstyp < Eingangstyp Eingangsbezeichner > {
	Komponentenname = Ausdruck ;
	Komponentenname = Ausdruck ;
	...
};	

Der Ausgangstyp ist eine Struktur, die den Ergebnistyp der Übersetzung darstellt. Der Eingangstyp gibt den Typ des Eingangsausdrucks an. Er steht in eckigen Klammern < > und geht einem Eingangsbezeichner voraus, der in den Übersetzerausdrücken als Aliasname für den Eingangsausdruck eingesetzt werden kann. Der Rumpf des Übersetzers ist von geschweiften Klammern { } umschlossen und endet mit einem Strichpunkt (;). Er besteht aus einer Liste von Komponentennamen und Namen für Übersetzungsausdrücke. Jede Komponentendeklaration muss eine eindeutige Komponente des Ausgangstyps angeben und einem Ausdruck eines Typs zugewiesen werden, der mit dem Komponententyp kompatibel ist. Dabei gelten die Regeln für den D-Zuweisungsoperator (=).

So könnten wir beispielsweise auf Grundlage von einigen der verfügbaren libc-Schnittstellen eine Struktur mit stabilen Informationen über stdio-Dateien definieren:

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

Ein hypothetischer D-Übersetzer für FILE in file_info ließe sich dann wie folgt in D deklarieren:

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

Der Eingangsausdruck in unserem hypothetischen Übersetzer besitzt den Typ FILE * und wird bezeichnet mit Eingangsname F. Der Name F kann dann in den Ausdrücken der Übersetzerkomponenten als eine Variable des Typs FILE * verwendet werden, die nur innerhalb des Rumpfs der Übersetzerdeklaration sichtbar ist. Zur Ermittlung des Werts der Ausgangskomponente file_fd nimmt der Übersetzer eine Typumwandlung und eine Dereferenzierung wie in der zuvor gezeigten hypothetischen Implementierung von fileno(3C) vor. Eine ähnliche Übersetzung erfolgt zur Ermittlung des Werts der EOF-Kennzeichnung.

Sun stellt Ihnen eine Reihe von Übersetzern für die Solaris-Schnittstellen zur Verfügung, die aus D-Programmen aufgerufen werden können. Dabei verspricht Sun, diese Übersetzer im Laufe der Weiterentwicklung der Implementierung der entsprechenden Schnittstellen im Einklang mit den zuvor definierten Regeln für die Schnittstellenstabilität zu pflegen. Bevor wir genau auf diese Übersetzer eingehen, soll geklärt werden, wie sich Übersetzer aus D aufrufen lassen. Außerdem wird die Übersetzereinrichtung selbst zur Nutzung durch Anwendungs- und Bibliotheksentwickler bereitgestellt, die eigene Übersetzer anbieten möchten, die wiederum von D-Programmierern zur Beobachtung des Status ihrer eigenen Softwarepakete verwendet werden können.

Übersetzungsoperator

Der D-Operator xlate dient zum Übersetzen eines Eingangsausdrucks in eine der definierten Ausgangsstrukturen. Der Operator xlate wird in Ausdrücken der folgenden Form verwendet:

xlate < Ausgangstyp > ( Eingangsausdruck )

Um beispielsweise den zuvor definierten hypothetischen Übersetzer für FILE-Strukturen aufzurufen und auf die Komponente file_fd zuzugreifen, würden Sie folgenden Ausdruck schreiben:

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

wobei f eine D-Variable des Typs FILE * ist. Dem xlate-Ausdruck selbst wird der durch Ausgangstyp definierte Typ zugewiesen. Ein fertig definierter Übersetzer ermöglicht es, nicht nur die Eingangsausdrücke in den Ausgangsstrukturtyp des Übersetzers, sondern auch in Zeiger auf diese Struktur zu übersetzen.

Wenn Sie einen Eingangsausdruck in eine Struktur übersetzen, können Sie entweder direkt mit dem Operator „.” eine bestimmte Komponente der Ausgabe dereferenzieren oder die gesamte übersetzte Struktur einer anderen D-Variable zuweisen, um eine Kopie der Werte aller Komponenten anzulegen. Wenn Sie eine einzelne Komponente dereferenzieren, generiert der D-Compiler nur für den Ausdruck dieser Komponente Code. Es ist nicht möglich, durch Anwendung des Operators & die Adresse einer übersetzten Struktur abzurufen, da das Datenobjekt selbst so lange nicht existiert, bis es kopiert oder eine seiner Komponenten referenziert wird.

Wenn Sie einen Eingangsausdruck eines Zeigers in eine Struktur übersetzen, können Sie entweder direkt mit dem Operator -> eine bestimmte Komponente der Ausgabe dereferenzieren oder den unären Operator * anwenden, um den Zeiger zu dereferenzieren. In diesem Fall verhält sich das Ergebnis so, als würden Sie den Ausdruck in eine Struktur übersetzen. Wenn Sie eine einzelne Komponente dereferenzieren, generiert der D-Compiler nur für den Ausdruck dieser Komponente Code. Es ist nicht möglich, einen übersetzten Zeiger einer anderen D-Variable zuzuweisen, da das Datenobjekt selbst so lange nicht existiert, bis es entweder kopiert oder eine seiner Komponenten referenziert wird. Deshalb kann es nicht adressiert werden.

In Übersetzerdeklarationen dürfen Ausdrücke für eine oder mehr Komponenten des Ausgangstyps ausgelassen werden. Wird über einen xlate-Ausdruck auf eine Komponente zugegriffen, für die kein Übersetzungsausdruck definiert ist, gibt der D-Compiler eine geeignete Fehlermeldung aus und bricht die Programmkompilierung ab. Wird mithilfe einer Strukturzuweisung der gesamte Ausgangstyp kopiert, werden alle Komponenten, für die keine Übersetzungsausdrücke definiert sind, mit Nullen angefüllt.

Auf der Suche nach einem passenden Übersetzer für eine xlate-Operation untersucht der D-Compiler die verfügbaren Übersetzer in dieser Reihenfolge:

Kann im Einklang mit diesen Regeln kein passender Übersetzer gefunden werden, gibt der D-Compiler eine entsprechende Fehlermeldung aus und die Programmkompilierung schlägt fehl.

Übersetzer für Prozessmodelle

In der DTrace-Bibliotheksdatei /usr/lib/dtrace/procfs.d sind verschiedene Übersetzer enthalten, die Sie in Ihren D-Programmen zum Übersetzen der Strukturen für Prozesse und Threads aus der Betriebssystem-Kernelimplementierung in die stabilen proc(4)-Strukturen psinfo und lwpsinfo verwenden können. Diese Strukturen kommen auch in den Dateien /proc/PID/psinfo und /proc/PID/lwps/LWPID/lwpsinfo im Solaris-Dateisystem /proc vor und sind in der Systemdefinitionsdatei /usr/include/sys/procfs.h definiert. Diese Strukturen definieren nützliche, stabile („Stable“) Informationen über Prozesse und Threads wie etwa die Prozess-ID, LWP-ID, Anfangsargumente und andere mit dem Befehl ps(1) abrufbare Daten. Unter proc(4) finden Sie eine vollständige Beschreibung der Strukturkomponenten und der Semantik.

Tabelle 40–1 procfs.d-Übersetzer

Eingangstyp 

Attribute des Eingangstyps 

Ausgangstyp 

Attribute des Ausgangstyps 

proc_t *

Private/Private/Common 

psinfo_t *

Stable/Stable/Common 

kthread_t *

Private/Private/Common 

lwpsinfo_t *

Stable/Stable/Common 

Stabile Übersetzungen

Übersetzer besitzen zwar die Fähigkeit, Informationen in eine stabile Datenstruktur umzuwandeln, lösen aber nicht unbedingt alle bei der Übersetzung von Daten anfallenden Stabilitätsprobleme. Verweist beispielsweise der Eingangsausdruck für eine xlate-Operation selbst auf als „Unstable“ gekennzeichnete Daten, so wird auch das entstehende D-Programm instabil, da sich die Programmstabilität stets aus der geringsten Stabilität der Menge der D-Programmanweisungen und -ausdrücke ergibt. Aus diesem Grund ist es manchmal notwendig, einen spezifischen, stabilen Eingangsausdruck für einen Übersetzer zu definieren, sodass stabile Programme erzeugt werden können. Der inline-Mechanismus in D erleichtert die Definition solcher stabilen Übersetzungen.

Die DTrace-Bibliothek procfs.d stellt die bereits zuvor beschriebenen Variablen curlwpsinfo und curpsinfo als stabile Übersetzungen bereit. Bei der Variable curlwpsinfo handelt es sich zum Beispiel eigentlich um die folgende inline-Deklaration:

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

Die Variable curlwpsinfo ist als inline-Übersetzung aus der Variable curthread (einem Zeiger auf die private Kernel-Datenstruktur, die einen Thread darstellt) in den stabilen Typ lwpsinfo_t definiert. Der D-Compiler verarbeitet diese Bibliotheksdatei und speichert die inline-Deklaration zwischen. Dadurch erscheint curlwpsinfo wie jede andere D-Variable. Die #pragma-Anweisung im Anschluss an die Deklaration setzt die Attribute des Namens curlwpsinfo explizit auf Stable/Stable/Common zurück, wodurch die Referenz auf curthread im inline-Ausdruck maskiert wird. Dank dieser Kombination aus D-Leistungsmerkmalen können D-Programmierer curthread gefahrlos als Quelle für eine Übersetzung verwenden, die im Rahmen von Änderungen in der Solaris-Implementierung von Sun aktualisiert werden kann.