zoukankan      html  css  js  c++  java
  • DTRACE简介(2)

     

          通过上一次的介绍,相信大家对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
    ...

  • 相关阅读:
    【原创】Silverlight之TextBox的LostFocus、GotFocus事件
    SQL Cursor 基本用法[用两次FETCH NEXT FROM INTO语句?]
    cannot convert from 'wchar_t *' to 'char *' 问题
    TEXTMETRICW 结构记录
    【D3D】Direct3D中LPRECT(上左右底)和LPoint(x,y)之前转换
    【原创】有关Silverlight中自动生成的类中 没有WCF层edmx模型新加入的对象 原因分析。
    【原创】有关Silverlight中异常“XmalParseEception” 通用解决思路
    hdu 1011(Starship Troopers,树形dp)
    hdu 2196(Computer 树形dp)
    树形dp(poj 1947 Rebuilding Roads )
  • 原文地址:https://www.cnblogs.com/zengkefu/p/5971490.html
Copyright © 2011-2022 走看看