Welcome 微信登录

首页 / 操作系统 / Linux / Android笔记-Linux Kernel Ftrace (Function Trace)解析

在软体开发时,通常都会面临到系统效能调教的需求,我们希望知道哪些区块的程式码或函式被执行的次数频繁,或是佔据较高的处理器时间,以便藉此优化程式码撰写的行为,或是改善耗CPU时间的算法,以Linux平台来说,OProfile(http://oprofile.sourceforge.net )会是一个大家常推荐的工具,OProfile支持Time-based透过系统Timer中断蒐集当下执行环境资讯,并加以统计,或基于Event-based,以ARM 来说就是Performance Monitor Unit(CP15)硬体支援的Performance控制单元 (更多资讯可以参考:http://infocenter.arm.com/help/topic/com.arm.doc.dai0195b/index.html),ARM PMU提供例如Instruction/Data Cache 使用状况(miss,write-back,write-buffer..etc),Memory/MMU 存取的状况, IRQ/FIQ Latency,Branch预测统计,I/D-TCM Status..etc,基于这机制的Profiling可以在影响系统效能最少的情况下,进行System-wide的性能统计资讯. 另一种选择,则是透过ARM接上JTAG介面,藉由ICE软体的Profiling功能进行分析.然而,如果我们希望更明确的知道每个Function被执行的次数 (OProfile Time-based的统计时间够长就可得出对应的比例,做为决定的依据),执行的流程,与个别时间成本,或是系统在排程 (Scheduling and Wake-up),中断,Block/Net,或Kernel记忆体配置..等,与Linux Kernel核心物件有关资讯的话,其实Ftrace会是另一个可以辅助的资讯来源,不同于OProfile,Ftrace会透过gcc -pg把每个函式前面都插入呼叫mcount函式的动作,在Branch统计部分,也会把if或是透过likely/unlikely巨集,进行植入是的统计,因此,Ftrace相比OProfile虽然可以提供比较完整的Kernel层级统计资讯,但因为OProfile主要是透过ARM或其他处理器平台的Performance Monitor单元,因此,OProfile可以在影响系统效能较低的情况下进行统计(ㄟ...Time-based Function profiling也是会影响到被测端的效能的.),但总体而言,都比mcount植入每个函式中,对系统效能的影响更算是轻量. 如何决定应用哪个模块进行效能分析,还是要依据当下开发时的目的与所遇到的问题来做决定.Ftrace最应该参考的文件就是Linux Kernel原始码中位于Documentation/ftrace.txt的文件,参考该文件资讯与Google一下,Ftrace作者为在RedHat服务的 Steven Rostedt,主要目的是为Linux Kernel提供一个系统效能分析的工具,以便用以除错或是改善/优化系统效能,Ftrace为一个以Function Trace为基础的工具,并包含了包括行程Context-Switch,Wake-Up/Ready到执行的时间成本,中断关闭的时间,以及是哪些函式呼叫所触发的,这都有助于在複杂的系统执行下,提供必要资讯以便定位问题.接下来,我们将介绍GCC对于Ftrace Profiling上,在编译器层级的支援,以及有关的builtin函式,让各位清楚这些机制底层运作的原理,最后,并以Ftrace为主,说明个机制的内容,但本文并不会深入探究到各Ftrace模组机制的实作部分,主要只以笔者自己认为值得说明的区块,来加以说明,对各项细节有兴趣的开发者,建议可以自行探究.GCC “-pg” Profiling 机制与builtin函式对Ftrace Branch Profiling的支援
Ftrace支援在有加入 “likely/unlikely” 条件判断式位置的Brnch Profiling与对整个核心 if 条件判断式的Brnch Profiling (当然,选择后者对效能影响也比较明显...要做记录的地方变多了.).使用者可以透过Kernel hacking --->Tracers --->Branch Profiling ---> 来选择“No branch profiling”,”Trace likely/unlikely profiler” 或 “Profile all if conditionalss”. 对系统进行Branch Profiling的动作. (Ftrace在 config中有这四个设定跟Branch Profiling有关CONFIG_TRACE_BRANCH_PROFILING,CONFIG_BRANCH_PROFILE_NONE,CONFIG_PROFILE_ANNOTATED_BRANCHES 与 CONFIG_PROFILE_ALL_BRANCHES)参考include/linux/compiler.h中的实作,如果选择“Profile all if conditionalss”,就会把全部的if条件判断字元,透过gcc precompile定义为巨集 __trace_if,如下所示#define if(cond, ...) __trace_if( (cond , ## __VA_ARGS__) )#define __trace_if(cond) if (__builtin_constant_p((cond)) ? !!(cond) : ({ int ______r; static struct ftrace_branch_data \__attribute__((__aligned__(4))) \__attribute__((section("_ftrace_branch"))) \______f = { .func = __func__, .file = __FILE__, .line = __LINE__, }; \______r = !!(cond); \______f.miss_hit[______r]++; \______r; }))如果if 条件式为常数(也就是说编译器可以在编译阶段就决定好if/else路径了),就不纳入统计,反之,就会根据条件式的结果(______r =0 or 1)统计命中的次数,作为if/else条件设计的参考. (其实,透过likely/unlikely优化编译阶段的Branch Predition是很有帮助的).如果是设定为”Trace likely/unlikely profiler”,就会把 likely与unlikely巨集定义如下所示/** Using __builtin_constant_p(x) to ignore cases where the return* value is always the same. This idea is taken from a similar patch* written by Daniel Walker.*/# ifndef likely# define likely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 1))# endif# ifndef unlikely# define unlikely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 0))# endif其中__branch_check__定义如下#define likely_notrace(x) __builtin_expect(!!(x), 1)#define unlikely_notrace(x) __builtin_expect(!!(x), 0)#define __branch_check__(x, expect) ({ int ______r; static struct ftrace_branch_data \__attribute__((__aligned__(4))) \__attribute__((section("_ftrace_annotated_branch"))) \______f = { .func = __func__, .file = __FILE__, .line = __LINE__, }; \______r = likely_notrace(x); ftrace_likely_update(&______f, ______r, expect); \______r; })函式ftrace_likely_update (位置在kernel/trace/trace_branch.c)实作如下所示,void ftrace_likely_update(struct ftrace_branch_data *f, int val, int expect){/** I would love to have a trace point here instead, but the* trace point code is so inundated with unlikely and likely* conditions that the recursive nightmare that exists is too* much to try to get working. At least for now.*/trace_likely_condition(f, val, expect);/* FIXME: Make this atomic! */if (val == expect)f->correct++;elsef->incorrect++;}