Solaris 动态跟踪指南

联合

联合是 ANSI-C 和 D 支持的另一种复合类型,它与结构密切相关。联合是一种复合类型,其中,定义了一组不同类型的成员,并且所有成员对象都占用同一存储区域。因此,根据联合的指定方式,联合是一个变体类型的对象,在任何给定时间内仅有一个成员有效。通常,使用其他变量或状态部分来指示当前处于有效状态的联合成员。联合的大小为其最大成员的大小,用于联合的内存对齐为联合成员所需的最大对齐值。

Solaris kstat 框架定义了一个包含联合的结构,用于在以下示例中说明和观察 C 和 D 联合。kstat 框架用于导出一组表示内核统计信息(如内存使用情况和 I/O 吞吐量)的指定计数器。使用该框架可以实现 mpstat(1M)iostat(1M) 等实用程序。此框架使用 struct kstat_named 来描述指定的计数器及其值,定义如下:

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

为了直观起见,缩短了检查声明。完整的结构定义可在 <sys/kstat.h> 头文件中找到,kstat_named(9S) 中对其进行了说明。以上声明在 ANSI-C 和 D 中均有效,该声明定义了一个结构,其中包含一个作为其成员之一的联合值,该值具有各种类型的成员,具体取决于计数器的类型。请注意,由于该联合本身是在另一种类型 struct kstat_named 中声明的,因此省略了该联合类型的正式名称。此声明样式称作匿名联合。名为 value 的成员具有上述声明描述的联合类型,但是此联合类型本身没有名称,因为无需在任何其他位置使用该类型。为结构成员 data_type 分配的值指示对类型为 struct kstat_named 的所有对象都有效的联合成员。对于 data_type 的值,定义了一组 C 预处理器标记。例如,标记 KSTAT_DATA_CHAR 等于零,指示成员 value.c 为该值的当前存储位置。

示例 7–3 演示了如何通过跟踪用户进程访问 kstat_named.value 联合。通过 kstat_data_lookup(3KSTAT) 函数,可从用户进程对 kstat 计数器进行采样,该函数将返回指向 struct kstat_named 的指针。为获取最新的计数器采样值,mpstat(1M) 实用程序将在执行时重复调用该函数。请转到您的 shell 并尝试运行 mpstat 1,然后观察输出。几秒钟之后,在 shell 中按 Ctrl-C 组合键以中止 mpstat。为观察计数器采样情况,我们要启用一个在 mpstat 命令每次调用 libkstat 中的 kstat_data_lookup(3KSTAT) 函数时都会触发的探测器。为此,我们将使用一个新的 DTrace 提供器:pidpid 提供器允许您在用户进程的 C 符号位置(如函数入口点)动态创建探测器。您可以通过编写以下格式的探测器说明,要求 pid 提供器在用户函数进入和返回位置创建探测器:

pidprocess-ID:object-name:function-name:entry
pidprocess-ID:object-name:function-name:return

例如,如果要在进程 ID 12345 中创建在进入 kstat_data_lookup(3KSTAT) 时触发的探测器,可编写以下探测器说明:

pid12345:libkstat:kstat_data_lookup:entry

pid 提供器将在指定用户进程与探测器说明对应的程序位置插入动态检测过程。该探测器实现强制到达受检测的程序位置的每个用户线程陷入操作系统内核并进入 DTrace,同时触发对应的探测器。因此,尽管实现位置与用户进程关联,但指定的 DTrace 谓词和操作仍在操作系统内核环境中执行。有关 pid 提供器的详细信息,请参见第 30 章

您可以在程序中插入称为宏变量的标识符,在编译程序时将计算该标识符,并将其替换为其他 dtrace 命令行参数,从而无需在每次要将程序应用于不同进程时,都对 D 程序源代码进行编辑。宏变量使用美元符号 $ 并后跟标识符或数字进行指定。如果您执行命令 dtrace -s script foo bar baz,D 编译器将自动定义宏变量 $1$2$3,分别作为标记 foobarbaz。您可以在 D 程序表达式或探测器说明中使用宏变量。例如,以下探测器说明检测指定为 dtrace 附加参数的任何进程 ID:

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

有关宏变量和可重用脚本的详细信息,请参见第 15 章。至此,我们已了解如何使用进程 ID 检测用户进程,现在让我们返回到采样联合。请转到编辑器并键入完整示例的源代码,然后将其保存在名为 kstat.d 的文件中:


示例 7–3 kstat.d:跟踪对 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;
}

现在,转到其中一个 shell 并执行命令 mpstat 1,以启动在进行统计信息采样并每秒报告一次的模式下运行的 mpstat(1M)。开始运行 mpstat 后,在其他 shell 中执行命令 dtrace -q -s kstat.d `pgrep mpstat。您将会看到与正在访问的统计信息对应的输出。按 Ctrl-C 组合键中止 dtrace,然后返回到 shell 提示符。


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

如果您在每个终端窗口中捕获输出,并通过统计信息从前一次迭代所报告的值中减去每个值,则应可将 dtrace 输出与 mpstat 输出关联。该示例程序在进入查找函数时记录计数器名称指针,然后在从 kstat_data_lookup(3KSTAT) 返回时执行大多数跟踪操作。如果 arg1()(返回值)不为 NULL(),D 内置函数 copyinstrcopyin 则会将函数结果从用户进程复制到 DTrace 中。完成 kstat 数据复制后,该示例将报告联合的 ui64 计数器值。此简化示例假定 mpstat 对使用 value.ui64 成员的计数器进行采样。作为一种练习,请尝试记录 kstat.d,以使用多个谓词并列显与 data_type 成员对应的联合成员。此外,还可以尝试创建用于计算连续数据值之差,以及实际生成与 mpstat 类似的输出的 kstat.d 版本。