Handbuch zur dynamischen Ablaufverfolgung in Solaris

Unionen

Unionen sind eine weitere Art der von ANSI-C und D unterstützten zusammengesetzten Typen. Sie sind eng mit Strukturen verwandt. Eine Union ist ein zusammengesetzter Typ, in dem Alternativen (Komponenten) unterschiedlicher Typen definiert sind und alle Alternativenobjekte denselben Speicherbereich belegen. Sie ist folglich ein Objekt eines veränderlichen Typs, in dem, abhängig von der Zuweisung der Union, zu einem gegebenen Zeitpunkt nur je eine Alternative gültig ist. In der Regel wird die aktuelle gültige Unionsalternative durch eine andere Variable oder ein Statuselement angegeben. Eine Union hat die Größe ihrer größten Alternative, und die für sie verwendete Speicherausrichtung entspricht der maximalen für die Unionsalternativen benötigten Ausrichtung.

Das Solaris-Framework kstat definiert eine Struktur, die die in folgendem Beispiel zur Veranschaulichung von C- und D-Unionen verwendete Union enthält. Das kstat-Framework dient zum Exportieren eines Satzes benannter Zähler, die Kernel-Statistiken wie die Speichernutzung und den E/A-Datendurchsatz darstellen. Mit dem Framework werden Dienstprogramme wie mpstat(1M) und iostat(1M) implementiert. In diesem Framework stellt struct kstat_named einen benannten Zähler und dessen Werte dar. Es ist wie folgt definiert:

struct kstat_named {
	char name[KSTAT_STRLEN]; /* name of counter */
	uchar_t data_type;	/* data type */
	union {
		char c[16];
		int32_t i32;
		uint32_t ui32;
		long l;
		ulong_t ul;
		...
	} value;	/* value of counter */
};	

Die untersuchte Deklaration ist aus Gründen der Einfachheit abgekürzt. Die vollständige Strukturdefinition befindet sich in der Definitionsdatei <sys/kstat.h> und ist in kstat_named(9S) beschrieben. Die obige Deklaration ist sowohl in ANSI-C als auch in D gültig. Sie definiert eine Struktur, zu deren Komponenten ein Unionswert mit Alternativen unterschiedlicher Typen zählt, die vom Typ des Zählers abhängen. Beachten Sie, dass auf einen formalen Namen des Unionstyps verzichtet wurde, da die Union selbst innerhalb eines anderen Typs, struct kstat_named, deklariert ist. Dieser Deklarationsstil wird als anonyme Union bezeichnet. Die Komponente namens value nimmt den in der vorangehenden Deklaration beschriebenen Union-Typ an, aber dieser Union-Typ selbst hat keinen Namen, da er an keiner anderen Stelle benutzt werden muss. Der Strukturkomponente data_type wird ein Wert zugewiesen, der angibt, welche Alternative der Union für die einzelnen Objekte des Typs struct kstat_named gültig ist. Als Werte für data_type sind verschiedene C-Preprozessor-Symbole definiert. So ist beispielsweise das Symbol KSTAT_DATA_CHAR gleich Null und gibt an, dass sich die Alternative value.c an der derzeitigen Speicherposition des Werts befindet.

Beispiel 7–3 zeigt den Zugriff auf die Union kstat_named.value durch die Ablaufverfolgung eines Benutzerprozesses. Die kstat-Zähler können von Benutzerprozessen mithilfe der Funktion kstat_data_lookup(3KSTAT) geprüft werden, die einen Zeiger auf struct kstat_named zurückgibt. Bei seiner Ausführung ruft das Dienstprogramm mpstat(1M) diese Funktion wiederholt auf, um die neuesten Zählerwerte zu prüfen. Führen Sie mpstat 1 in Ihrer Shell aus und beobachten Sie die Ausgabe. Brechen Sie mpstat nach einigen Sekunden durch Drücken von Strg-C in der Shell ab. Zum Beobachten der Zählerprüfung möchten wir einen Prüfpunkt aktivieren, der mit jedem Aufruf der Funktion kstat_data_lookup(3KSTAT) in libkstat durch den Befehl mpstat ausgelöst wird. Hierfür werden wir einen neuen DTrace-Provider einsetzen: pid. Der Provider pid ermöglicht die dynamische Erzeugung von Prüfpunkten in Benutzerprozessen an C-Symbolpositionen wie beispielsweise Eintrittspunkten. Mit Prüfpunktbeschreibungen in der folgenden Form können Sie den Provider pid dazu auffordern, zum Eintritt in eine und Rückkehr aus einer Benutzerfunktion einen Prüfpunkt zu erzeugen:

pidProzess-ID:Objektname:Funktionsname:entry

 pidProzess-ID: Objektname:Funktionsname :return

Wenn Sie beispielsweise einen Prüfpunkt in dem Prozess mit der ID erzeugen möchten, der beim Eintritt in kstat_data_lookup(3KSTAT) ausgelöst wird, formulieren Sie die Prüfpunktbeschreibung wie folgt:

pid12345:libkstat:kstat_data_lookup:entry

Der Provider pid fügt an der der Prüfpunktbeschreibung entsprechenden Programmposition eine dynamische Instrumentation in den angegebenen Benutzerprozess ein. Die Prüfpunktimplementierung „lockt“ jeden Benutzer-Thread, der die instrumentierte Programmposition erreicht, in den Betriebssystemkernel und erzwingt dessen Eintritt in DTrace, wodurch der entsprechende Prüfpunkt ausgelöst wird. Obwohl also die Instrumentationsposition mit einem Benutzerprozess verbunden ist, werden die von Ihnen angegebenen DTrace-Prädikate und -Aktionen weiterhin im Kontext des Betriebssystemkernels ausgeführt. Der Provider pid wird in Kapitel 30Der Provider pid ausführlicher behandelt.

Damit Sie einen D-Programmquellcode nicht jedes Mal bearbeiten müssen, wenn Sie das Programm auf einen anderen Prozess anwenden möchten, können Sie so genannte Makrovariablen in Ihr Programm einsetzen, die zur Kompilierungszeit des Programms ausgewertet und durch die zusätzlichen dtrace-Befehlszeilenargumente ersetzt werden. Makrovariablen werden mit dem Dollarzeichen $, gefolgt von einem Bezeichner oder einer Ziffer angegeben. Wenn Sie den Befehl dtrace -s Skript foo bar baz ausführen, definiert der D-Compiler die Makrovariablen $1, $2 und $3 automatisch als die Symbole foo, bar bzw. baz. Makrovariablen können in D-Programmausdrücken oder in Prüfpunktbeschreibungen benutzt werden. Die folgenden Prüfpunktbeschreibungen instrumentieren beispielsweise jede Prozess-ID, die als zusätzliches Argument für dtrace angegeben wird:

pid$1:libkstat:kstat_data_lookup:entry
{
	self->ksname = arg1;
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 != NULL/
{
	this->ksp = (kstat_named_t *)copyin(arg1, sizeof (kstat_named_t));
	printf("%s has ui64 value %u\n", copyinstr(self->ksname),
	    this->ksp->value.ui64);
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 == NULL/
{
	self->ksname = NULL;
}

Makrovariablen und wieder verwendbare Skripten werden in Kapitel 15Scripting ausführlicher besprochen. Nachdem wir jetzt wissen, wie Benutzerprozesse anhand ihrer Prozess-ID instrumentiert werden, kehren wir zum Prüfen von Unionen zurück. Geben Sie den Quellcode für unser vollständiges Beispiel in einen Texteditor ein und speichern Sie ihn unter dem Namen kstat.d:


Beispiel 7–3 kstat.d: Ablaufverfolgungsaufrufe von kstat_data_lookup(3KSTAT )

pid$1:libkstat:kstat_data_lookup:entry
{
	self->ksname = arg1;
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 != NULL/
{
	this->ksp = (kstat_named_t *) copyin(arg1, sizeof (kstat_named_t));
	printf("%s has ui64 value %u\n",
	    copyinstr(self->ksname), this->ksp->value.ui64);
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 == NULL/
{
	self->ksname = NULL;
}

Führen Sie jetzt in einer Shell den Befehl mpstat 1 aus, um mpstat(1M) in einem Modus zu starten, in dem statistische Daten gesammelt und einmal pro Sekunde gemeldet werden. Sobald mpstat läuft, führen Sie in der anderen Shell den Befehl dtrace -q -s kstat.d `pgrep mpstat` aus. Sie sehen eine den Statistiken, auf die zugegriffen wird, entsprechende Ausgabe. Drücken Sie Strg-C, um dtrace abzubrechen und zur Shell-Eingabeaufforderung zurückzukehren.


# dtrace -q -s kstat.d `pgrep mpstat`
cpu_ticks_idle has ui64 value 41154176
cpu_ticks_user has ui64 value 1137
cpu_ticks_kernel has ui64 value 12310
cpu_ticks_wait has ui64 value 903
hat_fault has ui64 value 0
as_fault has ui64 value 48053
maj_fault has ui64 value 1144
xcalls has ui64 value 123832170
intr has ui64 value 165264090
intrthread has ui64 value 124094974
pswitch has ui64 value 840625
inv_swtch has ui64 value 1484
cpumigrate has ui64 value 36284
mutex_adenters has ui64 value 35574
rw_rdfails has ui64 value 2
rw_wrfails has ui64 value 2
...
^C
#

Wenn Sie die Ausgabe in jedem Terminalfenster erfassen und jeden Wert von dem durch die vorige Iteration gemeldeten Wert in der Statistik subtrahieren, sollte es möglich sein, eine Beziehung zwischen der dtrace- und der mpstat-Ausgabe zu erkennen. Das Beispielprogramm zeichnet den Zählernamenzeiger beim Eintritt in die Lookup-Funktion auf und führt einen Großteil der Ablaufverfolgung nach der Rückkehr aus kstat_data_lookup(3KSTAT) durch. Wenn arg1() (der Rückgabewert) nicht NULL() ist, kopieren die in D integrierten Funktionen copyinstr und copyin die Funktionsresultate aus dem Benutzerprozess zurück nach DTrace. Nach dem Kopieren der kstat-Daten meldet das Beispielprogramm den ui64-Zählerwert aus der Union. In diesem vereinfachten Beispiel wird davon ausgegangen, dass mpstat Zähler prüft, die die Alternative value.ui64 ansprechen. Versuchen Sie nun übungshalber, kstat.d mehrere Prädikate ansprechen und die der Komponente data_type entsprechende Unionsalternative ausgeben zu lassen. Sie können auch eine Version von kstat.d schreiben, die die Differenz zwischen aufeinander folgenden Datenwerten berechnet und eine ähnliche Ausgabe wie mpstat erzeugt.