DTRACE简介之完结篇
By samwan on 四月 13, 2007
已经有好长一段时间没有更新blog了,不是我懒,确实是这段时间太忙。工作加上生活,算了,不找借口了,还是来把DTRACE简介作个完结吧。本来开始写的时候只准备用一篇文章来描述,等真正写出来就发现,不行了,Dtrace实在是太强大了,即便是加上今天的,也没有完全讲到,遗漏的地方就只有请各位看官自己去阅读《Solaris 动态跟踪指南》了。
看完了前面3篇文章的朋友对Dtrace的功能及D语言已经有了一个基本的认识了吧,那你们有没有发现少了点什么?我们提到了Dtrace提供器,但是在前面3篇文章中没有对Dtrace提供器作介绍。Dtrace提供器是Dtrace体系架构的精华,如果不了解Dtrace提供器,那么你就不能充分利用Dtrace。我们也提到,Dtrace提供器随操作系统版本和加载模块的情况有所不同,因此,我们在这里介绍的,也是基本的Dtrace提供器。提供器提供了探测器,探测器提供了参数,不同提供器的探测器参数是不一样的。
dtrace提供器
注意,不要把“dtrace提供器”和“Dtrace提供器”搞混了,前者只是后者的一个具体提供器(所以我们用小写以区分)。dtrace提供器提供了多个与Dtrace本身相关的探测器。
表1 - dtrace提供器提供的探测器
探测器 | 描述 |
参数 |
BEGIN | 在所有其他探测器之前触发,主要用于初始化操作,比如变量赋值,打印标题信息 | 无 |
END | 在所有其他探测器之后触发,主要用于处理已收集数据,比如格式化输出等 | 无 |
ERROR | 在执行探测器子句时发生错误时触发,不如访问空指针,被0除等 | 有 |
ERROR探测器的参数见下表:
表2 - ERROR探测器参数
参数 |
说明 |
arg1 | 引发错误的探测器的已启用探测器标志符即EPID |
arg2 | 导致故障的操作的索引(?) |
arg3 | 该操作的DIF偏移,如果不适用,则为-1 |
arg4 | 故障类型 |
arg5 | 故障类型的特定值 |
lockstat 提供器
lockstat提供器提供了与锁竞争相关的统计信息的探测器。lockstat(1M)命令就是lockstat提供器的使用者。在介绍lockstat提供器之前,先简单介绍一下内核中锁的相关知识。
现在的操作系统,已经不在是“原始社会”的DOS,只能允许一个任务独占CPU资源,现在的操作系统都是多用户多任务的作业系统,即便是在单CPU的硬件平台上,也可以“同时”并行执行多个任务(注意单CPU系统中的同时指的是在一个时间段,而不是某个时刻)。如果多个任务同时对同一份数据进行修改,势必会造成问题,这时,我们就需要引入“锁”机制用以确保一个时刻只有一个任务对某份数据进行修改。
所谓“锁”,简单的讲,就是内存中的一段数据,其最简单的形式是一个字节(byte)。内核中的很多数据结构都提供了相关的锁,有的锁是在内核源代码中定义的,有的锁则是在内核运行过程中动态产生的,其中动态产生的锁占内核锁的大部分。一个被占有的锁用锁字节'0xff'表示,一个空闲的锁用'0x00'表示。现代计算机系统都提供了对锁的原子操作指令,比如SPARC V9中的ldstub(Load and store unsigned byte), cas(compare and swap), swap(swap byte locations),这些指令可以保证在读取锁字节值的同时对锁字节进行赋值。在对带锁的数据进行访问之前,必须先获得对锁的控制权,否则不能进行访问(当然,这是内核制定的“游戏规则”,在内核中运行必须遵循这些规则,否则就会造成数据的破坏)。在使用完相关的数据之后,应该及时释放锁,否则可能会导致性能问题甚至“死锁”。
内核中主要有两种锁: 互斥锁(Mutex Lock)和读写锁(Reader/Writer Lock)。当对互斥锁进行访问时,如果访问者发现互斥锁已经被占有(即其值为0xff),访问者可以进入一个循环等待(Spin),或者进入睡眠(sleep),缺省情况下互斥锁都是自适应的(Adaptive),即当锁的持有者在CPU上运行时,访问者进入循环,并在每次循环结束时检查锁是否被释放;当锁的持有者不在CPU上运行时,访问者进入睡眠。只有当执行不允许休眠的代码(比如高级别中断)时,互斥锁才必须是spin类型的(此时也称为自旋锁)。内核中绝大部分互斥锁都是自适应的。读写锁只允许一个对锁的写持有者(修改锁保护的数据),或者多个读持有者(只读取锁保护的数据)。
lockstat提供器提供了两种类型的探测器:竞争事件(contention-event)探测器和持有事件(hold-event)探测器。
竞争事件探测器对应于同步原语的竞争,在一个线程*等待资源时触发。Solaris已经对竞争进行了优化,因此长时间的竞争是不会发生的。由于竞争的数量很少,因此启用竞争事件探测器不会影响系统性能。
持有事件探测器对应于对锁的获取、释放及其它操作的同步原语。由于Solaris中对锁的获取和释放操作非常频繁(在一个繁忙的系统上,每秒钟每个CPU数以百万计),启用这些探测器对系统的影响要比启用竞争探测器大,尽管如此,仍是安全的。
下表是lockstat提供器提供的与自适应锁相关的探测器,其arg0都包含指向代表相关自适应锁的kmutex_t结构指针(可以使用 echo "::print -t kmutex_t"|mdb -k来查看kmutex_t的具体结构)
表3 - 自适应锁探测器
探测器名称 | 说明 |
adaptive-acquire | 获取锁后立即触发的持有事件探测器 |
adaptive-block | 当在等待一个被占有的自适应锁的线程被唤醒并获得该互斥锁时触发的竞争事件探测器。如果adaptive-acquire和adaptive-block探测器都被启用,则adaptive-block先于adaptive-acquire触发。对于单个锁的获取操作,最多触发adaptive-block和adaptive-spin中的一个。arg1包含了以纳秒计算的休眠时间。 |
adaptive-spin | 当在循环等待一个被占有的自适应锁的线程成功获取该互斥锁时触发的竞争事件探测器。如果adaptive-acquire和adaptive-spin都被启用,则adaptive-spin先于adaptive-acquire触发。对于单个锁的获取操作,最多触发adaptive-block和adaptive-spin中的一个。arg1包含了在锁被获取之前的循环次数。 |
adaptive-release | 自适应锁被释放后立即触发的持有事件探测器。 |
表4 - 自旋锁探测器
探测器名称 | 说明 |
spin-acquire | 自旋锁被获取后立刻触发的持有事件探测器 |
spin-spin | 当等待被占有的自旋锁的线程成功获取该自旋锁后触发的竞争事件探测器。如果spin-acquire和spin-spin都被启用,则spin-spin先于spin-acquire被触发。arg1包含了自旋的次数(即循环次数)。 |
spin-release | 当自旋锁被释放后触发的持有事件探测器。 |
线程锁(Thread locks)是一种特殊类型的自旋锁,它用于锁定线程以改变其状态。
表5- 线程锁探测器
探测器名称 | 说明 |
thread-spin | 当线程在一个线程锁上作自旋循环后触发的竞争事件探测器。像其它的竞争事件探测器一样,如果竞争事件探测器和持有事件探测器都被启用,则thread-spin先于spin-acquire被触发。与其它竞争事件探测器不同之处是,thread-spin在锁被实际获得之前触发。因此,多个thread-spin探测器触发可能对应于一个spin-acquire探测器触发。 |
读写锁允许对锁的多个读持有者或者一个写持有者,但不允许同时存在读持有者和写持有者。读写锁多用于搜索操作多于更改操作的数据结构中。读写锁探测器的arg0包含指向相关自适应锁的krwlock_t结构的指针。
表6 - 读写锁探测器
探测器名称 | 说明 |
rw-acquire | 读写锁被获取后触发的持有事件探测器。如果锁被读持有者获取,则arg1包含常量RW_READER,否则arg1包含常量RW_WRITER |
rw-block | 当被持有锁阻塞的线程被唤醒并获得锁后触发的竞争事件探测器。arg1包含当前线程为获取该锁休眠的时间(纳秒)。arg2表示该锁是以读持有者获取(RW_READER)还是写持有者获取(RW_WRITER)。arg3和arg4包含进程被阻塞的更详细信息。只有当阻塞当前线程的锁是被写持有者占有时,arg3才为非0。arg4包含当前线程被阻塞时读持有者的数量。如果rw-block和rw-acquire都被启用,rw-block先于rw-acquire被触发。 |
rw-upgrade | 当线程成功将一个读写锁从读持有者升级为写持有者之后触发的持有事件探测器。 |
rw-downgrade | 当线程成功将一个读写锁从写持有者升级为读持有者之后触发的持有事件探测器。 |
rw-release | 当读写锁被释放后触发的持有事件探测器。arg1表示被释放的锁是以读持有者(RW_READER)占有,还是以写(RW_WRTIER)持有者占有。 |
profile 提供器
profile提供器提供了与按指定时间间隔触发和基于时间的中断相关的探测器。这些不固定(unanchored)的探测器不与任何特定的执行点关联,而是与异步中断相关联(与之对应的是同步中断,更多时候称为陷阱 trap,即在程序执行过程中的固定点发生的,比如系统调用)。这些探测器可用于对系统某些方面进行采样。
表7 - profile探测器的参数
参数 | 说明 |
arg0 | 探测器被触发时内核的程序指针计数器(PC),如果当时进程没有执行的内核态,则为0 |
arg1 | 探测器被触发时用户程序的指针计数器(PC),如果当时进程在内核态执行,则为0 |
由于进程要么执行在用户态,要么执行在内核态,因此arg0和arg1在同一时刻有且仅有一个为0。
表8 - profile探测器
探测器名称 | 说明 |
profile-n | profile-n探测器在每个CPU上以高中断级别按固定间隔触发。触发间隔以n指定,如果n只是数字而没有后缀,则profile-n探测器每秒钟触发n次,否则按照指定的时间缩写后缀触发。有效的时间后缀为ns(纳秒)/us(微秒)/ms(毫秒)/s(秒)/m(分钟)/h(小时)/d(天)/hz(每秒次数) |
tick-n | 与profile-n类似,tick-n也是在高中断级别在固定时间间隔触发。与profile-n不同的是,tick-n每个时间只在一个CPU上触发。具体触发的CPU可能会随着时间变化。n的定义与上同。 |
关于时钟精度(Timer Resolution)
profile提供器使用了操作系统的任意精度时钟。对于没有提供任意精度时钟的硬件平台,触发的频率将受限于系统的时钟频率,有hz内核变量指定,如果profile指定的频率大于系统的时钟频率,则每个时钟间隔将触发多次profile探测器。比如在 hz=100的系统上(即每秒钟中断100次),如果指定profile-1000,则每10微秒将触发10次profile探测器(而非每一微秒触发一次)。
profile提供器是动态产生探测器的,只有当被启用后才会有相应的探测器产生,因此在没有启用profile探测器之前,dtrace -lP profile是看不到任何东西的。下面的pid提供器也是如此。
FBT提供器
函数边界跟踪(Function Boundary Tracing -FBT)提供器提供了对Solaris内核绝大多数函数的进入和退出相关的探测器。内核中还有很小一部分函数,这种函数不调用其它的函数,被编译器高度优化成为所谓的“叶函数(Leaf Function)”。“叶函数”不能被FBT利用,在DTRACE中没有与之相关的探测器。
对FBT提供器的有效使用需要具备相关的操作系统内核知识,对于初学者,建议使用syscall,proc,sched等探测器。
FBT提供器提供两类探测器,entry在进入相应的函数时被触发,return在从相应的函数返回时被触发。
表9 - FBT探测器参数
探测器名称 |
参数说明 |
entry | 与相应的函数参数一样,可以作为64位整数int64_t访问,对于有的函数,可以用具有类型的参数来访问 args[]数组。 |
return | arg0-返回指令在函数中的偏移量(字节);如果函数有返回值,则其在arg1中,否则arg1的值未知(不能作为判断依据)。 |
syscall提供器
syscall提供器是dtrace最重要的一个提供器,也非常适合初学者使用。syscall提供器在每个系统调用的入口和返回处提供探测器,因此syscall的探测器总是成对的:entry-在进入相应的系统调用之前触发;return-在系统调用完成,但在返回用户态之前触发。syscall探测器的函数名与被检测的系统调用名一致,基本上都可以在/etc/name_to_sysnum文件中找大,详细信息还可以在第2章手册页查到(man -s2 intro)。有些系统调用还有相对应的64位形式(针对大于32位即4G文件的操作),见下表:
表10- syscall大文件探测器
系统调用 | 相应的大文件syscall探测器 |
creat(2) | creat64 (注意:不是你想象的create) |
fstat(2) | fstat64 |
fstatvfs(2) | fstatvfs64 |
getdents(2) | getdents64 |
getrlimit(2) | getrlimit64 |
lstat(2) | lstat64 |
mmap(2) | mmap64 |
open(2) | open64 |
pread(2) | pread64 |
pwrite(2) | pwrite64 |
setrlimit(2) | setrlimit64 |
stat(2) | stat64 |
statvfs(2) | statvfs64 |
有关大文件的信息,可以参考largefile(5)手册页。
在不确定的情况下,建议你把大文件syscall探测器也启用。
对应entry探测器,其参数(arg0...argn)对应于相应的系统调用(注意没有args[]数组);对于return探测器,arg0和arg1都包含系统调用的返回值。D变量errno如果非0,表示系统调用失败。
注意:系统调用返回值与errno是不一样的。只有根据相关系统调用手册,在判断返回值是出错的情况时,才能使用errno,否则不能使用,因为errno有可能是上一次系统调用的错误代码。
SDT提供器
静态定义跟踪(SDT)提供器在软件研发人员指定的位置创建探测器(有点类似于断点的概率) 。Solaris内核已经定义了一些SDT探测器,以后可能还会增加。软件开发人员也可以按照DTRACE规范定义自己的静态跟踪探测器。
表11- SDT探测器
探测器名称 |
描述 |
arg0 |
callout-start | 在执行callout(参见<sys/callo.h>)前瞬间触发的探测器。callout由内核时钟线程定时执行,是timeout(9F)的具体实现方式 | 对应于将要执行的callout的callout_t(参见<sys/callo.h>)结构的指针。 |
callout-end | 执行完callout之后立即触发的探测器 | 对应于已经执行的callout的callout_t结构的指针。 |
interrupt-start | 在调用设备中断处理程序前瞬间触发的探测器。 | 对应于中断设备的dev_info结构(参见<sys/ddi_impldefs.h>)的指针。 |
interrupt-complete | 从设备中断处理程序返回后立刻触发的探测器。 | 同上 |
sysinfo提供器
sysinfo提供器提供了按名称sys分类的与内核统计信息相关的探测器。这些统计信息提供了系统监控程序比如mpstat(1M)的数据。sysinfo探测器在相应的系统计数器增加前瞬间触发。
表12- sysinfo探测器
探测器名称 |
描述 |
bawrite | 缓冲区将被异步写出到设备时触发的探测器。 |
bread | 从设备物理读取数据到缓冲区时触发的探测器。bread在从设备请求缓冲区之后,但在阻塞之前触发。 |
bwrite | 将缓冲区写出到设备(同步或者异步)时触发的探测器。 |
cpu_ticks_idle | 系统时钟线程确认CPU处于空闲状态时触发的探测器。由于此探测器在系统时钟线程的环境下(context)触发,因此也在当时运行时钟线程的CPU上触发。cpu_t参数arg2指示处于空闲的CPU。 |
cpu_ticks_kernel | 系统时钟线程确认CPU运行在核心态时触发的探测器。由于此探测器在系统时钟线程的环境下(context)触发,因此也在当时运行时钟线程的CPU上触发。cpu_t参数arg2指示运行于核心态的CPU。 |
cpu_ticks_user | 系统时钟线程确认CPU运行在用户态时触发的探测器。由于此探测器在系统时钟线程的环境下(context)触发,因此也在当时运行时钟线程的CPU上触发。cpu_t参数arg2指示运行于用户态的CPU。 |
cpu_ticks_wait | 系统时钟线程确认CPU处于空闲状态,并且该CPU的运行队列上有线程在等待I/O时触发的探测器。由于此探测器在系统时钟线程的环境下(context)触发,因此也在当时运行时钟线程的CPU上触发。cpu_t参数arg2指示等待I/O的CPU。 |
idlethread | CPU进入空闲循环时触发的探测器。 |
intrblk | 中断线程被阻塞时触发的探测器。 |
inv_swtch | 正在运行的线程被强迫放弃CPU时触发的探测器。 |
lread | 从设备上逻辑读取缓冲区时触发的探测器。 |
lwrite | 将缓冲区逻辑写到设备上时触发的探测器。 |
modload | 内核模块被加载时触发的探测器。 |
modunload | 内核模块被卸载时触发的探测器。 |
msg | msgsnd(2)和msgrcv(2)被调用,但在消息队列被操作之前触发的探测器。 |
mutex_adenters | 试图获取已被占有的自适应锁时触发的探测器。 |
namei | 在文件系统上进行名字解析时触发的探测器 |
nthreads | 线程被创建时触发的探测器。 |
phread | 执行原始(RAW)读操作时触发的探测器。 |
phwrite | 执行原始(RAW)写操作时触发的探测器。 |
procovf | 由于系统缺乏进程表项而无法创建新进程时触发。 |
pswitch | 当CPU切换执行线程时触发的探测器。 |
readch | 在每次成功读取后,但在控制权交给执行读操作的线程之前触发的探测器。arg0包含成功读取的字节数。 |
rw_rdfails | 试图对被写持有者占有或者需要的读写锁进行读持有时触发的探测器。此探测器触发时,lockstat提供器的rw-block探测器也会触发。 |
rw_wrfails | 试图对被读持有者或者另外的写持有者占有的读写锁进行写持有时触发的探测器。此探测器触发时,lockstat提供器的rw-block探测器也会触发。 |
sema | semop(2)被调用,但在实际的信号量操作执行之前触发的探测器。 |
sysexec | exec(2)系统调用被执行时触发的探测器。 |
sysfork | fork(2)系统调用被执行时触发的摊测器。 |
sysread | read(2),readv(2),pread(2)系统调用被执行时触发的探测器。 |
sysvfork | vfork(2)系统调用被执行时触发的探测器。 |
syswrite | write(2),writev(2),pwrite(2)系统调用执行时触发的探测器。 |
trap | 发生处理器陷阱时触发的探测器。 |
ufsdirblk | 从UFS文件系统上读取目录块时触发的探测器。 |
ufsiget | 检索UFS的inode时触发的探测器。 |
ufsinopage | 内核中无关联数据页的inode被置为可重用之后触发的探测器。 |
ufsipage | 内核中有关联数据页的inode被置为可重用之后触发的探测器。此探测器在关联的数据页被刷新到磁盘上之后触发。 |
wait_ticks_io | 与cpu_ticks_wait一样,只是由于历史原因保留。 |
writech | 在每次成功写之后,但在将控制权交给执行写操作的线程之前触发的探测器。arg0包含成功写入的字节数。 |
xcalls | 交叉调用(Cross-call)将要发生前触发的探测器。交叉调用是操作系统中一个CPU请求另外一个CPU工作的 |
表13 - sysinfo 探测器参数
参数 |
说明 |
arg0 | 统计递增的量。对多数探测器而言,此值是1,某些探测器可能有其他值。 |
arg1 | 要递增的统计量当前值的指针。这是个64位数,按照arg0递增,可以通知解析指针得到当前值。 |
arg2 | 进行递增操作的CPU的cpu_t结构指针。(此结构定义在<sys/cpuvar.h>中) |
vminfo提供器
vm提供器提供了与虚拟内存管理(Virtual Memory)相关的内核统计数据。
表14- vminfo探测器
探测器 |
说明 |
anonfree | 当未被修改的匿名页被释放时触发的探测器。匿名页指的是未与具体文件想关联的内存页面。匿名页主要有堆内存(Heap),栈内存(Stack)以及通过映射空设备zero(7D)获得的内存。 |
anonpgin | 当匿名页从交换设备调入时触发的探测器。 |
anonpgout | 当修改的匿名页被调出到交换设备时触发的探测器。 |
as_fault | 在页面上发生既非写保护也非写时拷贝的错误时触发的探测器。 |
cow_fault | 当页面上发生写时拷贝错误时。arg0包含创建的页面数。 |
dfree | 页面被释放时触发的探测器。此探测器触发时,anonfree,execfree,fsfree之一也必随之触发。 |
execfree | 当未修改的可执行代码页被释放时触发的探测器。 |
execpgin | 当可执行代码页从后备存储设备调入时触发的探测器。 |
execpgout | 当已修改的可执行代码页调出到后备存储时触发的探测器。绝大多数可执行代码页的换页操作都与execfree相关。可执行代码页被修改的情况不常见。 |
fsfree | 当未修改的文件系统数据页被释放时触发的探测器。 |
fspgin | 当文件系统数据页从后备存储设备调入时触发的探测器。 |
fspgout | 当已修改的文件系统数据页调出到后备存储时触发的探测器。 |
kernel_asflt | 在内核地址空间发生页面故障时触发的探测器。kernel_asflt触发之前的瞬间会触发as_fault探测器。 |
maj_fault | 发生导致从后备存储或者交换设备执行I/O操作的页面故障时触发的探测器。maj_fault触发之前瞬间会触发pgin探测器。 |
pgfrec | 从可用页列表回收页面时触发的探测器。 |
pgin | 页面从后备存储或者交换设备调入时触发的探测器。pgin与maj_fault的不同之处在于maj_fault只在由于页面故障进行置页操作时触发,而pgin在任何置页操作时都会触发。 |
pgout | 页面被调出到后备存储或者交换设备上时触发的探测器。 |
pgpgin | 从后备存储或交换设备上调入页面时触发的探测器。与pgin不同之处是pgpgin的arg0包含调入的页面数,而pgin的arg0总为1。 |
pgpgout | 将页面调出到后备存储或交换设备上时触发的探测器。与pgout不同之处是pgpgout的arg0包含调出的页面数,而pgout的arg0总为1。 |
pgrec | 页面被回收时触发的探测器。 |
pgrrun | 页面调度进程(pager)被调度执行时触发的探测器。 |
pgswapin | 从换出进程换入页面时触发的探测器。arg0包含换入的页面数。 |
pgswapout | 在换出进程的页面被换出时触发的探测器。arg0包含换出的页面数。 |
prot_fault | 由于保护违规发生的页面故障触发的探测器。 |
rev | 页面守护进程开始对所有页面进行新的一轮扫描时触发的探测器。 |
scan | 当页面守护进程检查页面时触发的探测器。 |
softlock | 作为放置软件锁的一部分在一个页面故障发生时触发的探测器。 |
swapin | 当被交换出去的进程被交换回来时触发的探测器 |
swapout | 当进程被交换出去时触发的探测器 |
zfod | 当按需创建以零填充的页面 |
表15 - vminfo探测器参数
参数 |
说明 |
arg0 | 统计信息递增的值。多数情况下,此值为1 |
arg1 | 指向要递增的统计信息的当前值的指针。 |
proc提供器
proc提供器提供了与进程创建和终止,LWP创建和终止,执行新的程序已经发送和处理信号相关的探测器。
表16 - proc探测器
探测器 |
描述 |
create | 使用fork(2),forkall(2),fork1(2)或者vfork(2)创建进程时触发的探测器。args[0]指向对应于新的子进程的psinfo_t结构的指针。由于create探测器只在进程被成功创建之后触发,而LWP创建是进程创建的一部分,所以在新进程的create探测器触发之前,LWP的创建会触发lwp-create探测器。 |
exec | 当进程使用各种exec(2)系统调用准备装载新的程序映像,并在装载前触发的探测器。在exec探测器被触发之后,exec-failure或者exec-success会随之触发。args[0]指向新的程序映像。 |
exec-failure | 当exec(2)失败时触发的探测器。exec-failure只在exec探测器在同一个线程触发之后触发。args[0]包含errno(3C)的值。 |
exec-success | 当exec(2)成功时触发的探测器。exec-success只在exec探测器在同一个线程触发之后触发。exec-success探测器触发的时候,进程标量(如execname和curpsinfo)将包含新的程序映像被装载后的进程状态。 |
exit | 当前进程退出时触发的探测器。退出的原因(由siginfo(3HEAD)的SIGCHILD之一表示)在args[0]中。 |
fault | 当线程遇到机器故障时触发的探测器。args[0]包含了错误代码(在proc(4)中定义),args[1]指向与故障相关的siginfo结构。只有能够引起信号的故障才能触发此探测器。 |
lwp-create | 当LWP被创建时(主要通过thr_create(3C))触发的探测器。args[0]指向新线程的lwpsinfo_t结构,args[1]指向包含此新线程的进程的psinfo_t结构。 |
lwp-start | 在新创建的LWP环境中触发的探测器。此探测器在任何用户程序执行之前触发。如果当前LWP是进程的第一个LWP,则start探测器会先触发,然后就是lwp-start探测器触发。 |
lwp-exit | 当LWP由于收到信号或者通过调用thr_exit(3C)退出时触发的探测器。 |
signal-discard | 当信号发送到单线程进程,并且信号未被该进程阻塞但被该进程忽略时触发的探测器。在这些条件下,信号在生成时就被废弃。args[0]指向目标进程的lwpsinfo_t结构,args[1]指向目标进程的psinfo_t结构,args[2]包含信号代码。 |
signal-send | 当信号发送到线程或者进程时触发的探测器。此探测器在发送信号的进程和线程的环境中触发。args[0]指向接收信号的进程或线程的lwpsinfo_t结构,args[1]指向接收信号的进程或线程的psinfo_t结构,args[2]包含信号代码。signal-send触发之后通常会有接收到信号的进程和线程的signal-handle或者signal-clear探测器的触发。 |
signal-handle | 在进程处理信号之前触发的探测器。此探测器在处理信号的线程环境中触发。args[0]包含信号代码,args[1]包含信号的siginfo_t结构指针,args[2]包含进程中信号处理程序的地址。 |
signal-clear | 由于目标线程调用sigwait(2),sigwaitinfo(3RT)或者sigtimedwait(3RT)清除暂挂的信号时触发的探测器。在这些条件下,暂挂的信号被清除,信号代码返回给调用者。args[0]包含信号代码。此探测器在之前等待的线程环境下触发。 |
start | 在新创建的进程开始执行用户程序之前触发的探测器。 |
表17- proc探测器参数
探测器 | args[0] |
args[1] |
args[2] |
create | psrinfo_t \* | - | - |
exec | char \* | - | - |
exec-failure | int | - | - |
exit | int | - | - |
fault | int | siginfo_t \* | - |
lwp-create | lwpsinfo_t \* | psinfo_t \* | - |
lwp-start | - | - | - |
lwp-exit | - | - | - |
signal-discard | lwpsinfo_t \* | psinfo_t \* | int |
signal-send | lwpsinfo_t \* | psinfo_t \* | int |
signal-handle | int | siginfo_t \* | void(\*)(void) |
signal-clear | int | - | - |
start | - | - | - |
下面是上表中Dtrace定义的lwpsinfo_t数据结构
typedef struct lwpsinfo {
int pr_flag; /\* 进程状态标志 \*/
id_t pr_lwpid; /\* LWP ID号 \*/
uintptr_t pr_addr; /\* 线程数据结构在内核中的地址 \*/
uintptr_t pr_wchan; /\* 睡眠线程等待的同步对象的地址(比如互斥锁,读写锁等) \*/
char pr_stype; /\* 同步事件的类型 \*/
char pr_state; /\* 数字表示的线程状态 \*/
char pr_sname; /\* 可打印的线程状态 \*/
char pr_nice; /\* CPU使用的nice值 \*/
short pr_syscall; /\* 系统调用号(/etc/name_to_sysnum),只在线程执行系统调用时有效 \*/
int pr_pri; /\* 优先级,值越大优先级越高 \*/
char pr_clname[PRCLSZ]; /\* 进程调度分类名 \*/
processorid_t pr_onpro; /\* 上一次运行此线程的处理器 \*/
processorid_t pr_bindpro; /\* 此线程捆绑的处理器 \*/
psetid_t pr_bindpset; /\* 此线程捆绑的处理器集 \*/
} lwpsinfo_t;
Dtrace定义的psinfo_t结构
typedef struct psinfo {
int pr_nlwp; /\* 当前进程的活跃的LWP的数量 \*/
pid_t pr_pid; /\* 唯一的进程PID \*/
pid_t pr_ppid; /\* 父进程的PID \*/
pid_t pr_pgid; /\* 进程组领导进程的PID \*/
pid_t pr_sid; /\* 会话ID \*/
uid_t pr_uid; /\* 实际用户ID \*/
uid_t pr_euid; /\* 有效用户ID \*/
gid_t pr_gid; /\* 实际组ID \*/
gid_t pr_egid; /\* 有效组ID \*/
uintptr_t pr_addr; /\* 进程数据结构在内核中的地址 \*/
dev_t pr_ttydev; /\* 此进程控制的TTY设备 (如果未与TTY关联,则为 PRNODEV) \*/
timestruc_t pr_start; /\* 从UNIX纪元开始的进程启动时间 \*/
char pr_fname[PRFNSZ]; /\* 执行文件的名字 \*/
char pr_psargs[PRARGSZ]; /\* 参数列表 \*/
int pr_argc; /\* 参数个数 \*/
uintptr_t pr_argv; /\* 参数向量的地址 \*/
uintptr_t pr_envp; /\* 环境变量的地址 \*/
char pr_dmodel; /\* 程序的数据模式(ILP32或者LP64) \*/
taskid_t pr_taskid; /\* 任务ID \*/
projid_t pr_projid; /\* 项目ID \*/
poolid_t pr_poolid; /\* 资源池ID \*/
zoneid_t pr_zoneid; /\* 分区ZONE ID \*/
} psinfo_t;
sched提供器
sched提供器提供与CPU调度相关的探测器。
表18-sched探测器
探测器 |
说明 |
change-pri | 在线程优先级将要改变时触发的探测器。args[0]指向线程的lwpsinfo_t结构,进程当前的优先级在pr_pri中。args[1]指向包含当前线程的进程的psinfo_t结构,args[2]包含线程新的优先级。 |
dequeue | 可运行线程离开运行队列前瞬间触发的探测器。args[0]指向离开运行队列的线程的lwpsinfo_t结构,args[1]指向包含该线程的进程的psinfo_t结构,args[2]指向运行队列所在的CPU的cpuinfo_t结构,如果运行队列与CPU无关,则此结构的cpu_id为"-1"。 |
enqueue | 当可运行线程加载到运行队列是触发的探测器。 |
off-cpu | 当前CPU将要结束运行线程时触发的探测器。 |
on-cpu | 当前CPU将开始执行线程时触发的探测器。 |
preempt | 当前线程被抢占前瞬间触发的探测器。此探测器触发后,当前线程会选择一个新线程执行,因此在当前线程环境下会触发off-cpu探测器。在某些情况下,抢占的线程可能在另外一个CPU上运行,而调度程序找不到更高级别的进程,这时候,被抢占的线程仍然留在当前CPU上,因此会触发remain-cpu探测器。 |
remain-cpu | 当调度程序选择继续执行当前线程时触发的探测器。 |
schedctl-nopreempt | 当线程被抢占,又由于抢占控制请求重新插入到运行队列前面时触发的探测器。 |
schedctl-preempt | 使用抢占控制的线程被抢占被被放置到运行队列后面时触发的探测器。 |
schedctl-yield | 启用了抢占控制并且延长了执行时间片的线程释放CPU给其它线程时触发的探测器。 |
sleep | 当前线程在同步对象上休眠时触发的探测器。 |
surrender | 一个CPU被令一个CPU指示作出调度决定(通常是因为更高级别的线程可运行时)触发的探测器。 |
tick | 时钟线程周期审计服务(tick)触发的探测器。 |
wakeup | 当前线程唤醒一个在同步对象上休眠的线程时触发的探测器。 |
表19-sched探测器参数
探测器 | args[0] |
args[1] |
args[2] |
args[3] |
change-pri | lwpsinfo_t \* | psinfo_t \* | pri_t | - |
dequeue | lwpsinfo_t \* | psinfo_t \* | cpuinfo_t \* | - |
enqueue | lwpsinfo_t \* | psinfo_t \* | cpuinfo_t \* | int |
off-cpu | lwpsinfo_t \* | psinfo_t \* | - | - |
on-cpu | - | - | - | - |
preempt | - | - | - | - |
remain-cpu | - | - | - | - |
schedctl-nopreempt | lwpsinfo_t \* | psinfo_t \* | - | - |
schedctl-preempt | lwpsinfo_t \* | psinfo_t \* | - | - |
schedctl-yield | lwpsinfo_t \* | psinfo_t \* | - | - |
sleep | - | - | - | - |
surrender | lwpsinfo_t \* | psinfo_t \* | - | - |
tick | lwpsinfo_t \* | psinfo_t \* | - | - |
wakeup | lwpsinfo_t \* | psinfo_t \* | - | - |
下面是sched提供中中需要用到的cpuinfo_t结构
typedef struct cpuinfo {
processorid_t cpu_id; /\* CPU 标志 \*/
psetid_t cpu_pset; /\* 处理器集标志 \*/
chipid_t cpu_chip; /\* 芯片标志 \*/
lgrp_id_t cpu_lgrp; /\* 位置组标志 \*/
processor_info_t cpu_info; /\* CPU 信息 \*/
} cpuinfo_t;
io提供器
io提供器提供了与磁盘及NFS输入输出操作相关的探测器。
表20-io探测器
探测器 |
描述 |
start | 将要执行I/O请求时触发的探测器。args[0]指向对应于I/O请求的bufinto_t结构,args[1]指向I/O请求设备的devinfo_t结构,args[2]指向I/O请求的文件fileinto_t结构 |
done | I/O请求完成后触发的探测器。参数同上 |
wait-start | 线程开始等待指定的I/O请求完成之前瞬间触发的探测器。参数同上。此探测器触发后不久wait-done会在同一线程中触发。 |
wait-done | 等待的I/O请求完成时触发的探测器,参数同上。 |
下面是参数中会用到的结构
typedef struct bufinfo {
int b_flags; /\* 标志 \*/
size_t b_bcount; /\* 字节数\*/
caddr_t b_addr; /\* 缓冲区地址 \*/
uint64_t b_blkno; /\* 设备上的扩展块号 \*/
uint64_t b_lblkno; /\* 设备块号 \*/
size_t b_resid; /\* 未传输的字节数 \*/
size_t b_bufsize; /\* 分配的缓冲区的大小 \*/
caddr_t b_iodone; /\* I/O完成执行的程序 \*/
dev_t b_edev; /\* 设备号 \*/
} bufinfo_t;
typedef struct devinfo {
int dev_major; /\* 主设备号 \*/
int dev_minor; /\* 次设备号 \*/
int dev_instance; /\* 实例号 \*/
string dev_name; /\* 设备名 \*/
string dev_statname; /\* 设备名+实例号/次设备号 \*/
string dev_pathname; /\* 设备路径名 \*/
} devinfo_t;
typedef struct fileinfo {
string fi_name; /\* 文件名(不带目录部分) \*/
string fi_dirname; /\* 目录名 \*/
string fi_pathname; /\* 绝对路径名 \*/
offset_t fi_offset; /\* 文件内的偏移地址 \*/
string fi_fs; /\* 文件系统 \*/
string fi_mount; /\* 文件系统安装点 \*/
} fileinfo_t;
除此之外,还有pid提供器、mib提供器、fpuinfo提供器、plockstat提供器、fasttrap提供器等,限于时间关系,我就不在这里介绍了。感兴趣的朋友建议仔细阅读《Solaris动态跟踪指南》。