使用 ftrace 调试 Linux 内核,第 1 部分
简介: ftrace 是 Linux 内核中提供的一种调试工具。使用 ftrace 可以对内核中发生的事情进行跟踪,这在调试 bug 或者分析内核时非常有用。本系列文章对 ftrace 进行了介绍,分为三部分。本文是第一部分,介绍了内核相关的编译选项、用户态访问 ftrace 的接口、ftrace 的数据文件,并对 ftrace 提供的跟踪器的用途进行了介绍,以使读者更好的了解和使用该工具。
ftrace 是内建于 Linux 内核的跟踪工具,从 2.6.27 开始加入主流内核。使用 ftrace 可以调试或者分析内核中发生的事情。ftrace 提供了不同的跟踪器,以用于不同的场合,比如跟踪内核函数调用、对上下文切换进行跟踪、查看中断被关闭的时长、跟踪内核态中的延迟以及性能问题等。系统开发人员可以使用 ftrace 对内核进行跟踪调试,以找到内核中出现的问题的根源,方便对其进行修复。另外,对内核感兴趣的读者还可以通过 ftrace 来观察内核中发生的活动,了解内核的工作机制。
让内核支持 ftrace
使用 ftrace ,首先要将其编译进内核。内核源码目录下的 kernel/trace/Makefile 文件给出了 ftrace 相关的编译选项。
清单 1. ftrace 相关的配置选项列表
CONFIG_FUNCTION_TRACER
CONFIG_FUNCTION_GRAPH_TRACER
CONFIG_CONTEXT_SWITCH_TRACER
CONFIG_NOP_TRACER
CONFIG_SCHED_TRACER
…
ftrace 相关的配置选项比较多,针对不同的跟踪器有各自对应的配置选项。不同的选项有不同的依赖关系,内核源码目录下的 kernel/trace/Kconfig 文件描述了这些依赖关系。读者可以参考 Makefile 文件和 Konfig 文件,然后选中自己所需要的跟踪器。
通常在配置内核时,使用 make menuconfig 会更直观一些。以 2.6.33.1 版本的内核为例,要将 ftrace 编译进内核,可以选中 Kernel hacking (图 1 )下的 Tracers 菜单项(图 2 )。
图 1. Kernel hacking
图 2. Tracers
进入 Tracers 菜单下,可以看到内核支持的跟踪器列表。如图 3 所示,这里选中了所有的跟踪器,读者可以根据自己的需要选中特定的跟踪器。
图 3. 内核支持的跟踪器列表
这里要注意,如果是在 32 位 x86 机器上,编译时不要选中 General setup 菜单项(图 4 )下的 Optimize for size 选项(图 5 ),否则就无法看到图 3 中的 Kernel Function Graph Tracer 选项。这是因为在 Konfig 文件中,针对 32 位 x86 机器,表项 FUNCTION_GRAPH_TRACER 有一个特殊的依赖条件:
depends on !X86_32 || !CC_OPTIMIZE_FOR_SIZE
图 4. General setup
图 5. Optimize for size
ftrace 通过 debugfs 向用户态提供了访问接口,所以还需要将 debugfs 编译进内核。激活对 debugfs 的支持,可以直接编辑内核配置文件 .config ,设置 CONFIG_DEBUG_FS=y ;或者在 make menuconfig 时到 Kernel hacking 菜单下选中对 debugfs 文件系统的支持,如图 6 所示。
图 6. debugfs 编译选项
配置完成后,编译安装新内核,然后启动到新内核。 注意,激活 ftrace 支持后,编译内核时会使用编译器的 -pg 选项,这是在 kernel/trace/Makefile 文件中定义的,如清单 2 所示。
清单 2. 激活编译选项 -pg
ifdef CONFIG_FUNCTION_TRACER
ORIG_CFLAGS := $(KBUILD_CFLAGS)
KBUILD_CFLAGS = $(subst -pg,,$(ORIG_CFLAGS))
…
endif
…
使用 -pg 选项会在编译得到的内核映像中加入大量的调试信息。一般情况下,只是在开发测试阶段激活 ftrace 支持,以调试内核,修复 bug 。最终用于发行版的内核则会关闭 -pg 选项,也就无法使用 ftrace。
通过 debugfs 访问 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 的控制和输出文件,如图 7 所示。根据编译内核时针对 ftrace 的设定不同,该目录下实际显示的文件和目录与这里也会不同。
图 7. tracing 目录下的文件
回页首
ftrace 的数据文件
/sys/kernel/debug/trace 目录下文件和目录比较多,有些是各种跟踪器共享使用的,有些是特定于某个跟踪器使用的。在操作这些数据文件时,通常使用 echo 命令来修改其值,也可以在程序中通过文件读写相关的函数来操作这些文件的值。下面只对部分文件进行描述,读者可以参考内核源码包中 Documentation/trace 目录下的文档以及 kernel/trace 下的源文件以了解其余文件的用途。
- README文件提供了一个简短的使用说明,展示了 ftrace 的操作命令序列。可以通过 cat 命令查看该文件以了解概要的操作流程。
- current_tracer用于设置或显示当前使用的跟踪器;使用 echo 将跟踪器名字写入该文件可以切换到不同的跟踪器。系统启动后,其缺省值为 nop ,即不做任何跟踪操作。在执行完一段跟踪任务后,可以通过向该文件写入 nop 来重置跟踪器。
- available_tracers记录了当前编译进内核的跟踪器的列表,可以通过 cat 查看其内容;其包含的跟踪器与图 3 中所激活的选项是对应的。写 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 跟踪器
ftrace 当前包含多个跟踪器,用于跟踪不同类型的信息,比如进程调度、中断关闭等。可以查看文件 available_tracers 获取内核当前支持的跟踪器列表。在编译内核时,也可以看到内核支持的跟踪器对应的选项,如之前图 3 所示。
- 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 的配置和使用进行了介绍。本文是其中的第一部分,介绍了 ftrace 的编译配置、用户态访问 ftrace 的接口和 ftrace 的数据文件,并对 ftrace 所提供的部分跟踪器的用途进行了描述。由于篇幅的限制,本文没有对 ftrace 的具体使用和如何在代码中与 ftrace 进行交互进行描述,这些内容将分别在本系列文章的后续篇章中给出。
使用 ftrace 调试 Linux 内核,第 2 部分
http://www.voidcn.com/article/p-fvhdtmfa-bkz.html
简介: ftrace 是 Linux 内核中提供的一种调试工具。使用 ftrace 可以对内核中发生的事情进行跟踪,这在调试 bug 或者分析内核时非常有用。本系列文章对 ftrace 进行了介绍,分为三部分。本文是第二部分,通过示例介绍了如何使用 ftrace 中的跟踪器,并对不同格式的跟踪信息进行了描述,同时还对如何使用 ftrace 调试指定内核模块进行了介绍。通过对本文的学习,读者可以了解如何在实践中使用 ftrace 。
ftrace 操作概述
使用 ftrace 提供的跟踪器来调试或者分析内核时需要如下操作:
- 切换到目录 /sys/kernel/debug/tracing/ 下
- 查看 available_tracers 文件,获取当前内核支持的跟踪器列表
- 关闭 ftrace 跟踪,即将 0 写入文件 tracing_enabled
- 激活 ftrace_enabled ,否则 function 跟踪器的行为类似于 nop;另外,激活该选项还可以让一些跟踪器比如 irqsoff 获取更丰富的信息。建议使用 ftrace 时将其激活。要激活 ftrace_enabled ,可以通过 proc 文件系统接口来设置:
echo 1 > /proc/sys/kernel/ftrace_enabled
- 将所选择的跟踪器的名字写入文件 current_tracer
- 将要跟踪的函数写入文件 set_ftrace_filter ,将不希望跟踪的函数写入文件 set_ftrace_notrace。通常直接操作文件 set_ftrace_filter 就可以了
- 激活 ftrace 跟踪,即将 1 写入文件 tracing_enabled。还要确保文件 tracing_on 的值也为 1,该文件可以控制跟踪的暂停
- 如果是对应用程序进行分析的话,启动应用程序的执行,ftrace 会跟踪应用程序运行期间内核的运作情况
- 通过将 0 写入文件 tracing_on 来暂停跟踪信息的记录,此时跟踪器还在跟踪内核的运行,只是不再向文件 trace 中写入跟踪信息;或者将 0 写入文件 tracing_enabled 来关闭跟踪
- 查看文件 trace 获取跟踪信息,对内核的运行进行分析调试
接下来将对跟踪器的使用以及跟踪信息的格式通过实例加以讲解。
fucntion 跟踪器
function 跟踪器可以跟踪内核函数的调用情况,可用于调试或者分析 bug ,还可用于了解和观察 Linux 内核的执行过程。清单 1 给出了使用 function 跟踪器的示例。
清单 1. function 跟踪器使用示例
[cpp] view plain copy print ?
- <PRE
class
=cpp name=
"code"
- [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 > current_tracer
- [root@linux tracing]# echo 1 > tracing_on
- [root@linux tracing]# echo 1 > tracing_enabled
# 让内核运行一段时间,这样 ftrace 可以收集一些跟踪信息,之后再停止跟踪
- [root@linux tracing]# echo 0 > tracing_enabled
- [root@linux tracing]# cat trace | head -10
# tracer: function
1.
#
1.
# TASK-PID CPU# TIMESTAMP FUNCTION
1.
# | | | | |
- <idle>-0 [000] 20654.426521: _raw_spin_lock <-scheduler_tick
- <idle>-0 [000] 20654.426522: task_tick_idle <-scheduler_tick
- <idle>-0 [000] 20654.426522: cpumask_weight <-scheduler_tick
- <idle>-0 [000] 20654.426523: cpumask_weight <-scheduler_tick
- <idle>-0 [000] 20654.426523: run_posix_cpu_timers <-update_process_times
- <idle>-0 [000] 20654.426524: hrtimer_forward <-tick_sched_timer
- </PRE><BR>
- <BR>
- <PRE></PRE>
- <P>trace 文件给出的信息格式很清晰。首先,字段“tracer:”给出了当前所使用的跟踪器的名字,这里为 function 跟踪器。然后是跟踪信息记录的格式,TASK 字段对应任务的名字,PID 字段则给出了任务的进程 ID,字段 CPU\# 表示运行被跟踪函数的 CPU 号,这里可以看到 idle 进程运行在 0 号 CPU 上,其进程 ID 是 0 ;字段 TIMESTAMP 是时间戳,其格式为“<secs>.<usecs><CODE>”</CODE>,表示执行该函数时对应的时间戳;FUNCTION
- 一列则给出了被跟踪的函数,函数的调用者通过符号 “<-” 标明,这样可以观察到函数的调用关系。</P>
- <DIV
class
=ibm-alternate-rule>
- <HR>
- </DIV>
- <P
class
"ibm-ind-link ibm-back-to-top"
</P>
- <P><A name=major3><SPAN
class
=atitle>function_graph 跟踪器</SPAN></A></P>
- <P>在 function 跟踪器给出的信息中,可以通过 FUNCTION 列中的符号 “<-” 来查看函数调用关系,但是由于中间会混合不同函数的调用,导致看起来非常混乱,十分不方便。function_graph 跟踪器则可以提供类似 C 代码的函数调用关系信息。通过写文件 set_graph_function 可以显示指定要生成调用关系的函数,缺省会对所有可跟踪的内核函数生成函数调用关系图。清单 2 给出了使用 function_grapch 跟踪器的示例,示例中将内核函数 __do_fault 作为观察对象,该函数在内核运作过程中会被频繁调用。</P>
- <A name=listing2><STRONG>清单 2. function_graph 跟踪器使用示例</STRONG></A><SPAN style=
"FONT-SIZE: 16px"
</SPAN>
- <P>在文件 trace 的输出信息中,首先给出的也是当前跟踪器的名字,这里是 function_graph 。接下来是详细的跟踪信息,可以看到,函数的调用关系以类似 C 代码的形式组织。</P>
- <P>CPU 字段给出了执行函数的 CPU 号,本例中都为 1 号 CPU。DURATION 字段给出了函数执行的时间长度,以 us 为单位。FUNCTION CALLS 则给出了调用的函数,并显示了调用流程。注意,对于不调用其它函数的函数,其对应行以“;”结尾,而且对应的 DURATION 字段给出其运行时长;对于调用其它函数的函数,则在其“}”对应行给出了运行时长,该时间是一个累加值,包括了其内部调用的函数的执行时长。DURATION 字段给出的时长并不是精确的,它还包含了执行 ftrace 自身的代码所耗费的时间,所以示例中将内部函数时长累加得到的结果会与对应的外围调用函数的执行时长并不一致;不过通过该字段还是可以大致了解函数在时间上的运行开销的。清单
- 2 中最后通过 echo 命令重置了文件 set_graph_function 。</P>
- <DIV
class
=ibm-alternate-rule>
- <HR>
- </DIV>
- <P
class
"ibm-ind-link ibm-back-to-top"
</P>
- <P><A name=major4><SPAN
class
=atitle>sched_switch 跟踪器</SPAN></A></P>
- <P>sched_switch 跟踪器可以对进程的调度切换以及之间的唤醒操作进行跟踪,如清单 3 所示。</P>
- <A name=listing3><STRONG>清单 3. sched_switch 跟踪器使用示例</STRONG></A><SPAN style=
"FONT-SIZE: 16px"
</SPAN><PRE
class
=cpp name=
"code"
- [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 sched_switch > current_tracer
- [root@linux tracing]# echo 1 > tracing_on
- [root@linux tracing]# echo 1 > tracing_enabled
# 让内核运行一段时间,这样 ftrace 可以收集一些跟踪信息,之后再停止跟踪
- [root@linux tracing]# echo 0 > tracing_enabled
- [root@linux tracing]# cat trace | head -10
# tracer: sched_switch
1.
#
1.
# TASK-PID CPU# TIMESTAMP FUNCTION
1.
# | | | | |
- bash-1408 [000] 26208.816058: 1408:120:S + [000] 1408:120:S bash
- bash-1408 [000] 26208.816070: 1408:120:S + [000] 1408:120:S bash
- bash-1408 [000] 26208.816921: 1408:120:R + [000] 9:120:R events/0
- bash-1408 [000] 26208.816939: 1408:120:R ==> [000] 9:120:R events/0
- events/0-9 [000] 26208.817081: 9:120:R + [000] 1377:120:R gnome-terminal
- events/0-9 [000] 26208.817088: 9:120:S ==> [000] 1377:120:R gnome-terminal
- </PRE><BR>
- <BR>
- <P>在 sched_swich 跟踪器获取的跟踪信息中记录了进程间的唤醒操作和调度切换信息,可以通过符号‘ + ’和‘ ==> ’区分;唤醒操作记录给出了当前进程唤醒运行的进程,进程调度切换记录中显示了接替当前进程运行的后续进程。</P>
- <P>描述进程状态的格式为“Task-PID:Priority:Task-State”。以示例跟踪信息中的第一条跟踪记录为例,可以看到进程 bash 的 PID 为 1408 ,其对应的内核态优先级为 120 ,当前状态为 S(可中断睡眠状态),当前 bash 并没有唤醒其它进程;从第 3 条记录可以看到,进程 bash 将进程 events/0 唤醒,而在第 4 条记录中发生了进程调度,进程 bash 切换到进程 events/0 执行。</P>
- <P>在 Linux 内核中,进程的状态在内核头文件 include/linux/sched.h 中定义,包括可运行状态 TASK_RUNNING(对应跟踪信息中的符号‘ R ’)、可中断阻塞状态 TASK_INTERRUPTIBLE(对应跟踪信息中的符号‘ S ’)等。同时该头文件也定义了用户态进程所使用的优先级的范围,最小值为 MAX_USER_RT_PRIO(值为 100 ),最大值为 MAX_PRIO – 1(对应值为 139 ),缺省为 DEFAULT_PRIO(值为 120 );在本例中,进程优先级都是缺省值
- 120 。</P>
- <DIV
class
=ibm-alternate-rule>
- <HR>
- </DIV>
- <P
class
"ibm-ind-link ibm-back-to-top"
<BR>
- </P>
- <P><A name=major5><SPAN
class
=atitle>irqsoff 跟踪器</SPAN></A></P>
- <P>当关闭中断时,CPU 会延迟对设备的状态变化做出反应,有时候这样做会对系统性能造成比较大的影响。irqsoff 跟踪器可以对中断被关闭的状况进行跟踪,有助于发现导致较大延迟的代码;当出现最大延迟时,跟踪器会记录导致延迟的跟踪信息,文件 tracing_max_latency 则记录中断被关闭的最大延时。</P>
- <BR>
- <A name=listing4><STRONG>清单 4. irqsoff 跟踪器使用示例</STRONG></A><BR>
- <SPAN style=
"FONT-FAMILY: monospace"
<SPAN style=
"WHITE-SPACE: pre"
</SPAN></SPAN><PRE
class
=cpp name=
"code"
- [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 irqsoff > current_tracer
- [root@linux tracing]# echo 1 > tracing_on
- [root@linux tracing]# echo 1 > tracing_enabled
# 让内核运行一段时间,这样 ftrace 可以收集一些跟踪信息,之后再停止跟踪
- [root@linux tracing]# echo 0 > tracing_enabled
- [root@linux tracing]# cat trace | head -35
# tracer: irqsoff
1.
#
1.
# irqsoff latency trace v1.1.5 on 2.6.33.1
1.
# ——————————————————————–
1.
# latency: 34380 us, #6/6, CPU#1 | (M:desktop VP:0, KP:0, SP:0 HP:0 #P:2)
1.
# —————–
1.
# | task: -0 (uid:0 nice:0 policy:0 rt_prio:0)
1.
# —————–
1.
# => started at: reschedule_interrupt
1.
# => ended at: restore_all_notrace
1.
#
1.
#
1.
# _——=> CPU#
1.
# / _—–=> irqs-off
1.
# | / _—-=> need-resched
1.
# || / _—=> hardirq/softirq
1.
# ||| / –=> preempt-depth
1.
# |||| /–=> lock-depth
1.
# |||||/ delay
1.
# cmd pid |||||| time | caller
1.
# \ / |||||| \ | /
- <idle>-0 1dN… 4285us!: trace_hardirqs_off_thunk <-reschedule_interrupt
- <idle>-0 1dN… 34373us+: smp_reschedule_interrupt <-reschedule_interrupt
- <idle>-0 1dN… 34375us+: native_apic_mem_write <-smp_reschedule_interrupt
- <idle>-0 1dN… 34380us+: trace_hardirqs_on_thunk <-restore_all_notrace
- <idle>-0 1dN… 34384us : trace_hardirqs_on_caller <-restore_all_notrace
- <idle>-0 1dN… 34384us : <stack trace>
- => trace_hardirqs_on_thunk
- [root@linux tracing]# cat tracing_max_latency
- 34380 </PRE><BR>
- <BR>
- <P>从清单 4 中的输出信息中,可以看到当前 irqsoff 延迟跟踪器的版本信息。接下来是最大延迟时间,以 us 为单位,本例中为 34380 us ,查看文件 tracing_max_latency 也可以获取该值。从“task:”字段可以知道延迟发生时正在运行的进程为 idle(其 pid 为 0 )。中断的关闭操作是在函数 reschedule_interrupt 中进行的,由“=> started at:”标识,函数 restore_all_ontrace 重新激活了中断,由“=> ended at:”标识;中断关闭的最大延迟发生在函数
- trace_hardirqs_on_thunk 中,这个可以从最大延迟时间所在的记录项看到,也可以从延迟记录信息中最后的“=>”标识所对应的记录行知道这一点。</P>
- <P>在输出信息中,irqs-off、need_resched 等字段对应于进程结构
struct
task_struct 的字段或者状态标志,可以从头文件 arch/<platform>/include/asm/thread_info.h 中查看进程支持的状态标志,include/linux/sched.h 则给出了结构
struct
task_struct 的定义。其中,irqs-off 字段显示是否中断被禁止,为‘ d ’表示中断被禁止;need_resched 字段显示为‘ N ’表示设置了进程状态标志
- TIF_NEED_RESCHED。hardirq/softirq 字段表示当前是否发生硬件中断 / 软中断;preempt-depth 表示是否禁止进程抢占,例如在持有自旋锁的情况下进程是不能被抢占的,本例中进程 idle 是可以被其它进程抢占的。结构
struct
task_struct 中的 lock_depth 字段是与大内核锁相关的,而最近的内核开发工作中有一部分是与移除大内核锁相关的,这里对该字段不再加以描述。</P>
- <P>另外,还有用于跟踪禁止进程抢占的跟踪器 preemptoff 和跟踪中断 / 抢占禁止的跟踪器 preemptirqsoff,它们的使用方式与输出信息格式与 irqsoff 跟踪器类似,这里不再赘述。</P>
- <DIV
class
=ibm-alternate-rule>
- <HR>
- </DIV>
- <P
class
"ibm-ind-link ibm-back-to-top"
<BR>
- </P>
- <P><A name=major6><SPAN
class
=atitle>跟踪指定模块中的函数</SPAN></A></P>
- <P>前面提过,通过文件 set_ftrace_filter 可以指定要跟踪的函数,缺省目标为所有可跟踪的内核函数;可以将感兴趣的函数通过 echo 写入该文件。为了方便使用,set_ftrace_filter 文件还支持简单格式的通配符。</P>
- <UL>
- <LI><STRONG>begin\*</STRONG>选择所有名字以 begin 字串开头的函数 <LI><STRONG>\*middle\*</STRONG>选择所有名字中包含 middle 字串的函数 <LI><STRONG>\*end</STRONG>选择所有名字以 end 字串结尾的函数 </LI></UL>
- <P>需要注意的是,这三种形式不能组合使用,比如“begin\*middle\*end”实际的效果与“begin”相同。另外,使用通配符表达式时,需要用单引号将其括起来,如果使用双引号,shell 可能会对字符‘ \* ’进行扩展,这样最终跟踪的对象可能与目标函数不一样。</P>
- <P>通过该文件还可以指定属于特定模块的函数,这要用到 mod 指令。指定模块的格式为:</P>
- <TABLE border=0 cellSpacing=0 cellPadding=0 width=
"100%"
sizcache=
"0"
sizset=
"6"
<TBODY sizcache=
"0"
sizset=
"6"
<TR sizcache=
"0"
sizset=
"6"
<TD
class
=code-outline sizcache=
"0"
sizset=
"6"
<PRE
class
=displaycode name=
"code"
echo
':mod:[module_name]'
> set_ftrace_filter
- </PRE></TD></TR></TBODY></TABLE>
- <BR>
- <P>下面给出了一个指定跟踪模块 ipv6 中的函数的例子。可以看到,指定跟踪模块 ipv6 中的函数会将文件 set_ftrace_filter 的内容设置为只包含该模块中的函数。</P>
- <BR>
- <A name=listing5><STRONG>清单 5. 指定跟踪 ipv6 模块中的函数</STRONG></A><BR>
- <SPAN style=
"FONT-SIZE: 16px"
</SPAN><PRE
class
=cpp name=
"code"
- [root@linux tracing]# pwd
- /sys/kernel/debug/tracing
- [root@linux tracing]# echo
':mod:ipv6'
> set_ftrace_filter
- [root@linux tracing]# cat set_ftrace_filter | head -5
- ipv6_opt_accepted
- inet6_net_exit
- ipv6_gro_complete
- inet6_create
- ipv6_addr_copy
- </PRE><BR>
- <BR>
- <DIV
class
=ibm-alternate-rule>
- <HR>
- </DIV>
- <P
class
"ibm-ind-link ibm-back-to-top"
<BR>
- </P>
- <P><A name=major7><SPAN
class
=atitle>小结</SPAN></A></P>
- <P>本系列文章对 ftrace 的配置和使用进行了介绍。本文是其中的第二部分,介绍了 ftrace 所提供的跟踪器的使用,重点对不同格式的跟踪信息进行了描述,并对如何跟踪指定模块中的函数进行了介绍。当前 Linux 内核中 ftrace 支持多个跟踪器,由于篇幅限制,这里不能对其全部加以描述。读者可参照参考资料中给出的信息对 ftrace 跟踪器进行更深入的了解。下一篇文章将对如何在代码中通过 ftrace 提供的工具函数与 ftrace 交互进行描述。</P>
-
<BR>
-
<BR>
- <PRE></PRE>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 1
2
3
4
5
6
7
8
9
10 [cpp]
11 view plain
12 copy
13 print
14 ?
15
16
17
18
19
20
21
22
23
24
25 [root@linux tracing]# pwd
26
27 /sys/kernel/debug/tracing
28
29 [root@linux tracing]# echo 0 > tracing_enabled
30
31 [root@linux tracing]# echo 1 > /proc/sys/kernel/ftrace_enabled
32
33 [root@linux tracing]# echo function > current_tracer
34
35 [root@linux tracing]# echo 1 > tracing_on
36
37 [root@linux tracing]# echo 1 > tracing_enabled
38
39
40
41 # 让内核运行一段时间,这样 ftrace 可以收集一些跟踪信息,之后再停止跟踪
42
43
44
45 [root@linux tracing]# echo 0 > tracing_enabled
46
47 [root@linux tracing]# cat trace | head -10
48
49 # tracer: function
50
51 #
52
53 # TASK-PID CPU# TIMESTAMP FUNCTION
54
55 # | | | | |
56
57 <idle>-0 [000] 20654.426521: _raw_spin_lock <-scheduler_tick
58
59 <idle>-0 [000] 20654.426522: task_tick_idle <-scheduler_tick
60
61 <idle>-0 [000] 20654.426522: cpumask_weight <-scheduler_tick
62
63 <idle>-0 [000] 20654.426523: cpumask_weight <-scheduler_tick
64
65 <idle>-0 [000] 20654.426523: run_posix_cpu_timers <-update_process_times
66
67 <idle>-0 [000] 20654.426524: hrtimer_forward <-tick_sched_timer
68
69
70
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 1
2 [root@linux tracing]# pwd
3 /sys/kernel/debug/tracing
4 [root@linux tracing]# echo 0 > tracing_enabled
5 [root@linux tracing]# echo 1 > /proc/sys/kernel/ftrace_enabled
6 [root@linux tracing]# echo function > current_tracer
7 [root@linux tracing]# echo 1 > tracing_on
8 [root@linux tracing]# echo 1 > tracing_enabled
9
10 # 让内核运行一段时间,这样 ftrace 可以收集一些跟踪信息,之后再停止跟踪
11
12 [root@linux tracing]# echo 0 > tracing_enabled
13 [root@linux tracing]# cat trace | head -10
14 # tracer: function
15 #
16 # TASK-PID CPU# TIMESTAMP FUNCTION
17 # | | | | |
18 <idle>-0 [000] 20654.426521: _raw_spin_lock <-scheduler_tick
19 <idle>-0 [000] 20654.426522: task_tick_idle <-scheduler_tick
20 <idle>-0 [000] 20654.426522: cpumask_weight <-scheduler_tick
21 <idle>-0 [000] 20654.426523: cpumask_weight <-scheduler_tick
22 <idle>-0 [000] 20654.426523: run_posix_cpu_timers <-update_process_times
23 <idle>-0 [000] 20654.426524: hrtimer_forward <-tick_sched_timer
24
25
1
2 1trace 文件给出的信息格式很清晰。首先,字段“tracer:”给出了当前所使用的跟踪器的名字,这里为 function 跟踪器。然后是跟踪信息记录的格式,TASK 字段对应任务的名字,PID 字段则给出了任务的进程 ID,字段 CPU# 表示运行被跟踪函数的 CPU 号,这里可以看到 idle 进程运行在 0 号 CPU 上,其进程 ID 是 0 ;字段 TIMESTAMP 是时间戳,其格式为“<secs>.<usecs>,表示执行该函数时对应的时间戳;FUNCTION 一列则给出了被跟踪的函数,函数的调用者通过符号 “<-” 标明,这样可以观察到函数的调用关系。”
2
1
2 1
2
1
2 1function_graph 跟踪器
2
1
2 1在 function 跟踪器给出的信息中,可以通过 FUNCTION 列中的符号 “<-” 来查看函数调用关系,但是由于中间会混合不同函数的调用,导致看起来非常混乱,十分不方便。function_graph 跟踪器则可以提供类似 C 代码的函数调用关系信息。通过写文件 set_graph_function 可以显示指定要生成调用关系的函数,缺省会对所有可跟踪的内核函数生成函数调用关系图。清单 2 给出了使用 function_grapch 跟踪器的示例,示例中将内核函数 __do_fault 作为观察对象,该函数在内核运作过程中会被频繁调用。
2
1
2 1清单 2. function_graph 跟踪器使用示例
2
1
2 1在文件 trace 的输出信息中,首先给出的也是当前跟踪器的名字,这里是 function_graph 。接下来是详细的跟踪信息,可以看到,函数的调用关系以类似 C 代码的形式组织。
2
1
2 1CPU 字段给出了执行函数的 CPU 号,本例中都为 1 号 CPU。DURATION 字段给出了函数执行的时间长度,以 us 为单位。FUNCTION CALLS 则给出了调用的函数,并显示了调用流程。注意,对于不调用其它函数的函数,其对应行以“;”结尾,而且对应的 DURATION 字段给出其运行时长;对于调用其它函数的函数,则在其“}”对应行给出了运行时长,该时间是一个累加值,包括了其内部调用的函数的执行时长。DURATION 字段给出的时长并不是精确的,它还包含了执行 ftrace 自身的代码所耗费的时间,所以示例中将内部函数时长累加得到的结果会与对应的外围调用函数的执行时长并不一致;不过通过该字段还是可以大致了解函数在时间上的运行开销的。清单 2 中最后通过 echo 命令重置了文件 set_graph_function 。
2
1
2 1
2
1
2 1sched_switch 跟踪器
2
1
2 1sched_switch 跟踪器可以对进程的调度切换以及之间的唤醒操作进行跟踪,如清单 3 所示。
2
1
2 1清单 3. sched_switch 跟踪器使用示例
2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 1
2
3
4
5
6
7
8
9
10 [cpp]
11 view plain
12 copy
13 print
14 ?
15
16
17
18
19
20
21
22
23
24
25 [root@linux tracing]# pwd
26
27 /sys/kernel/debug/tracing
28
29 [root@linux tracing]# echo 0 > tracing_enabled
30
31 [root@linux tracing]# echo 1 > /proc/sys/kernel/ftrace_enabled
32
33 [root@linux tracing]# echo sched_switch > current_tracer
34
35 [root@linux tracing]# echo 1 > tracing_on
36
37 [root@linux tracing]# echo 1 > tracing_enabled
38
39
40
41 # 让内核运行一段时间,这样 ftrace 可以收集一些跟踪信息,之后再停止跟踪
42
43
44
45 [root@linux tracing]# echo 0 > tracing_enabled
46
47 [root@linux tracing]# cat trace | head -10
48
49 # tracer: sched_switch
50
51 #
52
53 # TASK-PID CPU# TIMESTAMP FUNCTION
54
55 # | | | | |
56
57 bash-1408 [000] 26208.816058: 1408:120:S + [000] 1408:120:S bash
58
59 bash-1408 [000] 26208.816070: 1408:120:S + [000] 1408:120:S bash
60
61 bash-1408 [000] 26208.816921: 1408:120:R + [000] 9:120:R events/0
62
63 bash-1408 [000] 26208.816939: 1408:120:R ==> [000] 9:120:R events/0
64
65 events/0-9 [000] 26208.817081: 9:120:R + [000] 1377:120:R gnome-terminal
66
67 events/0-9 [000] 26208.817088: 9:120:S ==> [000] 1377:120:R gnome-terminal
68
69
70
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 1
2 [root@linux tracing]# pwd
3 /sys/kernel/debug/tracing
4 [root@linux tracing]# echo 0 > tracing_enabled
5 [root@linux tracing]# echo 1 > /proc/sys/kernel/ftrace_enabled
6 [root@linux tracing]# echo sched_switch > current_tracer
7 [root@linux tracing]# echo 1 > tracing_on
8 [root@linux tracing]# echo 1 > tracing_enabled
9
10 # 让内核运行一段时间,这样 ftrace 可以收集一些跟踪信息,之后再停止跟踪
11
12 [root@linux tracing]# echo 0 > tracing_enabled
13 [root@linux tracing]# cat trace | head -10
14 # tracer: sched_switch
15 #
16 # TASK-PID CPU# TIMESTAMP FUNCTION
17 # | | | | |
18 bash-1408 [000] 26208.816058: 1408:120:S + [000] 1408:120:S bash
19 bash-1408 [000] 26208.816070: 1408:120:S + [000] 1408:120:S bash
20 bash-1408 [000] 26208.816921: 1408:120:R + [000] 9:120:R events/0
21 bash-1408 [000] 26208.816939: 1408:120:R ==> [000] 9:120:R events/0
22 events/0-9 [000] 26208.817081: 9:120:R + [000] 1377:120:R gnome-terminal
23 events/0-9 [000] 26208.817088: 9:120:S ==> [000] 1377:120:R gnome-terminal
24
25
1
2 1在 sched_swich 跟踪器获取的跟踪信息中记录了进程间的唤醒操作和调度切换信息,可以通过符号‘ + ’和‘ ==> ’区分;唤醒操作记录给出了当前进程唤醒运行的进程,进程调度切换记录中显示了接替当前进程运行的后续进程。
2
1
2 1描述进程状态的格式为“Task-PID:Priority:Task-State”。以示例跟踪信息中的第一条跟踪记录为例,可以看到进程 bash 的 PID 为 1408 ,其对应的内核态优先级为 120 ,当前状态为 S(可中断睡眠状态),当前 bash 并没有唤醒其它进程;从第 3 条记录可以看到,进程 bash 将进程 events/0 唤醒,而在第 4 条记录中发生了进程调度,进程 bash 切换到进程 events/0 执行。
2
1
2 1在 Linux 内核中,进程的状态在内核头文件 include/linux/sched.h 中定义,包括可运行状态 TASK_RUNNING(对应跟踪信息中的符号‘ R ’)、可中断阻塞状态 TASK_INTERRUPTIBLE(对应跟踪信息中的符号‘ S ’)等。同时该头文件也定义了用户态进程所使用的优先级的范围,最小值为 MAX_USER_RT_PRIO(值为 100 ),最大值为 MAX_PRIO - 1(对应值为 139 ),缺省为 DEFAULT_PRIO(值为 120 );在本例中,进程优先级都是缺省值 120 。
2
1
2 1
2
1
2 1irqsoff 跟踪器
2
1
2 1当关闭中断时,CPU 会延迟对设备的状态变化做出反应,有时候这样做会对系统性能造成比较大的影响。irqsoff 跟踪器可以对中断被关闭的状况进行跟踪,有助于发现导致较大延迟的代码;当出现最大延迟时,跟踪器会记录导致延迟的跟踪信息,文件 tracing_max_latency 则记录中断被关闭的最大延时。
2
1
2
3 1清单 4. irqsoff 跟踪器使用示例
2
3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110 1
2
3
4
5
6
7
8
9
10 [cpp]
11 view plain
12 copy
13 print
14 ?
15
16
17
18
19
20
21
22
23
24
25 [root@linux tracing]# pwd
26
27 /sys/kernel/debug/tracing
28
29 [root@linux tracing]# echo 0 > tracing_enabled
30
31 [root@linux tracing]# echo 1 > /proc/sys/kernel/ftrace_enabled
32
33 [root@linux tracing]# echo irqsoff > current_tracer
34
35 [root@linux tracing]# echo 1 > tracing_on
36
37 [root@linux tracing]# echo 1 > tracing_enabled
38
39
40
41 # 让内核运行一段时间,这样 ftrace 可以收集一些跟踪信息,之后再停止跟踪
42
43
44
45 [root@linux tracing]# echo 0 > tracing_enabled
46
47 [root@linux tracing]# cat trace | head -35
48
49 # tracer: irqsoff
50
51 #
52
53 # irqsoff latency trace v1.1.5 on 2.6.33.1
54
55 # --------------------------------------------------------------------
56
57 # latency: 34380 us, #6/6, CPU#1 | (M:desktop VP:0, KP:0, SP:0 HP:0 #P:2)
58
59 # -----------------
60
61 # | task: -0 (uid:0 nice:0 policy:0 rt_prio:0)
62
63 # -----------------
64
65 # => started at: reschedule_interrupt
66
67 # => ended at: restore_all_notrace
68
69 #
70
71 #
72
73 # _------=> CPU#
74
75 # / _-----=> irqs-off
76
77 # | / _----=> need-resched
78
79 # || / _---=> hardirq/softirq
80
81 # ||| / _--=> preempt-depth
82
83 # |||| /_--=> lock-depth
84
85 # |||||/ delay
86
87 # cmd pid |||||| time | caller
88
89 # \ / |||||| \ | /
90
91 <idle>-0 1dN... 4285us!: trace_hardirqs_off_thunk <-reschedule_interrupt
92
93 <idle>-0 1dN... 34373us+: smp_reschedule_interrupt <-reschedule_interrupt
94
95 <idle>-0 1dN... 34375us+: native_apic_mem_write <-smp_reschedule_interrupt
96
97 <idle>-0 1dN... 34380us+: trace_hardirqs_on_thunk <-restore_all_notrace
98
99 <idle>-0 1dN... 34384us : trace_hardirqs_on_caller <-restore_all_notrace
100
101 <idle>-0 1dN... 34384us : <stack trace>
102
103 => trace_hardirqs_on_thunk
104
105 [root@linux tracing]# cat tracing_max_latency
106
107 34380
108
109
110
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 1
2 [root@linux tracing]# pwd
3 /sys/kernel/debug/tracing
4 [root@linux tracing]# echo 0 > tracing_enabled
5 [root@linux tracing]# echo 1 > /proc/sys/kernel/ftrace_enabled
6 [root@linux tracing]# echo irqsoff > current_tracer
7 [root@linux tracing]# echo 1 > tracing_on
8 [root@linux tracing]# echo 1 > tracing_enabled
9
10 # 让内核运行一段时间,这样 ftrace 可以收集一些跟踪信息,之后再停止跟踪
11
12 [root@linux tracing]# echo 0 > tracing_enabled
13 [root@linux tracing]# cat trace | head -35
14 # tracer: irqsoff
15 #
16 # irqsoff latency trace v1.1.5 on 2.6.33.1
17 # --------------------------------------------------------------------
18 # latency: 34380 us, #6/6, CPU#1 | (M:desktop VP:0, KP:0, SP:0 HP:0 #P:2)
19 # -----------------
20 # | task: -0 (uid:0 nice:0 policy:0 rt_prio:0)
21 # -----------------
22 # => started at: reschedule_interrupt
23 # => ended at: restore_all_notrace
24 #
25 #
26 # _------=> CPU#
27 # / _-----=> irqs-off
28 # | / _----=> need-resched
29 # || / _---=> hardirq/softirq
30 # ||| / _--=> preempt-depth
31 # |||| /_--=> lock-depth
32 # |||||/ delay
33 # cmd pid |||||| time | caller
34 # \ / |||||| \ | /
35 <idle>-0 1dN... 4285us!: trace_hardirqs_off_thunk <-reschedule_interrupt
36 <idle>-0 1dN... 34373us+: smp_reschedule_interrupt <-reschedule_interrupt
37 <idle>-0 1dN... 34375us+: native_apic_mem_write <-smp_reschedule_interrupt
38 <idle>-0 1dN... 34380us+: trace_hardirqs_on_thunk <-restore_all_notrace
39 <idle>-0 1dN... 34384us : trace_hardirqs_on_caller <-restore_all_notrace
40 <idle>-0 1dN... 34384us : <stack trace>
41 => trace_hardirqs_on_thunk
42 [root@linux tracing]# cat tracing_max_latency
43 34380
44
1
2 1从清单 4 中的输出信息中,可以看到当前 irqsoff 延迟跟踪器的版本信息。接下来是最大延迟时间,以 us 为单位,本例中为 34380 us ,查看文件 tracing_max_latency 也可以获取该值。从“task:”字段可以知道延迟发生时正在运行的进程为 idle(其 pid 为 0 )。中断的关闭操作是在函数 reschedule_interrupt 中进行的,由“=> started at:”标识,函数 restore_all_ontrace 重新激活了中断,由“=> ended at:”标识;中断关闭的最大延迟发生在函数 trace_hardirqs_on_thunk 中,这个可以从最大延迟时间所在的记录项看到,也可以从延迟记录信息中最后的“=>”标识所对应的记录行知道这一点。
2
1
2 1在输出信息中,irqs-off、need_resched 等字段对应于进程结构 struct task_struct 的字段或者状态标志,可以从头文件 arch/<platform>/include/asm/thread_info.h 中查看进程支持的状态标志,include/linux/sched.h 则给出了结构 struct task_struct 的定义。其中,irqs-off 字段显示是否中断被禁止,为‘ d ’表示中断被禁止;need_resched 字段显示为‘ N ’表示设置了进程状态标志 TIF_NEED_RESCHED。hardirq/softirq 字段表示当前是否发生硬件中断 / 软中断;preempt-depth 表示是否禁止进程抢占,例如在持有自旋锁的情况下进程是不能被抢占的,本例中进程 idle 是可以被其它进程抢占的。结构 struct task_struct 中的 lock_depth 字段是与大内核锁相关的,而最近的内核开发工作中有一部分是与移除大内核锁相关的,这里对该字段不再加以描述。
2
1
2 1另外,还有用于跟踪禁止进程抢占的跟踪器 preemptoff 和跟踪中断 / 抢占禁止的跟踪器 preemptirqsoff,它们的使用方式与输出信息格式与 irqsoff 跟踪器类似,这里不再赘述。
2
1
2 1
2
1
2 1跟踪指定模块中的函数
2
1
2 1前面提过,通过文件 set_ftrace_filter 可以指定要跟踪的函数,缺省目标为所有可跟踪的内核函数;可以将感兴趣的函数通过 echo 写入该文件。为了方便使用,set_ftrace_filter 文件还支持简单格式的通配符。
2
1
2
3
4
5
6
7
8
9
10
11
12 1
2
3
4
5 begin*选择所有名字以 begin 字串开头的函数
6
7 *middle*选择所有名字中包含 middle 字串的函数
8
9 *end选择所有名字以 end 字串结尾的函数
10
11
12
需要注意的是,这三种形式不能组合使用,比如“begin*middle*end”实际的效果与“begin”相同。另外,使用通配符表达式时,需要用单引号将其括起来,如果使用双引号,shell 可能会对字符‘ * ’进行扩展,这样最终跟踪的对象可能与目标函数不一样。
通过该文件还可以指定属于特定模块的函数,这要用到 mod 指令。指定模块的格式为:
echo ':mod:[module_name]' > set_ftrace_filter
下面给出了一个指定跟踪模块 ipv6 中的函数的例子。可以看到,指定跟踪模块 ipv6 中的函数会将文件 set_ftrace_filter 的内容设置为只包含该模块中的函数。
清单 5. 指定跟踪 ipv6 模块中的函数
[cpp] view plain copy print ?
- [root@linux tracing]# pwd
- /sys/kernel/debug/tracing
- [root@linux tracing]# echo
':mod:ipv6'
> set_ftrace_filter
-
[root@linux tracing]# cat set_ftrace_filter | head -5
-
ipv6_opt_accepted
-
inet6_net_exit
-
ipv6_gro_complete
-
inet6_create
-
ipv6_addr_copy
1
2
3
4
5
6
7
8
9
10
11
12 1
2 [root@linux tracing]# pwd
3 /sys/kernel/debug/tracing
4 [root@linux tracing]# echo ':mod:ipv6' > set_ftrace_filter
5 [root@linux tracing]# cat set_ftrace_filter | head -5
6 ipv6_opt_accepted
7 inet6_net_exit
8 ipv6_gro_complete
9 inet6_create
10 ipv6_addr_copy
11
12
小结
本系列文章对 ftrace 的配置和使用进行了介绍。本文是其中的第二部分,介绍了 ftrace 所提供的跟踪器的使用,重点对不同格式的跟踪信息进行了描述,并对如何跟踪指定模块中的函数进行了介绍。当前 Linux 内核中 ftrace 支持多个跟踪器,由于篇幅限制,这里不能对其全部加以描述。读者可参照参考资料中给出的信息对 ftrace 跟踪器进行更深入的了解。下一篇文章将对如何在代码中通过 ftrace 提供的工具函数与 ftrace 交互进行描述。
使用 ftrace 调试 Linux 内核,第 3 部分
http://www.voidcn.com/article/p-sujwscyx-bkz.html
简介: ftrace 是 Linux 内核中提供的一种调试工具。使用 ftrace 可以对内核中发生的事情进行跟踪,这在调试 bug 或者分析内核时非常有用。本系列文章对 ftrace 进行了介绍,分为三部分。本文是第三部分,通过示例代码介绍如何在代码中使用 ftrace 提供的工具函数,以与 ftrace 交互。通过本文的讲解,读者可以在实际代码中使用 ftrace,方便了调试和分析。
内核头文件 include/linux/kernel.h 中描述了 ftrace 提供的工具函数的原型,这些函数包括 trace_printk、tracing_on/tracing_off 等。本文通过示例模块程序向读者展示如何在代码中使用这些工具函数。
使用 trace_printk 打印跟踪信息
ftrace 提供了一个用于向 ftrace 跟踪缓冲区输出跟踪信息的工具函数,叫做 trace_printk(),它的使用方式与 printk() 类似。可以通过 trace 文件读取该函数的输出。从头文件 include/linux/kernel.h 中可以看到,在激活配置 CONFIG_TRACING 后,trace_printk() 定义为宏:
#define trace_printk(fmt, args…) \
…
下面通过一个示例模块 ftrace_demo 来演示如何使用 trace_printk() 向跟踪缓冲区输出信息,以及如何查看这些信息。这里的示例模块程序中仅提供了初始化和退出函数,这样读者不会因为需要为模块创建必要的访问接口比如设备文件而分散注意力。注意,编译模块时要加入 -pg 选项。
清单 1. 示例模块 ftrace_demo
[cpp] view plain copy print ?
/*
- * ftrace_demo.c
- */
1.
#include <linux/init.h>
1.
#include <linux/module.h>
1.
#include <linux/kernel.h>
- MODULE_LICENSE(
"GPL"
);
static
int
ftrace_demo_init(
void
)
- {
- trace_printk(
"Can not see this in trace unless loaded for the second 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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 1
2 /*
3 * ftrace_demo.c
4 */
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/kernel.h>
8
9 MODULE_LICENSE("GPL");
10
11 static int ftrace_demo_init(void)
12 {
13 trace_printk("Can not see this in trace unless loaded for the second time\n");
14 return 0;
15 }
16
17 static void ftrace_demo_exit(void)
18 {
19 trace_printk("Module unloading\n");
20 }
21
22 module_init(ftrace_demo_init);
23 module_exit(ftrace_demo_exit);
24
示例模块非常简单,仅仅是在模块初始化函数和退出函数中输出信息。接下来要对模块的运行进行跟踪,如清单 2 所示。
清单 2. 对模块 ftrace_demo 进行跟踪
[cpp] view plain copy print ?
1
2
3
4 1 [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 # 事先加载模块 ftrace_demo [root@linux tracing]# echo ':mod:ftrace_demo' > set_ftrace_filter [root@linux tracing]# cat set_ftrace_filter ftrace_demo_init ftrace_demo_exit # 将模块 ftrace_demo 卸载 [root@linux tracing]# echo 1 > tracing_enabled # 重新进行模块 ftrace_demo 的加载与卸载操作 [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 */
2
3
4
[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
事先加载模块 ftrace_demo
[root@linux tracing]# echo ':mod:ftrace_demo' > set_ftrace_filter
[root@linux tracing]# cat set_ftrace_filter
ftrace_demo_init
ftrace_demo_exit
将模块 ftrace_demo 卸载
[root@linux tracing]# echo 1 > tracing_enabled
重新进行模块 ftrace_demo 的加载与卸载操作
[root@linux tracing]# cat trace
tracer: function_graph
CPU DURATION FUNCTION CALLS
| | | | | | |
-
1
21 | /* Can not see this in trace unless loaded for the second time */
2 -
1
21 | /* Module unloading */
2
在这个例子中,使用 mod 指令显式指定跟踪模块 ftrace_demo 中的函数,这需要提前加载该模块,否则在写文件 set_ftrace_filter 时会因为找不到该模块报错。这样在第一次加载模块时,其初始化函数 ftrace_demo_init 中调用 trace_printk 打印的语句就跟踪不到了。因此这里会将其卸载,然后激活跟踪,再重新进行模块 ftrace_demo 的加载与卸载操作。最终可以从文件 trace 中看到模块在初始化和退出时调用 trace_printk() 输出的信息。
这里仅仅是为了以简单的模块进行演示,故只定义了模块的 init/exit 函数,重复加载模块也只是为了获取初始化函数输出的跟踪信息。实践中,可以在模块的功能函数中加入对 trace_printk 的调用,这样可以记录模块的运作情况,然后对其特定功能进行调试优化。还可以将对 trace_printk() 的调用通过宏来控制编译,这样可以在调试时将其开启,在最终发布时将其关闭。
回页首
使用 tracing_on/tracing_off 控制跟踪信息的记录
在跟踪过程中,有时候在检测到某些事件发生时,想要停止跟踪信息的记录,这样,跟踪缓冲区中较新的数据是与该事件有关的。在用户态,可以通过向文件 tracing_on 写入 0 来停止记录跟踪信息,写入 1 会继续记录跟踪信息。而在内核代码中,可以通过函数 tracing_on() 和 tracing_off() 来做到这一点,它们的行为类似于对 /sys/kernel/debug/tracing 下的文件 tracing_on 分别执行写 1 和 写 0 的操作。使用这两个函数,会对跟踪信息的记录控制地更准确一些,这是因为在用户态写文件 tracing_on 到实际暂停跟踪,中间由于上下文切换、系统调度控制等可能已经经过较长的时间,这样会积累大量的跟踪信息,而感兴趣的那部分可能会被覆盖掉了。
现在对清单 1 中的代码进行修改,使用 tracing_off() 来控制跟踪信息记录的暂停。
清单 3. 使用 tracing_off 的模块 ftrace_demo
[cpp] view plain copy print ?
1
2
3
4 1 /* * ftrace_demo.c * modified to demostrate the usage of tracing_off */ #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> MODULE_LICENSE("GPL"); static int ftrace_demo_init(void) { trace_printk("ftrace_demo_init called\n"); tracing_off(); return 0; } static void ftrace_demo_exit(void) { trace_printk("ftrace_demo_exit called\n"); tracing_off(); } module_init(ftrace_demo_init); module_exit(ftrace_demo_exit);
2
3 /*
4
- ftrace_demo.c
-
1
21modified to demostrate the usage of tracing_off
2
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
static int ftrace_demo_init(void)
{
trace_printk("ftrace_demo_init called\n");
tracing_off();
return 0;
}
static void ftrace_demo_exit(void)
{
trace_printk("ftrace_demo_exit called\n");
tracing_off();
}
module_init(ftrace_demo_init);
module_exit(ftrace_demo_exit);
下面对其进行跟踪,如清单 4 所示。
清单 4. 跟踪
[cpp] view plain copy print ?
1
2
3
4 1 [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 1 > tracing_on [root@linux tracing]# echo function > current_tracer [root@linux tracing]# echo 1 > tracing_enabled # 加载模块 ftrace_demo,模块初始化函数 ftrace_demo_init 被调用 [root@linux tracing]# cat tracing_on 0 [root@linux tracing]# cat trace | wc -l 120210 [root@linux tracing]# cat trace | grep -n ftrace_demo_init 120187: insmod-2897 [000] 2610.504611: ftrace_demo_init <-do_one_initcall 120193: insmod-2897 [000] 2610.504667: ftrace_demo_init: ftrace_demo_init called [root@linux tracing]# echo 1 > tracing_on # 继续跟踪信息的记录 # 卸载模块 ftrace_demo,模块函数 ftrace_demo_exit 被调用 [root@linux tracing]# cat tracing_on 0 [root@linux tracing]# wc -l trace 120106 trace [root@linux tracing]# grep -n ftrace_demo_exit trace 120106: rmmod-2992 [001] 3016.884449: : ftrace_demo_exit called
2
3 [root@linux tracing]# pwd
4
/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 1 > tracing_on
[root@linux tracing]# echo function > current_tracer
[root@linux tracing]# echo 1 > tracing_enabled
加载模块 ftrace_demo,模块初始化函数 ftrace_demo_init 被调用
[root@linux tracing]# cat tracing_on
0
[root@linux tracing]# cat trace | wc -l
120210
[root@linux tracing]# cat trace | grep -n ftrace_demo_init
120187: insmod-2897 [000] 2610.504611: ftrace_demo_init <-do_one_initcall
120193: insmod-2897 [000] 2610.504667: ftrace_demo_init: ftrace_demo_init called
[root@linux tracing]# echo 1 > tracing_on # 继续跟踪信息的记录
卸载模块 ftrace_demo,模块函数 ftrace_demo_exit 被调用
[root@linux tracing]# cat tracing_on
0
[root@linux tracing]# wc -l trace
120106 trace
[root@linux tracing]# grep -n ftrace_demo_exit trace
120106: rmmod-2992 [001] 3016.884449: : ftrace_demo_exit called
在这个例子中,跟踪开始之前需要确保 tracing_on 的值为 1。跟踪开始后,加载模块 ftrace_demo,其初始化方法 ftrace_demo_init 被调用,该方法会调用 tracing_off() 函数来暂停跟踪信息的记录,这时文件 tracing_on 的值被代码设置为 0。查看文件 trace,可以看到 ftrace_demo_init 相关的记录位于跟踪信息的末端,这是因为从调用 trace_off() 到其生效需要一段时间,这段时间中的内核活动会被记录下来;相比从用户态读写 tracing_on 文件,这段时间开销要小了许多。卸载模块时的情况与此类似。从这里可以看到,在代码中使用 tracing_off() 可以控制将感兴趣的信息保存在跟踪缓冲区的末端位置,不会很快被新的信息所覆盖,便于及时查看。
实际代码中,可以通过特定条件(比如检测到某种异常状况,等等)来控制跟踪信息的记录,函数的使用方式类似如下的形式:
if (condition)
tracing_on() or tracing_off()
跟踪模块运行状况时,使用 ftrace 命令操作序列在用户态进行必要的设置,而在代码中则可以通过 traceing_on() 控制在进入特定代码区域时开启跟踪信息,并在遇到某些条件时通过 tracing_off() 暂停;读者可以在查看完感兴趣的信息后,将 1 写入 tracing_on 文件以继续记录跟踪信息。实践中,可以通过宏来控制是否将对这些函数的调用编译进内核模块,这样可以在调试时将其开启,在最终发布时将其关闭。
用户态的应用程序可以通过直接读写文件 tracing_on 来控制记录跟踪信息的暂停状态,以便了解应用程序运行期间内核中发生的活动。
回页首
小结
本系列文章对 ftrace 的配置和使用进行了介绍。本文是其中的第三部分,通过示例代码介绍了 ftrace 所提供的部分工具函数的使用,包括 trace_printk、tracing_on/tracing_off 。至此,本系列文章的所有内容都已经介绍完毕。通过本系列文章,读者可以了解如何使用 ftrace 来对内核进行调试和分析。ftrace 使用起来非常灵活,并且支持多种跟踪器,而且它的框架结构使得在添加新的跟踪器的时候会比较方便。读者可以在以后的工作和学习中探索 ftrace 的各种用法。
贴上2.6.35内核里ftrace.txt
ftrace – Function Tracer
========================
Copyright 2008 Red Hat Inc. Author: Steven Rostedt srostedt@redhat.com License: The GNU Free Documentation License, Version 1.2 (dual licensed under the GPL v2) Reviewers: Elias Oltmanns, Randy Dunlap, Andrew Morton, John Kacur, and David Teigland. Written for: 2.6.28-rc2
Introduction
Ftrace is an internal tracer designed to help out developers and designers of systems to find what is going on inside the kernel. It can be used for debugging or analyzing latencies and performance issues that take place outside of user-space.
Although ftrace is the function tracer, it also includes an infrastructure that allows for other types of tracing. Some of the tracers that are currently in ftrace include a tracer to trace context switches, the time it takes for a high priority task to run after it was woken up, the time interrupts are disabled, and more (ftrace allows for tracer plugins, which means that the list of tracers can always grow).
Implementation Details
See ftrace-design.txt for details for arch porters and such.
The File System
Ftrace uses the debugfs file system to hold the control files as well as the files to display output.
When debugfs is configured into the kernel (which selecting any ftrace option will do) the directory /sys/kernel/debug will be created. To mount this directory, you can add to your /etc/fstab file:
debugfs /sys/kernel/debug debugfs defaults 0 0
Or you can mount it at run time with:
mount -t debugfs nodev /sys/kernel/debug
For quicker access to that directory you may want to make a soft link to it:
ln -s /sys/kernel/debug /debug
Any selected ftrace option will also create a directory called tracing within the debugfs. The rest of the document will assume that you are in the ftrace directory (cd /sys/kernel/debug/tracing) and will only concentrate on the files within that directory and not distract from the content with the extended "/sys/kernel/debug/tracing" path name.
That's it! (assuming that you have ftrace configured into your kernel)
After mounting the debugfs, you can see a directory called "tracing". This directory contains the control and output files of ftrace. Here is a list of some of the key files:
Note: all time values are in microseconds.
current_tracer:
This is used to set or display the current tracer
that is configured.
available_tracers:
This holds the different types of tracers that
have been compiled into the kernel. The
tracers listed here can be configured by
echoing their name into current_tracer.
tracing_enabled:
This sets or displays whether the current_tracer
is activated and tracing or not. Echo 0 into this
file to disable the tracer or 1 to enable it.
trace:
This file holds the output of the trace in a human
readable format (described below).
trace_pipe:
The output is the same as the "trace" file but this
file is meant to be streamed with live tracing.
Reads from this file will block until new data is
retrieved. Unlike the "trace" file, this file is a
consumer. This means reading from this file causes
sequential reads to display more current data. Once
data is read from this file, it is consumed, and
will not be read again with a sequential read. The
"trace" file is static, and if the tracer is not
adding more data,they will display the same
information every time they are read.
trace_options:
This file lets the user control the amount of data
that is displayed in one of the above output
files.
tracing_max_latency:
Some of the tracers record the max latency.
For example, the time interrupts are disabled.
This time is saved in this file. The max trace
will also be stored, and displayed by "trace".
A new max trace will only be recorded if the
latency is greater than the value in this
file. (in microseconds)
buffer_size_kb:
This sets or displays the number of kilobytes each CPU
buffer can hold. The tracer buffers are the same size
for each CPU. The displayed number is the size of the
CPU buffer and not total size of all buffers. The
trace buffers are allocated in pages (blocks of memory
that the kernel uses for allocation, usually 4 KB in size).
If the last page allocated has room for more bytes
than requested, the rest of the page will be used,
making the actual allocation bigger than requested.
( Note, the size may not be a multiple of the page size due to buffer management overhead. )
This can only be updated when the current_tracer
is set to "nop".
tracing_cpumask:
This is a mask that lets the user only trace
on specified CPUS. The format is a hex string
representing the CPUS.
set_ftrace_filter:
When dynamic ftrace is configured in (see the
section below "dynamic ftrace"), the code is dynamically
modified (code text rewrite) to disable calling of the
function profiler (mcount). This lets tracing be configured
in with practically no overhead in performance. This also
has a side effect of enabling or disabling specific functions
to be traced. Echoing names of functions into this file
will limit the trace to only those functions.
This interface also allows for commands to be used. See the
"Filter commands" section for more details.
set_ftrace_notrace:
This has an effect opposite to that of
set_ftrace_filter. Any function that is added here will not
be traced. If a function exists in both set_ftrace_filter
and set_ftrace_notrace, the function will not be traced.
set_ftrace_pid:
Have the function tracer only trace a single thread.
set_graph_function:
Set a "trigger" function where tracing should start
with the function graph tracer (See the section
"dynamic ftrace" for more details).
available_filter_functions:
This lists the functions that ftrace
has processed and can trace. These are the function
names that you can pass to "set_ftrace_filter" or
"set_ftrace_notrace". (See the section "dynamic ftrace"
below for more details.)
The Tracers
Here is the list of current tracers that may be configured.
"function"
Function call tracer to trace all kernel functions.
"function_graph"
Similar to the function tracer except that the
function tracer probes the functions on their entry
whereas the function graph tracer traces on both entry
and exit of the functions. It then provides the ability
to draw a graph of function calls similar to C code
source.
"sched_switch"
Traces the context switches and wakeups between tasks.
"irqsoff"
Traces the areas that disable interrupts and saves
the trace with the longest max latency.
See tracing_max_latency. When a new max is recorded,
it replaces the old trace. It is best to view this
trace with the latency-format option enabled.
"preemptoff"
Similar to irqsoff but traces and records the amount of
time for which preemption is disabled.
"preemptirqsoff"
Similar to irqsoff and preemptoff, but traces and
records the largest time for which irqs and/or preemption
is disabled.
"wakeup"
Traces and records the max latency that it takes for
the highest priority task to get scheduled after
it has been woken up.
"hw-branch-tracer"
Uses the BTS CPU feature on x86 CPUs to traces all
branches executed.
"nop"
This is the "trace nothing" tracer. To remove all
tracers from tracing simply echo "nop" into
current_tracer.
Examples of using the tracer
Here are typical examples of using the tracers when controlling them only with the debugfs interface (without using any user-land utilities).
Output format:
Here is an example of the output format of the file "trace"
——– # tracer: function # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | bash-4251 [01] 10152.583854: path_put <-path_walk
bash-4251 [01] 10152.583855: dput <-path_put
bash-4251 [01] 10152.583855: _atomic_dec_and_lock <-dput
——–
A header is printed with the tracer name that is represented by the trace. In this case the tracer is "function". Then a header showing the format. Task name "bash", the task PID "4251", the CPU that it was running on "01", the timestamp in <secs>.<usecs> format, the function name that was traced "path_put" and the parent function that called this function "path_walk". The timestamp is the time at which the function was entered.
The sched_switch tracer also includes tracing of task wakeups and context switches.
ksoftirqd/1-7 [01] 1453.070013: 7:115:R + 2916:115:S ksoftirqd/1-7 [01] 1453.070013: 7:115:R + 10:115:S ksoftirqd/1-7 [01] 1453.070013: 7:115:R ==> 10:115:R events/1-10 [01] 1453.070013: 10:115:S ==> 2916:115:R kondemand/1-2916 [01] 1453.070013: 2916:115:S ==> 7:115:R ksoftirqd/1-7 [01] 1453.070013: 7:115:S ==> 0:140:R
Wake ups are represented by a "+" and the context switches are shown as "==>". The format is:
Context switches:
Previous task Next Task
<pid>:<prio>:<state> ==> <pid>:<prio>:<state>
Wake ups:
Current task Task waking up
<pid>:<prio>:<state> + <pid>:<prio>:<state>
The prio is the internal kernel priority, which is the inverse of the priority that is usually displayed by user-space tools. Zero represents the highest priority (99). Prio 100 starts the "nice" priorities with 100 being equal to nice -20 and 139 being nice 19. The prio "140" is reserved for the idle task which is the lowest priority thread (pid 0).
Latency trace format
When the latency-format option is enabled, the trace file gives somewhat more information to see why a latency happened. Here is a typical trace.
# tracer: irqsoff # irqsoff latency trace v1.1.5 on 2.6.26-rc8
——————————————————————– latency: 97 us, #3/3, CPU#0 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2)
—————–
| task: swapper-0 (uid:0 nice:0 policy:0 rt_prio:0)
—————– => started at: apic_timer_interrupt
=> ended at: do_softirq
# _——=> CPU# # / _—–=> irqs-off # | / _—-=> need-resched # || / _—=> hardirq/softirq # ||| / _–=> preempt-depth # |||| / # ||||| delay # cmd pid ||||| time | caller # \ / ||||| \ | / <idle>-0 0d..1 0us+: trace_hardirqs_off_thunk (apic_timer_interrupt) <idle>-0 0d.s. 97us : __do_softirq (do_softirq)
<idle>-0 0d.s1 98us : trace_hardirqs_on (do_softirq)
This shows that the current tracer is "irqsoff" tracing the time for which interrupts were disabled. It gives the trace version and the version of the kernel upon which this was executed on (2.6.26-rc8). Then it displays the max latency in microsecs (97 us). The number of trace entries displayed and the total number recorded (both are three: #3/3). The type of preemption that was used (PREEMPT). VP, KP, SP, and HP are always zero and are reserved for later use. #P is the number of online CPUS (#P:2).
The task is the process that was running when the latency occurred. (swapper pid: 0).
The start and stop (the functions in which the interrupts were disabled and enabled respectively) that caused the latencies:
apic_timer_interrupt is where the interrupts were disabled.
do_softirq is where they were enabled again.
The next lines after the header are the trace itself. The header explains which is which.
cmd: The name of the process in the trace.
pid: The PID of that process.
CPU#: The CPU which the process was running on.
irqs-off: 'd' interrupts are disabled. '.' otherwise. Note: If the architecture does not support a way to read the irq flags variable, an 'X' will always
be printed here.
need-resched: 'N' task need_resched is set, '.' otherwise.
hardirq/softirq: 'H' – hard irq occurred inside a softirq.
'h' – hard irq is running
's' – soft irq is running
'.' – normal context.
preempt-depth: The level of preempt_disabled
The above is mostly meaningful for kernel developers.
time: When the latency-format option is enabled, the trace file output includes a timestamp relative to the start of the
trace. This differs from the output when latency-format
is disabled, which includes an absolute timestamp.
delay: This is just to help catch your eye a bit better. And
needs to be fixed to be only relative to the same CPU.
The marks are determined by the difference between this
current trace and the next trace. '!' – greater than preempt_mark_thresh (default 100)
'+' – greater than 1 microsecond
' ' – less than or equal to 1 microsecond.
The rest is the same as the 'trace' file.
trace_options
The trace_options file is used to control what gets printed in the trace output. To see what is available, simply cat the file:
cat trace_options
print-parent nosym-offset nosym-addr noverbose noraw nohex nobin \ noblock nostacktrace nosched-tree nouserstacktrace nosym-userobj
To disable one of the options, echo in the option prepended with "no".
echo noprint-parent > trace_options
To enable an option, leave off the "no".
echo sym-offset > trace_options
Here are the available options:
print-parent – On function traces, display the calling (parent) function as well as the function being traced.
print-parent: bash-4000 [01] 1477.606694: simple_strtoul <-strict_strtoul
noprint-parent: bash-4000 [01] 1477.606694: simple_strtoul
sym-offset – Display not only the function name, but also the offset in the function. For example, instead of
seeing just "ktime_get", you will see
"ktime_get+0xb/0x20".
sym-offset: bash-4000 [01] 1477.606694: simple_strtoul+0x6/0xa0
sym-addr – this will also display the function address as well as the function name.
sym-addr: bash-4000 [01] 1477.606694: simple_strtoul <c0339346>
verbose – This deals with the trace file when the latency-format option is enabled.
bash 4000 1 0 00000000 00010a95 [58127d26] 1720.415ms \
(+0.000ms): simple_strtoul (strict_strtoul)
raw – This will display raw numbers. This option is best for use with user applications that can translate the raw
numbers better than having it done in the kernel.
hex – Similar to raw, but the numbers will be in a hexadecimal format.
bin – This will print out the formats in raw binary.
block – TBD (needs update)
stacktrace – This is one of the options that changes the trace itself. When a trace is recorded, so is the stack
of functions. This allows for back traces of
trace sites.
userstacktrace – This option changes the trace. It records a stacktrace of the current userspace thread.
sym-userobj – when user stacktrace are enabled, look up which
object the address belongs to, and print a
relative address. This is especially useful when
ASLR is on, otherwise you don't get a chance to
resolve the address to object/file/line after
the app is no longer running
The lookup is performed when you read
trace,trace_pipe. Example:
a.out-1623 [000] 40874.465068: /root/a.out[+0x480] <-/root/a.out[+0 x494] <- /root/a.out[+0x4a8] <- /lib/libc-2.7.so[+0x1e1a6]
sched-tree – trace all tasks that are on the runqueue, at every scheduling event. Will add overhead if
there's a lot of tasks running at once.
latency-format – This option changes the trace. When it is enabled, the trace displays
additional information about the
latencies, as described in "Latency
trace format".
sched_switch
This tracer simply records schedule switches. Here is an example of how to use it.
# echo sched_switch > current_tracer
# echo 1 > tracing_enabled
# sleep 1
# echo 0 > tracing_enabled
# cat trace
# tracer: sched_switch # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | bash-3997 [01] 240.132281: 3997:120:R + 4055:120:R
bash-3997 [01] 240.132284: 3997:120:R ==> 4055:120:R sleep-4055 [01] 240.132371: 4055:120:S ==> 3997:120:R bash-3997 [01] 240.132454: 3997:120:R + 4055:120:S
bash-3997 [01] 240.132457: 3997:120:R ==> 4055:120:R sleep-4055 [01] 240.132460: 4055:120:D ==> 3997:120:R bash-3997 [01] 240.132463: 3997:120:R + 4055:120:D
bash-3997 [01] 240.132465: 3997:120:R ==> 4055:120:R <idle>-0 [00] 240.132589: 0:140:R + 4:115:S
<idle>-0 [00] 240.132591: 0:140:R ==> 4:115:R ksoftirqd/0-4 [00] 240.132595: 4:115:S ==> 0:140:R <idle>-0 [00] 240.132598: 0:140:R + 4:115:S
<idle>-0 [00] 240.132599: 0:140:R ==> 4:115:R ksoftirqd/0-4 [00] 240.132603: 4:115:S ==> 0:140:R sleep-4055 [01] 240.133058: 4055:120:S ==> 3997:120:R […]
As we have discussed previously about this format, the header shows the name of the trace and points to the options. The "FUNCTION" is a misnomer since here it represents the wake ups and context switches.
The sched_switch file only lists the wake ups (represented with '+') and context switches ('==>') with the previous task or current task first followed by the next task or task waking up. The format for both of these is PID:KERNEL-PRIO:TASK-STATE. Remember that the KERNEL-PRIO is the inverse of the actual priority with zero (0) being the highest priority and the nice values starting at 100 (nice -20). Below is a quick chart to map the kernel priority to user land priorities.
Kernel Space User Space =============================================================== 0(high) to 98(low) user RT priority 99(high) to 1(low) with SCHED_RR or SCHED_FIFO
————————————————————— 99 sched_priority is not used in scheduling decisions(it must be specified as 0)
————————————————————— 100(high) to 139(low) user nice -20(high) to 19(low)
————————————————————— 140 idle task priority
—————————————————————
The task states are:
R – running : wants to run, may not actually be running
S – sleep : process is waiting to be woken up (handles signals)
D – disk sleep (uninterruptible sleep) : process must be woken up (ignores signals) T – stopped : process suspended
t – traced : process is being traced (with something like gdb)
Z – zombie : process waiting to be cleaned up
X – unknown
ftrace_enabled
The following tracers (listed below) give different output depending on whether or not the sysctl ftrace_enabled is set. To set ftrace_enabled, one can either use the sysctl function or set it via the proc file system interface.
sysctl kernel.ftrace_enabled=1
or
echo 1 > /proc/sys/kernel/ftrace_enabled
To disable ftrace_enabled simply replace the '1' with '0' in the above commands.
When ftrace_enabled is set the tracers will also record the functions that are within the trace. The descriptions of the tracers will also show an example with ftrace enabled.
irqsoff
When interrupts are disabled, the CPU can not react to any other external event (besides NMIs and SMIs). This prevents the timer interrupt from triggering or the mouse interrupt from letting the kernel know of a new mouse event. The result is a latency with the reaction time.
The irqsoff tracer tracks the time for which interrupts are disabled. When a new maximum latency is hit, the tracer saves the trace leading up to that latency point so that every time a new maximum is reached, the old saved trace is discarded and the new trace is saved.
To reset the maximum, echo 0 into tracing_max_latency. Here is an example:
# echo irqsoff > current_tracer
# echo latency-format > trace_options
# echo 0 > tracing_max_latency
# echo 1 > tracing_enabled
# ls -ltr
[…]
# echo 0 > tracing_enabled
# cat trace # tracer: irqsoff # irqsoff latency trace v1.1.5 on 2.6.26
——————————————————————– latency: 12 us, #3/3, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2)
—————–
| task: bash-3730 (uid:0 nice:0 policy:0 rt_prio:0)
—————– => started at: sys_setpgid
=> ended at: sys_setpgid
# _——=> CPU# # / _—–=> irqs-off # | / _—-=> need-resched # || / _—=> hardirq/softirq # ||| / _–=> preempt-depth # |||| / # ||||| delay # cmd pid ||||| time | caller # \ / ||||| \ | / bash-3730 1d… 0us : _write_lock_irq (sys_setpgid)
bash-3730 1d..1 1us+: _write_unlock_irq (sys_setpgid)
bash-3730 1d..2 14us : trace_hardirqs_on (sys_setpgid)
Here we see that that we had a latency of 12 microsecs (which is very good). The _write_lock_irq in sys_setpgid disabled interrupts. The difference between the 12 and the displayed timestamp 14us occurred because the clock was incremented between the time of recording the max latency and the time of recording the function that had that latency.
Note the above example had ftrace_enabled not set. If we set the ftrace_enabled, we get a much larger output:
# tracer: irqsoff # irqsoff latency trace v1.1.5 on 2.6.26-rc8
——————————————————————– latency: 50 us, #101/101, CPU#0 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2)
—————–
| task: ls-4339 (uid:0 nice:0 policy:0 rt_prio:0)
—————– => started at: __alloc_pages_internal
=> ended at: __alloc_pages_internal
# _——=> CPU# # / _—–=> irqs-off # | / _—-=> need-resched # || / _—=> hardirq/softirq # ||| / _–=> preempt-depth # |||| / # ||||| delay # cmd pid ||||| time | caller # \ / ||||| \ | / ls-4339 0…1 0us+: get_page_from_freelist (__alloc_pages_internal) ls-4339 0d..1 3us : rmqueue_bulk (get_page_from_freelist)
ls-4339 0d..1 3us : _spin_lock (rmqueue_bulk)
ls-4339 0d..1 4us : add_preempt_count (_spin_lock)
ls-4339 0d..2 4us : __rmqueue (rmqueue_bulk)
ls-4339 0d..2 5us : __rmqueue_smallest (__rmqueue)
ls-4339 0d..2 5us : __mod_zone_page_state (__rmqueue_smallest) ls-4339 0d..2 6us : __rmqueue (rmqueue_bulk)
ls-4339 0d..2 6us : __rmqueue_smallest (__rmqueue)
ls-4339 0d..2 7us : __mod_zone_page_state (__rmqueue_smallest) ls-4339 0d..2 7us : __rmqueue (rmqueue_bulk)
ls-4339 0d..2 8us : __rmqueue_smallest (__rmqueue) […] ls-4339 0d..2 46us : __rmqueue_smallest (__rmqueue)
ls-4339 0d..2 47us : __mod_zone_page_state (__rmqueue_smallest) ls-4339 0d..2 47us : __rmqueue (rmqueue_bulk)
ls-4339 0d..2 48us : __rmqueue_smallest (__rmqueue)
ls-4339 0d..2 48us : __mod_zone_page_state (__rmqueue_smallest) ls-4339 0d..2 49us : _spin_unlock (rmqueue_bulk)
ls-4339 0d..2 49us : sub_preempt_count (_spin_unlock)
ls-4339 0d..1 50us : get_page_from_freelist (__alloc_pages_internal) ls-4339 0d..2 51us : trace_hardirqs_on (__alloc_pages_internal)
Here we traced a 50 microsecond latency. But we also see all the functions that were called during that time. Note that by enabling function tracing, we incur an added overhead. This overhead may extend the latency times. But nevertheless, this trace has provided some very helpful debugging information.
preemptoff
When preemption is disabled, we may be able to receive interrupts but the task cannot be preempted and a higher priority task must wait for preemption to be enabled again before it can preempt a lower priority task.
The preemptoff tracer traces the places that disable preemption. Like the irqsoff tracer, it records the maximum latency for which preemption was disabled. The control of preemptoff tracer is much like the irqsoff tracer.
# echo preemptoff > current_tracer
# echo latency-format > trace_options
# echo 0 > tracing_max_latency
# echo 1 > tracing_enabled
# ls -ltr
[…]
# echo 0 > tracing_enabled
# cat trace # tracer: preemptoff # preemptoff latency trace v1.1.5 on 2.6.26-rc8
——————————————————————– latency: 29 us, #3/3, CPU#0 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2)
—————–
| task: sshd-4261 (uid:0 nice:0 policy:0 rt_prio:0)
—————– => started at: do_IRQ
=> ended at: __do_softirq
# _——=> CPU# # / _—–=> irqs-off # | / _—-=> need-resched # || / _—=> hardirq/softirq # ||| / _–=> preempt-depth # |||| / # ||||| delay # cmd pid ||||| time | caller # \ / ||||| \ | / sshd-4261 0d.h. 0us+: irq_enter (do_IRQ)
sshd-4261 0d.s. 29us : _local_bh_enable (__do_softirq)
sshd-4261 0d.s1 30us : trace_preempt_on (__do_softirq)
This has some more changes. Preemption was disabled when an interrupt came in (notice the 'h'), and was enabled while doing a softirq. (notice the 's'). But we also see that interrupts have been disabled when entering the preempt off section and leaving it (the 'd'). We do not know if interrupts were enabled in the mean time.
# tracer: preemptoff # preemptoff latency trace v1.1.5 on 2.6.26-rc8
——————————————————————– latency: 63 us, #87/87, CPU#0 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2)
—————–
| task: sshd-4261 (uid:0 nice:0 policy:0 rt_prio:0)
—————– => started at: remove_wait_queue
=> ended at: __do_softirq
# _——=> CPU# # / _—–=> irqs-off # | / _—-=> need-resched # || / _—=> hardirq/softirq # ||| / _–=> preempt-depth # |||| / # ||||| delay # cmd pid ||||| time | caller # \ / ||||| \ | / sshd-4261 0d..1 0us : _spin_lock_irqsave (remove_wait_queue)
sshd-4261 0d..1 1us : _spin_unlock_irqrestore (remove_wait_queue) sshd-4261 0d..1 2us : do_IRQ (common_interrupt)
sshd-4261 0d..1 2us : irq_enter (do_IRQ)
sshd-4261 0d..1 2us : idle_cpu (irq_enter)
sshd-4261 0d..1 3us : add_preempt_count (irq_enter)
sshd-4261 0d.h1 3us : idle_cpu (irq_enter)
sshd-4261 0d.h. 4us : handle_fasteoi_irq (do_IRQ) […] sshd-4261 0d.h. 12us : add_preempt_count (_spin_lock)
sshd-4261 0d.h1 12us : ack_ioapic_quirk_irq (handle_fasteoi_irq) sshd-4261 0d.h1 13us : move_native_irq (ack_ioapic_quirk_irq)
sshd-4261 0d.h1 13us : _spin_unlock (handle_fasteoi_irq)
sshd-4261 0d.h1 14us : sub_preempt_count (_spin_unlock)
sshd-4261 0d.h1 14us : irq_exit (do_IRQ)
sshd-4261 0d.h1 15us : sub_preempt_count (irq_exit)
sshd-4261 0d..2 15us : do_softirq (irq_exit)
sshd-4261 0d… 15us : __do_softirq (do_softirq)
sshd-4261 0d… 16us : __local_bh_disable (__do_softirq)
sshd-4261 0d… 16us+: add_preempt_count (__local_bh_disable) sshd-4261 0d.s4 20us : add_preempt_count (__local_bh_disable)
sshd-4261 0d.s4 21us : sub_preempt_count (local_bh_enable)
sshd-4261 0d.s5 21us : sub_preempt_count (local_bh_enable) […] sshd-4261 0d.s6 41us : add_preempt_count (__local_bh_disable)
sshd-4261 0d.s6 42us : sub_preempt_count (local_bh_enable)
sshd-4261 0d.s7 42us : sub_preempt_count (local_bh_enable)
sshd-4261 0d.s5 43us : add_preempt_count (__local_bh_disable) sshd-4261 0d.s5 43us : sub_preempt_count (local_bh_enable_ip)
sshd-4261 0d.s6 44us : sub_preempt_count (local_bh_enable_ip) sshd-4261 0d.s5 44us : add_preempt_count (__local_bh_disable)
sshd-4261 0d.s5 45us : sub_preempt_count (local_bh_enable) […] sshd-4261 0d.s. 63us : _local_bh_enable (__do_softirq)
sshd-4261 0d.s1 64us : trace_preempt_on (__do_softirq)
The above is an example of the preemptoff trace with ftrace_enabled set. Here we see that interrupts were disabled the entire time. The irq_enter code lets us know that we entered an interrupt 'h'. Before that, the functions being traced still show that it is not in an interrupt, but we can see from the functions themselves that this is not the case.
Notice that __do_softirq when called does not have a preempt_count. It may seem that we missed a preempt enabling. What really happened is that the preempt count is held on the thread's stack and we switched to the softirq stack (4K stacks in effect). The code does not copy the preempt count, but because interrupts are disabled, we do not need to worry about it. Having a tracer like this is good for letting people know what really happens inside the kernel.
preemptirqsoff
Knowing the locations that have interrupts disabled or preemption disabled for the longest times is helpful. But sometimes we would like to know when either preemption and/or interrupts are disabled.
Consider the following code:
local_irq_disable();
call_function_with_irqs_off();
preempt_disable();
call_function_with_irqs_and_preemption_off();
local_irq_enable();
call_function_with_preemption_off();
preempt_enable();
The irqsoff tracer will record the total length of call_function_with_irqs_off() and call_function_with_irqs_and_preemption_off().
The preemptoff tracer will record the total length of call_function_with_irqs_and_preemption_off() and call_function_with_preemption_off().
But neither will trace the time that interrupts and/or preemption is disabled. This total time is the time that we can not schedule. To record this time, use the preemptirqsoff tracer.
Again, using this trace is much like the irqsoff and preemptoff tracers.
# echo preemptirqsoff > current_tracer
# echo latency-format > trace_options
# echo 0 > tracing_max_latency
# echo 1 > tracing_enabled
# ls -ltr
[…]
# echo 0 > tracing_enabled
# cat trace # tracer: preemptirqsoff # preemptirqsoff latency trace v1.1.5 on 2.6.26-rc8
——————————————————————– latency: 293 us, #3/3, CPU#0 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2)
—————–
| task: ls-4860 (uid:0 nice:0 policy:0 rt_prio:0)
—————– => started at: apic_timer_interrupt
=> ended at: __do_softirq
# _——=> CPU# # / _—–=> irqs-off # | / _—-=> need-resched # || / _—=> hardirq/softirq # ||| / _–=> preempt-depth # |||| / # ||||| delay # cmd pid ||||| time | caller # \ / ||||| \ | / ls-4860 0d… 0us!: trace_hardirqs_off_thunk (apic_timer_interrupt) ls-4860 0d.s. 294us : _local_bh_enable (__do_softirq)
ls-4860 0d.s1 294us : trace_preempt_on (__do_softirq)
The trace_hardirqs_off_thunk is called from assembly on x86 when interrupts are disabled in the assembly code. Without the function tracing, we do not know if interrupts were enabled within the preemption points. We do see that it started with preemption enabled.
Here is a trace with ftrace_enabled set:
# tracer: preemptirqsoff # preemptirqsoff latency trace v1.1.5 on 2.6.26-rc8
——————————————————————– latency: 105 us, #183/183, CPU#0 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2)
—————–
| task: sshd-4261 (uid:0 nice:0 policy:0 rt_prio:0)
—————– => started at: write_chan
=> ended at: __do_softirq
# _——=> CPU# # / _—–=> irqs-off # | / _—-=> need-resched # || / _—=> hardirq/softirq # ||| / _–=> preempt-depth # |||| / # ||||| delay # cmd pid ||||| time | caller # \ / ||||| \ | / ls-4473 0.N.. 0us : preempt_schedule (write_chan)
ls-4473 0dN.1 1us : _spin_lock (schedule)
ls-4473 0dN.1 2us : add_preempt_count (_spin_lock)
ls-4473 0d..2 2us : put_prev_task_fair (schedule) […] ls-4473 0d..2 13us : set_normalized_timespec (ktime_get_ts)
ls-4473 0d..2 13us : __switch_to (schedule) sshd-4261 0d..2 14us : finish_task_switch (schedule)
sshd-4261 0d..2 14us : _spin_unlock_irq (finish_task_switch)
sshd-4261 0d..1 15us : add_preempt_count (_spin_lock_irqsave) sshd-4261 0d..2 16us : _spin_unlock_irqrestore (hrtick_set)
sshd-4261 0d..2 16us : do_IRQ (common_interrupt)
sshd-4261 0d..2 17us : irq_enter (do_IRQ)
sshd-4261 0d..2 17us : idle_cpu (irq_enter)
sshd-4261 0d..2 18us : add_preempt_count (irq_enter)
sshd-4261 0d.h2 18us : idle_cpu (irq_enter)
sshd-4261 0d.h. 18us : handle_fasteoi_irq (do_IRQ)
sshd-4261 0d.h. 19us : _spin_lock (handle_fasteoi_irq)
sshd-4261 0d.h. 19us : add_preempt_count (_spin_lock)
sshd-4261 0d.h1 20us : _spin_unlock (handle_fasteoi_irq)
sshd-4261 0d.h1 20us : sub_preempt_count (_spin_unlock) […] sshd-4261 0d.h1 28us : _spin_unlock (handle_fasteoi_irq)
sshd-4261 0d.h1 29us : sub_preempt_count (_spin_unlock)
sshd-4261 0d.h2 29us : irq_exit (do_IRQ)
sshd-4261 0d.h2 29us : sub_preempt_count (irq_exit)
sshd-4261 0d..3 30us : do_softirq (irq_exit)
sshd-4261 0d… 30us : __do_softirq (do_softirq)
sshd-4261 0d… 31us : __local_bh_disable (__do_softirq)
sshd-4261 0d… 31us+: add_preempt_count (__local_bh_disable) sshd-4261 0d.s4 34us : add_preempt_count (__local_bh_disable) […] sshd-4261 0d.s3 43us : sub_preempt_count (local_bh_enable_ip)
sshd-4261 0d.s4 44us : sub_preempt_count (local_bh_enable_ip) sshd-4261 0d.s3 44us : smp_apic_timer_interrupt (apic_timer_interrupt) sshd-4261 0d.s3 45us : irq_enter (smp_apic_timer_interrupt)
sshd-4261 0d.s3 45us : idle_cpu (irq_enter)
sshd-4261 0d.s3 46us : add_preempt_count (irq_enter)
sshd-4261 0d.H3 46us : idle_cpu (irq_enter)
sshd-4261 0d.H3 47us : hrtimer_interrupt (smp_apic_timer_interrupt) sshd-4261 0d.H3 47us : ktime_get (hrtimer_interrupt) […] sshd-4261 0d.H3 81us : tick_program_event (hrtimer_interrupt)
sshd-4261 0d.H3 82us : ktime_get (tick_program_event)
sshd-4261 0d.H3 82us : ktime_get_ts (ktime_get)
sshd-4261 0d.H3 83us : getnstimeofday (ktime_get_ts)
sshd-4261 0d.H3 83us : set_normalized_timespec (ktime_get_ts) sshd-4261 0d.H3 84us : clockevents_program_event (tick_program_event) sshd-4261 0d.H3 84us : lapic_next_event (clockevents_program_event) sshd-4261 0d.H3 85us : irq_exit (smp_apic_timer_interrupt)
sshd-4261 0d.H3 85us : sub_preempt_count (irq_exit)
sshd-4261 0d.s4 86us : sub_preempt_count (irq_exit)
sshd-4261 0d.s3 86us : add_preempt_count (__local_bh_disable) […] sshd-4261 0d.s1 98us : sub_preempt_count (net_rx_action)
sshd-4261 0d.s. 99us : add_preempt_count (_spin_lock_irq)
sshd-4261 0d.s1 99us+: _spin_unlock_irq (run_timer_softirq)
sshd-4261 0d.s. 104us : _local_bh_enable (__do_softirq)
sshd-4261 0d.s. 104us : sub_preempt_count (_local_bh_enable)
sshd-4261 0d.s. 105us : _local_bh_enable (__do_softirq)
sshd-4261 0d.s1 105us : trace_preempt_on (__do_softirq)
This is a very interesting trace. It started with the preemption of the ls task. We see that the task had the "need_resched" bit set via the 'N' in the trace. Interrupts were disabled before the spin_lock at the beginning of the trace. We see that a schedule took place to run sshd. When the interrupts were enabled, we took an interrupt. On return from the interrupt handler, the softirq ran. We took another interrupt while running the softirq as we see from the capital 'H'.
wakeup
In a Real-Time environment it is very important to know the wakeup time it takes for the highest priority task that is woken up to the time that it executes. This is also known as "schedule latency". I stress the point that this is about RT tasks. It is also important to know the scheduling latency of non-RT tasks, but the average schedule latency is better for non-RT tasks. Tools like LatencyTop are more appropriate for such measurements.
Real-Time environments are interested in the worst case latency. That is the longest latency it takes for something to happen, and not the average. We can have a very fast scheduler that may only have a large latency once in a while, but that would not work well with Real-Time tasks. The wakeup tracer was designed to record the worst case wakeups of RT tasks. Non-RT tasks are not recorded because the tracer only records one worst case and tracing non-RT tasks that are unpredictable will overwrite the worst case latency of RT tasks.
Since this tracer only deals with RT tasks, we will run this slightly differently than we did with the previous tracers. Instead of performing an 'ls', we will run 'sleep 1' under 'chrt' which changes the priority of the task.
# echo wakeup > current_tracer
# echo latency-format > trace_options
# echo 0 > tracing_max_latency
# echo 1 > tracing_enabled
# chrt -f 5 sleep 1
# echo 0 > tracing_enabled
# cat trace # tracer: wakeup # wakeup latency trace v1.1.5 on 2.6.26-rc8
——————————————————————– latency: 4 us, #2/2, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2)
—————–
| task: sleep-4901 (uid:0 nice:0 policy:1 rt_prio:5)
—————–
# _——=> CPU# # / _—–=> irqs-off # | / _—-=> need-resched # || / _—=> hardirq/softirq # ||| / _–=> preempt-depth # |||| / # ||||| delay # cmd pid ||||| time | caller # \ / ||||| \ | / <idle>-0 1d.h4 0us+: try_to_wake_up (wake_up_process)
<idle>-0 1d..4 4us : schedule (cpu_idle)
Running this on an idle system, we see that it only took 4 microseconds to perform the task switch. Note, since the trace marker in the schedule is before the actual "switch", we stop the tracing when the recorded task is about to schedule in. This may change if we add a new marker at the end of the scheduler.
Notice that the recorded task is 'sleep' with the PID of 4901 and it has an rt_prio of 5. This priority is user-space priority and not the internal kernel priority. The policy is 1 for SCHED_FIFO and 2 for SCHED_RR.
Doing the same with chrt -r 5 and ftrace_enabled set.
# tracer: wakeup # wakeup latency trace v1.1.5 on 2.6.26-rc8
——————————————————————– latency: 50 us, #60/60, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2)
—————–
| task: sleep-4068 (uid:0 nice:0 policy:2 rt_prio:5)
—————–
# _——=> CPU# # / _—–=> irqs-off # | / _—-=> need-resched # || / _—=> hardirq/softirq # ||| / _–=> preempt-depth # |||| / # ||||| delay # cmd pid ||||| time | caller # \ / ||||| \ | / ksoftirq-7 1d.H3 0us : try_to_wake_up (wake_up_process) ksoftirq-7 1d.H4 1us : sub_preempt_count (marker_probe_cb) ksoftirq-7 1d.H3 2us : check_preempt_wakeup (try_to_wake_up) ksoftirq-7 1d.H3 3us : update_curr (check_preempt_wakeup) ksoftirq-7 1d.H3 4us : calc_delta_mine (update_curr) ksoftirq-7 1d.H3 5us : __resched_task (check_preempt_wakeup) ksoftirq-7 1d.H3 6us : task_wake_up_rt (try_to_wake_up) ksoftirq-7 1d.H3 7us : _spin_unlock_irqrestore (try_to_wake_up) […] ksoftirq-7 1d.H2 17us : irq_exit (smp_apic_timer_interrupt) ksoftirq-7 1d.H2 18us : sub_preempt_count (irq_exit) ksoftirq-7 1d.s3 19us : sub_preempt_count (irq_exit) ksoftirq-7 1..s2 20us : rcu_process_callbacks (__do_softirq) […] ksoftirq-7 1..s2 26us : __rcu_process_callbacks (rcu_process_callbacks) ksoftirq-7 1d.s2 27us : _local_bh_enable (__do_softirq) ksoftirq-7 1d.s2 28us : sub_preempt_count (_local_bh_enable) ksoftirq-7 1.N.3 29us : sub_preempt_count (ksoftirqd) ksoftirq-7 1.N.2 30us : _cond_resched (ksoftirqd) ksoftirq-7 1.N.2 31us : __cond_resched (_cond_resched) ksoftirq-7 1.N.2 32us : add_preempt_count (__cond_resched) ksoftirq-7 1.N.2 33us : schedule (__cond_resched) ksoftirq-7 1.N.2 33us : add_preempt_count (schedule) ksoftirq-7 1.N.3 34us : hrtick_clear (schedule) ksoftirq-7 1dN.3 35us : _spin_lock (schedule) ksoftirq-7 1dN.3 36us : add_preempt_count (_spin_lock) ksoftirq-7 1d..4 37us : put_prev_task_fair (schedule) ksoftirq-7 1d..4 38us : update_curr (put_prev_task_fair) […] ksoftirq-7 1d..5 47us : _spin_trylock (tracing_record_cmdline) ksoftirq-7 1d..5 48us : add_preempt_count (_spin_trylock) ksoftirq-7 1d..6 49us : _spin_unlock (tracing_record_cmdline) ksoftirq-7 1d..6 49us : sub_preempt_count (_spin_unlock) ksoftirq-7 1d..4 50us : schedule (__cond_resched)
The interrupt went off while running ksoftirqd. This task runs at SCHED_OTHER. Why did not we see the 'N' set early? This may be a harmless bug with x86_32 and 4K stacks. On x86_32 with 4K stacks configured, the interrupt and softirq run with their own stack. Some information is held on the top of the task's stack (need_resched and preempt_count are both stored there). The setting of the NEED_RESCHED bit is done directly to the task's stack, but the reading of the NEED_RESCHED is done by looking at the current stack, which in this case is the stack for the hard interrupt. This hides the fact that NEED_RESCHED has been set. We do not see the 'N' until we switch back to the task's assigned stack.
function
This tracer is the function tracer. Enabling the function tracer can be done from the debug file system. Make sure the ftrace_enabled is set; otherwise this tracer is a nop.
# sysctl kernel.ftrace_enabled=1
# echo function > current_tracer
# echo 1 > tracing_enabled
# usleep 1
# echo 0 > tracing_enabled
# cat trace # tracer: function # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | bash-4003 [00] 123.638713: finish_task_switch <-schedule
bash-4003 [00] 123.638714: _spin_unlock_irq <-finish_task_switch bash-4003 [00] 123.638714: sub_preempt_count <-_spin_unlock_irq
bash-4003 [00] 123.638715: hrtick_set <-schedule
bash-4003 [00] 123.638715: _spin_lock_irqsave <-hrtick_set bash-4003 [00] 123.638716: add_preempt_count <-_spin_lock_irqsave bash-4003 [00] 123.638716: _spin_unlock_irqrestore <-hrtick_set
bash-4003 [00] 123.638717: sub_preempt_count <-_spin_unlock_irqrestore bash-4003 [00] 123.638717: hrtick_clear <-hrtick_set
bash-4003 [00] 123.638718: sub_preempt_count <-schedule bash-4003 [00] 123.638718: sub_preempt_count <-preempt_schedule
bash-4003 [00] 123.638719: wait_for_completion <-__stop_machine_run bash-4003 [00] 123.638719: wait_for_common <-wait_for_completion
bash-4003 [00] 123.638720: _spin_lock_irq <-wait_for_common bash-4003 [00] 123.638720: add_preempt_count <-_spin_lock_irq […]
Note: function tracer uses ring buffers to store the above entries. The newest data may overwrite the oldest data. Sometimes using echo to stop the trace is not sufficient because the tracing could have overwritten the data that you wanted to record. For this reason, it is sometimes better to disable tracing directly from a program. This allows you to stop the tracing at the point that you hit the part that you are interested in. To disable the tracing directly from a C program, something like following code snippet can be used:
int trace_fd; […] int main(int argc, char *argv[]) { […]
trace_fd = open(tracing_file("tracing_enabled"), O_WRONLY);
[…]
if (condition_hit()) { write(trace_fd, "0", 1); }
[…] }
Single thread tracing
By writing into set_ftrace_pid you can trace a single thread. For example:
# cat set_ftrace_pid no pid # echo 3111 > set_ftrace_pid # cat set_ftrace_pid 3111 # echo function > current_tracer # cat trace | head # tracer: function
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | | yum-updatesd-3111 [003] 1637.254676: finish_task_switch <-thread_return yum-updatesd-3111 [003] 1637.254681: hrtimer_cancel <-schedule_hrtimeout_range yum-updatesd-3111 [003] 1637.254682: hrtimer_try_to_cancel <-hrtimer_cancel yum-updatesd-3111 [003] 1637.254683: lock_hrtimer_base <-hrtimer_try_to_cancel yum-updatesd-3111 [003] 1637.254685: fget_light <-do_sys_poll
yum-updatesd-3111 [003] 1637.254686: pipe_poll <-do_sys_poll # echo -1 > set_ftrace_pid # cat trace |head # tracer: function
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
##### CPU 3 buffer started #### yum-updatesd-3111 [003] 1701.957688: free_poll_entry <-poll_freewait yum-updatesd-3111 [003] 1701.957689: remove_wait_queue <-free_poll_entry yum-updatesd-3111 [003] 1701.957691: fput <-free_poll_entry
yum-updatesd-3111 [003] 1701.957692: audit_syscall_exit <-sysret_audit yum-updatesd-3111 [003] 1701.957693: path_put <-audit_syscall_exit
If you want to trace a function when executing, you could use something like this simple program:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h>
#define _STR(x) #x #define STR(x) _STR(x) #define MAX_PATH 256
const char *find_debugfs(void) { static char debugfs[MAX_PATH+1];
static int debugfs_found;
char type[100];
FILE *fp;
if (debugfs_found) return debugfs;
if ((fp = fopen("/proc/mounts","r")) == NULL) { perror("/proc/mounts");
return NULL; }
while (fscanf(fp, "%*s %" STR(MAX_PATH)
"s %99s %*s %*d %*d\n",
debugfs, type) == 2) { if (strcmp(type, "debugfs") == 0) break; }
fclose(fp);
if (strcmp(type, "debugfs") != 0) { fprintf(stderr, "debugfs not mounted");
return NULL; }
strcat(debugfs, "/tracing/");
debugfs_found = 1;
return debugfs; }
const char *tracing_file(const char *file_name) { static char trace_file[MAX_PATH+1];
snprintf(trace_file, MAX_PATH, "%s/%s", find_debugfs(), file_name); return trace_file; }
int main (int argc, char **argv) { if (argc < 1) exit(-1);
if (fork() > 0) { int fd, ffd;
char line[64];
int s;
ffd = open(tracing_file("current_tracer"), O_WRONLY);
if (ffd < 0) exit(-1); write(ffd, "nop", 3);
fd = open(tracing_file("set_ftrace_pid"), O_WRONLY);
s = sprintf(line, "%d\n", getpid());
write(fd, line, s);
write(ffd, "function", 8);
close(fd);
close(ffd);
execvp(argv[1], argv+1); }
return 0; }
hw-branch-tracer (x86 only)
This tracer uses the x86 last branch tracing hardware feature to collect a branch trace on all cpus with relatively low overhead.
The tracer uses a fixed-size circular buffer per cpu and only traces ring 0 branches. The trace file dumps that buffer in the following format:
# tracer: hw-branch-tracer # # CPU# TO <- FROM 0 scheduler_tick+0xb5/0x1bf <- task_tick_idle+0x5/0x6 2 run_posix_cpu_timers+0x2b/0x72a <- run_posix_cpu_timers+0x25/0x72a 0 scheduler_tick+0x139/0x1bf <- scheduler_tick+0xed/0x1bf 0 scheduler_tick+0x17c/0x1bf <- scheduler_tick+0x148/0x1bf 2 run_posix_cpu_timers+0x9e/0x72a <- run_posix_cpu_timers+0x5e/0x72a 0 scheduler_tick+0x1b6/0x1bf <- scheduler_tick+0x1aa/0x1bf
The tracer may be used to dump the trace for the oops'ing cpu on a kernel oops into the system log. To enable this, ftrace_dump_on_oops must be set. To set ftrace_dump_on_oops, one can either use the sysctl function or set it via the proc system interface.
sysctl kernel.ftrace_dump_on_oops=n
or
echo n > /proc/sys/kernel/ftrace_dump_on_oops
If n = 1, ftrace will dump buffers of all CPUs, if n = 2 ftrace will only dump the buffer of the CPU that triggered the oops.
Here's an example of such a dump after a null pointer dereference in a kernel module:
[57848.105921] BUG: unable to handle kernel NULL pointer dereference at 0000000000000000 [57848.106019] IP: [<ffffffffa0000006>] open+0x6/0x14 [oops] [57848.106019] PGD 2354e9067 PUD 2375e7067 PMD 0 [57848.106019] Oops: 0002 [#1] SMP [57848.106019] last sysfs file: /sys/devices/pci0000:00/0000:00:1e.0/0000:20:05.0/local_cpus [57848.106019] Dumping ftrace buffer: [57848.106019] ——————————— […] [57848.106019] 0 chrdev_open+0xe6/0x165 <- cdev_put+0x23/0x24 [57848.106019] 0 chrdev_open+0x117/0x165 <- chrdev_open+0xfa/0x165 [57848.106019] 0 chrdev_open+0x120/0x165 <- chrdev_open+0x11c/0x165 [57848.106019] 0 chrdev_open+0x134/0x165 <- chrdev_open+0x12b/0x165 [57848.106019] 0 open+0x0/0x14 [oops] <- chrdev_open+0x144/0x165 [57848.106019] 0 page_fault+0x0/0x30 <- open+0x6/0x14 [oops] [57848.106019] 0 error_entry+0x0/0x5b <- page_fault+0x4/0x30 [57848.106019] 0 error_kernelspace+0x0/0x31 <- error_entry+0x59/0x5b [57848.106019] 0 error_sti+0x0/0x1 <- error_kernelspace+0x2d/0x31 [57848.106019] 0 page_fault+0x9/0x30 <- error_sti+0x0/0x1 [57848.106019] 0 do_page_fault+0x0/0x881 <- page_fault+0x1a/0x30 […] [57848.106019] 0 do_page_fault+0x66b/0x881 <- is_prefetch+0x1ee/0x1f2 [57848.106019] 0 do_page_fault+0x6e0/0x881 <- do_page_fault+0x67a/0x881 [57848.106019] 0 oops_begin+0x0/0x96 <- do_page_fault+0x6e0/0x881 [57848.106019] 0 trace_hw_branch_oops+0x0/0x2d <- oops_begin+0x9/0x96 […] [57848.106019] 0 ds_suspend_bts+0x2a/0xe3 <- ds_suspend_bts+0x1a/0xe3 [57848.106019] ——————————— [57848.106019] CPU 0 [57848.106019] Modules linked in: oops [57848.106019] Pid: 5542, comm: cat Tainted: G W 2.6.28 #23 [57848.106019] RIP: 0010:[<ffffffffa0000006>] [<ffffffffa0000006>] open+0x6/0x14 [oops] [57848.106019] RSP: 0018:ffff880235457d48 EFLAGS: 00010246 […]
function graph tracer
This tracer is similar to the function tracer except that it probes a function on its entry and its exit. This is done by using a dynamically allocated stack of return addresses in each task_struct. On function entry the tracer overwrites the return address of each function traced to set a custom probe. Thus the original return address is stored on the stack of return address in the task_struct.
Probing on both ends of a function leads to special features such as:
- measure of a function's time execution
- having a reliable call stack to draw function calls graph
This tracer is useful in several situations:
- you want to find the reason of a strange kernel behavior and need to see what happens in detail on any areas (or specific
ones).
- you are experiencing weird latencies but it's difficult to find its origin.
- you want to find quickly which path is taken by a specific function
- you just want to peek inside a working kernel and want to see what happens there.
# tracer: function_graph # # CPU DURATION FUNCTION CALLS # | | | | | | |
0) | sys_open() { 0) | do_sys_open() { 0) | getname() { 0) | kmem_cache_alloc() { 0) 1.382 us | __might_sleep(); 0) 2.478 us | } 0) | strncpy_from_user() { 0) | might_fault() { 0) 1.389 us | __might_sleep(); 0) 2.553 us | } 0) 3.807 us | } 0) 7.876 us | } 0) | alloc_fd() { 0) 0.668 us | _spin_lock(); 0) 0.570 us | expand_files(); 0) 0.586 us | _spin_unlock();
There are several columns that can be dynamically enabled/disabled. You can use every combination of options you want, depending on your needs.
- The cpu number on which the function executed is default enabled. It is sometimes better to only trace one cpu (see tracing_cpu_mask file) or you might sometimes see unordered
function calls while cpu tracing switch.
hide: echo nofuncgraph-cpu > trace_options
show: echo funcgraph-cpu > trace_options
- The duration (function's time of execution) is displayed on the closing bracket line of a function or on the same line
than the current function in case of a leaf one. It is default
enabled.
hide: echo nofuncgraph-duration > trace_options
show: echo funcgraph-duration > trace_options
- The overhead field precedes the duration field in case of reached duration thresholds.
hide: echo nofuncgraph-overhead > trace_options
show: echo funcgraph-overhead > trace_options
depends on: funcgraph-duration
ie:
0) | up_write() { 0) 0.646 us | _spin_lock_irqsave(); 0) 0.684 us | _spin_unlock_irqrestore(); 0) 3.123 us | } 0) 0.548 us | fput(); 0) + 58.628 us | }
[…]
0) | putname() { 0) | kmem_cache_free() { 0) 0.518 us | __phys_addr(); 0) 1.757 us | } 0) 2.861 us | } 0) ! 115.305 us | } 0) ! 116.402 us | }
+ means that the function exceeded 10 usecs.
! means that the function exceeded 100 usecs.
- The task/pid field displays the thread cmdline and pid which executed the function. It is default disabled.
hide: echo nofuncgraph-proc > trace_options
show: echo funcgraph-proc > trace_options
ie:
# tracer: function_graph
#
# CPU TASK/PID DURATION FUNCTION CALLS
# | | | | | | | | | 0) sh-4802 | | d_free() { 0) sh-4802 | | call_rcu() { 0) sh-4802 | | __call_rcu() { 0) sh-4802 | 0.616 us | rcu_process_gp_end(); 0) sh-4802 | 0.586 us | check_for_new_grace_period(); 0) sh-4802 | 2.899 us | } 0) sh-4802 | 4.040 us | } 0) sh-4802 | 5.151 us | } 0) sh-4802 | + 49.370 us | }
- The absolute time field is an absolute timestamp given by the system clock since it started. A snapshot of this time is
given on each entry/exit of functions
hide: echo nofuncgraph-abstime > trace_options
show: echo funcgraph-abstime > trace_options
ie:
#
# TIME CPU DURATION FUNCTION CALLS
# | | | | | | | |
360.774522 | 1) 0.541 us | }
360.774522 | 1) 4.663 us | }
360.774523 | 1) 0.541 us | __wake_up_bit();
360.774524 | 1) 6.796 us | }
360.774524 | 1) 7.952 us | }
360.774525 | 1) 9.063 us | }
360.774525 | 1) 0.615 us | journal_mark_dirty();
360.774527 | 1) 0.578 us | __brelse();
360.774528 | 1) | reiserfs_prepare_for_journal() {
360.774528 | 1) | unlock_buffer() {
360.774529 | 1) | wake_up_bit() {
360.774529 | 1) | bit_waitqueue() {
360.774530 | 1) 0.594 us | __phys_addr();
You can put some comments on specific functions by using trace_printk() For example, if you want to put a comment inside the __might_sleep() function, you just have to include <linux/ftrace.h> and call trace_printk() inside __might_sleep()
trace_printk("I'm a comment!\n")
will produce:
1) | __might_sleep() { 1) | /* I'm a comment! */ 1) 1.449 us | }
You might find other useful features for this tracer in the following "dynamic ftrace" section such as tracing only specific functions or tasks.
dynamic ftrace
If CONFIG_DYNAMIC_FTRACE is set, the system will run with virtually no overhead when function tracing is disabled. The way this works is the mcount function call (placed at the start of every kernel function, produced by the -pg switch in gcc), starts of pointing to a simple return. (Enabling FTRACE will include the -pg switch in the compiling of the kernel.)
At compile time every C file object is run through the recordmcount.pl script (located in the scripts directory). This script will process the C object using objdump to find all the locations in the .text section that call mcount. (Note, only the .text section is processed, since processing other sections like .init.text may cause races due to those sections being freed).
A new section called "mcount_loc" is created that holds references to all the mcount call sites in the .text section. This section is compiled back into the original object. The final linker will add all these references into a single table.
On boot up, before SMP is initialized, the dynamic ftrace code scans this table and updates all the locations into nops. It also records the locations, which are added to the available_filter_functions list. Modules are processed as they are loaded and before they are executed. When a module is unloaded, it also removes its functions from the ftrace function list. This is automatic in the module unload code, and the module author does not need to worry about it.
When tracing is enabled, kstop_machine is called to prevent races with the CPUS executing code being modified (which can cause the CPU to do undesirable things), and the nops are patched back to calls. But this time, they do not call mcount (which is just a function stub). They now call into the ftrace infrastructure.
One special side-effect to the recording of the functions being traced is that we can now selectively choose which functions we wish to trace and which ones we want the mcount calls to remain as nops.
Two files are used, one for enabling and one for disabling the tracing of specified functions. They are:
set_ftrace_filter
and
set_ftrace_notrace
A list of available functions that you can add to these files is listed in:
available_filter_functions
# cat available_filter_functions put_prev_task_idle kmem_cache_create pick_next_task_rt get_online_cpus pick_next_task_fair mutex_lock […]
If I am only interested in sys_nanosleep and hrtimer_interrupt:
# echo sys_nanosleep hrtimer_interrupt \ > set_ftrace_filter # echo function > current_tracer
# echo 1 > tracing_enabled
# usleep 1
# echo 0 > tracing_enabled
# cat trace # tracer: ftrace # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | usleep-4134 [00] 1317.070017: hrtimer_interrupt <-smp_apic_timer_interrupt usleep-4134 [00] 1317.070111: sys_nanosleep <-syscall_call
<idle>-0 [00] 1317.070115: hrtimer_interrupt <-smp_apic_timer_interrupt
To see which functions are being traced, you can cat the file:
# cat set_ftrace_filter hrtimer_interrupt sys_nanosleep
Perhaps this is not enough. The filters also allow simple wild cards. Only the following are currently available
<match>* – will match functions that begin with <match>
*<match> – will match functions that end with <match>
*<match>* – will match functions that have <match> in it
These are the only wild cards which are supported.
<match>*<match> will not work.
Note: It is better to use quotes to enclose the wild cards, otherwise the shell may expand the parameters into names
of files in the local directory.
# echo 'hrtimer*' > set_ftrace_filter
Produces:
# tracer: ftrace # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | bash-4003 [00] 1480.611794: hrtimer_init <-copy_process
bash-4003 [00] 1480.611941: hrtimer_start <-hrtick_set bash-4003 [00] 1480.611956: hrtimer_cancel <-hrtick_clear
bash-4003 [00] 1480.611956: hrtimer_try_to_cancel <-hrtimer_cancel <idle>-0 [00] 1480.612019: hrtimer_get_next_event <-get_next_timer_interrupt <idle>-0 [00] 1480.612025: hrtimer_get_next_event <-get_next_timer_interrupt <idle>-0 [00] 1480.612032: hrtimer_get_next_event <-get_next_timer_interrupt <idle>-0 [00] 1480.612037: hrtimer_get_next_event <-get_next_timer_interrupt <idle>-0 [00] 1480.612382: hrtimer_get_next_event <-get_next_timer_interrupt
Notice that we lost the sys_nanosleep.
# cat set_ftrace_filter hrtimer_run_queues hrtimer_run_pending hrtimer_init hrtimer_cancel hrtimer_try_to_cancel hrtimer_forward hrtimer_start hrtimer_reprogram hrtimer_force_reprogram hrtimer_get_next_event hrtimer_interrupt hrtimer_nanosleep hrtimer_wakeup hrtimer_get_remaining hrtimer_get_res hrtimer_init_sleeper
This is because the '>' and '>>' act just like they do in bash. To rewrite the filters, use '>' To append to the filters, use '>>'
To clear out a filter so that all functions will be recorded again:
# echo > set_ftrace_filter
# cat set_ftrace_filter
#
Again, now we want to append.
# echo sys_nanosleep > set_ftrace_filter
# cat set_ftrace_filter sys_nanosleep # echo 'hrtimer*' >> set_ftrace_filter
# cat set_ftrace_filter hrtimer_run_queues hrtimer_run_pending hrtimer_init hrtimer_cancel hrtimer_try_to_cancel hrtimer_forward hrtimer_start hrtimer_reprogram hrtimer_force_reprogram hrtimer_get_next_event hrtimer_interrupt sys_nanosleep hrtimer_nanosleep hrtimer_wakeup hrtimer_get_remaining hrtimer_get_res hrtimer_init_sleeper
The set_ftrace_notrace prevents those functions from being traced.
# echo '*preempt*' '*lock*' > set_ftrace_notrace
Produces:
# tracer: ftrace # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | bash-4043 [01] 115.281644: finish_task_switch <-schedule
bash-4043 [01] 115.281645: hrtick_set <-schedule
bash-4043 [01] 115.281645: hrtick_clear <-hrtick_set
bash-4043 [01] 115.281646: wait_for_completion <-__stop_machine_run bash-4043 [01] 115.281647: wait_for_common <-wait_for_completion
bash-4043 [01] 115.281647: kthread_stop <-stop_machine_run bash-4043 [01] 115.281648: init_waitqueue_head <-kthread_stop
bash-4043 [01] 115.281648: wake_up_process <-kthread_stop bash-4043 [01] 115.281649: try_to_wake_up <-wake_up_process
We can see that there's no more lock or preempt tracing.
Dynamic ftrace with the function graph tracer
Although what has been explained above concerns both the function tracer and the function-graph-tracer, there are some special features only available in the function-graph tracer.
If you want to trace only one function and all of its children, you just have to echo its name into set_graph_function:
echo __do_fault > set_graph_function
will produce the following "expanded" trace of the __do_fault() function:
0) | __do_fault() { 0) | filemap_fault() { 0) | find_lock_page() { 0) 0.804 us | find_get_page(); 0) | __might_sleep() { 0) 1.329 us | } 0) 3.904 us | } 0) 4.979 us | } 0) 0.653 us | _spin_lock(); 0) 0.578 us | page_add_file_rmap(); 0) 0.525 us | native_set_pte_at(); 0) 0.585 us | _spin_unlock(); 0) | unlock_page() { 0) 0.541 us | page_waitqueue(); 0) 0.639 us | __wake_up_bit(); 0) 2.786 us | } 0) + 14.237 us | } 0) | __do_fault() { 0) | filemap_fault() { 0) | find_lock_page() { 0) 0.698 us | find_get_page(); 0) | __might_sleep() { 0) 1.412 us | } 0) 3.950 us | } 0) 5.098 us | } 0) 0.631 us | _spin_lock(); 0) 0.571 us | page_add_file_rmap(); 0) 0.526 us | native_set_pte_at(); 0) 0.586 us | _spin_unlock(); 0) | unlock_page() { 0) 0.533 us | page_waitqueue(); 0) 0.638 us | __wake_up_bit(); 0) 2.793 us | } 0) + 14.012 us | }
You can also expand several functions at once:
echo sys_open > set_graph_function
echo sys_close >> set_graph_function
Now if you want to go back to trace all functions you can clear this special filter via:
echo > set_graph_function
Filter commands
A few commands are supported by the set_ftrace_filter interface. Trace commands have the following format:
<function>:<command>:<parameter>
The following commands are supported:
- mod This command enables function filtering per module. The
parameter defines the module. For example, if only the write* functions in the ext3 module are desired, run:
echo 'write*:mod:ext3' > set_ftrace_filter
This command interacts with the filter in the same way as
filtering based on function names. Thus, adding more functions
in a different module is accomplished by appending (>>) to the
filter file. Remove specific module functions by prepending
'!':
echo '!writeback*:mod:ext3' >> set_ftrace_filter
- traceon/traceoff These commands turn tracing on and off when the specified
functions are hit. The parameter determines how many times the
tracing system is turned on and off. If unspecified, there is
no limit. For example, to disable tracing when a schedule bug
is hit the first 5 times, run:
echo '__schedule_bug:traceoff:5' > set_ftrace_filter
These commands are cumulative whether or not they are appended
to set_ftrace_filter. To remove a command, prepend it by '!'
and drop the parameter:
echo '!__schedule_bug:traceoff' > set_ftrace_filter
trace_pipe
The trace_pipe outputs the same content as the trace file, but the effect on the tracing is different. Every read from trace_pipe is consumed. This means that subsequent reads will be different. The trace is live.
# echo function > current_tracer
# cat trace_pipe > /tmp/trace.out & [1] 4153 # echo 1 > tracing_enabled
# usleep 1
# echo 0 > tracing_enabled
# cat trace # tracer: function # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | |
#
# cat /tmp/trace.out bash-4043 [00] 41.267106: finish_task_switch <-schedule
bash-4043 [00] 41.267106: hrtick_set <-schedule
bash-4043 [00] 41.267107: hrtick_clear <-hrtick_set
bash-4043 [00] 41.267108: wait_for_completion <-__stop_machine_run bash-4043 [00] 41.267108: wait_for_common <-wait_for_completion
bash-4043 [00] 41.267109: kthread_stop <-stop_machine_run bash-4043 [00] 41.267109: init_waitqueue_head <-kthread_stop
bash-4043 [00] 41.267110: wake_up_process <-kthread_stop bash-4043 [00] 41.267110: try_to_wake_up <-wake_up_process
bash-4043 [00] 41.267111: select_task_rq_rt <-try_to_wake_up
Note, reading the trace_pipe file will block until more input is added. By changing the tracer, trace_pipe will issue an EOF. We needed to set the function tracer before we "cat" the trace_pipe file.
trace entries
Having too much or not enough data can be troublesome in diagnosing an issue in the kernel. The file buffer_size_kb is used to modify the size of the internal trace buffers. The number listed is the number of entries that can be recorded per CPU. To know the full size, multiply the number of possible CPUS with the number of entries.
# cat buffer_size_kb 1408 (units kilobytes)
Note, to modify this, you must have tracing completely disabled. To do that, echo "nop" into the current_tracer. If the current_tracer is not set to "nop", an EINVAL error will be returned.
# echo nop > current_tracer
# echo 10000 > buffer_size_kb
# cat buffer_size_kb 10000 (units kilobytes)
The number of pages which will be allocated is limited to a percentage of available memory. Allocating too much will produce an error.
# echo 1000000000000 > buffer_size_kb
-bash: echo: write error: Cannot allocate memory # cat buffer_size_kb 85
More details can be found in the source code, in the kernel/trace/*.c files.
http://www.ibm.com/developerworks/cn/linux/l-cn-ftrace/
参考一下这个link也不错。