Handbuch zur dynamischen Ablaufverfolgung in Solaris

Thread-lokale Variablen

DTrace bietet die Möglichkeit, im Gegensatz zu den zuvor in diesem Kapitel besprochenen globalen Variablen auch Variablen mit einem Speicherbereich im jeweiligen Betriebssystem-Thread (lokal) zu deklarieren. Thread-lokale Variablen erweisen sich als nützlich, wenn Sie einen Prüfpunkt aktivieren und jeden den Prüfpunkt auslösenden Thread mit einem Etikett oder auf andere Weise markieren möchten. Ein solches Programm zu schreiben, ist in D eine einfache Angelegenheit, denn thread-lokale Variablen haben im D-Code einen gemeinsamen Namen, beziehen sich aber auf unterschiedliche Datenspeicherbereiche der verschiedenen Threads. Thread-lokale Variablen werden durch Anwendung des Operators -> auf den speziellen Bezeichner self referenziert:

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

In diesem D-Beispielfragment wird der Prüfpunkt am read(2)-Systemaufruf aktiviert und jedem Thread, der den Prüfpunkt auslöst, wird die thread-lokale Variable read zugewiesen. Ebenso wie globale Variablen werden auch thread-lokale Variablen bei ihrer ersten Zuweisung automatisch erzeugt und nehmen den auf der rechten Seite der ersten Zuweisungsanweisung verwendeten Typ an (in diesem Beispiel int).

Mit jeder Referenzierung der Variable self->read in Ihrem D-Programm wird das Datenobjekt referenziert, das dem Betriebssystem-Thread angehört, der zum Zeitpunkt der Auslösung des entsprechenden DTrace-Prüfpunkts ausgeführt wurde. Eine thread-lokale Variable kann man sich als einen assoziativen Vektor vorstellen, der durch ein die Identität des Threads im System beschreibendes Tupel implizit indiziert wird. Threads haben über die gesamte Lebensdauer des Systems eine eindeutige Identität: Wenn der Thread beendet wird und aufgrund derselben Betriebssystem-Datenstruktur ein weiterer Thread erzeugt wird, erhält dieser in DTrace nicht dieselbe thread-lokale Speicheridentität.

Nachdem Sie eine thread-lokale Variable definiert haben, können Sie diese für jeden beliebigen Thread im System referenzieren, selbst wenn die betreffende Variable für einen bestimmten Thread noch keine Zuweisung erhalten hat. Der Datenspeicherbereich für eine Kopie der thread-lokalen Variable im Thread wird gemäß der Definition mit Nullen angefüllt. Ebenso wie bei den Elementen assoziativer Vektoren erfolgt die Speicherreservierung für eine thread-lokale Variable erst, wenn dieser ein Wert ungleich Null zugewiesen wird. Ebenso analog zu den Elementen assoziativer Vektoren bewirkt die Zuweisung mit Null einer thread-lokalen Variable, dass DTrace die Zuweisung des Speicherbereichs aufhebt. Weisen Sie thread-lokalen Variablen, die nicht mehr benötigt werden, immer den Wert Null zu. In Kapitel 16Optionen und Tunables sind weitere Techniken zur Feinabstimmung des dynamischen Variablenbereichs beschrieben, aus dem thread-lokale Variablen allokiert werden.

Sie können in einem D-Programm thread-lokale Variablen jedes beliebigen Typs, einschließlich assoziativer Vektoren, definieren. Sehen Sie hier einige Beispieldefinitionen für thread-lokale Variablen:

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

Wie jede D-Variable müssen auch thread-lokale Variablen vor ihrer Verwendung nicht explizit deklariert werden. Wenn Sie dies trotzdem wünschen, bauen Sie die Deklaration außerhalb der Programmklauseln ein und stellen Sie ihnen das Schlüsselwort self voran:

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

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

Thread-lokale Variablen werden von globalen Variablen getrennt in separaten Namensräumen geführt. Folglich können die Namen mehrmals verwendet werden. Denken Sie daran, dass es sich bei x und self->x nicht um dieselbe Variable handelt, wenn Sie Namen in Ihrem Programm mehrmals verwenden! Das folgende Beispiel verdeutlicht die Verwendung von thread-lokalen Variablen. Geben Sie das folgende Programm in einen Texteditor ein und speichern Sie es unter dem Namen rtime.d:


Beispiel 3–1 rtime.d: Berechnen der in read(2) abgelaufenen Zeit

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

Wechseln Sie nun zu Ihrer Shell und starten Sie die Programmausführung. Nach einigen Sekunden sollte die Ausgabe beginnen. Wenn keine Ausgabe erscheint, geben Sie den ein oder anderen Befehl ein.


# 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
#

In rtime.d wird mit der thread-lokalen Variable t beim Eintritt in read(2) durch einen beliebigen Thread eine Zeitmarke erfasst. In der Rückkehrklausel gibt das Programm dann die in read(2); verbrachte Zeit aus, die es ermittelt, indem es self->t von der aktuellen Zeitmarke subtrahiert. Die integrierten D-Variablen pid und tid melden die Prozess-ID und Thread-ID des Threads, der read(2) durchführt. Da self->t nach der Ausgabe dieser Information nicht mehr benötigt wird, wird ihr anschließend der Wert 0 zugewiesen, um DTrace die Möglichkeit zu geben, den Speicherbereich von t für den aktuellen Thread anderweitig zu verwenden.

Normalerweise sehen Sie eine etliche Zeilen lange Ausgabe, auch ohne Befehle eingeben zu müssen, da read(2) im Hintergrund ständig von Serverprozessen und Dämonen ausgeführt wird. Ändern Sie die zweite Klausel von rtime.d ab, indem Sie die Variable execname einsetzen, um zusätzlich den Namen des read(2) ausführenden Prozesses zu erfahren:

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

Wenn Sie auf einen Prozess stoßen, der Sie besonders interessiert, fügen Sie ein Prädikat ein, um seinem read(2)-Verhalten genauer auf den Grund zu gehen:

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