linux怎么测量性能,Linux 下的一个全新的性能测量和调式诊断工具 Systemt

杨 燚 (yang.y.yi@gmail.com), 计算机科学硕士, Intel

2007 年 8 月 31 日

系列文章详细地介绍了一个Linux下的全新的调式、诊断和性能测量工具Systemtap和它所依赖的基础kprobe以及促使开发该工具的先驱

DTrace并给出实际使用例子使读者更进一步了解和认识这些工具。

本文是该系列文章之一,它讲解了kprobe的原理、编程接口、局限性和使用注意事项并给出实际使用示例帮助读者理解和认识kprobe。本系列文章之二

讲解了DTrace以及Systemtap与DTrace比较。本系列文章之三讲解了Systemtap的原理,并通过一个例子向读者展示

Systemtap的工作机理。

一、kprobe简介

kprobe是一个动态地收集调试和性能信息的工具,它从Dprobe项目派生而来,是一种非破坏性工具,用户用它几乎可以跟踪任何函数或被执行的指令以

及一些异步事件(如timer)。它的基本工作机制是:用户指定一个探测点,并把一个用户定义的处理函数关联到该探测点,当内核执行到该探测点时,相应的

关联函数被执行,然后继续执行正常的代码路径。

kprobe实现了三种类型的探测点: kprobes, jprobes和kretprobes (也叫返回探测点)。

kprobes是可以被插入到内核的任何指令位置的探测点,jprobes则只能被插入到一个内核函数的入口,而kretprobes则是在指定的内核函

数返回时才被执行。

一般,使用kprobe的程序实现作一个内核模块,模块的初始化函数来负责安装探测点,退出函数卸载那些被安装的探测点。kprobe提供了接口函数

(APIs)来安装或卸载探测点。目前kprobe支持如下架构:i386、x86_64、ppc64、ia64(不支持对slot1指令的探测)、

sparc64 (返回探测还没有实现)。

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

二、kprobe实现原理

当安装一个kprobes探测点时,kprobe首先备份被探测的指令,然后使用断点指令(即在i386和x86_64的int3指令)来取代被探测指令

的头一个或几个字节。当CPU执行到探测点时,将因运行断点指令而执行trap操作,那将导致保存CPU的寄存器,调用相应的trap处理函数,而

trap处理函数将调用相应的notifier_call_chain(内核中一种异步工作机制)中注册的所有notifier函数,kprobe正是通

过向trap对应的notifier_call_chain注册关联到探测点的处理函数来实现探测处理的。当kprobe注册的notifier被执行

时,它首先执行关联到探测点的pre_handler函数,并把相应的kprobe

struct和保存的寄存器作为该函数的参数,接着,kprobe单步执行被探测指令的备份,最后,kprobe执行post_handler。等所有这

些运行完毕后,紧跟在被探测指令后的指令流将被正常执行。

jprobe通过注册kprobes在被探测函数入口的来实现,它能无缝地访问被探测函数的参数。jprobe处理函数应当和被探测函数有同样的原型,而

且该处理函数在函数末必须调用kprobe提供的函数jprobe_return()。当执行到该探测点时,kprobe备份CPU寄存器和栈的一些部

分,然后修改指令寄存器指向jprobe处理函数,当执行该jprobe处理函数时,寄存器和栈内容与执行真正的被探测函数一模一样,因此它不需要任何特

别的处理就能访问函数参数,

在该处理函数执行到最后时,它调用jprobe_return(),那导致寄存器和栈恢复到执行探测点时的状态,因此被探测函数能被正常运行。需要注意,

被探测函数的参数可能通过栈传递,也可能通过寄存器传递,但是jprobe对于两种情况都能工作,因为它既备份了栈,又备份了寄存器,当然,前提是

jprobe处理函数原型必须与被探测函数完全一样。

kretprobe也使用了kprobes来实现,当用户调用register_kretprobe()时,kprobe在被探测函数的入口建立了一个探

测点,当执行到探测点时,kprobe保存了被探测函数的返回地址并取代返回地址为一个trampoline的地址,kprobe在初始化时定义了该

trampoline并且为该trampoline注册了一个kprobe,当被探测函数执行它的返回指令时,控制传递到该trampoline,因此

kprobe已经注册的对应于trampoline的处理函数将被执行,而该处理函数会调用用户关联到该kretprobe上的处理函数,处理完毕后,设

置指令寄存器指向已经备份的函数返回地址,因而原来的函数返回被正常执行。

被探测函数的返回地址保存在类型为kretprobe_instance的变量中,结构kretprobe的maxactive字段指定了被探测函数可以

被同时探测的实例数,函数register_kretprobe()将预分配指定数量的kretprobe_instance。如果被探测函数是非递归的

并且调用时已经保持了自旋锁(spinlock),那么maxactive为1就足够了;

如果被探测函数是非递归的且运行时是抢占失效的,那么maxactive为NR_CPUS就可以了;如果maxactive被设置为小于等于0,

它被设置到缺省值(如果抢占使能, 即配置了

CONFIG_PREEMPT,缺省值为10和2*NR_CPUS中的最大值,否则缺省值为NR_CPUS)。

如果maxactive被设置的太小了,一些探测点的执行可能被丢失,但是不影响系统的正常运行,在结构kretprobe中nmissed字段将记录被

丢失的探测点执行数,它在返回探测点被注册时设置为0,每次当执行探测函数而没有kretprobe_instance可用时,它就加1。

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

三、kprobe的接口函数

kprobe为每一类型的探测点提供了注册和卸载函数。

1.register_kprobe

它用于注册一个kprobes类型的探测点,其函数原型为:

int register_kprobe(struct kprobe *kp);

为了使用该函数,用户需要在源文件中包含头文件linux/kprobes.h。

该函数的参数是struct kprobe类型的指针,struct

kprobe包含了字段addr、pre_handler、post_handler和fault_handler,addr指定探测点的位

置,pre_handler指定执行到探测点时执行的处理函数,post_handler指定执行完探测点后执行的处理函数,fault_handler

指定错误处理函数,当在执行pre_handler、post_handler以及被探测函数期间发生错误时,它会被调用。在调用该注册函数前,用户必须

先设置好struct kprobe的这些字段,用户可以指定任何处理函数为NULL。

该注册函数会在kp->addr地址处注册一个kprobes类型的探测点,当执行到该探测点时,将调用函数

kp->pre_handler,执行完被探测函数后,将调用kp->post_handler。如果在执行

kp->pre_handler或kp->post_handler时或在单步跟踪被探测函数期间发生错误,将调用

kp->fault_handler。

该函数成功时返回0,否则返回负的错误码。

探测点处理函数pre_handler的原型如下:

int pre_handler(struct kprobe *p, struct pt_regs *regs);

用户必须按照该原型参数格式定义自己的pre_handler,当然函数名取决于用户自己。参数p就是指向该处理函数关联到的kprobes探测点的指

针,可以在该函数内部引用该结构的任何字段,就如同在使用调用register_kprobe时传递的那个参数。参数regs指向运行到探测点时保存的寄

存器内容。kprobe负责在调用pre_handler时传递这些参数,用户不必关心,只是要知道在该函数内你能访问这些内容。

一般地,它应当始终返回0,除非用户知道自己在做什么。

探测点处理函数post_handler的原型如下:

void post_handler(struct kprobe *p, struct pt_regs *regs,

unsigned long flags);

前两个参数与pre_handler相同,最后一个参数flags总是0。

错误处理函数fault_handler的原刑如下:

int fault_handler(struct kprobe *p, struct pt_regs *regs, int trapnr);

前两个参数与pre_handler相同,第三个参数trapnr是与错误处理相关的架构依赖的trap号(例如,对于i386,通常的保护错误是13,

而页失效错误是14)。

如果成功地处理了异常,它应当返回1。

2.register_jprobe

该函数用于注册jprobes类型的探测点,它的原型如下:

int register_jprobe(struct jprobe *jp);

为了使用该函数,用户需要在源文件中包含头文件linux/kprobes.h。

用户在调用该注册函数前需要定义一个struct

jprobe类型的变量并设置它的kp.addr和entry字段,kp.addr指定探测点的位置,它必须是被探测函数的第一条指令的地址,entry

指定探测点的处理函数,该处理函数的参数表和返回类型应当与被探测函数完全相同,而且它必须正好在返回前调用jprobe_return()。如果被探测

函数被声明为asmlinkage、fastcall或影响参数传递的任何其他形式,那么相应的处理函数也必须声明为相应的形式。

该注册函数在jp->kp.addr注册一个jprobes类型的探测点,当内核运行到该探测点时,jp->entry指定的函数会被执行。

如果成功,该函数返回0,否则返回负的错误码。

3.register_kretprobe

该函数用于注册类型为kretprobes的探测点,它的原型如下:

int register_kretprobe(struct kretprobe *rp);

为了使用该函数,用户需要在源文件中包含头文件linux/kprobes.h。

该注册函数的参数为struct kretprobe类型的指针,用户在调用该函数前必须定义一个struct

kretprobe的变量并设置它的kp.addr、handler以及maxactive字段,kp.addr指定探测点的位置,handler指定探

测点的处理函数,maxactive指定可以同时运行的最大处理函数实例数,它应当被恰当设置,否则可能丢失探测点的某些运行。

该注册函数在地址rp->kp.addr注册一个kretprobe类型的探测点,当被探测函数返回时,rp->handler会被调用。

如果成功,它返回0,否则返回负的错误码。

kretprobe处理函数的原型如下:

int kretprobe_handler(struct kretprobe_instance *ri, struct pt_regs *regs);

参数regs指向保存的寄存器,ri指向类型为struct

kretprobe_instance的变量,该结构的ret_addr字段表示返回地址,rp指向相应的kretprobe_instance变

量,task字段指向相应的task_struct。结构struct

kretprobe_instance是注册函数register_kretprobe根据用户指定的maxactive值来分配的,kprobe负责在

调用kretprobe处理函数时传递相应的kretprobe_instance。

4.unregister_*probe

对应于每一个注册函数,有相应的卸载函数。

void unregister_kprobe(struct kprobe *kp);

void unregister_jprobe(struct jprobe *jp);

void unregister_kretprobe(struct kretprobe *rp);

上面是对应与三种探测点类型的卸载函数,当使用探测点的模块卸载或需要卸载已经注册的探测点时,需要使用相应的卸载函数来卸载已经注册的探测

点,kp,jp和rp分别为指向结构struct kprobe,struct jprobe和struct

kretprobe的指针,它们应当指向调用对应的注册函数时使用的那个结构,也就说注册和卸载必须针对同样的探测点,否则会导致系统崩溃。这些卸载函数

可以在注册后的任何时刻调用。

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

四、kprobe的特点和限制

kprobe允许在同一地址注册多个kprobes,但是不能同时在该地址上有多个jprobes。

通常,用户可以在内核的任何位置注册探测点,特别是可以对中断处理函数注册探测点,但是也有一些例外。如果用户尝试在实现kprobe的代码(包括

kernel/kprobes.c和arch/*/kernel/kprobes.c以及do_page_fault和

notifier_call_chain)中注册探测点,register_*probe将返回-EINVAL.

如果为一个内联(inline)函数注册探测点,kprobe无法保证对该函数的所有实例都注册探测点,因为gcc可能隐式地内联一个函数。因此,要记

住,用户可能看不到预期的探测点的执行。

一个探测点处理函数能够修改被探测函数的上下文,如修改内核数据结构,寄存器等。因此,kprobe可以用来安装bug解决代码或注入一些错误或测试代

码。

如果一个探测处理函数调用了另一个探测点,该探测点的处理函数不将运行,但是它的nmissed数将加1。多个探测点处理函数或同一处理函数的多个实例能

够在不同的CPU上同时运行。

除了注册和卸载,kprobe不会使用mutexe或分配内存。

探测点处理函数在运行时是失效抢占的,依赖于特定的架构,探测点处理函数运行时也可能是中断失效的。因此,对于任何探测点处理函数,不要使用导致睡眠或进

程调度的任何内核函数(如尝试获得semaphore)。

kretprobe是通过取代返回地址为预定义的trampoline的地址来实现的,因此栈回溯和gcc内嵌函数

__builtin_return_address()调用将返回trampoline的地址而不是真正的被探测函数的返回地址。

如果一个函数的调用次数与它的返回次数不相同,那么在该函数上注册的kretprobe探测点可能产生无法预料的结果(do_exit()就是一个典型的

例子,但do_execve() 和 do_fork()没有问题)。

当进入或退出一个函数时,如果CPU正运行在一个非当前任务所有的栈上,那么该函数的kretprobe探测可能产生无法预料的结果,因此kprobe并

不支持在x86_64上对__switch_to()的返回探测,如果用户对它注册探测点,注册函数将返回-EINVAL。

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

五、如何让内核支持kprobe

kprobe已经被包含在2.6内核中,但是只有最新的内核才提供了上面描述的全部功能,因此如果读者想实验本文附带的内核模块,需要最新的内核,作者在

2.6.18内核上测试的这些代码。内核缺省时并没有使能kprobe,因此用户需使能它。

为了使能kprobe,用户必须在编译内核时设置CONFIG_KPROBES,即选择在“Instrumentation

Support“中的“Kprobes”项。如果用户希望动态加载和卸载使用kprobe的模块,还必须确保“Loadable module

support” (CONFIG_MODULES)和“Module unloading”

(CONFIG_MODULE_UNLOAD)设置为y。如果用户还想使用kallsyms_lookup_name()来得到被探测函数的地址,也要确

保CONFIG_KALLSYMS设置为y,当然设置CONFIG_KALLSYMS_ALL为y将更好。

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

六、kprobe使用实例

本文附带的包包含了三个示例模块,kprobe-exam.c是kprobes使用示例,jprobe-exam.c是jprobes使用示

例,kretprobe-exam.c是kretprobes使用示例,读者可以下载该包并执行如下指令来实验这些模块:

$ tar -jxvf kprobes-examples.tar.bz2

$ cd kprobes-examples

$ make

$ su -

$ insmod kprobe-example.ko

$ dmesg

$ rmmod kprobe-example

$ dmesg

$ insmod jprobe-example.ko

$ cat kprobe-example.c

$dmesg

$ rmmod jprobe-example

$ dmesg

$ insmod kretprobe-example.ko

$ dmesg

$ ls -Rla / > /dev/null &

$ dmesg

$ rmmod kretprobe-example

$ dmesg

$

示例模块kprobe-exame.c探测schedule()函数,在探测点执行前后分别输出当前正在运行的进程、所在的CPU以及

preempt_count(),当卸载该模块时将输出该模块运行时间以及发生的调度次数。这是该模块在作者系统上的输出:

kprobe registered

current task on CPU#1: swapper (before scheduling), preempt_count = 0

current task on CPU#1: swapper (after scheduling), preempt_count = 0

current task on CPU#0: insmod (before scheduling), preempt_count = 0

current task on CPU#0: insmod (after scheduling), preempt_count = 0

current task on CPU#1: klogd (before scheduling), preempt_count = 0

current task on CPU#1: klogd (after scheduling), preempt_count = 0

current task on CPU#1: klogd (before scheduling), preempt_count = 0

current task on CPU#1: klogd (after scheduling), preempt_count = 0

current task on CPU#1: klogd (before scheduling), preempt_count = 0

Scheduling times is 5918 during of 7655 milliseconds.

kprobe unregistered

示例模块jprobe-exam.c是一个jprobes探测例子,它示例了获取系统调用open的参数,但读者不要试图在实际的应用中这么使用,因为

copy_from_user可能导致睡眠,而kprobe并不允许在探测点处理函数中这么做(请参看前面内容了解详细描述)。

这是该模块在作者系统上的输出:

Registered a jprobe.

process 'cat' call open('/etc/ld.so.cache', 0, 0)

process 'cat' call open('/lib/libc.so.6', 0, -524289)

process 'cat' call open('/usr/lib/locale/locale-archive', 32768, 1)

process 'cat' call open('/usr/share/locale/locale.alias', 0, 438)

process 'cat' call open('/usr/lib/locale/en_US.UTF-8/LC_CTYPE', 0, 0)

process 'cat' call open('/usr/lib/locale/en_US.utf8/LC_CTYPE', 0, 0)

process 'cat' call open('/usr/lib/gconv/gconv-modules.cache', 0, 0)

process 'cat' call open('kprobe-exam.c', 32768, 0)

process 'rmmod' call open('/etc/ld.so.cache', 0, 0)

process 'rmmod' call open('/lib/libc.so.6', 0, -524289)

process 'rmmod' call open('/proc/modules', 0, 438)

jprobe unregistered

示例模块kretprobe-exam.c是一个返回探测例子,它探测系统调用open并输出返回值小于0的情况。它也有意设置maxactive为1,

以便示例丢失探测运行的情况,当然,只有系统并发运行多个sys_open才可能导致这种情况,因此,读者需要有SMP的系统或者有超线程支持才能看到这

种情况。如果读者比较仔细,会看到在前面的命令有”ls -Rla / > /dev/null

& ,那是专门为了导致出现丢失探测运行的。

这是该模块在作者系统上的输出:

Registered a return probe.

sys_open returns -2

sys_open returns -2

sys_open returns -2

sys_open returns -2

sys_open returns -2

sys_open returns -2

sys_open returns -2

sys_open returns -2

sys_open returns -2

sys_open returns -2

sys_open returns -2

sys_open returns -2

kretprobe unregistered

Missed 11 sys_open probe instances.

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

小结

本文详细地讲解了kprobe的方方面面并给出实际的例子代码帮助读者学习和使用kprobe。本文是系列文章“Linux下的一个全新的性能测量和调式

诊断工具 -- Systemtap”之一,有兴趣的读者可以阅读该系列文章之二和三。

一、DTrace 简介

DTrace是一个强大的动态跟踪框架,它允许管理员、开发者和服务团队精确地回答关于操作系统和用户程序的任何问题。用户可以使用它管理成千上万个探测

点,为每一个探测点指定执行条件(Predicates)和执行的动作(Actions),动态管理跟踪缓存和探测点开销。用户通过它能够对正在运行的系

统跟踪来查看问题,也可以根据系统崩溃时产生的dump数据来查看问题。开发人员则可以实现新的提供者(Provider)和消费者(Consumer)

以及配置管理探测点的工具。

DTrace对探测点上下文没有任何限制,也就说可以对任何函数进行探测,这是因为DTrace框架是完全非阻塞的,它基本上没有显式或隐式地调用内核其

他功能。

Dtrace主要包含以下组件:

消费者(Consumer)

消费者是一个用户态应用,如dtrace,它能够通过Dtrace框架提供的接口库来访问DTrace内核组件

提供者(Provider)

提供者实现作内核模块,每一个模块建立某一种类的探测点,消费者能够使能提供者实现的任何探测点,并且可以把任何动作(action)绑定到这些探测点。

提供者也能根据用户的跟踪请求创建新的探测点。

DTrace已经实现了16个提供者(当然用户可以实现自己的提供者),包括dtrace、lockstat、profile、fbt(Function

Boundary Tracing)、syscall、sdt(Statcially Defined

Tracing)、sysinfo、vminfo、proc、sched、io、mib、fpuinfo、pid、plockstat和

fasttrap。

探测点(Probes)

探测点是由提供者创建的用于标识要探测的模块和函数。每一个探测点有一个名字。每一个探测点可以用四元组

provider:module:function:name来标识,它也有独一无二的整数标识符。

预测(Predicates)

预测是一个用/括起来的表达式,类似于条件,它在探测点命中时计算以决定关联的动作是否被执行。预测是主要的条件结构,在D脚本中的复杂的控制流都是用它

来实现的。对于任何探测点,该部分可以被完全省略,这时,关联到探测点的动作(action)将在任何一次命中后都被执行。预测必须是一个可计算的表达

式。在D语言中,0值解释为假,1解释为真。

动作(Actions)

动作是用D语言编写的一些语句,它被Dtrace虚拟机在内核中执行。关联到探测点的动作会在该探测点命中时执行。一般地,动作用来记录指定的系统状态信

息,但是它也能改变系统状态,这种动作称为破坏性动作(destructive action),缺省情况下不允许这种动作。

D脚本语言

D语言类似于awk脚本,用户使用它来编写动作和预测。

DTrace可以被用于性能监视、用户进程跟踪、匿名跟踪和推测跟踪(匿名跟踪没有消费者,这只能被超级用户执行,而且至多只能有一个匿名跟踪;推测跟踪

则是指当探测点被命中后,动作是根据条件来执行的,predicates被用于实现推测跟踪),并且在没有使能跟踪的情况下没有任何开销,对系统性能没有

任何影响。

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

二、DTrace原理

DTrace的核心组件全部在内核中实现,包括探测点处理、缓存以及instrumentation,用户态进程作为DTrace消费者

(consumer)通过利用DTrace库能够和内核中的DTrace组件进行通信,DTrace提供的应用工具(dtrace就是一个典型的消费者,

只是它使得用户可以非常方便地使用DTrace提供的任何功能)。DTrace框架不执行任何instrumentation,它只是负责指派

instrumentation给对应的提供者(provider)。当DTrace核心组件指令提供者执行instrumentation时,提供者自

己确定要instrument的点,然后回调DTrace框架提供的接口来创建探测点。为了创建一个探测点,提供者需要指定模块名、函数名以及探测点名。

每一个探测点用一个独一无二的四元组表示(简介部分已经讲到),四元组包括提供者、模块、函数名和探测点名,创建的探测点并不会被立即执行,它还需要消费

者使能(探测点也可以被消费者失效),只有使能的探测点才会被执行。DTrace框架提供的探测点创建接口函数在执行成功时会给提供者返回一个探测点标识

符。这些创建的探测点会通知给消费者,消费者能使能它感兴趣的探测点,当使能探测点时,DTrace将创建一个使能控制块(ECB --

Enabling Control

Block)并绑定到该探测点,如果该探测点原来是失效的(即没有其它ECB绑定到该探测点),DTrace还将调用该探测点的提供者来真正使能该探测点

(当然,如果该探测点已经是使能的,即它的ECB链不为空,这个操作是不必要的)。当CPU执行到一个使能的探测点(即运行到提供该探测点的提供者)时,

对应的提供者会把控制传递到DTrace框架的入口并传递探测点的标识符作为第一个参数,DTrace框架得到控制后会失效当前CPU上的中断,然后执行

该探测点ECB链上的每一个ECB指定的活动(一个探测点可以绑定多个动作),然后使能中断并把控制返回到该提供者。提供者不需要考虑如何多路复用处理一

个探测点的多个消费者,ECB能很好地处理它。

每一个ECB可以有预测或条件(predicates),如果ECB有预测或条件,那么动作只有在该条件满足时才被执行。每一个ECB可以有多个动作,它

们都会在条件满足时执行。如果动作要求保存一些数据,数据将被保存到对应于相应的消费者的缓存。动作不可以保存数据到内核内存,不可以修改寄存器,也不可

以对系统状态做任何变化(破坏性动作除外,那要求用户必须是特权用户)。

DTrace为每一个DTrace消费者在每一个CPU上分配一些内核缓存,消费者状态指向该缓存,消费者的所有ECB都有一个指向消费者状态的指针。当

一个ECB的动作要求保存数据时,数据就被保存到这些缓存中,一个ECB保存的数据量是一个常数,不同的ECB该常数可以不同。在处理ECB之前,会核对

那些缓存看是否有足够的空间保存数据,如果没有足够的空间,对应于该缓存的一个丢失计数器会加1,该ECB上的所有动作将被跳过。消费者需要定期地读取缓

存中的数据,否则会导致数据丢失。缓存实现完全是免锁的,即消费者读的同时,ECB的动作仍能在需要时立刻保存数据,不必锁等待。它是这样来实现的:每一

个CPU上有两个缓存,一个是活动的(active),一个是非活动的(inactive),当消费者想读取指定CPU的缓存时,两个缓存被交换,即活动

的变成非活动的,非活动的变成活动的,这个交换操作可以在极其短的时间内完成,并使用中断失效来保护(ECB的动作保存数据或其它消费者的读取操作需要访

问这些缓存,因而必须被同步)。在交换之后,消费者从不活动的缓存读取数据,而ECB能够向活动的缓存写数据。在每一个CPU上的缓存中,每一次的数据保

存都会把探测点标识符(EPID -- Enabled Probe

Identifier,EPID与ECB一一对应,因此可用于查询对应的ECB已经保存的数据的长度)放在首部。

动作(actions)和预测/条件(predicates)用D语言编写,它们会转换成DIF(D Intermediate

Forat,DTrace实现的一种虚拟机指令),DIF简化了仿真和代码生成。

它DIF指令是解释执行的,安全检查可以在解释时进行,因此安全性可以得到保证。当DIF指令装入内核时,操作码,保留位,寄存器,字符串引用,变量引用

以及一些基本的安全检查都要被严格检查。一些运行时错误(如除0)并不能从静态分析中发现,因此它们被DIF虚拟机处理。当遇到这样的指令,DIF虚拟机

将不执行它们,并导致当前ECB的处理终止,相应的消费者得到一个运行时错误。从设备的I/O内存区装入数据也是不允许的,DIF可以根据要装入的数据地

址很容易地做判断。对于无效的数据装入地址,DIF虚拟机判断它是否有效很复杂,因此借用硬件已有的错误处理机制来实现,DTrace修改页失效处理函数

来检查这种情况并做特殊处理,当遇到这样的情况,将导致页失效,页失效处理函数会检查导致页失效的指令是否来自DIF虚拟机,如果是,页失效处理函数将设

置一个标志位来表示发生了页失效,然后跳到导致页失效指令的下一条指令执行,也即让DIF虚拟机继续执行其余的DIF虚拟机指令,因此DIF虚拟机能够在

执行下一条虚拟机指令前能够有机会检查那个标志位。每次DIF虚拟机在执行一条虚拟机指令前都会检查那个标志位,如果被设置了,当前的ECB处理将被终

止,相应的消费者将得到一个运行时错误。这种实现增加了页失效处理函数的开销,但是相对于整个页失效处理函数的开销,它是非常小的,因此对系统性能的影响

可以忽略不记。

D语言类似于awk脚本语言,它支持所有ANSI

C支持的操作符,变量类型,也支持typedef并能够定义struct、union和enum。也允许访问内核定义的数据类型和全局变量。D语言支持C

语法的变量声明,变量可以声明,也可以不声明,如果不声明,它的类型在赋值时确定,实际类型为所赋值的类型。

D语言也可以调用DTrace提供的函数和变量。D语言支持全局变量、语句内变量、线程本地变量和关联数组。为了区分语句内变量和线程本地变量,在引用变

量时需要有前缀,this->表示语句内变量,self->表示线程本地变量。语句内变量类似于C语言的静态局部变量,线程本地变量就是每一

个访问该变量的线程都有自己的变量存储,不同的线程互不影响,C语言没有类似的变量与之对应。D语言也支持一种特殊的数据类型,聚合

(Aggregations)。为了实现聚合,需要实现相应的聚合函数。用一个实例可以很好地说明何为聚合:

syscall::write:entry

{

@counts[execname] = count();

}

其中@表示该变量是聚合,counts就是聚合变量的名称,用户可以随意取名,execname是一个DTrace变量,表示当前进程名,用户可以根据需

要指定不同的值,它是这个聚合的索引,类似于数组的索引,只是它可以为整数,也可以是字符串。count()是聚合函数,它返回

@counts[execname] + 1,也就说语句@counts[execname] =

count()实现了@counts[execname] 加1。

聚合不同于关联数组,赋值操作必须通过聚合函数来做,索引也不同,关联数组的索引是逗号分割的一个或多个表达式列表。

用户态工具dtrace能把D语言转换成DIF指令。一个D程序的结构如下:

probe-descriptions

/predicate/

{

action-statements

}

probe-descriptions用于指定探测点,它是一个四元组,与前面讲到的探测点四元组的组成一样,包括提供者、模块、函数名、探测点名,只是

这四个元素的每一个都可以不提供(那表示匹配任何值),predicate就是预测或条件,它可以没有,action-statements就是动作,它

可以是一条或多条D语句。

DTrace能够跟踪用户态应用(Systemtap目前不能),这是由pid提供者实现的。

DTrace还实现了一种特殊的跟踪推测跟踪(Speculative

Tracing)。有时消费者在执行探测点时并不知道是否该探测是它所需要的,只有在探测执行后一段时间才能知道,因此预测没法覆盖这种情况。推测跟踪的

原理是先执行探测并暂时保存数据在一个临时缓存,如果它发现那些数据是感兴趣的,就提交到真正的缓存,否则就丢弃那些数据。

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

三、实例解析

下面是一个实际的使用示例:

# dtrace -n syscall:::entry'/pid == 31337/{ @syscalls[probefunc] = count(); }'

dtrace: description 'syscall:::entry' matched 215 probes

^C

open 1

lwp_park 2

times 4

fcntl 5

close 6

sigaction 6

read 10

ioctl 14

sigprocmask 106

write 1092

选项-n用于使能指定的探测点,syscall:::entry就是前面提到的四元组,它用于描述哪些探测点将被使能,如果某个元素为空,表示将匹配该元

素对应的任何组件。该例子中的四元组指定的探测点为系统调用入口。/括起来的部分就是预测(Predicate),它表示只有对当前进程PID为

31337的进程的系统调用执行后面指定的动作,大括号包含的部分为真正的动作,@表示它为Dtrace会聚,会聚是一个变量类型,它被用于采集到的数据

的综合处理,一般在性能监视中使用最为普遍,syscalls为会聚的变量名,probefunc为会聚的键,它是Dtrace内嵌变量,表示被探测的函

数的名称,你可以把syscalls理解为关联数组,当然可以随意取名,count为Dtrace为会聚实现的内部函数,它返回它被调用的次数,注意,不

同的探测点count互不相干。

一、简介

SystemTap是一个诊断Linux系统性能或功能问题的开源软件。它使得对运行时的Linux系统进行诊断调式变得更容易、更简单。有了它,开发者

或调试人员不再需要重编译、安装新内核、重启动等烦人的步骤。

为了诊断系统问题或性能,开发者或调试人员只需要写一些脚本,然后通过SystemTap提供的命令行接口就可以对正在运行的内核进行诊断调试,以前需要

的修改或插入调试代码、重新编译内核、安装内核和重启动等这些琐碎的工作完全消除。目前该工具并不支持对用户态应用的诊断调试,但是它们在以后会被添加进

去。当前该项目的主要开发人员为来自Red Hat, IBM,

Intel和Hitachi的工程师。其中Redhat主要负责脚本转换/翻译器和运行时库,IBM负责kprobe和relayfs,Intel负责转

换器安全检查以及performance monitor tapset。

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值