ftrace介绍

Kernel trace 工具的发展历史

  Linux 跟踪工具在内核的发展就像是肥皂剧一样,剧情婉转,主题反复,并且相互竞争。本章试图总结当前Linux 众多跟踪工具的现状和历史原因,并最终引出 ftrace 工具。

  编者对于以下内容并非亲历者(主要来自 LWN 和 maillist),如有歪曲请拍砖。以下部分虽属八卦,但是,学习一些模块的设计方案和历史进程是我们学习方法论的一部分,从这些纠结的方案和博弈中可以看到很多权衡和利弊,帮助更好的获得其中的思想。

Why Trace the kernel ?

  很多人在接触这类工具时可能会感到困惑,内核代码早就已经优化的非常好了,并且基本不会有什么 Bug,那么为什么还需要插入这么多探针去查看内核的状态呢?

  其实这种工具在生产环境中被很多人使用去跟踪问题,比如系统此时无法理解的行为(CPU 居高,IO 忙等),这时候系统管理员需要利用这种工具在不干扰常系统运作的情况下去 Linux 这个庞大的内核中查找原因。

Dtrace Envy 和 SystemTap

  首先,我们先来介绍 Dtrace Envy。Linux 社区中过去有一个名词叫 DtraceEnvy,从字母上可以发现 Linux 社区对 Dtrace 的深恶痛绝。不仅是在以前的Linux 中没有像 Dtrace 一样如此强大的实现,而且 Dtrace 经常被 Sun 公司用来作为营销手段的噱头(同样的还有 ZFS),并用来与 Linux 进行对比。

  那时还是 07 年,大家在提到 Solaris 系统时,Dtrace 总是处在人们关注的前列。而 SystemTap 当时还在几个大公司 Red Hat、Intel、IBM 和日立内部使用。Dtrace 的拥护者会嘲讽 SystemTap 只是个山寨货,而 SystemTap 的粉丝认为SystemTap 是个独立发展的项目,并且以超越 Dtrace 为目标。

  这两个工具都是在内核中插入探针,当一个线程执行碰到探针时,预先定义的脚本(D 语言脚本或者 SystemTap 脚本)就会运行。

  Dtrace 使用了大量的预先定义的探针插入到 Solaris 内核中,这种方式称为静态探针,这些探针都在 Dtrace 的文档中做了详述。并且一些简单的通配符(*)可以用来实现多个探针的选择。Sun 宣称无用的探针并不会增加太多的负载。

  SystemTap 没有采用 Dtrace 的方式,估计是 SystemTap 没有人有这么大精力去实现大量的静态探针放到内核中(Linus 会发飙的)。SystemTap 采用的动态探针(kprobes 实现),它是在内核运行时被动态插入。SystemTap 采用脚本语言来定义一些动作并且打开一些启用的探针来(SystemTap 的探针库)插入到内核中,没有用到的探针不会插入。

  从上述的差异可以看到 Dtrace 的静态探针是有优势的,首先 Dtrace 大量的探针是被预定义并且可以被很好的文档化(这对于系统管理员是非常好的,它们通常不熟悉内核源码)。而 SystemTap 是可以在内核的任意位置,这就需要开发者熟悉内核的源码并且把探针放到合适的位置。这就可能带来安全性的问题,一些不恰当的探针放置是会带来系统崩溃的(这种情况非常少),即使非常少的位置是敏感的,但也足以使使用者感到不便,需要时时担心可能的不恰当触发。当然,SystemTap 也有少量的预定义探针。

  接着我们比较两者的语言,Dtrace 采用的 D 语言同样继承了安全的原则,它运行在虚拟机中并且带有安全检测,并且拒绝控制语句(只有三元操作符),但在探针上可以附着表达式(相当于一个“if”)。而 SystemTap 同样采取了大刀阔斧的姿态,不仅有完备的控制语句,SystemTap 脚本(STP)直接翻译成 C,并且在SystemTap 脚本中可以嵌入 C。可以想象,在这里面如果出现了死循环就基本意味着 crash 了。

Kernel summit 2008 的故事

  2008 年,虽然 SystemTap 有了长足的发展,但是内核社区和 SystemTap 项目团队仍然没有一个很好的沟通和协商,SystemTap 仍然没有进入普通用户的工具范畴。在 kernel summit 中(Ksummit-2008-discuss DTrace),Matthew Wilcox表示 Dtrace 已经对 Linux 造成了足够的威胁。Linux 通常被认为是新技术的前沿,但是当 Sun 把 Solaris 开放后,一系列的新特征已经领先 Linux,比如 ZFS和 Dtrace。

  即使当时 SystemTap 和 Btrfs 被认为是 Linux 对抗的利器,但是,在社区还是有人对 SystemTap 表示失望。James Bottomley(iSCSI 的贡献者)在邮件中提到 Ksummit-2008-discuss DTrace:好比 Winodws 的用户期望在 Linux 上有Outlook 一样,Solaris 用户期望 SystemTap 在 Linux 上也能成为和 DTrace 一样的工具。同时,James Bottomley 表示 SystemTap 非常难用并且造成系统的崩溃。另外 Ted Ts’o 同样赞成 SystemTap 非常难以使用,但是他期望在 SystemTap中增加更多的“tapsets”来帮助对内核不熟悉的人更好的使用 SystemTap。

  SystemTap 的开发者 Frank Ch. Eigler 表示 SystemTap 早已考虑性能、难以使用的问题并且正在解决中,他认为内核社区缺少与 SystemTap 团队的沟通,并且提出了一系列的建议希望内核团队参与到 SystemTap 与 kernel 的整合中来(Frank Ch. Eigler fche at redhat.com)。

  但是,Ted Ts’o 同样回应称 SystemTap 只会将“tapsets”的开发任务交给内核,而不去思考如何让 SystemTap 变得易用并且更容易与内核整合,他同时指责 SystemTap 光想着让企业发行版使用,而不去努力改善与内核的问题(Theodore Tso tytso at mit.edu)。

  Frank Ch. Eigler 表示大部分系统管理员是不会去使用 SystemTap 思考内核的性能追踪的,SystemTap 通常是被整合到 PostgreSQL 这种基础软件中来使用,因此,SystemTap 更加注重对此类问题的解决。

  同时,社区中有人期望把 Dtrace 移植到 Linux 中,但是这也困难重重,先不说 Dtrace 移植到 Linux 主线的技术问题,光光是 CDDL 协议就是不容于 Linux的。好吧,接下来就是各种水友开始讨论 CDDL 与 GPL 的协议问题(一群程序员开始 Google…)

  最后还是 Linus 强调了最重要的一点,这个同时也是针对 utrace 的:SystemTap 的实现是不被内核开发者喜欢的,因为它创造、利用内核模块的实现是不同于内核的,对于 SystemTap 的细节,如锁实现和使用等等都是 off kernel的。他希望 SystemTap 在实现设计上能离内核越来越近。

ptrace

  在介绍 utrace 之前首先需要介绍 ptrace,它是 Linux 内核跟踪工具的化石。过去(07年以前)很长一段时间里,大部分人利用它来调试应用或者内核模块,但是它的问题也很多:低效(需要内核/用户态切换)、实现难以维护(架构设计混乱)、用户难以使用(接口复杂)、一个进程只能被一个进程跟踪(使用信号方法跟踪进程,信号使得目标只能 wait 一个进程)、会改变跟踪进程的状态(进程父子关系)、打断系统调用并且一直有大量的安全问题存在。

  ptrace 的维护者在 utrace 出现之后就已经在呼吁让 ptrace 基于 utrace 实现,因为 ptrace 代码是混乱的,散乱在各个架构中,维护者早已经对横跨多个架构的代码厌倦了。

  我们需要注意到 gdb 和 strace 都是基于 ptrace,这个原因也导致了 ustrace迟迟不能进入内核主线。

utrace

  utrace 在 07 年的时候进入内核的视线(Introducing utrace),它的目的是完全取代 ptrace 在用户态跟踪的作用。utrace 只是实现了一个在内核中的engine,它提供了一个框架供更多的人基于它来实现对用户程序的控制任务(Trace,Debug,user-mode-linux)。

  utrace 的体系架构可以参考《玩转 utrace》。

  utrace 自 07 年在 LWN 中提出,然后迅速沉寂消失在视线之外,在 09 又重新希望进入内核主线中,当时 Red Hat 和 Fedora kernels 其实已经支持 utrace很多年了。utrace 进入内核最重要的问题是在内核中没有真正的用户(以 utrace为框架的实现),utrace 如果只是一个 engine 就没有进入内核的必要。

  Frank Eigler(utrace 开发者)提供了 ftrace(本文的重点)基于 utrace 的实现,但是内核认为这个实现只是个插件罢了。而另外一个 utrace 真正的用户ptrace-over-utrace 是被看做 utrace 进入内核的关键,但是有很多人以不成熟来反对。虽然 ptrace 有大量的 ugly code 并且难以维护,但是毕竟已经存在这么多年了,取代 ptrace 是一个高风险的项目并且谁来做这个清理工作,如果不做清理工作,两者同时进入内核会有两个 ptrace 实现,带来更多的问题。还有就是 utrace 并不是被所有架构支持,如果 ptrace 使用 utrace 的实现而删除自己的实现,在其他架构中(如 ARM 和 MIPS)就无法使用 ptrace 调用。并且ptrace-over-utrace 如果想替代原来的 ptrace 实现,必须有一些 killerfeatures(通过 utrace 的框架还是能实现很多的)。Nesterov 和 McGrath 这两个ptrace 的维护者站出来表示自己更愿意维护 ptrace-over-utrace 的方案,并且乐意去推出新的代码。

  问 题 之 二 是 utrace 会 阻 碍 SystemTap 进 入 内 核 ( 当 时 有 很 多 人 期 望SystemTap尽快进入内核)。因为utrace没有一个系统调用,而是内核里的API(内核接口会不稳定)。SystemTap 利用了 utrace 的许多 ABI 来实现用户态的跟踪,如果 utrace 进入内核,SystemTap 在主线外可能就导致在用户态跟踪上一团糟(主线与非主线最重要的问题就是版本兼容性)并且需要寻找其他的方式。(PS:现在 systemtap 可以直接使用新的主流内核中的 kprobes 和基于 inode 的uprobes 机制,不再依赖非主流的 utrace 补丁。同时它也可以直接使用主流内核中的 perf 静态探针 by agentzh)

  在 09 年的 Linux 2.6.30 进入主线失败后,utrace 就准备好了长期在主线外的命运,Red Hat 不得不承担起 utrace 的维护任务并且长期在自己的内核中保持。目前来说,utrace 和 SystemTap 像是一对难兄难弟。

LTTng

  LTTng 是另外一个动态跟踪工具,它整合了内核和用户态跟踪,对于大量的跟踪事件流有非常高的性能,并且有一系列的分析和抓取工具。

  对于目前的 Linux 内核来说,LTTng 只不过是众多 Tracing 工具的一个,它制造了太多重复的工作:比如 Ring Buffer(内核中已经有两个 Ring buffer 被perf 和 ftrace 使用),并且有自己的系统调用接口(内核已经有此类的接口)。增加 LTTng 意味着有更混乱的 Tracing ABI 在内核中。

  相比较而言,LTTng 进入内核比 SystemTap 和 utrace 更困难。

ftrace

  相比前面的 SystemTap、utrace 和 LTTng,ftrace 就幸运多了。ftrace 是用来帮助开发者和系统设计者寻找系统负载的原因,它可以用来调试和分析内核中的负载和性能原因。

  ftrace 之所以能顺利进入内核中,主要还是因为它充分利用了内核中既有的设计和其他组件。它利用了 debugfs、kprobes、uprobes 等等,并且因为其简单的静态探针和安全性,ftrace 的内核之路顺利而平坦。

  后面我们将重点介绍这款 tracing 工具。

tracepoint

  tracepoint 是在内核中预定义的一系列跟踪点并且提供了 hook 让使用者可以调用函数来进行分析,tracepoint 在有 probe attach 时会在每次执行到tracepoint 时调用相应的函数。

  tracepoint 在 08 年的时候被置入内核中,ftrace 也同样使用 tracepoint作为 tracer 使用。

kprobe

  kprobes 在 Linux 2.6.9 主线上实现,它主要在内核模块中被使用。在模块init 函数中安装 probe,然后在 exit 时释放。

  对于 kprobe 来说,更多的时候是被内核模块开发者使用。因为 kprobe 比较麻烦且相对于 SystemTap 这类工具来说,kprobe 只有较少的使用空间。

uprobe

  实现了动态的介入用户程序的运行并且不打断地收集信息。它的原理与kprobe 类似,都是在探针函数的第一个字节用 breakpoint 指令替代,当 CPU 运行到 breakpoint 指令时,uprobes 得到通知并找到相应 uprobe 来执行对应的函数。

小结

  目前 Linux 内核上众多跟踪工具的实现其实非常混乱,像 latencyTop、powertop、usbmon、blktrace 都是好用的工具,但是每个工具都需要自己的 hooks在内核,这对于内核而言是十分蛋疼的事情。而在 Solaris 中,powertop 这类是在 Dtrace 之上实现的,不需要改变内核。

  从 Linux 性能工具的发展可以看到,upstream kernel 和商用 kernel,以及其他系统(Solaris)的博弈和权衡是十分激烈的。对于 Linux 社区而言,Linus对于代码的把控是十分必要的,在 11 年 kernel summit 之后,开发者已经普遍赞成收敛系统调用的增加,这对于 LTTng,utrace 进入内核的希望就更小了。

  ftrace 的作用是帮助开发人员了解 Linux 内核的运行时行为,以便进行故障调试或性能分析。最早 ftrace 是一个 function tracer,仅能够记录内核的函数调用流程。如今 ftrace 已经成为一个 framework,采用 plugin 的方式支持开发人员添加更多种类的 trace 功能。

ftrace 简介

ftrace 支持的功能

  Ftrace 是内建于 Linux 内核的跟踪工具,从 2.6.27 开始加入主流内核。使用 ftrace 可以调试或者分析内核中发生的事情。ftrace 提供了不同的跟踪器,以用于不同的场合。比如:跟踪内核函数调用、对上下文切换进行跟踪、查看中断被关闭的时长、跟踪内核态中的延迟以及性能问题等。系统开发人员可以使用 ftrace 对内核进行跟踪调试,以找到内核中出现的问题的根源,方便对其进行修复。另外,对内核感兴趣的读者还可以通过 ftrace 来观察内核中发生的活动,了解内核的工作机制。

  Ftrace 由 RedHat 的 Steve Rostedt 负责维护。到 2.6.30 为止,已经支持的 tracer 包括:

  Function tracer 和 Function graph tracer: 跟踪函数调用。

  Schedule switch tracer: 跟踪进程调度情况。

  Wakeup tracer:跟踪进程的调度延迟,即高优先级进程从进入 ready 状态到获得 CPU 的延迟时间。该 tracer 只针对实时进程。

  Irqsoff tracer:当中断被禁止时,系统无法响应外部事件,比如键盘和鼠标,时钟也无法产生 tick 中断。这意味着系统响应延迟,irqsoff 这个 tracer能够跟踪并记录内核中哪些函数禁止了中断,对于其中中断禁止时间最长的,irqsoff 将在 log 文件的第一行标示出来,从而使开发人员可以迅速定位造成响应延迟的罪魁祸首。

  Preemptoff tracer:和前一个 tracer 类似,preemptoff tracer 跟踪并记录禁止内核抢占的函数,并清晰地显示出禁止抢占时间最长的内核函数。Preemptirqsoff tracer: 同上,跟踪和记录禁止中断或者禁止抢占的内核函数,以及禁止时间最长的函数。

  Branch tracer: 跟踪内核程序中的 likely/unlikely 分支预测命中率情况。 Branch tracer 能够记录这些分支语句有多少次预测成功。从而为优化程序提供线索。

  Hardware branch tracer:利用处理器的分支跟踪能力,实现硬件级别的指令跳转记录。在 x86 上,主要利用了 BTS 这个特性。

  Initcall tracer:记录系统在 boot 阶段所调用的 init call 。

  Mmiotrace tracer:记录 memory map IO 的相关信息。

  Power tracer:记录系统电源管理相关的信息。

  Sysprof tracer:缺省情况下,sysprof tracer 每隔 1 msec 对内核进行一次采样,记录函数调用和堆栈信息。

  Kernel memory tracer: 内存 tracer, 主要用来跟踪 slab allocator 的分配情况。包括 kfree,kmem_cache_alloc 等 API 的调用情况,用户程序可以根据 tracer 收集到的信息分析内部碎片情况,找出内存分配最频繁的代码片断,等等。

  Workqueue statistical tracer:这是一个 statistic tracer,统计系统中所有的 workqueue 的工作情况,比如有多少个 work 被插入 workqueue,多少个已经被执行等。开发人员可以以此来决定具体的 workqueue 实现,比如是使用 single threaded workqueue 还是 per cpu workqueue。

  Event tracer: 跟踪系统事件,比如 timer,系统调用,中断等。

  这里还没有列出所有的 tracer,ftrace 是目前非常活跃的开发领域,新的tracer 将不断被加入内核。

ftrace 与其他 trace 工具的关系和比较

  Ftrace 最初是在 2.6.27 中出现的,那个时候,systemTap 已经开始崭露头角,其他的 trace 工具包括 LTTng 等也已经发展多年。那为什么人们还要再开发一个 trace 工具呢?

  SystemTap 项目是 Linux 社区对 SUN Dtrace 的反应,目标是达到甚至超越 Dtrace 。因此 SystemTap 设计比较复杂,Dtrace 作为 SUN 公司的一个项目开发了多年才最终稳定发布,况且得到了 Solaris 内核中每个子系统开发人员的大力支持。 SystemTap 想要赶超 Dtrace,困难不仅是一样,而且更大,因此她始终处在不断完善自身的状态下,在真正的产品环境,人们依然无法放心的使用她。不当的使用和 SystemTap 身的不完善都有可能导致系统崩溃。

  Ftrace 的设计目标简单,本质上是一种静态代码插装技术,不需要支持某种编程接口让用户自定义 trace 行为。静态代码插装技术更加可靠,不会因为用户的不当使用而导致内核崩溃。 ftrace 代码量很小,稳定可靠。实际上,即使是 Dtrace,大多数用户也只使用其静态 trace 功能。因此 ftrace 的设计非常务实。

  从 2.6.30 开始,ftrace 支持 event tracer,其实现和功能与 LTTng 非常类似,或许将来 ftrace 会同 LTTng 进一步融合,各自取长补短。 ftrace 有定义良好的 ASCII 接口,可以直接阅读,这对于内核开发人员非常具有吸引力,因为只需内核代码加上 cat 命令就可以工作了,相当方便; LTTng 则采用binary 接口,更利于专门工具分析使用。此外他们内部 ring buffer 的实现不相同,ftrace 对所有 tracer 都采用同一个 ring buffer,而 LTTng 则使用各自不同的 ring buffer。

  目前,或许将来 LTTng 都只能是内核主分支之外的工具。她主要受到嵌入式工程师的欢迎,而内核开发人员则更喜欢 ftrace。

  Ftrace 的实现依赖于其他很多内核特性,比如 tracepoint,debugfs,kprobe,IRQ-Flags 等。限于篇幅,关于这些技术的介绍请读者自行查阅相关的参考资料。

ftrace 的实现原理

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

ftrace 的整体构架

  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 代码:

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

  用 gcc 编译:

gcc -S test.c

  反汇编如下:

_foo:
	pushl %ebp
	movl %esp, %ebp
	subl $8, %esp
	movl $LC0, (%esp)
	call _printf
	leave
	ret

  再加入-pg 选项编译:

gcc -pg -S test.c

  得到的反汇编如下:

_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 中:

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_stub
	line 10
	popl %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 的实现

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

  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 模块封装了和底层硬件的接口,主要支持两种功能:Branch trace store(BTS) 和precise-event based sampling (PEBS) 。 ftrace 主要使用了 BTS 功能。

Branch tracer 的实现

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

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

#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 __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 的功能配置

  要使用 ftrace,首先需要将其编译进内核。内核源码目录下的kernel/trace/Makefile 文件给出了 ftrace 相关的编译选项:

CONFIG_FUNCTION_TRACER
CONFIG_FUNCTION_GRAPH_TRACER
CONFIG_CONTEXT_SWITCH_TRACER
CONFIG_NOP_TRACER

  ftrace 相关的配置选项比较多,针对不同的跟踪器有各自对应的配置选项。不同的选项有不同的依赖关系,内核源码目录下的 kernel/trace/Kconfig 文件描述了这些依赖关系。读者可以参考 Makefile 文件和 Konfig 文件,然后选中自己所需要的跟踪器。

  通常在配置内核时,使用 make menuconfig 会更直观一些。以 3.4 版本的内核为例,要将 ftrace 编译进内核,可以选中 Kernel hacking 下的 Tracers菜单项:
ftrace介绍_第2张图片
ftrace介绍_第3张图片
  进入 Tracers 菜单后,可以看到内核支持的跟踪器列表:

ftrace介绍_第4张图片
  在里面选中要 ftrace 支持的功能即可。

Ftrace 依赖的配置

  ftrace 通过 debugfs 向用户态提供了访问接口,所以还需要将 debugfs编译进内核。激活对 debugfs 的支持,可以直接编辑内核配置文件 .config ,设置 CONFIG_DEBUG_FS=y ;或者在 make menuconfig 时到 Kernel hacking 菜单下选中对 debugfs 文件系统的支持:
ftrace介绍_第5张图片

完成配置

  配置完成后,编译安装新内核,然后启动到新内核。 注意,激活 ftrace 支持后,编译内核时会使用编译器的 -pg 选项,这是在 kernel/trace/Makefile文件中定义的:

ifdef CONFIG_FUNCTION_TRACER
ORIG_CFLAGS := $(KBUILD_CFLAGS)
KBUILD_CFLAGS = $(subst -pg,,$(ORIG_CFLAGS))
...
endif

  使用 -pg 选项会在编译得到的内核映像中加入大量的调试信息。如果不使用 CONFIG_DYNAMIC_FTRACE 功能而开启 ftrace 功能,则可能会严重影响系统性能。

  目前在我们公司的方案中是使用了 CONFIG_DYNAMIC_FTRACE 功能且默认开启了 ftrace 的。也就是说,拿到客户的成品后,我们不用重新修改配置烧写代码,就可以在不破坏现场的情况下来使用 ftrace 了。

ftrace 的接口

  ftrace 通过 debugfs 向用户提供访问接口。配置内核时激活 debugfs 后会创建目录 /sys/kernel/debug,debugfs 文件系统就是挂载到该目录的。要挂载该目录,需要将如下内容添加到 /etc/fstab 文件:

debugfs /sys/kernel/debug debugfs defaults 0 0

  或者可以在运行时挂载:

mount -t debugfs nodev /sys/kernel/debug

  激活内核对 ftrace 的支持后会在 debugfs 下创建一个 tracing 目录(/sys/kernel/debug/tracing)。该目录下包含了 ftrace 的控制和输出文件。根据编译内核时针对 ftrace 的设定不同,该目录下实际显示的文件和目录也会不同。如下图所示(其中/d 是/sys/kernel/debug 的软链接):

/sys/kernel/debug/tracing # ls -l
total 0
-r--r--r--    1 root     root             0 Jan  1  1970 README
-r--r--r--    1 root     root             0 Jan  1  1970 available_events
-r--r--r--    1 root     root             0 Jan  1  1970 available_filter_functions
-r--r--r--    1 root     root             0 Jan  1  1970 available_tracers
-rw-r--r--    1 root     root             0 Jan  1  1970 buffer_size_kb
-r--r--r--    1 root     root             0 Jan  1  1970 buffer_total_size_kb
-rw-r--r--    1 root     root             0 Jan  1  1970 current_tracer
-r--r--r--    1 root     root             0 Jan  1  1970 dyn_ftrace_total_info
-r--r--r--    1 root     root             0 Jan  1  1970 enabled_functions
drwxr-xr-x   60 root     root             0 Jan  1  1970 events
--w-------    1 root     root             0 Jan  1  1970 free_buffer
-rw-r--r--    1 root     root             0 Jan  1  1970 function_profile_enabled
drwxr-xr-x    2 root     root             0 Jan  1  1970 instances
-rw-r--r--    1 root     root             0 Jan  1  1970 max_graph_depth
drwxr-xr-x    2 root     root             0 Jan  1  1970 options
drwxr-xr-x    6 root     root             0 Jan  1  1970 per_cpu
-r--r--r--    1 root     root             0 Jan  1  1970 printk_formats
-r--r--r--    1 root     root             0 Jan  1  1970 saved_cmdlines
-rw-r--r--    1 root     root             0 Jan  1  1970 saved_cmdlines_size
-r--r--r--    1 root     root             0 Jan  1  1970 saved_tgids
-rw-r--r--    1 root     root             0 Jan  1  1970 set_event
-rw-r--r--    1 root     root             0 Jan  1  1970 set_event_pid
-rw-r--r--    1 root     root             0 Jan  1  1970 set_ftrace_filter
-rw-r--r--    1 root     root             0 Jan  1  1970 set_ftrace_notrace
-rw-r--r--    1 root     root             0 Jan  1  1970 set_ftrace_pid
-r--r--r--    1 root     root             0 Jan  1  1970 set_graph_function
-r--r--r--    1 root     root             0 Jan  1  1970 set_graph_notrace
-rw-r--r--    1 root     root             0 Jan  1  1970 snapshot
-rw-r--r--    1 root     root             0 Jan  1  1970 stack_max_size
-r--r--r--    1 root     root             0 Jan  1  1970 stack_trace
-r--r--r--    1 root     root             0 Jan  1  1970 stack_trace_filter
-rw-r--r--    1 root     root             0 Jan  1  1970 trace
-rw-r--r--    1 root     root             0 Jan  1  1970 trace_clock
--w--w----    1 root     root             0 Jan  1  1970 trace_marker
-rw-r--r--    1 root     root             0 Jan  1  1970 trace_options
-r--r--r--    1 root     root             0 Jan  1  1970 trace_pipe
drwxr-xr-x    2 root     root             0 Jan  1  1970 trace_stat
-rw-r--r--    1 root     root             0 Jan  1  1970 tracing_cpumask
-rw-r--r--    1 root     root             0 Jan  1  1970 tracing_max_latency
-rw-r--r--    1 root     root             0 Jan  1  1970 tracing_on
-rw-r--r--    1 root     root             0 Jan  1  1970 tracing_thresh
/sys/kernel/debug/tracing #

ftrace 的接口文件

  /sys/kernel/debug/tracing 目录下文件和目录比较多,有些是各种跟踪器共享使用的,有些是特定于某个跟踪器使用的。在操作这些数据文件时,通常使用 echo 命令来修改其值,也可以在程序中通过文件读写相关的函数来操作这些文件的值。下面只对部分文件进行描述,读者可以参考内核源码包中Documentation/trace 目录下的文档以及 kernel/trace 下的源文件以了解其余文件的用途。

  README 文件提供了一个简短的使用说明,展示了 ftrace 的操作命令序列。可以通过 cat 命令查看该文件以了解概要的操作流程。

  current_tracer 用于设置或显示当前使用的跟踪器。使用 echo 将跟踪器名字写入该文件可以切换到不同的跟踪器。系统启动后,其缺省值为 nop,即不做任何跟踪操作。在执行完一段跟踪任务后,可以通过向该文件写入 nop 来重置跟踪器。

  available_tracers 记录了当前编译进内核的跟踪器的列表。可以通过 cat查看其内容,其包含的跟踪器与4.1节的最后一幅图中所激活的选项是对应的。写 current_tracer 文件时用到的跟踪器名字必须在该文件列出的跟踪器名字列表中。

  trace 文件提供了查看获取到的跟踪信息的接口。可以通过 cat 等命令查看该文件以分析跟踪到的内核活动记录,也可以将其内容保存为记录文件,以备后续查看。

  tracing_enabled 用于控制 current_tracer 中的跟踪器是否可以跟踪内核函数的调用情况。写入 0 会关闭跟踪活动,写入 1 则激活跟踪功能,其缺省值为 1。

  set_graph_function 设置要清晰显示调用关系的函数,显示的信息结构类似于 C 语言代码,这样在分析内核运作流程时会更加直观一些。在使用function_graph 跟踪器时,缺省对所有函数都生成调用关系序列,可以通过写该文件来指定需要特别关注的函数。

  buffer_size_kb 用于设置单个 CPU 所使用的跟踪缓存的大小。跟踪器会将跟踪到的信息写入缓存,每个 CPU 的跟踪缓存是一样大的。跟踪缓存实现为环形缓冲区的形式,如果跟踪到的信息太多,则旧的信息会被新的跟踪信息覆盖掉。注意,要更改该文件的值,需要先将 current_tracer 设置为 nop 才可以。

  tracing_on 用于控制跟踪的暂停。有时候在观察到某些事件时想暂时关闭跟踪,可以将 0 写入该文件以停止跟踪,这样跟踪缓冲区中比较新的部分是与所关注的事件相关的,写入 1 可以继续跟踪。

  available_filter_functions 记录了当前可以跟踪的内核函数。对于不在该文件中列出的函数,无法跟踪其活动。

  set_ftrace_filter 和 set_ftrace_notrace 在 编 译 内核 时 配 置 了 动态ftrace (选中 CONFIG_DYNAMIC_FTRACE 选项)后使用。前者用于显示指定要跟踪的函数,后者作用相反,用于指定不跟踪的函数。如果一个函数名同时出现在这两个文件中,则这个函数的执行状况不会被跟踪。这些文件还支持简单形式的含有通配符的表达式,这样可以用一个表达式一次指定多个目标函数。具体使用在后续章节中会有描述。注意,要写入这两个文件的函数名必须可以在文件available_filter_functions 中看到。缺省为可以跟踪所有内核函数,文件set_ftrace_notrace 的值则为空。

ftrace 跟踪器

  trace 当前包含多个跟踪器,用于跟踪不同类型的信息,比如进程调度、中断关闭等。可以查看文件 available_tracers 获取内核当前支持的跟踪器列表。在编译内核时,也可以看到内核支持的跟踪器对应的选项。

/sys/kernel/debug/tracing # cat available_tracers
blk function_graph wakeup_dl wakeup_rt wakeup function nop
/sys/kernel/debug/tracing #

  nop 跟踪器不会跟踪任何内核活动,将 nop 写入 current_tracer 文件可以删除之前所使用的跟踪器,并清空之前收集到的跟踪信息,即刷新 trace 文件。

  function 跟 踪 器 可 以 跟 踪 内 核 函 数 的 执 行 情 况 。 可 以 通 过 文 件set_ftrace_filter 显示指定要跟踪的函数。

  function_graph 跟踪器可以显示类似 C 源码的函数调用关系图,这样查看起来比较直观一些。可以通过文件 set_grapch_function 显示指定要生成调用流程图的函数。

  sched_switch 跟踪器可以对内核中的进程调度活动进行跟踪。

  irqsoff 跟踪器和 preemptoff 跟踪器分别跟踪关闭中断的代码和禁止进程抢占的代码,并记录关闭的最大时长,preemptirqsoff 跟踪器则可以看做它们的组合。

  ftrace 还支持其它一些跟踪器,比如 initcall、ksym_tracer、mmiotrace、sysprof 等。ftrace 框架支持扩展添加新的跟踪器。读者可以参考内核源码包中 Documentation/trace 目录下的文档以及 kernel/trace 下的源文件,以了解其它跟踪器的用途和如何添加新的跟踪器。

ftrace 过滤器

  前面已经提到过两个常用的过滤器了:set_ftrace_filter/set_ftrace_notrace 和 set_graph_function。前者对function tracer 进行过滤,后者对 function_graph tracer 进行过滤。这里只介绍这两种常用的过滤器。

  还有一些过滤器,如 set_ftrace_pid 对进程进行过滤,stack_trace_filter对堆栈进行过滤。

过滤器的设置

  有两种方式来设置过滤器:使用“>”和“>>”。前者会先清空过滤器,再重新设置过滤器;而后者不会清空过滤器,只会追加新的过滤信息。注意,如果清空过滤器,则表示对所有可以跟踪的函数进行跟踪。

  要删除过滤信息,只需要在需要删除的信息前面加个“!”。例如:

/sys/kernel/debug/tracing # echo jbd2_alloc > set_ftrace_filter
/sys/kernel/debug/tracing # cat set_ftrace_filter
jbd2_alloc
/sys/kernel/debug/tracing #
/sys/kernel/debug/tracing # echo jbd2_free > set_ftrace_filter
/sys/kernel/debug/tracing # cat set_ftrace_filter
jbd2_free
/sys/kernel/debug/tracing #
/sys/kernel/debug/tracing # echo jbd2_alloc >> set_ftrace_filter
/sys/kernel/debug/tracing # cat set_ftrace_filter
jbd2_alloc
jbd2_free
/sys/kernel/debug/tracing #
/sys/kernel/debug/tracing # echo !jbd2_alloc >> set_ftrace_filter
/sys/kernel/debug/tracing # cat set_ftrace_filter
jbd2_free
/sys/kernel/debug/tracing #

  除 此 之 外 , 还 可 以 向 set_ftrace_notrace 中 添 加 过 滤 信 息 来 删 除set_ftrace_filter 中已经设置的过滤信息。即有相同的函数同时出现在set_ftrace_filter 和 set_ftrace_notrace 中时,这些函数将不被跟踪。

过滤信息的适用范围

  添 加 的 过 滤 信 息 必 须 是 可 以 跟 踪 的 , 即 需 要 出 现 在available_filter_functions 列表中,否则会返回添加失败:

/sys/kernel/debug/tracing # cat available_filter_functions | grep jbd2_free
jbd2_free
/sys/kernel/debug/tracing # echo jbd2_free > set_ftrace_filter
/sys/kernel/debug/tracing # cat set_ftrace_filter
jbd2_free
/sys/kernel/debug/tracing #
/sys/kernel/debug/tracing # echo asdasd > set_ftrace_filter
ash: write error: Invalid argument
/sys/kernel/debug/tracing #

  如上,添加“asdasd”到 set_ftrace_filter 过滤器时,返回1或报“ash: write error: Invalid argument”,表示没有添加成功,因为 available_filter_functions 列表中没有记录 symbol 为“asdfg”的函数。

  我们可以通过 enabled_functions 窗口来查看,目前已经被选中的过滤信息:

/sys/kernel/debug/tracing # cat current_tracer
nop
/sys/kernel/debug/tracing # echo function_graph > current_tracer
/sys/kernel/debug/tracing # cat current_tracer
function_graph
/sys/kernel/debug/tracing # echo jbd2_free > set_ftrace_filter
/sys/kernel/debug/tracing # cat set_ftrace_filter
jbd2_free
/sys/kernel/debug/tracing # cat set_graph_function
#### all functions enabled ####
/sys/kernel/debug/tracing # cat enabled_functions
jbd2_free (1)
/sys/kernel/debug/tracing # echo function > current_tracer
/sys/kernel/debug/tracing # cat enabled_functions
jbd2_free (1)
/sys/kernel/debug/tracing # echo nop > current_tracer
/sys/kernel/debug/tracing # cat enabled_functions
/sys/kernel/debug/tracing #

  如上,看上去 enabled_functions 窗口只对 function tracer 有效。

过滤器通配符的功能

  过滤器支持使用通配符来设置过滤信息的功能,来为我们提供更方面的设置操作。有如下3种形式的通配符(使用“*”符号):

<match>* - 将匹配以 <match> 开头的函数
*<match> - 将匹配以 <match> 结尾的函数
*<match>* - 将匹配包含 <match> 的函数这些是唯一受支持的通配符。
<match>*<match> 将不起作用。

  使用通配符表达式时,需要用单引号将其括起来,如果使用双引号,shell 可能会对字符‘*’进行扩展,这样最终跟踪的对象可能与目标函数不一样。其使用范例在下一章中会介绍,这里就不再罗列。

过滤器命令的功能

  此外,set_ftrace_filter 过滤器还支持少量 Filter commands 的功能。

  Filter commands 的格式如下:

::

支持的 command 如下:
  mod 此 command 使能了每个模块的 function 过滤。parameter 指定目标模块(使用 lsmod 看到的模块),若只需要 trace ext3 模块中的 write*函数,运行:

	echo '*write*:mod:vin_io' > set_ftrace_filter
/sys/kernel/debug/tracing # lsmod
Module                  Size  Used by    Not tainted
vin_io                 53757  0
sha256_generic         10905  0
/sys/kernel/debug/tracing # echo '*write*:mod:vin_io' > set_ftrace_filter
/sys/kernel/debug/tracing # cat set_ftrace_filter
cci_write_a8_d8 [vin_io]
cci_write_a8_d16 [vin_io]
cci_write_a16_d8 [vin_io]
cci_write_a16_d16 [vin_io]
cci_write_a0_d16 [vin_io]
cci_write [vin_io]
cci_write_a16_d8_continuous_helper [vin_io]
sensor_write [vin_io]
sensor_write_array [vin_io]
vin_gpio_write [vin_io]
os_gpio_write [vin_io]
/sys/kernel/debug/tracing #

  向 filter 节点添加不同模块可以使用追加的方式(>>),移除指定的模块functions 可以重新追加"!":

	echo '*gpio*:mod:vin_io' >> set_ftrace_filter

  **traceon/traceoff**此 command 用于在当指定的 function 命中后,开启或关闭 tracing。parameter 确定 tracing system 的开/关次数。若没有指定,则无限制。比如,当__schedule_bug 函数被命中后关闭 tracing,以后每次命中都会关闭 tracing,直到命中了 5 次后,traceoff 命令将失效。

/sys/kernel/debug/tracing # echo '__schedule_bug:traceoff:5' > set_ftrace_filter

  值得注意的是,这些 command 是累计的,不管是否使用追加的方式添加到set_ftrace_filter。重新追加 '!'来移除相应的 command,不要带 parameter:

/sys/kernel/debug/tracing # echo '!__schedule_bug:traceoff' > set_ftrace_filter

  **dump/cpudump**此命令在 liunx-3.10 以上的版本才支持的:

  • dump
      当该函数被命中时,它会将 ftrace 环形缓冲区的内容dump到控制台。 如果您需要调试某些内容,并希望在某个函数被命中时dump跟踪,这将非常有用。 也许它是在发生三重故障之前调用的函数,并且不允许您获得常规转储。

  • cpudump
      当该函数被命中时,它会将当前 CPU 的 ftrace 环形缓冲区的内容dump到控制台。 与“dump”命令不同,它只为执行触发dump的函数的 CPU 打印出环形缓冲区的内容。

注意事项

  一般情况下,我们都需要先设置过滤器,然后在进行 tracing。要不然,默认时,所有可以被跟踪的函数(available_filter_functions 中记录了接近3万个(测试环境有23966个函数)可以被跟踪的函数)都被跟踪,很容易把系统弄死机。

/sys/kernel/debug/tracing # cat available_filter_functions | wc
    23966     23966    463605

ftrace 的使用

  ftrace 在内核态工作,用户通过 debugfs 接口来控制和使用 ftrace 。从2.6.30 开始,ftrace 支持两大类 tracer:传统 tracer 和 Non-Tracer Tracer。下面将分别介绍它们的使用。

传统 Tracer

  使用传统的 ftrace 需要如下几个步骤:

    1. 选择一种 tracer;

    选择 tracer 的控制文件叫作 current_tracer。选择 tracer 就是将tracer 的名字写入这个文件,比如,用户打算使用 function tracer,可输入如下命令:

		/sys/kernel/debug/tracing # echo function_graph > current_tracer

    2. 使能 ftrace/tracer;

    使能 ftrace 使用下面的命令:

		/sys/kernel/debug/tracing # echo 1 > /proc/sys/kernel/ftrace_enabled

    系统默认也是使能的。文件 tracing_enabled 控制 tracer 的开始和结束。

		/sys/kernel/debug/tracing # echo 1 > tracing_enable

    上面的命令使能 tracer。同样,将 0 写入 tracing_enable 文件便可以停止 tracer。

    3. 执行需要 trace 的应用程序,比如需要跟踪 ls,就执行 ls;

    4. 关闭 tracer;

    5. 查看 trace 文件;

    ftrace 的输出信息主要保存在 3 个文件中:

     trace,该文件保存 ftrace 的输出信息,其内容可以直接阅读。

     latency_trace,保存与 trace 相同的信息,不过组织方式略有不同。主要为了用户能方便地分析系统中有关延迟的信息。

     trace_pipe,是一个管道文件,内容与 trace 节点相同,但它以流形式保存 trace 结果。在没有数据可读时,对此节点的读会被 block 住,直到有新数据产生。与 trace 节点不同的是,它的数据是消耗型的。在此节点可以一直往下读,从而显示更多的当前数据,数据一旦从节点中读出,就被消耗了,不会再被读到。而 trace 节点是静态的,如果没有新的 tracing 结果,每次读都会显示相同的信息。此节点很有用,因为 ring buffer 大小是有限的,只能保存一定量的tracing 结果,太多则前面的结果将会被覆盖,而通过此节点可以把tracing 结果持续导出,作整体分析。在 android 下可以执行以下命令把 tracing 结果实时保存到 pc 中(ps:trace_pipe是不断生成的数据):

			adb pull /sys/kernel/debug/tracing/trace_pipe E:\temp_file\ftrace.bin
			[   ?] /sys/kernel/debug/tracing/trace_pipe: 14855445/?

     用户通过读写 debugfs 文件系统中的控制文件完成上述步骤。下面将详细解析各种传统 tracer 的使用方式。

function tracer执行流程

/ #
/ # mount -t debugfs null /sys/kernel/debug/	#1.挂载debugfs
/ # mkdir -p tmp/temp_file						#2.创建temp_file目录
/ # mount /dev/mmcblk0p1 tmp/temp_file/			#3.将mmcblk0p1挂载到temp_file
/ #
/ # cd sys/kernel/debug/tracing/					#4.cd到tracing目录
/sys/kernel/debug/tracing # echo 0 > tracing_on		#5.暂停tracing跟踪
/sys/kernel/debug/tracing # cat available_tracers	#6.查看支持的支持的跟踪器
blk function_graph wakeup_dl wakeup_rt wakeup function nop
/sys/kernel/debug/tracing # echo nop> current_tracer	#7.设置当前跟踪器为nop清空数据 
/sys/kernel/debug/tracing # echo function > current_tracer	#8.设置当前跟踪器为function	 
/sys/kernel/debug/tracing # echo '*jbd2*' > set_ftrace_filter	#9.设置要指定的跟踪的函数,跟踪器为function有效
/sys/kernel/debug/tracing # echo 1 > tracing_on		#10.开启tracing跟踪
/sys/kernel/debug/tracing # cp /tmp/temp_file/bootlogo.bmp /tmp/temp_file/bootlogo.bmp.bak #10.跟踪cp的操作
/sys/kernel/debug/tracing # echo 0 > tracing_on		#12.暂停tracing跟踪
/sys/kernel/debug/tracing # cat trace				#13.查看跟踪情况
# tracer: function
#
# entries-in-buffer/entries-written: 110/110   #P:4
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
 jbd2/mmcblk0p4--886   [001] ...1  2982.905548: jbd2_journal_commit_transaction <-kjournald2
 jbd2/mmcblk0p4--886   [001] ...3  2982.905559: __jbd2_journal_clean_checkpoint_list <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...2  2982.905561: jbd2_clear_buffer_revoked_flags <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...2  2982.905566: jbd2_journal_switch_revoke_table <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2982.905574: jbd2_journal_write_revoke_records <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2982.905581: jbd2_journal_get_descriptor_buffer <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2982.905583: jbd2_journal_next_log_block <-jbd2_journal_get_descriptor_buffer
 jbd2/mmcblk0p4--886   [001] ...1  2982.905587: jbd2_journal_bmap <-jbd2_journal_next_log_block
 jbd2/mmcblk0p4--886   [001] ...1  2982.905640: jbd2_journal_next_log_block <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2982.905642: jbd2_journal_bmap <-jbd2_journal_next_log_block
 jbd2/mmcblk0p4--886   [001] ...1  2982.905663: jbd2_journal_write_metadata_buffer <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...3  2982.905681: jbd2_buffer_frozen_trigger <-jbd2_journal_write_metadata_buffer
 jbd2/mmcblk0p4--886   [001] ...3  2982.905684: __jbd2_journal_file_buffer <-jbd2_journal_write_metadata_buffer
 jbd2/mmcblk0p4--886   [001] ...3  2982.905686: __jbd2_journal_temp_unlink_buffer <-__jbd2_journal_file_buffer
 jbd2/mmcblk0p4--886   [001] ...1  2982.905690: jbd2_block_tag_csum_set <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2982.905693: jbd2_descriptor_block_csum_set <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2982.905783: jbd2_journal_get_log_tail <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2982.907391: jbd2_journal_file_buffer <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...3  2982.907393: __jbd2_journal_file_buffer <-jbd2_journal_file_buffer
 jbd2/mmcblk0p4--886   [001] ...3  2982.907395: __jbd2_journal_temp_unlink_buffer <-__jbd2_journal_file_buffer
 jbd2/mmcblk0p4--886   [001] ...1  2982.907399: jbd2_journal_get_descriptor_buffer <-journal_submit_commit_record
 jbd2/mmcblk0p4--886   [001] ...1  2982.907400: jbd2_journal_next_log_block <-jbd2_journal_get_descriptor_buffer
 jbd2/mmcblk0p4--886   [001] ...1  2982.907402: jbd2_journal_bmap <-jbd2_journal_next_log_block
 jbd2/mmcblk0p4--886   [001] ...1  2982.907484: jbd2_commit_block_csum_set.isra.1 <-journal_submit_commit_record
 jbd2/mmcblk0p4--886   [001] ...3  2982.907661: __jbd2_journal_insert_checkpoint <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...3  2982.907663: jbd2_journal_grab_journal_head <-__jbd2_journal_insert_checkpoint
 jbd2/mmcblk0p4--886   [001] ...3  2982.907665: __jbd2_journal_refile_buffer <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...3  2982.907666: __jbd2_journal_unfile_buffer <-__jbd2_journal_refile_buffer
 jbd2/mmcblk0p4--886   [001] ...3  2982.907668: __jbd2_journal_temp_unlink_buffer <-__jbd2_journal_unfile_buffer
 jbd2/mmcblk0p4--886   [001] ...3  2982.907681: jbd2_journal_put_journal_head <-__jbd2_journal_unfile_buffer
             ash-1017  [001] ...1  2992.652357: jbd2__journal_start <-__ext4_journal_start_sb
             ash-1017  [001] ...1  2992.652405: jbd2_journal_free_transaction <-start_this_handle
             ash-1017  [001] ...1  2992.652425: jbd2_journal_get_write_access <-__ext4_journal_get_write_access
             ash-1017  [001] ...1  2992.652427: jbd2_journal_add_journal_head <-jbd2_journal_get_write_access
             ash-1017  [001] ...3  2992.652433: __jbd2_journal_file_buffer <-do_get_write_access
             ash-1017  [001] ...3  2992.652435: jbd2_journal_grab_journal_head <-__jbd2_journal_file_buffer
             ash-1017  [001] ...1  2992.652438: jbd2_journal_cancel_revoke <-do_get_write_access
             ash-1017  [001] ...1  2992.652439: jbd2_journal_put_journal_head <-jbd2_journal_get_write_access
             ash-1017  [001] ...1  2992.652448: jbd2_journal_dirty_metadata <-__ext4_handle_dirty_metadata
             ash-1017  [001] ...3  2992.652450: __jbd2_journal_file_buffer <-jbd2_journal_dirty_metadata
             ash-1017  [001] ...3  2992.652452: __jbd2_journal_temp_unlink_buffer <-__jbd2_journal_file_buffer
             ash-1017  [001] ...1  2992.652456: jbd2_journal_stop <-__ext4_journal_stop
             ash-1017  [001] ...1  2992.652474: jbd2__journal_start <-__ext4_journal_start_sb
             ash-1017  [001] ...1  2992.652486: jbd2_journal_free_transaction <-start_this_handle
             ash-1017  [001] ...1  2992.652497: jbd2_journal_get_write_access <-__ext4_journal_get_write_access
             ash-1017  [001] ...1  2992.652498: jbd2_write_access_granted.isra.0.part.1 <-jbd2_journal_get_write_access
             ash-1017  [001] ...1  2992.652501: jbd2_journal_dirty_metadata <-__ext4_handle_dirty_metadata
             ash-1017  [001] ...1  2992.652506: jbd2__journal_start <-__ext4_journal_start_sb
             ash-1017  [001] ...1  2992.652508: jbd2_journal_get_write_access <-__ext4_journal_get_write_access
             ash-1017  [001] ...1  2992.652509: jbd2_write_access_granted.isra.0.part.1 <-jbd2_journal_get_write_access
             ash-1017  [001] ...1  2992.652512: jbd2_journal_dirty_metadata <-__ext4_handle_dirty_metadata
             ash-1017  [001] ...1  2992.652513: jbd2_journal_stop <-__ext4_journal_stop
             ash-1017  [001] ...1  2992.652515: jbd2_journal_stop <-__ext4_journal_stop
 jbd2/mmcblk0p4--886   [001] ...1  2998.009539: jbd2_journal_commit_transaction <-kjournald2
 jbd2/mmcblk0p4--886   [001] ...3  2998.009550: __jbd2_journal_clean_checkpoint_list <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...2  2998.009553: jbd2_clear_buffer_revoked_flags <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...2  2998.009558: jbd2_journal_switch_revoke_table <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2998.009566: jbd2_journal_write_revoke_records <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2998.009573: jbd2_journal_get_descriptor_buffer <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2998.009575: jbd2_journal_next_log_block <-jbd2_journal_get_descriptor_buffer
 jbd2/mmcblk0p4--886   [001] ...1  2998.009578: jbd2_journal_bmap <-jbd2_journal_next_log_block
 jbd2/mmcblk0p4--886   [001] ...1  2998.009631: jbd2_journal_next_log_block <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2998.009633: jbd2_journal_bmap <-jbd2_journal_next_log_block
 jbd2/mmcblk0p4--886   [001] ...1  2998.009654: jbd2_journal_write_metadata_buffer <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...3  2998.009672: jbd2_buffer_frozen_trigger <-jbd2_journal_write_metadata_buffer
 jbd2/mmcblk0p4--886   [001] ...3  2998.009675: __jbd2_journal_file_buffer <-jbd2_journal_write_metadata_buffer
 jbd2/mmcblk0p4--886   [001] ...3  2998.009677: __jbd2_journal_temp_unlink_buffer <-__jbd2_journal_file_buffer
 jbd2/mmcblk0p4--886   [001] ...1  2998.009680: jbd2_block_tag_csum_set <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2998.009684: jbd2_descriptor_block_csum_set <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2998.009771: jbd2_journal_get_log_tail <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...1  2998.011275: jbd2_journal_file_buffer <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...3  2998.011278: __jbd2_journal_file_buffer <-jbd2_journal_file_buffer
 jbd2/mmcblk0p4--886   [001] ...3  2998.011279: __jbd2_journal_temp_unlink_buffer <-__jbd2_journal_file_buffer
 jbd2/mmcblk0p4--886   [001] ...1  2998.011283: jbd2_journal_get_descriptor_buffer <-journal_submit_commit_record
 jbd2/mmcblk0p4--886   [001] ...1  2998.011285: jbd2_journal_next_log_block <-jbd2_journal_get_descriptor_buffer
 jbd2/mmcblk0p4--886   [001] ...1  2998.011287: jbd2_journal_bmap <-jbd2_journal_next_log_block
 jbd2/mmcblk0p4--886   [001] ...1  2998.011316: jbd2_commit_block_csum_set.isra.1 <-journal_submit_commit_record
 jbd2/mmcblk0p4--886   [001] ...3  2998.011884: __jbd2_journal_remove_checkpoint <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...3  2998.011886: jbd2_journal_put_journal_head <-__jbd2_journal_remove_checkpoint
 jbd2/mmcblk0p4--886   [001] ...3  2998.011888: __jbd2_journal_drop_transaction <-__jbd2_journal_remove_checkpoint
 jbd2/mmcblk0p4--886   [001] ...3  2998.011890: jbd2_journal_free_transaction <-__jbd2_journal_remove_checkpoint
 jbd2/mmcblk0p4--886   [001] ...3  2998.011904: __jbd2_journal_insert_checkpoint <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...3  2998.011905: jbd2_journal_grab_journal_head <-__jbd2_journal_insert_checkpoint
 jbd2/mmcblk0p4--886   [001] ...3  2998.011907: __jbd2_journal_refile_buffer <-jbd2_journal_commit_transaction
 jbd2/mmcblk0p4--886   [001] ...3  2998.011908: __jbd2_journal_unfile_buffer <-__jbd2_journal_refile_buffer
 jbd2/mmcblk0p4--886   [001] ...3  2998.011909: __jbd2_journal_temp_unlink_buffer <-__jbd2_journal_unfile_buffer
 jbd2/mmcblk0p4--886   [001] ...3  2998.011912: jbd2_journal_put_journal_head <-__jbd2_journal_unfile_buffer
             ash-1017  [001] ...1  3001.323174: jbd2__journal_start <-__ext4_journal_start_sb
             ash-1017  [001] ...1  3001.323222: jbd2_journal_free_transaction <-start_this_handle
             ash-1017  [001] ...1  3001.323233: jbd2_journal_get_write_access <-__ext4_journal_get_write_access
             ash-1017  [001] ...1  3001.323236: jbd2_journal_add_journal_head <-jbd2_journal_get_write_access
             ash-1017  [001] ...3  3001.323242: __jbd2_journal_file_buffer <-do_get_write_access
             ash-1017  [001] ...3  3001.323244: jbd2_journal_grab_journal_head <-__jbd2_journal_file_buffer
             ash-1017  [001] ...1  3001.323247: jbd2_journal_cancel_revoke <-do_get_write_access
             ash-1017  [001] ...1  3001.323249: jbd2_journal_put_journal_head <-jbd2_journal_get_write_access
             ash-1017  [001] ...1  3001.323257: jbd2_journal_dirty_metadata <-__ext4_handle_dirty_metadata
             ash-1017  [001] ...3  3001.323260: __jbd2_journal_file_buffer <-jbd2_journal_dirty_metadata
             ash-1017  [001] ...3  3001.323261: __jbd2_journal_temp_unlink_buffer <-__jbd2_journal_file_buffer
             ash-1017  [001] ...1  3001.323265: jbd2_journal_stop <-__ext4_journal_stop
             ash-1017  [001] ...1  3001.323284: jbd2__journal_start <-__ext4_journal_start_sb
             ash-1017  [001] ...1  3001.323296: jbd2_journal_free_transaction <-start_this_handle
             ash-1017  [001] ...1  3001.323306: jbd2_journal_get_write_access <-__ext4_journal_get_write_access
             ash-1017  [001] ...1  3001.323308: jbd2_write_access_granted.isra.0.part.1 <-jbd2_journal_get_write_access
             ash-1017  [001] ...1  3001.323311: jbd2_journal_dirty_metadata <-__ext4_handle_dirty_metadata
             ash-1017  [001] ...1  3001.323316: jbd2__journal_start <-__ext4_journal_start_sb
             ash-1017  [001] ...1  3001.323318: jbd2_journal_get_write_access <-__ext4_journal_get_write_access
             ash-1017  [001] ...1  3001.323319: jbd2_write_access_granted.isra.0.part.1 <-jbd2_journal_get_write_access
             ash-1017  [001] ...1  3001.323321: jbd2_journal_dirty_metadata <-__ext4_handle_dirty_metadata
             ash-1017  [001] ...1  3001.323323: jbd2_journal_stop <-__ext4_journal_stop
             ash-1017  [001] ...1  3001.323324: jbd2_journal_stop <-__ext4_journal_stop
/sys/kernel/debug/tracing #

function_graph tracer执行流程

/ #
/ # mount -t debugfs null /sys/kernel/debug/	#1.挂载debugfs
/ # mkdir -p tmp/temp_file						#2.创建temp_file目录
/ # mount /dev/mmcblk0p1 tmp/temp_file/			#3.将mmcblk0p1挂载到temp_file
/ #
/ # cd sys/kernel/debug/tracing/					#4.cd到tracing目录
/sys/kernel/debug/tracing # cat available_tracers	#5.查看支持的支持的跟踪器
blk function_graph wakeup_dl wakeup_rt wakeup function nop
/sys/kernel/debug/tracing # echo 0 > tracing_on		#6.暂停tracing跟踪
/sys/kernel/debug/tracing # echo function_graph > current_tracer	#7.设置当前跟踪器为function_graph 	 
/sys/kernel/debug/tracing # echo '*jbd2*' > set_graph_function		#8.设置要显示的跟踪的函数,跟踪器为function_graph有效
/sys/kernel/debug/tracing # echo 1 > tracing_on		#9.开启tracing跟踪
/sys/kernel/debug/tracing # cp /tmp/temp_file/bootlogo.bmp /tmp/temp_file/bootlogo.bmp.bak #10.跟踪cp的操作
/sys/kernel/debug/tracing # echo 0 > tracing_on		#11.暂停tracing跟踪
/sys/kernel/debug/tracing # cat trace				#12.查看跟踪情况
# tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
 1)   0.750 us    |  jbd2_buffer_frozen_trigger();
 1)               |  __jbd2_journal_file_buffer() {
 1)   1.292 us    |    __jbd2_journal_temp_unlink_buffer();
 1)   5.000 us    |  }
 1)               |  jbd2_journal_file_buffer() {
 1)               |    __jbd2_journal_file_buffer() {
 1)   0.542 us    |      __jbd2_journal_temp_unlink_buffer();
 1)   3.625 us    |    }
 1)   6.959 us    |  }
 1) + 12.333 us   |  jbd2_journal_free_transaction();
 1)               |  __jbd2_journal_refile_buffer() {
 1)               |    __jbd2_journal_unfile_buffer() {
 1)   1.292 us    |      __jbd2_journal_temp_unlink_buffer();
 1)   0.375 us    |      jbd2_journal_put_journal_head();
 1)   6.083 us    |    }
 1)   9.042 us    |  }
 3)               |  jbd2__journal_start() {
 3)   0.459 us    |    jbd2_journal_free_transaction();
 3) + 47.166 us   |  }
 3)               |  jbd2_journal_get_write_access() {
 3)   0.459 us    |    jbd2_write_access_granted.isra.0.part.1();
 3) + 16.000 us   |    jbd2_journal_add_journal_head();
 3)               |    __jbd2_journal_file_buffer() {
 3)   0.833 us    |      jbd2_journal_grab_journal_head();
 3)   4.750 us    |    }
 3)   2.583 us    |    jbd2_journal_cancel_revoke();
 3)   0.916 us    |    jbd2_journal_put_journal_head();
 3) + 42.041 us   |  }
 3)               |  jbd2_journal_get_write_access() {
 3)   0.334 us    |    jbd2_write_access_granted.isra.0.part.1();
 3)   9.833 us    |    jbd2_journal_add_journal_head();
 3)               |    __jbd2_journal_file_buffer() {
 3)   0.375 us    |      jbd2_journal_grab_journal_head();
 3)   2.750 us    |    }
 3)   0.333 us    |    jbd2_journal_cancel_revoke();
 3)   0.417 us    |    jbd2_journal_put_journal_head();
 3) + 25.125 us   |  }
 3)               |  jbd2_journal_dirty_metadata() {
 3)               |    __jbd2_journal_file_buffer() {
 3)   0.750 us    |      __jbd2_journal_temp_unlink_buffer();
 3)   3.542 us    |    }
 3)   7.500 us    |  }
 3)               |  jbd2_journal_dirty_metadata() {
 3)               |    __jbd2_journal_file_buffer() {
 3)   0.292 us    |      __jbd2_journal_temp_unlink_buffer();
 3)   2.666 us    |    }
 3)   5.500 us    |  }
 3)               |  jbd2_journal_get_write_access() {
 3)   0.667 us    |    jbd2_journal_add_journal_head();
 3)               |    __jbd2_journal_file_buffer() {
 3)   0.375 us    |      jbd2_journal_grab_journal_head();
 3)   2.958 us    |    }
 3)   0.334 us    |    jbd2_journal_cancel_revoke();
 3)   0.375 us    |    jbd2_journal_put_journal_head();
 3) + 13.959 us   |  }
 3)               |  jbd2_journal_dirty_metadata() {
 3)               |    __jbd2_journal_file_buffer() {
 3)   0.375 us    |      __jbd2_journal_temp_unlink_buffer();
 3)   2.959 us    |    }
 3)   5.709 us    |  }
 3)   0.292 us    |  jbd2__journal_start();
 3)               |  jbd2_journal_get_write_access() {
 3)   0.333 us    |    jbd2_write_access_granted.isra.0.part.1();
 3)   2.666 us    |  }
 3)   0.375 us    |  jbd2_journal_dirty_metadata();
 3)   0.625 us    |  jbd2_journal_stop();
 3)   1.333 us    |  jbd2_journal_file_inode();
 3)               |  jbd2_journal_get_write_access() {
 3)   0.375 us    |    jbd2_write_access_granted.isra.0.part.1();
 3)   2.791 us    |  }
 3)   0.291 us    |  jbd2_journal_dirty_metadata();
 3) + 12.458 us   |  jbd2_journal_stop();
 3)               |  jbd2__journal_start() {
 3)   0.250 us    |    jbd2_journal_free_transaction();
 3) + 14.209 us   |  }
 3)   7.458 us    |  jbd2_journal_stop();
 3)               |  jbd2__journal_start() {
 3)   0.250 us    |    jbd2_journal_free_transaction();
 3) + 13.084 us   |  }
 3)   7.750 us    |  jbd2_journal_stop();
 ------------------------------------------
 1)  jbd2/mm-886   =>    ash-1017
 ------------------------------------------

 1)               |  jbd2__journal_start() {
 1)   0.709 us    |    jbd2_journal_free_transaction();
 1) + 25.000 us   |  }
 1)               |  jbd2_journal_get_write_access() {
 3)   0.292 us    |  jbd2__journal_start();
 3)               |  jbd2_journal_get_write_access() {
 3)   0.333 us    |    jbd2_write_access_granted.isra.0.part.1();
 3)   2.666 us    |  }
 3)   0.375 us    |  jbd2_journal_dirty_metadata();
 3)   0.625 us    |  jbd2_journal_stop();
 3)   1.333 us    |  jbd2_journal_file_inode();
 3)               |  jbd2_journal_get_write_access() {
 3)   0.375 us    |    jbd2_write_access_granted.isra.0.part.1();
 3)   2.791 us    |  }
 3)   0.291 us    |  jbd2_journal_dirty_metadata();
 3) + 12.458 us   |  jbd2_journal_stop();
 3)               |  jbd2__journal_start() {
 3)   0.250 us    |    jbd2_journal_free_transaction();
 3) + 14.209 us   |  }
 3)   7.458 us    |  jbd2_journal_stop();
 3)               |  jbd2__journal_start() {
 3)   0.250 us    |    jbd2_journal_free_transaction();
 3) + 13.084 us   |  }
 3)   7.750 us    |  jbd2_journal_stop();
 1)               |  jbd2__journal_start() {
 1)   0.709 us    |    jbd2_journal_free_transaction();
 1) + 25.000 us   |  }
 1)               |  jbd2_journal_get_write_access() {
 1)   0.458 us    |    jbd2_write_access_granted.isra.0.part.1();
 1)   3.458 us    |  }
 1)   0.917 us    |  jbd2_journal_dirty_metadata();
 1)   9.417 us    |  jbd2_journal_stop();
 1)               |  jbd2__journal_start() {
 1)   0.208 us    |    jbd2_journal_free_transaction();
 1) + 13.833 us   |  }
 1)               |  jbd2_journal_get_write_access() {
 1)   0.333 us    |    jbd2_write_access_granted.isra.0.part.1();
 1)   2.708 us    |  }
 1)   0.291 us    |  jbd2_journal_dirty_metadata();
 1)   0.292 us    |  jbd2__journal_start();
 1)               |  jbd2_journal_get_write_access() {
 1)   0.333 us    |    jbd2_write_access_granted.isra.0.part.1();
 1)   2.625 us    |  }
 1)   0.250 us    |  jbd2_journal_dirty_metadata();
 1)   0.292 us    |  jbd2_journal_stop();
 1)   7.500 us    |  jbd2_journal_stop();
 3)               |  jbd2__journal_start() {
 3)   0.542 us    |    jbd2_journal_free_transaction();
 3) + 23.334 us   |  }
 3)               |  jbd2_journal_stop() {
 3)               |    jbd2_log_start_commit() {
 3) + 12.375 us   |      __jbd2_log_start_commit();
 3) + 16.334 us   |    }
 3) + 29.167 us   |  }
 3)               |  jbd2__journal_start() {
 3)   0.250 us    |    jbd2_journal_free_transaction();
 3) + 36.042 us   |  }
 3)   7.833 us    |  jbd2_journal_stop();
 2)   0.458 us    |  jbd2_buffer_frozen_trigger();
 2)               |  __jbd2_journal_file_buffer() {
 2)   1.084 us    |    __jbd2_journal_temp_unlink_buffer();
 2)   5.167 us    |  }
 2)   0.250 us    |  jbd2_buffer_frozen_trigger();
 2)               |  __jbd2_journal_file_buffer() {
 2)   0.375 us    |    __jbd2_journal_temp_unlink_buffer();
 2)   3.042 us    |  }
 2)   0.250 us    |  jbd2_buffer_frozen_trigger();
 2)               |  __jbd2_journal_file_buffer() {
 2)   0.333 us    |    __jbd2_journal_temp_unlink_buffer();
 2)   3.000 us    |  }
 2)               |  jbd2_journal_file_buffer() {
 2)               |    __jbd2_journal_file_buffer() {
 2)   0.833 us    |      __jbd2_journal_temp_unlink_buffer();
 2)   4.625 us    |    }
 2)   8.375 us    |  }
 2)               |  jbd2_journal_file_buffer() {
 2)               |    __jbd2_journal_file_buffer() {
 2)   0.375 us    |      __jbd2_journal_temp_unlink_buffer();
 2)   2.959 us    |    }
 2)   5.584 us    |  }
 2)               |  jbd2_journal_file_buffer() {
 2)               |    __jbd2_journal_file_buffer() {
 2)   0.375 us    |      __jbd2_journal_temp_unlink_buffer();
 2)   3.084 us    |    }
 2)   5.709 us    |  }
 2) + 11.000 us   |  jbd2_journal_free_transaction();
 2)               |  __jbd2_journal_refile_buffer() {
 2)               |    __jbd2_journal_unfile_buffer() {
 2)   8.917 us    |      __jbd2_journal_temp_unlink_buffer();
 2)   0.375 us    |      jbd2_journal_put_journal_head();
 2) + 13.792 us   |    }
 2) + 16.958 us   |  }
 2)               |  __jbd2_journal_refile_buffer() {
 2)               |    __jbd2_journal_unfile_buffer() {
 2)   3.042 us    |      __jbd2_journal_temp_unlink_buffer();
 2)   0.375 us    |    __jbd2_journal_temp_unlink_buffer();
 2)   3.042 us    |  } /* __jbd2_journal_file_buffer */
 2)   0.250 us    |  jbd2_buffer_frozen_trigger();
 2)               |  __jbd2_journal_file_buffer() {
 2)   0.333 us    |    __jbd2_journal_temp_unlink_buffer();
 2)   3.000 us    |  }
 2)               |  jbd2_journal_file_buffer() {
 2)               |    __jbd2_journal_file_buffer() {
 2)   0.833 us    |      __jbd2_journal_temp_unlink_buffer();
 2)   4.625 us    |    }
 2)   8.375 us    |  }
 2)               |  jbd2_journal_file_buffer() {
 2)               |    __jbd2_journal_file_buffer() {
 2)   0.375 us    |      __jbd2_journal_temp_unlink_buffer();
 2)   2.959 us    |    }
 2)   5.584 us    |  }
 2)               |  jbd2_journal_file_buffer() {
 2)               |    __jbd2_journal_file_buffer() {
 2)   0.375 us    |      __jbd2_journal_temp_unlink_buffer();
 2)   3.084 us    |    }
 2)   5.709 us    |  }
 2) + 11.000 us   |  jbd2_journal_free_transaction();
 2)               |  __jbd2_journal_refile_buffer() {
 2)               |    __jbd2_journal_unfile_buffer() {
 2)   8.917 us    |      __jbd2_journal_temp_unlink_buffer();
 2)   0.375 us    |      jbd2_journal_put_journal_head();
 2) + 13.792 us   |    }
 2) + 16.958 us   |  }
 2)               |  __jbd2_journal_refile_buffer() {
 2)               |    __jbd2_journal_unfile_buffer() {
 2)   3.042 us    |      __jbd2_journal_temp_unlink_buffer();
 2)   0.417 us    |      jbd2_journal_put_journal_head();
 2)   9.042 us    |    }
 2) + 11.250 us   |  }
 2)               |  __jbd2_journal_refile_buffer() {
 2)               |    __jbd2_journal_unfile_buffer() {
 2)   3.166 us    |      __jbd2_journal_temp_unlink_buffer();
 2)   0.375 us    |      jbd2_journal_put_journal_head();
 2)   8.083 us    |    }
 2) + 10.459 us   |  }
 ------------------------------------------
 3)   kworker-30   =>  jbd2/mm-886
 ------------------------------------------

 3) + 13.292 us   |  jbd2_journal_free_transaction();
 1)               |  jbd2__journal_start() {
 1)   0.583 us    |    jbd2_journal_free_transaction();
 1) + 47.583 us   |  }
 1)               |  jbd2_journal_get_write_access() {
 1)   1.584 us    |    jbd2_journal_add_journal_head();
 1)               |    __jbd2_journal_file_buffer() {
 1)   0.834 us    |      jbd2_journal_grab_journal_head();
 1)   4.917 us    |    }
 1)   0.708 us    |    jbd2_journal_cancel_revoke();
 1)   0.750 us    |    jbd2_journal_put_journal_head();
 1) + 22.750 us   |  }
 1)               |  jbd2_journal_dirty_metadata() {
 1)               |    __jbd2_journal_file_buffer() {
 1)   0.750 us    |      __jbd2_journal_temp_unlink_buffer();
 1)   3.542 us    |    }
 1)   7.417 us    |  }
 1)   10.000 us   |  jbd2_journal_stop();
 1)               |  jbd2__journal_start() {
 1)   0.250 us    |    jbd2_journal_free_transaction();
 1) + 13.875 us   |  }
 1)               |  jbd2_journal_get_write_access() {
 1)   0.500 us    |    jbd2_write_access_granted.isra.0.part.1();
 1)   3.417 us    |  }
 1)   0.417 us    |  jbd2_journal_dirty_metadata();
 1)   0.291 us    |  jbd2__journal_start();
 1)               |  jbd2_journal_get_write_access() {
 1)   0.292 us    |    jbd2_write_access_granted.isra.0.part.1();
 1)   2.667 us    |  }
 1)   0.250 us    |  jbd2_journal_dirty_metadata();
 1)   0.333 us    |  jbd2_journal_stop();
 1)   7.459 us    |  jbd2_journal_stop();
 ------------------------------------------
 2)  jbd2/mm-886   =>    ash-1017
 ------------------------------------------

 2)               |  jbd2__journal_start() {
 2)   0.458 us    |    jbd2_journal_free_transaction();
 2) + 26.000 us   |  }
 2)               |  jbd2_journal_get_write_access() {
 2)   0.708 us    |    jbd2_write_access_granted.isra.0.part.1();
 2)   3.917 us    |  }
 2)   0.667 us    |  jbd2_journal_dirty_metadata();
 2)   9.333 us    |  jbd2_journal_stop();
 2)               |  jbd2__journal_start() {
 2)   0.250 us    |    jbd2_journal_free_transaction();
 2) + 13.792 us   |  }
 2)               |  jbd2_journal_get_write_access() {
 1)   0.500 us    |    jbd2_write_access_granted.isra.0.part.1();
 1)   3.417 us    |  } /* jbd2_journal_get_write_access */
 1)   0.417 us    |  jbd2_journal_dirty_metadata();
 1)   0.291 us    |  jbd2__journal_start();
 1)               |  jbd2_journal_get_write_access() {
 1)   0.292 us    |    jbd2_write_access_granted.isra.0.part.1();
 1)   2.667 us    |  }
 1)   0.250 us    |  jbd2_journal_dirty_metadata();
 1)   0.333 us    |  jbd2_journal_stop();
 1)   7.459 us    |  jbd2_journal_stop();
 2)               |  jbd2__journal_start() {
 2)   0.458 us    |    jbd2_journal_free_transaction();
 2) + 26.000 us   |  }
 2)               |  jbd2_journal_get_write_access() {
 2)   0.708 us    |    jbd2_write_access_granted.isra.0.part.1();
 2)   3.917 us    |  }
 2)   0.667 us    |  jbd2_journal_dirty_metadata();
 2)   9.333 us    |  jbd2_journal_stop();
 2)               |  jbd2__journal_start() {
 2)   0.250 us    |    jbd2_journal_free_transaction();
 2) + 13.792 us   |  }
 2)               |  jbd2_journal_get_write_access() {
 2)   0.334 us    |    jbd2_write_access_granted.isra.0.part.1();
 2)   2.708 us    |  }
 2)   0.292 us    |  jbd2_journal_dirty_metadata();
 2)   0.291 us    |  jbd2__journal_start();
 2)               |  jbd2_journal_get_write_access() {
 2)   0.334 us    |    jbd2_write_access_granted.isra.0.part.1();
 2)   2.625 us    |  }
 2)   0.292 us    |  jbd2_journal_dirty_metadata();
 2)   0.250 us    |  jbd2_journal_stop();
 2)   7.625 us    |  jbd2_journal_stop();
/sys/kernel/debug/tracing #

blk tracer

  Blk tracer 是用来对 block 模块进行跟踪的。例如,在 android 系统中插入一个 U 盘后,对此 U 盘设备(sda1)的行为进行跟踪,具体的执行流程,如下图所示:
ftrace介绍_第6张图片

  当向 sda1分区的 filetest 文件写入“12345”的数据后,出现一些“WS”类型的信息被记录下来了。输出信息的具体格式请参考 blktrace 的相关文档。

  blk tracer 可以很方便的跟踪块 IO 子系统通用块层的数据流动线路。

  可以对感兴趣的 disk 或分区启用 trace。启用方法很简单,举个例子,使能对 mmcblk0p1分区的 trace。

# echo 1 > /sys/block/mmcblk0/mmcblk0p1/trace/enable
# echo blk > /d/tracing/current_tracer
# cat /d/tracing/trace:
<...>-14762 (-----) [001] ...1 354237.801020: 179,1 A W 5625185 + 1 <- (179,1)
46433
<...>-14762 (-----) [001] ...1 354237.801022: 179,1 Q W 5625185 + 1 [<...>]
<...>-14762 (-----) [001] ...1 354237.801026: 179,1 B W 5625185 + 1 [<...>]
<...>-14762 (-----) [001] ...1 354237.801028: 179,1 M W 5625185 + 1 [<...>]
<...>-14762 (-----) [001] ...2 354237.801040: 179,1 I W 5600588 + 40 [<...>]
<...>-14762 (-----) [001] ...1 354237.801046: 179,1 m N cfq122 insert_request
……
<...>-15129 (-----) [001] ...1 354237.801165: 179,1 m N cfq122 slice expired
t=1
<...>-15129 (-----) [001] ...1 354237.801169: 179,1 m N cfq122 resid=4
<...>-15129 (-----) [001] ...1 354237.801174: 179,1 m N cfq122 sl_used=1
disp=1 charge=1 iops=0 sect=40
<...>-93 (-----) [000] ...1 354237.801179: 179,1 m N cfq workload slice:10
<...>-93 (-----) [000] ...1 354237.801182: 179,1 m N cfq15129 set_active
wl_prio:0 wl_type:1
<...>-93 (-----) [000] ...2 354237.802140: 179,1 C W 5600588 + 40 [0]

  通过 trace 结果可以明确看到请求添加、执行的整体流程。但打印是相当多的,要把握住一段时间的 IO 情况,需要借助工具来进行分析。
  没 有 找 到 内 核 的 说 明 文 档 , 需 要 看 代 码(linux-3.4/kernel/trace/blktrace.c)来确定每一列的含义:

# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID TGID CPU# |||| TIMESTAMP DEV ACT FLG
# | | | | |||| | | | |
<...>-14762 (-----) [001] ...1 354237.800897: 179,1 A W 5625175 + 1 <- (179,1) 46423
<...>-14762 (-----) [001] ...1 354237.800899: 179,1 Q W 5625175 + 1 [<...>]

  第6列:设备号
  第7列:正在进行的 action

static const struct {
const char *act[2];
int (*print)(struct trace_seq *s, const struct trace_entry *ent);
} what2act[] = {
[__BLK_TA_QUEUE] = {{ "Q", "queue" }, blk_log_generic },
[__BLK_TA_BACKMERGE] = {{ "M", "backmerge" }, blk_log_generic },
[__BLK_TA_FRONTMERGE] = {{ "F", "frontmerge" }, blk_log_generic },
[__BLK_TA_GETRQ] = {{ "G", "getrq" }, blk_log_generic },
[__BLK_TA_SLEEPRQ] = {{ "S", "sleeprq" }, blk_log_generic },
[__BLK_TA_REQUEUE] = {{ "R", "requeue" }, blk_log_with_error },
[__BLK_TA_ISSUE] = {{ "D", "issue" }, blk_log_generic },
[__BLK_TA_COMPLETE] = {{ "C", "complete" }, blk_log_with_error },
[__BLK_TA_PLUG] = {{ "P", "plug" }, blk_log_plug },
[__BLK_TA_UNPLUG_IO] = {{ "U", "unplug_io" }, blk_log_unplug },
[__BLK_TA_UNPLUG_TIMER] = {{ "UT", "unplug_timer" }, blk_log_unplug },
[__BLK_TA_INSERT] = {{ "I", "insert" }, blk_log_generic },
[__BLK_TA_SPLIT] = {{ "X", "split" }, blk_log_split },
[__BLK_TA_BOUNCE] = {{ "B", "bounce" }, blk_log_generic },
[__BLK_TA_REMAP] = {{ "A", "remap" }, blk_log_remap },
};

  Q: 对应 trace_block_bio_queue,添加在 generic_make_request_checks()函数末尾(linux-3.4/block/blk-core.c)

  M: 对应 trace_block_bio_backmerge,添加在 bio_attempt_back_merge()函数中(linux-3.4/block/blk-core.c)

  F: 对应 trace_block_bio_frontmerge,添加在 bio_attempt_front_merge()函数中(linux-3.4/block/blk-core.c)

  G: 对 应 trace_block_getrq , 添 加 在 get_request() 函 数 末 尾(linux-3.4/block/blk-core.c)

  S: 对 应 trace_block_sleeprq , 添 加 在 get_request_wait() 函 数 中(linux-3.4/block/blk-core.c)

  R: 对应 trace_block_rq_requeue,添加在 blk_requeue_request()函数中(linux-3.4/block/blk-core.c)

  D: 对 应 trace_block_rq_issue , 添 加 在 blk_peek_request() 函 数 中(linux-3.4/block/blk-core.c)

  C: 对应 trace_block_rq_complete,添加在 blk_update_request()函数起始位置(linux-3.4/block/blk-core.c)

  P: 对应 trace_block_plug,添加在 blk_queue_bio()函数中的两处,当plug->list 为空时,和在 blk_flush_plug_list()之后,没有在 blk_start_plug()函数里加,它只是保证进程的 plug 域不为空,没有实际的 plug 操作。(linux-3.4/block/blk-core.c)

  U: 对 应 trace_block_unplug , 添 加 在 queue_unplugged() 函 数 中 ,blk_finish_plug()会调用到(linux-3.4/block/blk-core.c)

  UT:

  I: 对应 trace_block_rq_insert,添加在__elv_add_request()函数起始位置(linux-3.4/block/elevator.c)

  X: 对 应 trace_block_split , 添 加 在 bio_split() 函 数 中(linux-3.4/fs/bio.c)

  B: 对应 trace_block_bio_bounce,添加在__blk_queue_bounce()函数中(linux-3.4/mm/bounce.c)

  A: 对应 trace_block_bio_remap,添加在 blk_partition_remap()函数中(linux-3.4/block/blk-core.c)

  还有3个 trace point 没有对应的 action:

    trace_block_rq_remap

    trace_block_bio_complete

    trace_block_rq_abort

  第8列:请求标志

void blk_fill_rwbs(char *rwbs, u32 rw, int bytes)
{
	int i = 0;
	if (rw & REQ_FLUSH)
		rwbs[i++] = 'F';
	if (rw & WRITE)
		rwbs[i++] = 'W';
	else if (rw & REQ_DISCARD)
		rwbs[i++] = 'D';
	else if (bytes)
		rwbs[i++] = 'R';
	else
		rwbs[i++] = 'N';
	if (rw & REQ_FUA)
		rwbs[i++] = 'F';
	if (rw & REQ_RAHEAD)
		rwbs[i++] = 'A';
	if (rw & REQ_SYNC)
		rwbs[i++] = 'S';
	if (rw & REQ_META)
		rwbs[i++] = 'M';
	if (rw & REQ_SECURE)
		rwbs[i++] = 'E';
	rwbs[i] = '\0';
}

  第9列:起始扇区号
  第10列:扇区数
  第11列:进程名(action 是 A 时,为 “(设备号) 相对分区位置”)

  blk tracer 还提供了一些选项:

# ls -l /sys/block/mmcblk0/mmcblk0p1/trace
-rw-r--r-- root root 4096 2015-01-06 13:59 act_mask
-rw-r--r-- root root 4096 2015-01-14 13:34 enable
-rw-r--r-- root root 4096 2015-01-06 13:59 end_lba
-rw-r--r-- root root 4096 2015-01-06 13:59 pid
-rw-r--r-- root root 4096 2015-01-06 13:59 start_lba

  act_mask在使能对 mmcblk0p1分区的 trace 后,

# cat act_mask
read,write,flush,sync,queue,requeue,issue,complete,fs,pc,ahead,meta,discard,drv_data,fua
static const struct {
int mask;
const char *str;
} mask_maps[] = {
{ BLK_TC_READ, "read" },
{ BLK_TC_WRITE, "write" },
{ BLK_TC_FLUSH, "flush" },
{ BLK_TC_SYNC, "sync" },
{ BLK_TC_QUEUE, "queue" },
{ BLK_TC_REQUEUE,"requeue"},
{ BLK_TC_ISSUE, "issue" },
{ BLK_TC_COMPLETE, "complete" },
{ BLK_TC_FS, "fs" },
{ BLK_TC_PC, "pc" },
{ BLK_TC_AHEAD, "ahead" },
{ BLK_TC_META, "meta" },
{ BLK_TC_DISCARD, "discard" },
{ BLK_TC_DRV_DATA, "drv_data" },
{ BLK_TC_FUA, "fua" },
};

  注意在 action 数组里有__BLK_TA_QUEUE,在此数组有 BLK_TC_QUEUE,两者有什么区别?TA 指的是 trace action,tc 指的是 trace category。一个 category可以包含多个 action。

  可以在 linux-3.4/include/linux/blktrace_api.h 中,查看所有 action 和category 的对应关系:

/*
* Trace actions in full. Additionally, read or write is masked
*/
#define BLK_TA_QUEUE (__BLK_TA_QUEUE | BLK_TC_ACT(BLK_TC_QUEUE))
#define BLK_TA_BACKMERGE (__BLK_TA_BACKMERGE |
BLK_TC_ACT(BLK_TC_QUEUE))
#define BLK_TA_FRONTMERGE (__BLK_TA_FRONTMERGE |
BLK_TC_ACT(BLK_TC_QUEUE))
......

  read:只跟踪读请求

  write:只跟踪写请求

  queue:Q、M、F、G、S、P、U、I、A

  requeue:R

  issue:D

  complete:C

  在 blk_add_trace_rq()函数中,区分 fs 和 pc:

  pc:(rq->cmd_type == REQ_TYPE_BLOCK_PC),scsi 命令

  fs:else,fs 请求

  ahead:在代码中注释是跟踪 readahead,但要注意的是,它不是指文件系统的预读(没有往下发 REQ_RAHEAD 标志),目前只在直接对块设备的 block 进行操作时有使用(ll_rw_block()和 submit_bh(),读写方式为 READA)。

  meta:跟踪设置了 REQ_META 标志的请求

  discard:跟踪设置了 REQ_DISCARD 标志的请求

  drv_data: 与 scsi 驱动有关,暂不跟进

  貌似 act_mask 的 store 代码有点问题:

    echo discard > act_mask 后,act_mask 被设置为 read,flush,sync

    fua: echo fua > act_mask 后,act_mask 被设置为 read,write,flush,sync而在前面加个",",则可以正常设置,如 echo ,discard > act_mask错误原因,在 sysfs_blk_trace_attr_store()函数中,由以下一段代码:

if (sscanf(buf, "%llx", &value) != 1) {
/* Assume it is a list of trace category names */
	ret = blk_trace_str2mask(buf);
	if (ret < 0)
		goto out;
	value = ret;
}

  在 sscanf 阶段,discard 的首字母 d 被当成是十六进制数值,因此 sscanf可以获取到 value,不会再执行大括号中的代码,在后面把 value 值赋给 mask,因此 mask 变为0x0d,正好对应的是 read,flush,sync。

::: note
a. 各 category 之间是"或"的关系,只要有一个符合,请求将会被 trace。若请求不符合 act_mask 中的所有 category,那么这个请求不会被 trace。
b. act_mask 必须包含以下 category 中的一个(__blk_add_trace()),否则将 trace 不到任何东西。
:::

what |= ddir_act[rw & WRITE];
what |= MASK_TC_BIT(rw, SYNC);
what |= MASK_TC_BIT(rw, RAHEAD);
what |= MASK_TC_BIT(rw, META);
what |= MASK_TC_BIT(rw, DISCARD);
what |= MASK_TC_BIT(rw, FLUSH);
what |= MASK_TC_BIT(rw, FUA);

  若有一个"与"关系的 mask,就可以更灵活一些,比如只 trace 写请求的完成状态,可以设置 write,complete 到 mask 中,但目前还没有这个功能。echowrite,complete > act_mask 跟踪的是写请求的全部 action 和各类型请求(不只是写)的 complete 操作。

  • start_lba 和 end_lba 显然,使用两者可以对 disk 或分区的特定区域进行 trace。若未指定start_lba,则是分区的起始地址,若未指定 end_lba,则是分区的结束地址(disk是-1ULL)。注意,它们是在整个 disk 中的绝对地址,而不是相对分区的地址。

  • pid 只对指定 pid 的进程进行跟踪。

Non-tracer tracer

  从 2.6.30 开始,ftrace 还支持几种 Non-tracer tracer,所谓 Non-tracertracer 主要包括以下几种:

Max Stack Tracer
Profiling (branches / unlikely / likely / Functions)
Event tracing

  和传统的 tracer 不同,Non-Tracer Tracer 并不对每个内核函数进行跟踪,而是一种类似逻辑分析仪的模式,即对系统进行采样,但似乎也不完全如此。无论怎样,这些 tracer 的使用方法和前面所介绍的 tracer 的使用稍有不同。下面我将试图描述这些 tracer 的使用方法。

function_profile

  前面我们已经提到过这个 tracer 了,它可以跟踪所有执行到的函数的调用次数,执行时间等,可以给我们呈现一个整体的 top 图,非常有用。用户可以使用如下命令打开该 tracer(系统默认是关闭的):

# echo 1 > function_profile_enabled

  之后拷贝了一个1.68M 的文件到 data 分区,然后查看结果:
ftrace介绍_第7张图片

  结果记录在 trace_stat 目录下的 functionX 中,“X”代表 CPU 号,即执行在此 cpu 号中的所有函数。

  由于默认也是跟踪所有可以跟踪的函数(available_filter_functions),为避免信息量太大,这里还是设置了过滤“jbd2”。

  从输出结果来看,记录的信息包括:函数执行的命中次数,函数执行的总时间、平均时间和方差。方差值越大,离散程度越大,即函数的执行时间波动越大。

  有两个参数可以改变 function_profile 记录的时间类型。这两个节点在options 文件夹里,分别是 graph_time 和 sleep_time:
ftrace介绍_第8张图片
  默认是打开的。

    graph_time:表示所记录的函数执行的时间,包含此函数内所有调用的子函数执行的时间,如果不使能,则只包含本函数执行的时间;

    sleep_time:表示所记录的函数执行的时间,包含此函数在执行期间,被抢占或调度或任务切换所花掉的时间;

  我们还可以看到,输出信息以函数执行的总时间从大到小的顺序进行了sort,更方便我们分析问题。

  另外,function_profile 可以和传统 tracer 同时运行。传统 tracer 的结果记录在 ring buffer(trace/trace_pipe 节点),function_profile 的结果记录在 trace_stat/functionX 节点。

  和 function tracer 不同的是,function_profile 会累积已经跟踪过的函数(命中数,总时间等);而 function tracer 只记录时间戳,如果同一个函数再次命中,则再记录一次新的信息(新的时间戳等)。所以 function tracer 可以跟踪函数执行的先后顺序,而 function_profile 可以统计函数执行的整体情况。

Max Stack Tracer

  此功能可以查看到内核最大的栈调用深度。通用如下的开关来使能此功能:

# echo 1 > /proc/sys/kernel/stack_tracer_enabled

  默认是关闭的。具体的执行流程如下:
ftrace介绍_第9张图片

trace_printk

  内核头文件 include/linux/kernel.h 中描述了 ftrace 提供的工具函数的原型,这些函数包括 trace_printk、tracing_on/tracing_off 等。本节将通过示例模块程序向读者展示如何在代码中使用这些工具函数。

  ftrace 提供了一个用于向 ftrace 跟踪缓冲区输出跟踪信息的工具函数,叫做 trace_printk(),它的使用方式与 printk() 类似。可以通过 trace 文件读取该函数的输出。从头文件 include/linux/kernel.h 中可以看到,在激活配置 CONFIG_TRACING 后,trace_printk()定义为宏:

#define trace_printk(fmt, ...) \
do { \
	char _______STR[] = __stringify((__VA_ARGS__)); \
	if (sizeof(_______STR) > 3) \
		do_trace_printk(fmt, ##__VA_ARGS__); \
	else 
		trace_puts(fmt); \
} while (0)

  下面通过一个示例模块 ftrace_demo 来演示如何使用 trace_printk() 向跟踪缓冲区输出信息,以及如何查看这些信息。这里的示例模块程序中仅提供了初始化和退出函数,这样读者不会因为需要为模块创建必要的访问接口比如设备文件而分散注意力。注意,编译模块时要加入 -pg 选项。

  清单 1. 示例模块 ftrace_demo:

/* ftrace_demo.c */
#include 
#include 
#include 
MODULE_LICENSE("GPL");
static int ftrace_demo_init(void)
{
	trace_printk("Can not see this in trace unless loaded for thesecond time\n");
	return 0;
}

static void ftrace_demo_exit(void)
{
	trace_printk("Module unloading\n");
}
module_init(ftrace_demo_init);
module_exit(ftrace_demo_exit);

  示例模块非常简单,仅仅是在模块初始化函数和退出函数中输出信息。接下来要对模块的运行进行跟踪。

  清单 2. 对模块 ftrace_demo 进行跟踪

[root@linux tracing]# pwd
/sys/kernel/debug/tracing
[root@linux tracing]# echo 0 > tracing_enabled
[root@linux tracing]# echo 1 > /proc/sys/kernel/ftrace_enabled
[root@linux tracing]# echo function_graph > current_tracer
# insmod ftrace_demo.ko
[root@linux tracing]# echo ':mod:ftrace_demo' > set_ftrace_filter
[root@linux tracing]# cat set_ftrace_filter
ftrace_demo_init
ftrace_demo_exit
# rmmod ftrace_demo.ko
[root@linux tracing]# echo 1 > tracing_enabled
# insmod ftrace_demo.ko
# rmmod ftrace_demo.ko
[root@linux tracing]# cat trace
# tracer: function_graph
# #
CPU DURATION FUNCTION CALLS
# | | | | | | |
1) | /* Can not see this in trace unless loaded for
the second time */
0) | /* Module unloading */

  在这个例子中,使用 mod 指令显式指定跟踪模块 ftrace_demo 中的函数,这需要提前加载该模块,否则在写文件 set_ftrace_filter 时会因为找不到该模块报错。

  在 第 一 次 加 载 模 块 时 , 其 初 始 化 函 数 ftrace_demo_init 中 调 用trace_printk 打印的语句跟踪不到,因为 tracing_enabled 还没有打开。之后将其卸载,再激活跟踪,最后重新进行模块 ftrace_demo 的加载与卸载操作。最终可以从文件 trace 中看到模块在初始化和退出时调用 trace_printk() 输出的信息。

  这里仅仅是为了以简单的模块进行演示,故只定义了模块的 init/exit 函数,重复加载模块也只是为了获取初始化函数输出的跟踪信息。实践中,可以在模块的功能函数中加入对 trace_printk 的调用,这样可以记录模块的运作情况,然后对其特定功能进行调试优化。还可以将对 trace_printk() 的调用通宏来控制编译,这样可以在调试时将其开启,在最终发布时将其关闭。

ftrace_dump_on_oops

Having Ftrace configured and enabling ftrace_dump_on_oops in the
kernel boot parameters, or by echoing a "1" into
/proc/sys/kernel/ftrace_dump_on_oops, will enable Ftrace to dump to the
console the entire trace buffer in ASCII format on oops or panic.
# echo 1 > /proc/sys/kernel/ftrace_dump_on_oops

  ftrace_dump_on_oops 默认为0,即是关闭的。

ftrace event

  Tracepoints (Documentation/trace/tracepoints.txt) 使用内核已有的event tracing 框架,而不用创建自定义的内核模块来注册 probe function。并不是所有的 Tracepoints 都可以用 event tracing 系统来 trace 得到。内核开发者必须提供定义 tracing 信息保存到 tracing buffer 的方式以及 tracing信息的打印方式的代码片段。

  可以通过/d/tracing/available_events 节点来查看支持的 event,每个event 的显示格式为 GRP:EVENT,GRP 为 event 所在的子系统,EVENT 则是 event名。在 linux-3.4 上,支持的 events 有 340 个(通过 busybox wc -l/d/tracing/available_events 来查看)

  • 查看属于 ext4 组的所有 event
# cat /d/tracing/available_events | grep ext4
cat /d/tracing/available_events | grep ext4
ext4:ext4_free_inode
ext4:ext4_request_inode
ext4:ext4_allocate_inode
ext4:ext4_evict_inode
ext4:ext4_drop_inode
ext4:ext4_mark_inode_dirty
ext4:ext4_begin_ordered_truncate
ext4:ext4_write_begin
ext4:ext4_da_write_begin
……

  /d/tracing/events/的目录结构如下:

/d/tracing/events
|
|__enable
|__header_event
|__header_page
|__GRP
|
|__enable
|__filter
|__EVENT
|
|__enable
|__filter
|__format
  • 可以通过’set_event’节点或’enable’切换键来启用 event
    (1) 启用指定 event
      echo [GRP:]EVENT > /d/tracing/set_event
      echo [GRP2:]EVENT2 >> /d/tracing/set_event (追加)
      [注意: ‘>>’ 是必要的,否则先会关闭所有 event 再作设置。]
      或
      echo 1 > /d/tracing/events/GRP/EVENT/enable
      echo 1 > /d/tracing/events/GRP2/EVENT2/enable
    (2) 启用某个子系统
      echo 1 > /d/tracing/events/GRP/enable
      或
      echo GRP:* > /d/tracing/set_event
    (3) 启用所有 events
      echo 1 > /d/tracing/events/enable
      或
      echo : > /d/tracing/set_event
    (4) 关闭 event
      echo 0 到 enable 切换键
      或
      使用"!"来取消对 set_event 节点的输入
     /d/tracing/events/GRP/enable 和/d/tracing/events/enable,只能输入 0和 1,有以下几个输出值:
      0 - 此节点影响的所有 event 都被关闭
      1 - 此节点影响的所有 event 都被启用
      X - 部分 event 被启用
      ? - 此节点不影响任何 event

  • Event formats

  每个 trace event 都有一个与之关联的’format’节点,它包含了一个被记录event 的每个域的描述。这信息可用于识别二进制的 trace 流,可以从这里找到在 event filter 中可用到的域的名称。

  它还显示了的 event 被打印成文本时使用的格式字符串,在 profiling 时和event 名及 ID 一起打印。

  每个 event 都定义了与之相关的以’common_'为前缀的域。其它域在相应的event 头文件中使用 TRACE_EVENT 宏定义。

  每个域的格式:

field:field-type field-name; offset:N; size:N;

  例子:

#cat /d/tracing/events/mmc/mmc_blk_erase_start/format
name: mmc_blk_erase_start
ID: 307
format:
		field:unsigned short common_type; offset:0; size:2; signed:0;
		field:unsigned char common_flags; offset:2; size:1; signed:0;
		field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
		field:int common_pid; offset:4; size:4; signed:1;
		field:int common_padding; offset:8; size:4; signed:1;
		field:unsigned int cmd; offset:12; size:4; signed:0;
		field:unsigned int addr; offset:16; size:4; signed:0;
		field:unsigned int size; offset:20; size:4; signed:0;
  • Event filtering

  有时候 trace 打印过多,有很多信息是不想要的,我们可以设置 filter 表达式来过滤出需要的信息,它返回的是一个 boolean 值。在 event 被记录到 tracebuffer 的同时,检测相应的 field 值来是判断否满足 filter 表达式,只有匹配的 event 才会输出,不匹配的被丢弃。默认没有设置 filter 表达式,它不会过滤任何东西。

  filter 表达式的语法:

    可以使用’&&’ 和’||'来连接多个语句

    每个语句是一个简单的比较表达式:

		field-name relational-operator value

    field-name 需要使用 event format 里面的各个 field 名。

    relational-operators,目前分为两种类型:
    数值可使用:

		==, !=, <, <=, >, >=

    字符串可使用:

		==, !=

    清除 event 的 filter

      echo 0 > /d/tracing/events/GRP/EVENT/filter

    清除整个子系统的 filter

      echo 0 > /d/tracing/events/GRP/filter

  有时候我们只需关注某些参数为特定值时的情况,比如要 trace 对某个文件的访问,或者传入参数高于某个值时的调用情况等等。目前,对于字符串,只支持完全匹配。这个有待改进,若能使用通配符来匹配,可以大大增加使用 trace的灵活性。

  比如,要 trace mmc 开始发 trim/discard 命令时,扇区数少于 2048 的情况:

#cd /d/tracing/events/mmc/mmc_blk_erase_start
#echo 'size < 2048' > filter

  若使用了 format 里面没有的名字,设置将不会成功。

#cd /d/tracing/events/mmc/mmc_blk_erase_start
#echo 'count < 2048' > filter
#cat filter
count < 2048
^
parse_error: Field not found

  另外,还可以对整个子系统设置 filter 表达式,对于表达式中有非 common域的情况,只有 event format 中包含这些域,才会进行 filter 表达式设置。

  trace event 的使用方法并不复杂,但它底下包含了多个子系统,总 event数有几百个,我们需要知道各个 event 的作用和代码上下文,配合 fileter 的使用,才能灵活的用作性能分析和调试。

#ls /d/tracing/events
arm-ipi
asoc
binder
block
budget_cooling
compaction
cpu_autohotplug
cpufreq_interactive
enable
ext4
ftrace
gpio
header_event
header_page
irq
jbd2
kmem
mmc
module
napi
net
oom
power
printk
random
rcu
regmap
regulator
rpm
sched
scsi
signal
skb
smp
sock
sync
task
thermal
timer
udp
vmscan
workqueue
writeback

kprobe_events/kprobe_profile

  使用内核的 kprobe 功能可以动态添加 event,只需要按规定格式往/sys/kernel/debug/tracing/kprobe_events 写入一段字符串,即可完成事件的添加。但前提是需要打开 kprobes 选项,才能使用此功能。配置选项如下:
ftrace介绍_第10张图片
ftrace介绍_第11张图片

These events are similar to tracepoint based events. Instead of
Tracepoint,
this is based on kprobes (kprobe and kretprobe). So it can probe wherever
kprobes can probe (this means, all functions body except for __kprobes
functions). Unlike the Tracepoint based event, this can be added and
removed
dynamically, on the fly.
To enable this feature, build your kernel with CONFIG_KPROBE_EVENT=y.
Similar to the events tracer, this doesn't need to be activated via
current_tracer. Instead of that, add probe points via
/sys/kernel/debug/tracing/kprobe_events, and enable it via
/sys/kernel/debug/tracing/events/kprobes/<EVENT>/enabled.

  动态添加 event 的格式如下(linux-4.9/Documentation/trace/kprobetrace.txt)

p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] : Set a probe
r[:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] : Set a return probe
-:[GRP/]EVENT : Clear a probe
GRP : Group name. If omitted, use "kprobes" for it.
EVENT : Event name. If omitted, the event name is generated
based on SYM+offs or MEMADDR.
MOD : Module name which has given SYM.
SYM[+offs] : Symbol+offset where the probe is inserted.
MEMADDR : Address where the probe is inserted.
FETCHARGS : Arguments. Each probe can have up to 128 args.
%REG : Fetch register REG
@ADDR : Fetch memory at ADDR (ADDR should be in kernel)
@SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data
symbol)
$stackN : Fetch Nth entry of stack (N >= 0)
$stack : Fetch stack address.
$retval : Fetch return value.(*)
+|-offs(FETCHARG) : Fetch memory at FETCHARG +|- offs address.(**)
NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
(u8/u16/u32/u64/s8/s16/s32/s64), "string" and bitfield
are supported.
(*) only for return probe.
(**) this is useful for fetching a field of data structures.

  这个格式的说明太含蓄了,结合linux-4.9/Documentation/trace/kprobetrace.txt 中的一个动态添加 event 的示例来说明

echo 'p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' >
/sys/kernel/debug/tracing/kprobe_events

  说明文档对此示例的说明:

This sets a kprobe on the top of do_sys_open() function with recording
1st to 4th arguments as "myprobe" event. Note, which register/stack entry
is assigned to each function argument depends on arch-specific ABI.

  在 do_sys_open()函数首部设置一个 kprobe,插入一个“myprobe”事件来记录第 1~4 个参数。注意,函数的各个参数相应的寄存器或栈 entry 按照各个架
构的 ABI 来指定。

现在我们把这个传入 kprobe_events 节点的字符串分解来看:

  p:myprobe对应于 p[:[GRP/]EVENT],这里没有指定组名(GRP),默认使用 kprobes 作为组名,也就是等价于:

  p:kprobes/myprobe实际上事件名(EVENT)也可以不指定,即直接用一个 p 就行,EVENT 将会按照(SYM+offs 或者 MEMADDR)来命名。

  可以设置“p、r”两种类型的事件,“p”事件是放在函数的首部或一般位置,“r”事件放在函数返回处。

  do_sys_open对应于[MOD:]SYM[+offs]|MEMADDR,这里只指定了要跟踪的符号,对于“p”类型事件,表示在函数入口处添加。

  MOD 指定了目标 symbol 所在模块,SYM+offs 和 MEMADDR 都可以指定 event添加的位置,这里的 SYM 也可以当作是一个内存地址,offs 是相对于这个 SYM的地址偏移。

  dfd=%ax filename=%dx flags=%cx mode=+4($stack)

  对应于 FETCHARGS,说明文档上的说明要用正则表达式的方式来看,并不直观。重新解析一下 FETCHARGS 的格式:

    [NAME=]%REG|@ADDR|@SYM[+|-offs]| s t a c k N ∣ stackN| stackNstack|$retval[:TYPE] …

    或

    [NAME=]+|-offs(%REG|@ADDR|@SYM[+|-offs]| s t a c k N ∣ stackN| stackNstack|$retval)[:TYPE] …

    %REG : 取寄存器 REG 中的值(在 arm 架构中,函数的第 1~4 个参数放在寄存器 r0~r3 中,第四个之后的参数放在栈中);

    @ADDR : 取内存的 ADDR 地址中的值(ADDR 必须是 kernel 地址);

    @SYM[+|-offs] : 在 SYM +|- offs 位置的内存中取数据 (SYM 必须是一个data symbol,只有这样 SYM 才能转换成内存地址);

    $stackN : 取栈中第 Nth 个入口中的值 (N >= 0);

    $stack : 取栈地址;

    $retval : 取函数返回值,只能用于“r”类型事件;

    +|-offs(FETCHARG) : 从 FETCHARG +|- offs 位置取数据,可用于取数据结构中的某个域的值。可以嵌套,如+0(+0($stack))。+0 等价于@;

		dfd=%ax 对应于 NAME=%REG,表示在 event 相应的结构体中增加 dfd 字段,取值为寄存器 ax 中的值;

    +4($stack)表示从(栈地址 + 4)位置的内存中取数据;

  取值参数可划分为两类,一是取相应的值:%REG、 s t a c k 、 stack、 stackretval,二是在指定的位置中取数据:@ADDR、@SYM[+|-offs]、$stackN、+|-offs(FETCHARG)。要注意此间的区别,如"string"类型必须要使用后一类。
示例中没有使用 TYPE,目前支持的 TYPE 有,basic types(u8/u16/u32/u64/s8/s16/s32/s64)、“string” 和 bitfield。从后面的实验来看,没有指定 TYPE,则默认使用 u32 类型。

  basic types:

  从字面就可以看出是什么意思了,需要留意的是’s’是用 10 进制打印,'u’是用 16 进制打印。

  “string”:从指定的位置开始往后查找到’\0’为止,若超过 1 个 page(pageout)还没找到’\0’,那么直接存储 NULL。

  bitfield:有三个参数 bit-width、offset 和 container-size (usually 32).相应语法:

	b@/

  直接运行此示例,并不能添加成功,查看 dmesg,发现是指定的第 0 个参数就有误(为了确认-22 的错误是不是添加 event 时出现的,又重新添加了一次)

# dmesg | busybox tail -10
dmesg | busybox tail -10
<6>[ 8405.662181] healthd: battery l=95 v=4106 t=30.0 h=2 st=2 c=317 chg=u
<6>[ 8465.662195] healthd: battery l=95 v=4108 t=30.0 h=2 st=2 c=319 chg=u
<6>[ 8525.662172] healthd: battery l=95 v=4109 t=30.0 h=2 st=2 c=319 chg=u
<6>[ 8585.662181] healthd: battery l=95 v=4110 t=30.0 h=2 st=2 c=319 chg=u
<6>[ 8645.662190] healthd: battery l=95 v=4111 t=30.0 h=2 st=2 c=318 chg=u
<6>[ 8705.662187] healthd: battery l=95 v=4111 t=30.0 h=2 st=2 c=317 chg=u
<6>[ 8765.662093] healthd: battery l=95 v=4112 t=30.0 h=2 st=2 c=319 chg=u
<6>[ 8793.847914] Parse error at argument[0]. (-22)
<6>[ 8825.661289] healthd: battery l=95 v=4112 t=30.0 h=2 st=2 c=320 chg=u
<6>[ 8827.871598] Parse error at argument[0]. (-22)

  但这个“argument[0]”指的是哪个?减少一些参数,保留到“dfd=%ax”,如下,问题依旧。(/d 是/sys/kernel/debug 目录的软链接)

echo 'p:myprobe do_sys_open dfd=%ax' > /d/tracing/kprobe_events

  把“dfd=%ax”也去掉,没有返回错误。

echo 'p:myprobe do_sys_open' > /d/tracing/kprobe_events

  查看/d/tracing/events 目录,此时会出现一个名为 kprobes 的目录,

# ls -l /d/tracing/events
……
drwxr-xr-x root root 1970-01-01 08:00 kmem
drwxr-xr-x root root 1970-01-26 05:24 kprobes
drwxr-xr-x root root 1970-01-01 08:00 mmc
……

  目录中包含有新添加的 event,“myprobe”,至此可以确认 event 添加成功了。

# ls -l /d/tracing/events/kprobes
-rw-r--r-- root root 0 1970-01-26 05:24 enable
-rw-r--r-- root root 0 1970-01-26 05:24 filter
drwxr-xr-x root root 1970-01-26 05:24 myprobe

c如一般的 static event 一样,myprobe 目录下有 enable、filter、format、id 四个节点,使用方法也和 static event 相同。

# ls -l /d/tracing/events/kprobes
-rw-r--r-- root root 0 1970-01-26 05:24 enable
-rw-r--r-- root root 0 1970-01-26 05:24 filter
drwxr-xr-x root root 1970-01-26 05:24 myprobe

  看看 myprobe 的打印格式,

#cat /d/tracing/events/kprobes/myprobe/format
name: myprobe
ID: 381
format:
		field:unsigned short common_type; offset:0; size:2; signed:0;
		field:unsigned char common_flags; offset:2; size:1; signed:0;
		field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
		field:int common_pid; offset:4; size:4; signed:1;
		field:int common_padding; offset:8; size:4; signed:1;
		field:unsigned long __probe_ip; offset:12; size:4; signed:0;
print fmt: "(%lx)", REC->__probe_ip

  事件添加成功了,但是没有其它信息,我们跟踪 do_sys_open,还希望知道文件名、标志、打开模式等信息,再看回示例,示例的格式是我们想要的,但是为什么添加不成功呢?

  按照示例的说明,函数参数的位置需要根据架构相应的 ABI 来指定。

  在 arm 架构中,函数的第 1~4 个参数放在寄存器 r0~r3 中,第四个之后的参数放在栈中。

  因此作下调整(注意"r"需要小写),

echo 'p:myprobe do_sys_open dfd=%r0 filename=%r1 flags=%r2 mode=%r3' >
/d/tracing/kprobe_events

  event 添加成功,format 发生了以下变化,event 相应的结构体增加了 dfd、filename、flags、mode 等字段,类型都是默认的 32 位无符号数(u32),printffmt 也把这些字段打印出来了。

#cat /d/tracing/events/kprobes/myprobe/format
name: myprobe
ID: 382
format:
		field:unsigned short common_type; offset:0; size:2; signed:0;
		field:unsigned char common_flags; offset:2; size:1; signed:0;
		field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
		field:int common_pid; offset:4; size:4; signed:1;
		field:int common_padding; offset:8; size:4; signed:1;
		field:unsigned long __probe_ip; offset:12; size:4; signed:0;
		field:u32 dfd; offset:16; size:4; signed:0;
		field:u32 filename; offset:20; size:4; signed:0;
		field:u32 flags; offset:24; size:4; signed:0;
		field:u32 mode; offset:28; size:4; signed:0;
print fmt: "(%lx) dfd=%lx filename=%lx flags=%lx mode=%lx", REC->__probe_ip, REC->dfd,
REC->filename, REC->flags, REC->mode

  这样一个 event 就算是添加完成了,但还是有缺陷,这里的 filename 打印的其实是文件名字符串的地址,而我们想知道的是这个字符串。说明文档里有指明可以支持"string"格式,修改传入的字符串:

echo 'p:myprobe do_sys_open dfd=%r0 filename=%r1:string flags=%r2 mode=%r3' >
/d/tracing/kprobe_events

  但 event 添加失败,

<4>[19942.014018] CPU3: Booted secondary processor
<4>[19942.525642] CPU4: Booted secondary processor
<6>[19942.526251] CPU Budget:update CPU 4 cpufreq max to 1608000 min to 600000
<5>[19946.000928] CPU3: shutdown
<5>[19946.500756] CPU2: shutdown
<5>[19947.000720] CPU1: shutdown
<5>[19947.500839] CPU4: shutdown
<6>[19947.737994] string type has no corresponding fetch method.
<6>[19947.738030] Parse error at argument[1]. (-22)
<6>[19948.945616] healthd: battery l=99 v=4205 t=30.0 h=2 st=2 c=314 chg=u

  在一个相关问题的回复中找到了答案 (Re: kprobe string, https://www.mail-archive.com/[email protected]/msg01633.html)

  主要是"string"格式不能跟在直接取值的参数之后,作如下修改,则可添加
成功:

echo 'p:myprobe do_sys_open dfd=%r0 filename=+0(%r1):string flags=%r2 mode=%r3' >
/d/tracing/kprobe_events

  时间对应的 format 也发生了相应的变化:

#cat /d/tracing/events/kprobes/myprobe/format
name: myprobe
ID: 384
format:
field:unsigned short common_type; offset:0; size:2; signed:0;
field:unsigned char common_flags; offset:2; size:1; signed:0;
field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
field:int common_pid; offset:4; size:4; signed:1;
field:int common_padding; offset:8; size:4; signed:1;
field:unsigned long __probe_ip; offset:12; size:4; signed:0;
field:u32 dfd; offset:16; size:4; signed:0;
field:__data_loc char[] filename; offset:20; size:4; signed:1;
field:u32 flags; offset:24; size:4; signed:0;
field:u32 mode; offset:28; size:4; signed:0;
print fmt: "(%lx) dfd=%lx filename=\"%s\" flags=%lx mode=%lx", REC->__probe_ip, REC->dfd,
__get_str(filename), REC->flags, REC->mode

  event 添加成功后,还需要开始进行 trace

# echo 1 > /d/tracing/events/kprobes/myprobe/enable
# echo 1 > /d/tracing/tracing_on

  得到的结果如下:

# cat trace
cat trace
# tracer: nop
# #
entries-in-buffer/entries-written: 82/82 #P:3
# #
_-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
sh-4818 [000] d... 76398.194486: myprobe: (do_sys_open+0x0/0x1d4) dfd=ffffff9c
filename="/acct/uid/0/tasks" flags=20042 mode=1b6
<...>-1540 [000] d... 76399.768099: myprobe: (do_sys_open+0x0/0x1d4) dfd=ffffff9c
filename="/sys/class/power_supply/battery/present" flags=20000 mode=0
<...>-1540 [000] d... 76399.768392: myprobe: (do_sys_open+0x0/0x1d4) dfd=ffffff9c
filename="/sys/class/power_supply/battery/capacity" flags=20000 mode=0
<...>-1540 [000] d... 76399.768566: myprobe: (do_sys_open+0x0/0x1d4) dfd=ffffff9c
filename="/sys/class/power_supply/battery/voltage_now" flags=20000 mode=0

  动态添加 event 功能,在我们公司的方案中默认是关闭的(即没有使能kprobes 选项)。要使用此功能可能要重新配置、编译并烧写固件。

  此外,在动态添加 event 功能中,并没有看到“能写”或“能添加自定义代码”的功能。

  我们更多的时候,希望能通过在指点的函数内动态添加探测点来记录我们需要的信息,如统计写文件的数据量等。这可以通过另一个工具 kprobes 来完成,而 ftrace 只是利用了 kprobes 的一小部分功能。

你可能感兴趣的:(linux)