ftrace 简介

原文:http://www.ibm.com/developerworks/cn/linux/l-cn-ftrace/

ftrace 的实现

研究 tracer 的实现是非常有乐趣的。理解 ftrace 的实现能够启发我们在自己的系统中设计更好的 trace 功能。

ftrace 的整体构架

Ftrace 的整体构架:


图 1. ftrace 组成
ftrace 简介_第1张图片

Ftrace有两大组成部分,一是 framework,另外就是一系列的 tracer 。每个 tracer 完成不同的功能,它们统一由 framework管理。 ftrace 的 trace 信息保存在 ring buffer 中,由 framework 负责管理。 Framework 利用debugfs 系统在 /debugfs 下建立 tracing 目录,并提供了一系列的控制文件。

本文并不打算系统介绍 tracer 和 ftrace framework 之间的接口,只是打算从纯粹理论的角度,简单剖析几种具体 tracer 的实现原理。假如读者需要开发新的 tracer,可以参考某个 tracer 的源代码。

Function tracer 的实现

Ftrace 采用 GCC 的 profile 特性在所有内核函数的开始部分加入一段 stub 代码,ftrace 重载这段代码来实现 trace 功能。

gcc 的 -pg 选项将在每个函数入口处加入对 mcount 的调用代码。比如下面的 C 代码。


清单 1. test.c
//test.c 
 void foo(void) 
 { 
   printf( “ foo ” ); 
 }

用 gcc 编译:

gcc – S test.c

反汇编如下:


清单 2. test.c 不加入 pg 选项的汇编代码
	_foo: 
        pushl   %ebp 
        movl    %esp, %ebp 
        subl    $8, %esp 
        movl    $LC0, (%esp) 
        call    _printf 
        leave 
        ret

再加入 -gp 选项编译:

gcc – pg – S test.c

得到的汇编如下:


清单 3. test.c 加入 pg 选项后的汇编代码
_foo: 
        pushl   %ebp 
        movl    %esp, %ebp 
        subl    $8, %esp 
 LP3: 
        movl    $LP3,%edx 
        call    _mcount 
        movl    $LC0, (%esp) 
        call    _printf 
        leave 
        ret

增加 pg 选项后,gcc 在函数 foo 的入口处加入了对 mcount 的调用:call _mcount 。原本 mcount 由 libc实现,但您知道内核不会连接 libc 库,因此 ftrace 编写了自己的 mcount stub 函数,并借此实现 trace 功能。

在每个内核函数入口加入 trace 代码,必然会影响内核的性能,为了减小对内核性能的影响,ftrace 支持动态 trace 功能。

当 CONFIG_DYNAMIC_FTRACE 被选中后,内核编译时会调用一个 perl 脚本:recordmcount.pl 将每个函数的地址写入一个特殊的段:__mcount_loc

在内核初始化的初期,ftrace 查询 __mcount_loc 段,得到每个函数的入口地址,并将 mcount 替换为 nop 指令。这样在默认情况下,ftrace 不会对内核性能产生影响。

当用户打开 ftrace 功能时,ftrace 将这些 nop 指令动态替换为 ftrace_caller,该函数将调用用户注册的 trace 函数。其具体的实现在相应 arch 的汇编代码中,以 x86 为例,在 entry_32.s 中:


清单 4. entry_32.s
	ENTRY(ftrace_caller) 
       cmpl $0, function_trace_stop 
       jne  ftrace_stub 
       pushl %eax 
       pushl %ecx 
       pushl %edx 
       movl 0xc(%esp), %eax 
       movl 0x4(%ebp), %edx 
       subl $MCOUNT_INSN_SIZE, %eax 
 .globl ftrace_call 
 ftrace_call: 
       call ftrace_stubline 10popl %edx 
       popl %ecx 
       popl %eax 

 .globl ftrace_stub 
 ftrace_stub: 
       ret 
 END(ftrace_caller)

Function tracer 将 line10 这行代码替换为 function_trace_call() 。这样每个内核函数都将调用 function_trace_call() 。

在 function_trace_call() 函数内,ftrace 记录函数调用堆栈信息,并将结果写入 ring buffer,稍后,用户可以通过 debugfs 的 trace 文件读取该 ring buffer 中的内容。

Irqsoff tracer 的实现

Irqsofftracer 的实现依赖于 IRQ-Flags 。 IRQ-Flags 是 Ingo Molnar维护的一个内核特性。使得用户能够在中断关闭和打开时得到通知,ftrace重载了其通知函数,从而能够记录中断禁止时间。即,中断被关闭时,记录下当时的时间戳。此后,中断被打开时,再计算时间差,由此便可得到中断禁止时间。

IRQ-Flags 封装开关中断的宏定义:


清单 5. IRQ-Flags 中断代码
#define local_irq_enable() \ 
    do { trace_hardirqs_on (); raw_local_irq_enable(); } while (0)

ftrace 在文件 ftrace_irqsoff.c 中重载了 trace_hardirqs_on 。具体代码不再罗列,主要是使用了 sched_clock()函数来获得时间戳。

hw-branch 的实现

Hw-branch 只在 IA 处理器上实现,依赖于 x86 的 BTS 功能。 BTS 将 CPU 实际执行到的分支指令的相关信息保存下来,即每个分支指令的源地址和目标地址。

软件可以指定一块 buffer,处理器将每个分支指令的执行情况写入这块 buffer,之后,软件便可以分析这块 buffer 中的功能。

Linux内核的 DS 模块封装了 x86 的 BTS 功能。 Debug Support 模块封装了和底层硬件的接口,主要支持两种功能:Branchtrace store(BTS) 和 precise-event based sampling (PEBS) 。 ftrace 主要使用了BTS 功能。

branch tracer 的实现

内核代码中常使用 likely 和 unlikely 提高编译器生成的代码质量。 Gcc可以通过合理安排汇编代码最大限度的利用处理器的流水线。合理的预测是 likely 能够提高性能的关键,ftrace 为此定义了 branchtracer,跟踪程序中 likely 预测的正确率。

为了实现 branch tracer,重新定义了 likely 和 unlikely 。具体的代码在 compiler.h 中。


清单 6. likely/unlikely 的 trace 实现
# 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 的实现如下:


清单 7. _branch_check_ 的实现
#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() 将记录 likely 判断的正确性,并将结果保存在 ring buffer 中,之后用户可以通过 ftrace 的 debugfs 接口读取分支预测的相关信息。从而调整程序代码,优化性能。

总结

本文讲解了 ftrace 的基本使用。在实践中,ftrace 是一个非常有效的性能调优和 debug分析工具,每个人使用她的方法和角度都不相同,一定有很多 best practice,这非本文所能涉及。但希望通过本文的讲解,能让读者对ftrace 形成一个基本的了解,进而在具体工作中使用她。


参考资料

  • [1] 参看 LWN 上的文章 a look at ftrace,该文介绍了 ftrace 的简单概念。

  • [2] 参看文章 lwn 上关于 debugfs 的介绍文章了解 debugFS。

  • [3] 参看文章内核文档 tracepoint.txt了解 tracepoint。

  • [4] 参看 IBM developerworks 文章使用 Kprobes 调试内核了解 Kprobes。

  • [5] 参看文章内核文档 IRQ-Flags了解 IRQ-Flags。

  • [6] 参看文章内核文档 ftrace.txt了解 ftrace 更详细的信息。

  • 在 developerWorks Linux 专区 寻找为 Linux 开发人员(包括Linux 新手入门)准备的更多参考资料,查阅我们最受欢迎的文章和教程。

  • 在 developerWorks 上查阅所有 Linux 技巧 和 Linux 教程。

关于作者

刘明,从事嵌入式软件开发,热爱开源软件。喜欢学习和使用 linux,目前致力于数据库方面的工作和研究


你可能感兴趣的:(linux,汇编,function,gcc,buffer,branch)