Handbuch zur dynamischen Ablaufverfolgung in Solaris

Ü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.