Solaris 动态跟踪指南

第 40 章 转换器

第 39 章中,我们学习了有关 DTrace 如何计算和报告程序稳定性属性的内容。理论上,我们想仅使用“稳定”或“发展中”的接口来构造 DTrace 程序。遗憾的是,在调试低级问题或度量系统性能时,可能需要启用与操作系统内部例程(如内核中的函数)关联的探测器,而不是与更稳定的接口(如系统调用)关联的探测器。在软件栈深层的探测位置获取的数据通常是实现产物的集合,而不是更稳定的数据结构(例如,与 Solaris 系统调用接口关联的数据结构)。为了帮助您编写稳定的 D 程序,DTrace 提供了用于将实现产物转换为可通过 D 程序语句访问的稳定数据结构的工具。

转换器声明

转换器是由接口供应商提供的 D 赋值语句的集合,可用于将输入表达式转换为结构类型的对象。为了解转换器的必要性和用法,我们将以 stdio.h 中定义的 ANSI-C 标准库例程为例。这些例程作用于名为 FILE 的数据结构,该数据结构的实现产物是从 C 程序编制器中抽象出来的。创建数据结构抽象的标准方法是:在单独的专用头文件中保留相应的结构定义的同时,在公共头文件中仅提供数据结构的转发声明。

如果要编写 C 程序并且希望知道对应于 FILE 结构的文件描述符,则可以使用 fileno(3C) 函数获取该描述符,而不是直接取消对 FILE 结构成员的引用。Solaris 头文件通过将 FILE 定义为不透明的转发声明标记来强制实施此规则,这样包括 <stdio.h> 的 C 程序将不能直接取消对 FILE 结构成员的引用。在 libc.so.1 库中,可以假设按照以下形式使用 C 语言实现 fileno()

int
fileno(FILE *fp)
{
	struct file_impl *ip = (struct file_impl *)fp;

	return (ip->fd);
}

假设 fileno() 接受 FILE 指针作为参数,并将其强制转换为指向相应内部 libc 结构 struct file_impl 的指针,然后返回该实现结构的 fd 成员的值。为什么 Solaris 按照此形式实现接口?通过在客户机程序中对当前 libc 实现的详细信息进行抽象,Sun 可以保持对强大二进制兼容性的承诺,同时继续发展和更改 libc 的内部实现详细信息。在示例中,fd 成员可以更改 struct file_impl(甚至修补程序)中的大小和位置,调用 fileno(3C) 的现有二进制文件不会受此更改的影响,因为它们不依赖于这些人为因素。

不过,观察软件(如 DTrace)必须能够全面监测实现的内部以提供有用的结果,但它不具有调用在 Solaris 库或内核中定义的任意 C 函数的能力。可以在 D 程序中声明 struct file_impl 的副本以便检测 stdio.h 中声明的例程,但您的 D 程序将依赖于库的“专用”实现产物,该库可能在将来的微发行版或次发行版甚至修补程序中中断。理论上,我们要提供绑定到库的实现并进行相应更新的结构,以供 D 程序使用,并且能提供与更好的稳定性关联的其他抽象层。

使用以下形式的声明创建新的转换器:

translator output-type < input-type input-identifier > {
	member-name = expression ;
	member-name = expression ;
	...
};	

output-type 指定一个结构,这将是转换的结果类型。input-type 指定输入表达式的类型,并括在尖括号 < > 中,后跟可在转换器表达式中用作输入表达式的别名的 input-identifier。转换器的主体括在花括号 { } 中,以分号 (;) 结尾,由 member-name 和与转换表达式对应的标识符的列表组成。每个成员声明必须命名一个唯一的 output-type 成员,且必须根据 D 赋值 (=) 运算符的规则指定与成员类型兼容的类型表达式。

例如,可以根据一些可用的 libc 接口,定义一个有关 stdio 文件的稳定信息结构:

struct file_info {
	int file_fd;   /* file descriptor from fileno(3C) */
	int file_eof;  /* eof flag from feof(3C) */
};

然后可以在 D 中声明从 FILEfile_info 的假设 D 转换器,如下所示:

translator struct file_info < FILE *F > {
	file_fd = ((struct file_impl *)F)->fd;
	file_eof = ((struct file_impl *)F)->eof;
};

在假设的转换器中,输入表达式为 FILE * 类型,且指定了 input-identifier F。然后可以在转换器成员表达式中将标识符 F 用作 FILE *(仅在转换器声明的主体中可见)类型的变量。要确定 file_fd 输出成员的值,转换器将按照上面所示的 fileno(3C) 假设实现的类似方式,执行强制类型转换和取消引用。还将执行类似的转换以获取 EOF 指示符的值。

Sun 提供了一组可与 Solaris 接口一起使用的转换器(可在 D 程序中调用这些转换器),并承诺,当相应接口的实现发生变化时,将根据先前定义的接口稳定性规则维护这些转换器。在本章的后面部分中,我们将在学习如何通过 D 调用转换器之后,来学习这些转换器。对于想要提供自定义的转换器以供 D 程序员观察其软件包的状态的应用程序和库开发者,转换器工具本身也可供他们使用。

转换运算符

D 运算符 xlate 用于执行从输入表达式到所定义的某种转换输出结构的转换。xlate 运算符用于以下格式的表达式:

xlate < output-type > ( input-expression )

例如,要对上面定义的 FILE 结构调用假设的转换器并访问 file_fd 成员,可以编写以下表达式:

xlate <struct file_info *>(f)->file_fd;

其中,fFILE * 类型的 D 变量。为 xlate 表达式本身指定 output-type 定义的类型。定义转换器之后,可以使用该转换器将输入表达式转换为转换器输出结构类型或指向该结构的指针。

如果将输入表达式转换为结构,则可以使用 "." 运算符立即取消引用输出的特定成员,或者可以将整个已转换的结构赋给另一个 D 变量,以创建一份所有成员的值的副本。如果取消引用单个成员,则 D 编译器将仅生成对应于该成员的表达式的代码。不可以将 & 运算符应用于已转换的结构来获取其地址,因为在复制数据对象本身或引用它的某个成员之前,数据对象本身不存在。

如果将输入表达式转换为指向某结构的指针,则可以使用 -> 运算符立即取消引用输出的特定成员,或者可以使用一元 * 运算符取消引用该指针,在此情况下,产生的结果就像已经将表达式转换为结构一样。如果取消引用单个成员,则 D 编译器将仅生成对应于该成员的表达式的代码。不可以将已转换的指针赋给另一个 D 变量,因为在复制对象或引用它的某个成员之前,数据对象本身不存在,所以无法对其寻址。

转换器声明可以省略输出类型的一个或多个成员的表达式。如果使用 xlate 表达式访问未定义转换表达式的成员,则 D 编译器将产生相应的错误消息,并中止程序编译。如果通过结构赋值的方式复制整个输出类型,则将使用零填充未定义转换表达式的任何成员。

为了找到 xlate 操作的匹配转换器,D 编译器将按以下顺序检查可用的转换器集:

如果根据这些规则,未找到匹配的转换器,D 编译器将产生相应的错误消息,并且程序编译失败。

进程模型转换器

DTrace 库文件 /usr/lib/dtrace/procfs.d 提供了一组供在 D 程序中使用的转换器,使用这些转换器可以将进程和线程的操作系统内核实现结构转换为稳定的 proc(4) 结构 psinfolwpsinfo。这些结构也可以在 Solaris /proc 文件系统文件(/proc/pid/psinfo/proc/pid/lwps/lwpid/lwpsinfo)中使用,这些结构在系统头文件 /usr/include/sys/procfs.h 中定义。这些结构定义有关进程和线程的有用“稳定”信息(如由 ps(1) 命令显示的 ID、LWP ID、初始参数和其他数据)。有关结构成员和语义的完整说明,请参阅 proc(4)

表 40–1 procfs.d 转换器

输入类型 

输入类型属性 

输出类型 

输出类型属性 

proc_t *

专用/专用/公用 

psinfo_t *

稳定/稳定/公用 

kthread_t *

专用/专用/公用 

lwpsinfo_t *

稳定/稳定/公用 

稳定转换

虽然转换器提供了将信息转换为稳定的数据结构的功能,但并不需要解决转换数据时可能产生的所有稳定性问题。例如,如果 xlate 操作本身的输入表达式引用“不稳定”的数据,则产生的 D 程序也将“不稳定”,因为程序稳定性的计算结果始终为累积的 D 程序语句和表达式的最低稳定性。所以,要允许构造稳定的程序,有时必须为转换器定义特定的稳定输入表达式。可以使用 D 内置机制来协助进行此类稳定转换

DTrace procfs.d 库提供先前说明的 curlwpsinfocurpsinfo 变量作为稳定转换。例如,curlwpsinfo 变量实际上是按如下方式声明的 inline

inline lwpsinfo_t *curlwpsinfo = xlate <lwpsinfo_t *> (curthread);
#pragma D attributes Stable/Stable/Common curlwpsinfo

curlwpsinfo 变量被定义为从 curthread 变量(指向表示线程的内核“专用”数据结构的指针)到“稳定”lwpsinfo_t 类型的内置转换。D 编译器将处理此库文件,并高速缓存 inline 声明,从而使 curlwpsinfo 显示为任何其他 D 变量。紧跟在声明后面的#pragma 语句用于将 curlwpsinfo 标识符的属性显式重置为“稳定/稳定/公用”,从而屏蔽对内置表达式中的 curthread 的引用。D 功能的这种组合允许 D 程序员安全地使用 curthread 作为转换源,Sun 可以同时将该转换源更新为 Solaris 实现中的相应更改。