用KFI和Graphviz跟踪/优化内核代码
作者:刘旭晖 Raymond转载请注明出处
Email:
colorant@163.com
这是大概一年半前写的文章了,最近在做KFI的后续升级KFT的时候,打算再记录一下自己的工作,发现原理部分到现在也没有什么变,懒得重复原理部分,把以前的文章重贴上来(以前贴在google的个人主页上,最近好像访问不了了,郁闷)
1 简介
KFI是用来详细跟踪,记录和分析内核的函数调用过程的一套工具,它包括
Ø 内核层中的代码
Ø 用户层中的控制程序
Ø 用户层中的trace结果分析工具
你可以用它来记录所有的函数调用,也可以通过设置
Ø 启动/结束的触发条件(Trigger)
Ø 指定函数列表( Function Filter)
Ø 函数执行时间筛选(Function Duration Filter ,Mintime / Maxtime)
Ø 选择记录 中断内 / 非中断内函数
Ø 设置log尺寸
等手段来跟踪特定的函数调用过程
详细的使用方法和介绍可以参考下列文档:
需要注意的是,KFI也是随着Kernel的发展在不断的发展中,目前甚至连名字都改为KFT了,所以文档要以适合你的内核版本的KFI版本为准。
KFT的最新发展情况可以参考下列网址 :
2 KFI工作原理简单分析
2.1 代码生成
KFI得以工作的最基础的根基应该是GCC的一个编译选项: -finstrument-functions
这个编译选项使得Gcc在编译代码的时候,会自动地在函数调用的进入和退出的地方加入对以下两个函数的调用:
void __cyg_profile_func_enter (void *this_fn, void *call_site);
void __cyg_profile_func_exit (void *this_fn, void *call_site);
其中this_fn 指向当前函数的地址,call_site为调用当前函数的代码的地址。
KFI就是在kfi模块代码中实现了这两个函数,并通过这两个函数作为整个模块的关键出路口,构建了一套trace的体系。
另外 函数可以被赋予一个no_instrument_function的属性,Gcc对具有这种属性前缀的函数就不会自动添加上诉两个函数调用。 这种属性可以用于KFI本身的实现代码中的函数,以及一些对时间要求很严格的中断函数等场合。
实际上,不仅在kernel中可以使用-finstrument-functions编译选项,在用户程序中同样可以使用这个编译选项,因而它同样被其它工具用来跟踪用户层的代码。
2.2 运行
为了减少KFI在运行过程中对系统带来的额外负担,KFI的函数会根据触发条件,Filter等判断是否已经注册一个run的section,这个run是否已经开始,完成,当前函数是否满足Filter条件等,尽可能快的运行返回。
数据记录保存在事先在内核中分配的内存中,在一个Run完成后,再由应用层通过KFI的应用程序读取输出到用户空间,这样做也尽可能的减少了对系统得额外负担(相对于Prink直接输出,或定期的写数据到文件系统中等Trace结果输出方案)。当然,这也使得记录的容量受到限制,在不设置任何filter的情况下,可能KFI所被许可分配的内存在极短的时间内就被使用耗尽,导致可能无法完整正确的得到所需的log。所以这也就要求使用KFI时需要尽可能准确的设置触发和过滤条件。
2.3 结果分析
用KD分析KFI的输出结果的时候,可以列出函数执行的Local时间(不包含其子函数执行的时间),Local时间能比较好的体现该函数的实际执行情况。
不过需要注意一点的是:如果在filter中设置了一些条件,将某个函数的一些子函数都给滤掉了,那么这个Local时间将会包含这些子函数的执行时间,因而也就不能精确的反映该函数真正的本地代码执行时间了,所以在结果分析的时候也还要综合考虑到这些影响。
3 使用Graphviz将KFI的Trace结果图形化
3.1 目的
虽然从KFI的原始Trace数据乃至经过KD分析的结果,已经足以进行分析,不过有一点遗憾就是,KD的输出结果还是不够直观,不能快速的给出一个系统运行的流程框架结构。
因此,我们可以借助一些辅助工具,比如Graphviz将KFI的输出结果图形化,从而给出一个函数调用关系框图,既有利于分析系统运行过程,也有助于一些调度关系负责的源码的学习阅读。
3.2 Graphviz简介
Grapyviz是一个根据特定的文本描述,自动绘制框图的工具。只要按照他的语法,描述一下框图中的节点属性和链接关系,就可以快速的生成所需的框图。
例如下面一段文本:
digraph finite_state_machine {
rankdir=LR;
size="8,5"
node [shape = doublecircle]; LR_0 LR_3 LR_4 LR_8;
node [shape = circle];
LR_0 -> LR_2 [ label = "SS(B)" ];
LR_0 -> LR_1 [ label = "SS(S)" ];
LR_1 -> LR_3 [ label = "S($end)" ];
LR_2 -> LR_6 [ label = "SS(b)" ];
LR_2 -> LR_5 [ label = "SS(a)" ];
LR_2 -> LR_4 [ label = "S(A)" ];
LR_5 -> LR_7 [ label = "S(b)" ];
LR_5 -> LR_5 [ label = "S(a)" ];
LR_6 -> LR_6 [ label = "S(b)" ];
LR_6 -> LR_5 [ label = "S(a)" ];
LR_7 -> LR_8 [ label = "S(b)" ];
LR_7 -> LR_5 [ label = "S(a)" ];
LR_8 -> LR_6 [ label = "S(b)" ];
LR_8 -> LR_5 [ label = "S(a)" ];
}
可以生成如下图示:
此外,也可以通过设置各种图形属性,得到你所需的图像效果,如另外一例:
具体的安装,使用和语法等,请参考Graphviz的官方站点:
http://www.graphviz.org/
3.3 应用于KFI的步骤
对于我们来说,需要做的就是将KFI的Trace输出结果,转换成Graphviz所能识别的描述文本。
用Graphviz来图形化函数调用流程这件事当然早就有人做过了,详细原理和步骤,代码请参考下面的文章:
在上例中,作者是将其运用于应用层的代码,自己简单的编写了
void __cyg_profile_func_enter (void *this_fn, void *call_site);
void __cyg_profile_func_exit (void *this_fn, void *call_site);
这两个函数的实现,从而得到Trace结果。
并编制了Pvtrace程序将Trace结果转换成Graphviz所需的描述文本。
基于上诉文章的代码,为了最快的达到我们所需要的结果,只需要修改了KFI对Trace的Raw数据进行函数名解析的脚本 kfiresolve.py 使其输出结果近似满足Pvtrace程序的输入要求,并适当修改pvtrace的代码已适应KFI的输出数据框架,就能快速将KFI的trace结果转换成Graphviz的描述文本了。
修改过的脚本和Pvtrace代码参考附件。
3.4 实例
通过上述方法,跟踪一次TFFS文件系统的读数据请求的流程,可以达到如下函数调用图:
结合KD的分析结果和上图,我们就能快速准确的分析出函数调用的关系和各种性能参数指标。