利用 Dtrace,可以声明只作用于每个操作系统线程的局部变量存储空间,这与本章前面介绍的全局变量空间相对。在要启用探测器并使用一些标记或其他数据来标记将触发探测器的每个线程的情况下,可使用线程局部变量。在 D 中创建解决此问题的程序很方便,因为线程局部变量在 D 代码中共享一个公用名称,但引用与各个线程关联的独立数据存储空间。通过将 -> 运算符应用于特殊标识符 self 可引用线程局部变量:
syscall::read:entry { self->read = 1; }
此 D 代码段示例在 read(2) 系统调用中启用探测器,并将名为 read 的线程局部变量与触发该探测器的每个线程关联。与全局变量类似,线程局部变量在首次赋值时自动创建,并采用该赋值语句右边使用的类型(本例中为 int)。
每次在 D 程序中引用 self->read 变量时,引用的数据对象是与触发相应的 DTrace 探测器时执行的操作系统线程关联的对象。可将线程局部变量视为关联数组,其隐式索引是系统中线程标识组成的元组。线程的标识在系统的生命周期中是唯一的:如果该线程退出,并使用相同的操作系统数据结构创建一个新线程,则此新线程不会重用同一个 DTrace 线程局部存储标识。
定义线程局部变量后,即使先前未将要引用的变量指定给该特定线程,也可以在系统中的任何线程中引用该局部变量。如果尚未指定线程局部变量的线程副本,则该副本的数据存储空间将定义为使用零填充。与关联数组元素一样,在对线程局部变量指定非零值之前,不会为该变量分配基础存储空间。另外还与关联数组元素一样,为线程局部变量赋值零将会导致 DTrace 解除分配基础存储空间。不再使用的线程局部变量将赋值零。有关微调动态变量空间(从该空间分配线程局部变量)的其他方法,请参见第 16 章。
可以在 D 程序中定义任何类型的线程局部变量,包括关联数组。以下是一些线程局部变量定义的示例:
self->x = 123; /* integer value */ self->s = "hello"; /* string value */ self->a[123, 'a'] = 456; /* associative array */
与任何 D 变量一样,在使用线程局部变量之前,无需对其进行显式声明。如果确实要创建声明,可以通过在程序子句外部在声明之前加上关键字 self 来进行声明:
self int x; /* declare int x as a thread-local variable */ syscall::read:entry { self->x = 123; }
线程局部变量保存在与全局变量不同的名称空间中,因此可以重用这些名称。请记住,如果在程序中过载名称,x 和 self->x 是不同的变量。以下示例说明了如何使用线程局部变量。在文本编辑器中,键入以下程序并将其保存在名为 rtime.d 的文件中:
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; }
现在进入 shell 并开始运行程序。等待几秒钟,您将会看到一些输出。如果没有显示输出,请尝试运行一些命令。
# 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 # |
rtime.d 使用名为 t 的线程局部变量捕获任何线程进入 read(2) 的时间标记。然后在返回子句中,程序通过从当前时间标记中减去 self->t,来列显 read(2) 中所花费的时间。内置 D 变量 pid 和 tid 报告执行 read(2) 的线程的进程 ID 和线程 ID。因为报告此信息后将不再需要 self->t,随后将会对其赋值 0,使 DTrace 可以在当前线程中重用与 t 关联的基础存储空间。
通常,在没有执行任何操作的情况下,也可以看到多行输出,这是因为即使您未执行任何操作,服务器进程和守护进程始终都在后台执行 read(2)。尝试将 rtime.d 的第二条子句更改为使用 execname 变量,以列显执行 read(2) 的进程的名称,从而了解更多信息:
printf("%s/%d spent %d nsecs in read(2)\n", execname, tid, timestamp - self->t);
如果发现特别关注的进程,添加一个谓词以了解有关其 read(2) 行为的更多信息:
syscall::read:entry /execname == "Xsun"/ { self->t = timestamp; }