关联数组
第3 章• 变量61
线程局部变量
利用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 来进行声明:
线程局部变量
62 Solaris 动态跟踪指南• 2006 年7 月
self int x; /* declare int x as a thread-local variable */
syscall::read:entry
{
self->x = 123;
}
线程局部变量保存在与全局变量不同的名称空间中,因此可以重用这些名称。请记住,如
果在程序中过载名称,x 和self->x 是不同的变量。以下示例说明了如何使用线程局部变
量。在文本编辑器中,键入以下程序并将其保存在名为rtime.d 的文件中:
示例3–1 rtime.d: 计算read(2) 中花费的时间
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.
*/
线程局部变量
第3 章• 变量63
示例3–1 rtime.d: 计算read(2) 中花费的时间(续)
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",
线程局部变量
64 Solaris 动态跟踪指南• 2006 年7 月
execname, tid, timestamp - self->t);
如果发现特别关注的进程,添加一个谓词以了解有关其read(2) 行为的更多信息:
syscall::read:entry
/execname == "Xsun"/
{
self->t = timestamp;
}
子句局部变量
也可以定义每个D程序子句将重用其存储空间的D变量。子句局部变量与C、C++ 或Java
语言程序中的自动变量类似,这些变量在每次调用函数期间处于活动状态。与所有D程序
变量一样,子句局部变量会在首次赋值时自动创建。通过将-> 运算符应用于特殊标识符
this 可引用这些变量并对这些变量赋值:
BEGIN
{
this->secs = timestamp / 1000000000;
...
}
如果要在使用子句局部变量前对其进行显式声明,可使用this 关键字进行声明:
this int x; /* an integer clause-local variable */
this char c; /* a character clause-local variable */
BEGIN
{
this->x = 123;
子句局部变量
第3 章• 变量65
this->c = ’D’;
}
子句局部变量仅在给定探测子句的生命周期中处于活动状态。在DTrace 执行与给定探测器
的子句关联的操作后,将会回收所有子句局部变量的存储空间,并重用于下一条子句。因
此,子句局部变量是唯一不使用零进行初始填充的D变量。请注意,如果程序的单个探测
器包含多条子句,在执行这些子句时,所有子句局部变量将会保持不变,如下例所示:
示例3–2 clause.d: 子句局部变量
int me; /* an integer global variable */
this int foo; /* an integer clause-local variable */
tick-1sec
{
/*
* Set foo to be 10 if and only if this is the first clause executed.
*/
this->foo = (me % 3 == 0) ? 10 : this->foo;
printf("Clause 1 is number %d; foo is %d\n", me++ % 3, this->foo++);
}
tick-1sec
{
/*
* Set foo to be 20 if and only if this is the first clause executed.
*/
this->foo = (me % 3 == 0) ? 20 : this->foo;
printf("Clause 2 is number %d; foo is %d\n", me++ % 3, this->foo++);
子句局部变量
66 Solaris 动态跟踪指南• 2006 年7 月
示例3–2 clause.d: 子句局部变量(续)
}
tick-1sec
{
/*
* Set foo to be 30 if and only if this is the first clause executed.
*/
this->foo = (me % 3 == 0) ? 30 : this->foo;
printf("Clause 3 is number %d; foo is %d\n", me++ % 3, this->foo++);
}
因为子句始终按程序顺序执行,且子句局部变量在启用相同探测器的不同子句中具有持久
性,所以运行上述程序将始终产生同样的输出:
# dtrace -q -s clause.d
Clause 1 is number 0; foo is 10
Clause 2 is number 1; foo is 11
Clause 3 is number 2; foo is 12
Clause 1 is number 0; foo is 10
Clause 2 is number 1; foo is 11
Clause 3 is number 2; foo is 12
Clause 1 is number 0; foo is 10
Clause 2 is number 1; foo is 11
Clause 3 is number 2; foo is 12
Clause 1 is number 0; foo is 10
子句局部变量
第3 章• 变量67
Clause 2 is number 1; foo is 11
Clause 3 is number 2; foo is 12
^C
虽然子句局部变量在启用相同探测器的各条子句中具有持久性,但在为给定探测器执行的
第一条子句中,未定义这些变量的值。在使用每条子句局部变量前,请务必为其指定相应
的值,否则程序可能产生意外结果。
可使用任何标量变量类型定义子句局部变量,但不可以使用子句局部作用域定义关联数
组。子句局部变量的作用域仅适用于相应的变量数据,而不适用于为变量定义的名称和类
型标识。一旦定义子句局部变量,就可以在任何后续D程序子句中使用该名称和类型签
名。存储位置在不同子句中不会一直保持不变。
可以使用子句局部变量积累计算的中间结果,或作为其他变量的临时副本。访问子句局部
变量要比访问关联数组快得多。因此,如果需要在同一个D程序子句中多次引用关联数组
值,首先将该值复制到一条子句局部变量中,然后重复引用该局部变量,将获得更高的效
率。
内置变量
下表提供了D内置变量的完整列表。所有这些变量都是标量全局变量;D语言中当前未定
义线程局部变量、子句局部变量或内置关联数组。
表3–1DTrace 内置变量
类型和名称说明
int64_t arg0, ..., arg9 探测器的前10 个输入参数表示为原始的64 位整
数。如果传递到当前探测器的参数少于10 个,则
其余变量将返回0。
args[] 为当前探测器键入的参数(如果存在)。可使用
整数索引访问args[] 数组,但将每个元素定义为
与给定探测器参数对应的类型。例如,如果通过
read(2) 系统调用探测器来引用args[],
则args[0] 为int 类型,args[1] 为void * 类型,
args[2] 为size_t 类型。
uintptr_t caller 进入当前探测器之前的当前线程的程序计数器位
置。
chipid_t chip 当前物理芯片的CPU 芯片标识符。有关更多信
息,请参见第26 章。
内置变量
68 Solaris 动态跟踪指南• 2006 年7 月
表3–1DTrace 内置变量(续)
类型和名称说明
processorid_t cpu 当前CPU 的CPU 标识符。有关更多信息,请参
见第26 章。
cpuinfo_t *curcpu 当前CPU 的CPU 信息。有关更多信息,请参见
第26 章。
lwpsinfo_t *curlwpsinfo 与当前线程关联的轻量进程(lightweight process,
LWP) 的LWP 状态。此结构将在proc(4) 手册页中
详细说明。
psinfo_t *curpsinfo 与当前线程关联的进程的进程状态。此结构将在
proc(4) 手册页中详细说明。
kthread_t *curthread 当前线程(kthread_t) 的操作系统内核的内部数据
结构的地址。kthread_t 在 中定
义。有关此变量和其他操作系统数据结构的更多
信息,请参阅Solaris Internals。
string cwd 与当前线程关联的进程的当前工作目录名称。
uint_t epid 当前探测器的已启用的探测器ID (enabled probe
ID, EPID)。该整数唯一标识使用特定谓词和操作
集启用的特定探测器。
int errno 此线程最后一次执行的系统调用返回的错误值。
string execname 传递到exec(2) 以执行当前进程的名称。
gid_t gid 当前进程的实际组ID。
uint_t id 当前探测器的探测器ID。此ID 是当前探测器的
系统范围内的唯一标识符,由DTrace 发布,并列
在dtrace -l 的输出中。
uint_t ipl 触发探测器时当前CPU 的中断优先级(interrupt
priority level, IPL)。有关Solaris 操作系统内核中的
中断级别和中断处理的更多信息,请参阅Solaris
Internals。
lgrp_id_t lgrp 当前CPU 所属的延迟组的延迟组ID。有关更多
信息,请参见第26 章。
pid_t pid 当前进程的进程ID。
pid_t ppid 当前进程的父进程ID。
string probefunc 当前探测器说明的函数名称部分。
string probemod 当前探测器说明的模块名称部分。
string probename 当前探测器说明的名称部分。
内置变量
第3 章• 变量69
表3–1DTrace 内置变量(续)
类型和名称说明
string probeprov 当前探测器说明的提供器名称部分。
psetid_t pset 包含当前CPU 的处理器集的处理器集ID。有关
更多信息,请参见第26 章。
string root 与当前线程关联的进程的根目录名称。
uint_t stackdepth 触发探测器时当前线程的栈帧深度。
id_t tid 当前线程的线程ID。对于与用户进程关联的线
程,该值等于pthread_self(3C) 调用的结果。
uint64_t timestamp 纳秒时间标记计数器的当前值。此计数器从过去
的任意时间点递增,仅用于相对计算。
uid_t uid 当前进程的实际用户ID。
uint64_t uregs[] 触发探测器时当前线程的已保存的用户模式寄存
器值。uregs[] 数组的用法将在第33 章中讨论。
uint64_t vtimestamp 纳秒时间标记的当前值,实际是当前线程在CPU
中已运行的时间减去DTrace 谓词和操作中所花费
时间而得到的值。此计数器从过去的任意时间点
递增,仅用于相对时间计算。
uint64_t walltimestamp 自1970 年1 月1 日00:00 世界标准时间以来的当前
纳秒数。
D语言中的内置函数(如trace())将在第10 章中讨论。
外部变量
D使用反引号字符(‘) 作为访问操作系统(而非D程序)中所定义变量的特殊作用域运算
符。例如,Solaris 内核包含一个名为kmem_flags 的系统可调参数的C 声明,用于启用内存
分配器调试功能。有关kmem_flags 的更多信息,请参见《Solaris Tunable Parameters
Reference Manual》。此可调参数在内核源代码中声明为C 变量,如下所示:
int kmem_flags;
要在D程序中访问此变量的值,请使用D表示法:
‘kmem_flags
DTrace 将每个内核符号与在相应操作系统C 代码中用于该符号的类型关联,从而使基于源
代码访问本机操作系统数据结构更方便。要使用外部操作系统变量,需要访问相应的操作
系统源代码。
外部变量
70 Solaris 动态跟踪指南• 2006 年7 月
从D程序访问外部变量时,访问的是其他程序的内部实现详细信息(如操作系统内核或其
设备驱动程序)。这些实现详细信息不能构成可靠的稳定接口。在下次升级软件的相应部
分时,所编写的依赖于这些详细信息的任何D程序可能会停止工作。因此,外部变量通常
由内核和设备驱动程序开发者以及服务人员使用,以便使用DTrace 调试性能或功能问题。
要了解D程序稳定性的更多信息,请参见第39 章。
内核符号名称存储在与D变量和函数标识符不同的名称空间中,因此您无需担心这些名称
会与D变量发生冲突。在变量前加上一个反引号时,D编译器为了找到匹配的变量定义,
会使用已装入模块的列表按顺序搜索已知的内核符号。由于Solaris 内核支持使用独立的名
称空间动态装入的模块,所以在活动的操作系统内核中可多次使用相同的变量名称。可以
通过在符号名称中指定应访问其变量的内核模块的名称并加上反引号,来解决这些名称冲
突。例如,每个可装入内核模块通常提供一个_fini(9E) 函数,以便引用名为foo 的内核模
块提供的_fini 函数的地址,可写为:
foo‘_fini
根据操作数类型的一般规则,可将任何D运算符应用于外部变量,但那些修改值的运算符
除外。启动DTrace 后,D编译器将装入对应于活动内核模块的变量名称集,因此不需要声
明这些变量。不能将修改变量值的任何运算符应用于外部变量,例如= 或+=。出于安全原
因,DTrace 将会阻止损坏或破坏所观察软件的状态。
外部变量
第3 章• 变量71
72
D 程序结构
D程序由一组子句组成,这些子句说明要启用的探测器和要绑定到这些探测器的谓词和操
作。D程序也包含变量声明(如第3 章中所介绍)和新类型定义(如第8 章中所介绍)。
本章正式介绍D程序的总体结构和用于构造与多个探测器匹配的探测器说明的功能。还将
讨论如何结合D程序使用C 预处理程序cpp。
探测器子句和声明
如我们已提供过的示例所示,D程序源文件由一条或多条探测器子句组成,这些子句说明
DTrace 启用的检测过程。每条探测器子句具有以下一般形式:
probe descriptions
/ predicate /
{
action statements
}
谓词和操作语句列表可以省略。探测器子句外部的任何指令都称为声明。只可以在探测器
子句外部使用声明。封闭的{ } 内部不允许使用声明,声明不能分布在如上所示的探测器子
句的元素之间。可以使用空格分隔任何D程序元素,并缩进操作语句。
可以使用声明来声明D变量和外部C 符号(如第3 章中所讨论),或者定义要在D中使用
的新类型(如第8 章中所介绍)。称为pragma 的特殊D编译器指令也可以出现在D程序中
的任何位置(包括探测器子句的外部)。在以# 字符开头的行上指定Dpragma。例如,D
pragma 可用于设置运行时DTrace 选项;有关详细信息,请参见第16 章。
4第4 章
73
探测器说明
每条D程序子句都以一个或多个探测器说明的列表开头,每个说明都采用以下一般形式:
provider:module:function:name
如果省略探测器说明中的一个或多个字段,D编译器将从右向左解释指定的字段。例如,
探测器说明foo:bar 将匹配包括函数foo 和名称bar 的探测器,而不考虑探测器的提供器和
模块字段的具体值。所以,探测器说明更准确地说是一种模式,可用于根据名称匹配一个
或多个探测器。
应编写指定所有四个字段分界符的D探测器说明,以便可以在左侧指定需要的提供器。如
果未指定提供器,在多个提供器以相同的名称发布探测器时可能会得到意外的结果。类似
地,将来版本的DTrace 可能包括新的提供器,这些提供器的探测器可能会无意中与所指定
的探测器说明部分匹配。可以指定提供器,但保留所有模块、函数和名称字段为空,以与
提供器的任何探测器匹配。例如,说明syscall::: 可用于匹配DTrace syscall 提供器发布
的每个探测器。
探测器说明还支持与shell globbing 模式匹配语法(如sh(1) 中所说明)类似的模式匹配语
法。在将探测器与说明匹配之前,DTrace 将在每个说明字段中扫描字符*、? 和[。如果这
些字符中的某一个出现在探测器说明字段中,并且前面没有\,会将该字段视为一种模式。
说明模式必须与给定探测器的整个相应字段匹配。要成功匹配和启用探测器,完整的探测
器说明必须与每个字段匹配。非模式的探测器说明字段必须与该探测器的相应字段完全匹
配。空的说明字段将与任何探测器匹配。
下表中是探测器名称模式中可识别的特殊字符:
表4–1与字符匹配的探测器名称模式
符号说明
* 匹配任何字符串,包括空字符串。
? 匹配任何单个字符。
[ ... ] 匹配任何一个封闭字符。使用- 分隔的一对字符将匹配这对字符之间的任何
字符,包括这对字符。如果[ 之后的第一个字符是!,则将与集合中未包括的
任何字符匹配。
\ 将下一个字符解释为其本身,不具有任何特殊含义。
可以在探测器说明的任何或所有四个字段中使用模式匹配字符。通过在命令行中将模式与
dtrace -l 一起使用,可以使用模式列出匹配的探测器。例如,命令dtrace -l -f kmem_* 列
出函数中名称以前缀kmem_ 开头的所有DTrace 探测器。
如果要为多个探测器说明或说明模式指定相同的谓词和操作,可以将说明放置在逗号分隔
的列表中。例如,以下D程序将跟踪每次触发探测器时的时间标记,这些探测器与包含单
词"lwp" 或"sock" 的系统调用入口关联。
探测器说明
74 Solaris 动态跟踪指南• 2006 年7 月
syscall::*lwp*:entry, syscall::*sock*:entry
{
trace(timestamp);
}
探测器说明也可以使用整数探测器ID 指定探测器。例如,子句:
12345
{
trace(timestamp);
}
可用于启用由dtrace -l -i 12345 报告的探测器ID 12345。应始终使用人工可读的探测器说
明编写D程序。装入和卸载DTrace 提供器内核模块时或者在重新引导后,不能保证整数探
测器ID 仍然不变。
谓词
谓词是置于斜杠/ / 中的表达式,在触发探测器时进行计算,以确定是否应执行关联的操
作。谓词是用于在D程序中生成更复杂的控制流的主要条件结构。您可以完全省略任何探
测器的探测器子句的谓词部分,在此情况下,触发探测器时将始终执行操作。
谓词表达式可以使用前面说明的任何D运算符,可以引用任何D数据对象(如变量和常
数)。谓词表达式的计算结果必须为整数或指针类型的值,以便可以将其视为true 或
false。与所有D表达式一样,零值将解释为false,任何非零值将解释为true。
操作
探测器操作由分号(;) 分隔的语句列表说明,括在花括号{ } 中。如果仅想说明在特定CPU
中触发特定探测器而不跟踪任何数据,或不执行任何其他操作,可以指定一个内部不包含
任何语句的空花括号集合。
阅读(407) | 评论(0) | 转发(0) |