DTRACE简介

Posted zengkefu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DTRACE简介相关的知识,希望对你有一定的参考价值。

 

 

      通过上一次的介绍,相信大家对DTRACE已经有了一个初步的认识。上一次结束时专门留了一个例子,可能大家第一次看有很多不明白的地方,没有关系,随着我们对DTRACE更多的介绍,很快就会”云开雾散“了。

      D语言作为一种编程语言,自然就有其语法、关键字、数据结构、运算符、函数等,我将一一介绍。

      D语言中标志符名称与C语言类似,由字母、数字和下划线组成,其中第一个字符必须是字母或者下划线。D语言预留了一些关键字供DTRACE本身使用,关键字不能用做D变量的名称。D关键字列表参阅《Solaris动态跟踪指南》,这里只列出一些常用的。
     

       表1 - 常用DTRACE关键字

 

关键字  描述
inline  编译期间将指定的D变量替换为预定义的值或者表达式,inline可以申明类型
sizeof  计算对象的大小
self  表示将D变量存放在线程(thread)的私有空间里
this  表示D变量的有效范围在this所在的子句内
 

 

    

 

 

      D语言中定义了整数类型和浮点类型,以及用于表示ASCII字符串的string类型。整数类型随机器字长的不同而不同。机器字长可以用命令isainfo -b来查看。

       表2 - D整数类型

 

 类型名称 32位机器字长
64位机器字长
 char 1个字节   1个字节
 short 2个字节  2个字节
 int 4个字节  4个字节
 long 4个字节  8个字节
 longlong 8个字节  8个字节

 

 

 

 

 

 

       一点小知识,C语言中有ILP32和LP64两种数据模型,ILP32指的就是int/long/pointer(指针)是32位,LP64指的是long/pointer是64位。

       对于整数类型,又分为带符号(signed)和无符号(unsigned)两种,因为是否带符号决定了对其最高位(most-significant)的解释。无符号整型通常是在相应的整型前面添加unsigned或者u限定符。

        表3 - D整数类型别名

     

 类型名称 说明 
 int8_t / uint8_t 1字节带符号整数 / 1字节无符号整数
 int16_t / uint16_t 2字节带符号整数 / 2字节无符号整数
 int32_t / uint32_t 4字节带符号整数 / 4字节无符号整数
 int64_t / uint64_t 8字节带符号整数 / 8字节无符号整数
 intptr_t / uintptr_t 大小等于指针的带符号整数 / 大小等于指针的无符号整数

 

 

 

 

 

 

      D语言中也定义了转义序列如‘\\n‘表示回车。

      D语言中定义了算术运算符、关系运算符、逻辑运算符、按位运算符、赋值运算符、递增和递减运算符、条件表达式(即 ? : 运算符),由于这些运算符及其优先级与C语言基本相同,就不在这里占用篇幅了。D语言也支持”强制类型转换“,即把一种类型转换为另一种兼容类型,比如将指针转换为整数。

      除了上面的数据类型,D语言还提供有数组(array)关联数组(associative array),关联数组中有一种特殊类型叫做聚合(aggregation),在后面会看到。关联数组通过一个称为键(key)的名称来检索数据,用过Perl的朋友相信不会陌生。定义关联数组,只需作以下赋值操作即可:

      name[key]=expression;

      例如: people["sam.wan",30]=100

      D语言中的变量是不需要预定义就可以直接使用的。但是在没有赋值之前,是不能出现在谓词中和赋值运算等号右侧。请看下面的3个例子:

例子1
# dtrace -n ‘BEGIN{a=1;exit(0);}END{printf("a=%d\\n",a);}‘
dtrace: description ‘BEGIN‘ matched 2 probes
CPU     ID                    FUNCTION:NAME
  0      1                           :BEGIN
  0      2                             :END a=1


例子2
# dtrace -n ‘BEGIN/a==0/{exit(0);}END{printf("a=%d\\n",a);}‘
dtrace: invalid probe specifier BEGIN/a==0/{exit(0);}END{printf("a=%d\\n",a);}: in predicate: failed to resolve a: Unknown variable name


例子3
# dtrace -n ‘BEGIN{a=a+1;exit(0);}END{printf("a=%d\\n",a);}‘
dtrace: invalid probe specifier BEGIN{a=a+1;exit(0);}END{printf("a=%d\\n",a);}: in action list: a has not yet been declared or assigned

      缺省情况下,D语言中定义的变量是全局范围的。在多线程环境中,全局变量是不安全的,因为可能多个线程都会进行访问,因此,D语言引入了线程局部变量标志符self。通过在一个变量前面添加self->修饰,可以将该变量存放在线程自己的局部空间中,这样不会受到其它线程的影响,这种方法对于现在越来越多的并发操作环境十分有利。线程局部变量与全局变量在不同的名称空间(name space)中,因此即使名字相同也不会冲突,比如self->aaa和aaa是两个不同的变量。

       特别需要提醒注意的是,在使用完一个变量之后,要将该标量赋值为‘0‘,这样DTRACE就会回收释放其所占用的内存空间。对用一个好的程序员来说,释放空间和分配空间同样重要。

      D语言中还有一种特殊的变量叫“子句局部变量(Clause Local)”,通过在变量名前添加this->修饰符完成。子句局部变量的作用域只在其定义的子句内有效。D语言中的变量缺省情况下会被赋值为0,但是子句局部变量除外。

      除用户定义的变量外,D语言本身提供了一些非常有用的内置变量,所有这些内置变量都是全局变量。

      表4 - DTrace内置变量

 

 类型和名称 说明 
int64_t arg0,...,arg9  探测器的前10个输入参数(64位整数)。如果当前探测器参数个数少于10,则未定义的参数值不确定 
args[] 与arg0...arg9不同,args[]是有类型的,其类型对应与当前探测器的参数类型。
uintptr_t caller 进入当前探测器之前的当前线程的程序计数器(PC)位置
chipid_t chip 当前物理芯片的CPU芯片标志符
processorid_t cpu 当前CPU的编号
cpuinfo_t \*curcpu 当前CPU的信息(具体结构后面会讲到)
lwpsinfo_t \*curlwpsinfo 与当前线程关联的轻量进程(LightWeight Process,LWP)的信息(具体结构见后)
psinfo_t \*curpsinfo 与当前线程关联的进程的信息
kthread_t \*curthread 当前线程在内核中的数据结构(kthread_t)的地址,kthread_t的定义在<sys/thread.h>中。
string cwd 当前进程的工作路径(Current Working Directory)
uint_t epid 当前探测器的已启用的探测器ID号。
int errno 当前线程最后一次执行的系统调用的返回错误值
string execname 当前进程的名称
gid_t gid 组ID
uint_t id 当前探测器的唯一ID号,dtrace -l的第一列
uint_t ipl 触发探测器时当前CPU的中断优先级(Interrupt Priority Level,IPL)。
lgrp_id_t lgrp 当前CPU所属的延迟组(Latency Group)的ID
pid_t pid 当前进程号
pid_t ppid 当前进程的父进程
string probefunc 当前探测器的函数名
string probemod 当前探测器的模块名
string probename 当前探测器的名字
string probeprov 当前探测器的提供器名
psetid_t pset 当前CPU所属的处理器集(Processor Set)的ID
string root 当前进程的根目录名
uint_t stackdepth 当前线程的栈帧(Stack Frame)的深度。即其调用的函数的层次数。
id_t tid 当前线程的线程ID
uint64_t timestamp 以纳秒(ns)为单位的时间计数器。此计数器从过去的任意点递增,仅用于相对计算中。
uid_t uid 当前进程的实际用户ID
uint64_t uregs[] 当前线程的用户寄存器值
uint64_t vtimestamp 以纳秒(ns)为单位的时间计数器,实际是当前线程在CPU中已运行的时间减去DTrace谓词和操作所花费的时间。同timestamp一样,仅用于相对计算。
uint64_t walltimestamp 自1970年1月1日00:00世界标准时间以来的纳秒数。


 

 

     Dtrace还可以使用反引号(backquote `)访问操作系统内核中定义的变量,但不能进行修改。

     内核中定义的变量可以通过查看内核的变量符号表(Name Symbol)来找到。

     #echo "::nm"|mdb -k|more

     比如我们想通过DTrace来查看每秒钟freemem的值。freemem表示当前系统中可用的内存页数

     #dtrace -qn ‘tick-1sec{printf("%d pages of freemem\\n",`freemem)}‘

     由于Solaris可用动态加载模块,各个模块可能有相同的变量名,为了避免冲突,可用使用模块名来区分,比如访问a模块的x变量:a`x

      DTrace还提供操作和子例程。

     如果DTrace子句为空,则会采用缺省操作。缺省操作即显示已启用的探测器的标志符。
 

     数据记录操作

      数据记录操作总会往指定的缓冲区中放入数据。 
      void trace(expression) 将expression的结果放到指定的缓冲区(在后面会提到)。

      void tracemem(address,size_t nbytes),从address地址复制nbytes的内容到指定缓冲区。

      void printf(string format,...) 格式化输出。具体格式可用参见printf(3C)手册页。

      void printa(aggregation)/void printa(string format,aggregation) 显示及格式化聚合(在后面会提到)

      void stack(void)/void stack(int nframes) 将指定长度的栈帧记录拷贝到指定的缓冲区。

      void ustack(int nframes,int size)/void ustack(int nframes)/void ustack(void),同上,只是操作的是用户栈    

      破坏性操作

       void stop(void) 停止触发当前探测器的进程

       void raise(int signal) 将指定的信号signal发送至触发当前探测器的进程

       void copyout(void \*buf,uintptr_t addr,size_t nbytes) 从buf地址拷贝nbytes字节到当前进程的addr地址处。

       void copyoutstr(string str,uintptr_t addr,size_t maxlen) 将字符串string拷贝到当前进程的addr地址处

       void system(string program,..) 执行程序

       内核破坏性操作(下面的操作将会影响整个系统的运行)

       void breakpoint(void) 发生一个内核断点

       void panic(void) 触发panic()操作(这个相信大家都再熟悉不过了)

       void chill(int nanoseconds) DTrace执行nanoseconds时间的spin操作(循环),如果nanoseconds> 500milliseconds,则会失败。

        特殊操作

        推测性操作(Speculative Actions),有speculate(),commit(),discard(),在后面会提到。

        void exit(int status) 立即停止DTrace跟踪。

        子例程

        与操作不同,子例程只会影响DTrace的内部状态。

        void \*alloca(size_t size)  分配size字节的临时空间,返回一个8字节对齐的指针。

        string basename(char \*str)  从str中去除前缀和目录名

        void bcopy(void \*src,void \*dest,sizt_t size) 从src拷贝size字节到dest。

        string cleanpath(char \*str) 去除str中的/./和/../等

        void \*copyin(uintptr_t addr,size_t size) 从用户地址空间addr处拷贝size字节到Dtrace临时缓冲区中,并返回缓冲区地址。

        string \*copyinstr(uintptr_t addr) 从用户地址空间addr除拷贝已null结尾的ASCII字符串到Dtrace临时缓冲区,并返回缓冲区地址。

         void copyinto(uintptr_t addr,size_t size,void \*dest) 从用户地址空间addr处拷贝size字节到Dtrace临时缓冲区的dest处。

          string dirname(char \*str) 返回str的目录名

          size_t msgdsize(mblk_t \*mp) 返回mp指向的数据消息的字节数

          size_t msgsize(mblk_t \*mp) 返回mp消息字节数

          int mutex_owned(kmutex_t \*mutex) 如果当前线程拥有互斥锁mutex,则返回非零;否则返回0

          kthread_t \*mutex_owner(kmutex_t \*mutex) 返回mutex互斥锁的属主的线程数据结构kthread_t的指针。如果没有属主或者该互斥锁是自旋锁(Spin Mutex),则返回null。

         int mutex_type_adaptive(kmutex_t \*mutex) 如果mutex是自适应互斥锁(MUTEX_ADAPTIVE类型),则返回非0,否则返回0。

         int progenyof(pid_t pid) 如果触发当前探测器的进程是指定进程的子孙,则返回非0

         int rand(void) 返回一个伪随机整数

         int rw_iswriter(krwlock_t \*rwlock) 如果指定的读写锁rwlock被一个写入者占有或者要求获得,则返回非0,否则返回0

         int rw_write_held(krwlock_t \*rwlock) 如果指定的读写锁当前被一个写入者占有,则返回非0,否则返回0

         int speculation(void) 为speculate()操作预留一个推测性跟踪缓冲区,并返回这个缓冲区的标志符。

         string strjoin(char \*str1,char \*str2) 串联str1和str2到临时空间,并返回其地址。

         size_t strlen(string str) 返回指定字串的长度(不包括结尾的空字节null)

      看了这么多操作和子例程,是不是有点受打击了?没有关系,慢慢来,先熟悉一下,在今后具体使用时,就会印象深刻。

      关于前面的copyin/copyinstr/copyinto子例程再多作一点说明:

      在Solaris(UNIX)系统中,用户程序是运行在用户地址空间里面,当用户程序执行系统调用比如open(2)时,才会进入到内核空间执行(我们通常称之为"陷入trap"技术分享,为了访问用户地址空间的字符串,就必须将其拷贝到内核空间里面来,否则内核找不到相应的地址,就会报错。看下面的一个例子。

      我们想知道是什么程序在调用open(2),以及打开什么文件。这里很自然我们会用到syscall提供器的open:entry探测器。此探测器的参数可用从open(2)的手册页查到(所有的syscall提供器提供的探测器都可用在相对应的系统调用手册中查到)

 

      int open(const char \*path, int oflag, /\* mode_t mode \*/);

      我们只关心第一个参数arg0,这是一个字符串指针(即将要打开的文件名)。对于字符串指针,可以使用"%s"进行格式化输出。

#!/usr/sbin/dtrace -qs
syscall::open:entry,
syscall::open64:entry
{
     printf("%s[%d] opened %s\\n",execname,pid,arg0);
}

       运行一下看看


 # ./who_open_what.d
dtrace: failed to compile script ./who_open_what.d: line 5: printf( ) argument #4 is incompatible with conversion #3 prototype:
        conversion: %s
         prototype: char [] or string (or use stringof)
          argument: int64_t


       错误,为什么,因为传递给内核的是一个用户地址空间的指针,内核无法访问该地址,内核只看到一个指针,因此Dtrace认为格式化用的是"%s",但是传递的却是一个int64_t类型,不匹配。

       正确的程序应该是:

 

#!/usr/sbin/dtrace -qs
syscall::open:entry,
syscall::open64:entry
{
     printf("%s[%d] opened %s\\n",execname,pid,copyinstr(arg0));
}

        再来看看

 


# ./who_open_what.d
nfsmapid[272] opened /etc/default/nfs
nfsmapid[272] opened /etc/resolv.conf
init[1] opened /etc/inittab
init[1] opened /etc/svc/volatile/init-next.state
init[1] opened /etc/svc/volatile/init-next.state
init[1] opened /etc/inittab
...

 



































以上是关于DTRACE简介的主要内容,如果未能解决你的问题,请参考以下文章

DTRACE简介

dtrace 脚本输出是啥意思?

ZFS+Dtrace+Zones+KVM=SMARTOS + dtrace 详细文档

在 Oracle Linux 上使用 DTrace

用 C 编写一个 DTrace 消费者

如何在 dtrace 中找到参数的意思