操作系统的内核是目前最难以捉摸的软件之一。从您的系统启动时起,它始终在后台运行。每个用户都在内核的帮助下完成他们的计算工作,但他们从不直接与内核交互。与内核的交互是通过发出系统调用,或者由各种库或应用程序代表用户发出这些调用来发生的,这些库或应用程序是他们日常使用的。
在之前的文章中,我介绍了如何使用 strace
跟踪系统调用。但是,使用 strace
,您的可见性是有限的。它允许您查看使用特定参数调用的系统调用,并在工作完成后,查看指示它们通过还是失败的返回值或状态。但是您不知道在此期间内核内部发生了什么。除了服务系统调用之外,内核内部还发生了很多其他活动,而您对此一无所知。
Ftrace 简介
本文旨在通过使用一种名为 ftrace
的机制来阐明内核函数的跟踪。它使任何 Linux 用户都可以轻松访问内核跟踪,并且借助它,您可以了解很多关于 Linux 内核内部的信息。
ftrace
生成的默认输出通常非常庞大,因为内核始终处于繁忙状态。为了节省空间,我将输出保持在最低限度,并且在许多情况下完全截断了输出。
我在这些示例中使用 Fedora,但它们应该在任何最新的 Linux 发行版上都能工作。
启用 ftrace
Ftrace
现在是 Linux 内核的一部分,您不再需要安装任何东西即可使用它。如果您使用的是最新的 Linux 操作系统,则 ftrace
很可能已经启用。要验证 ftrace
功能是否可用,请运行 mount 命令并搜索 tracefs
。如果您看到类似于以下内容的输出,则表示 ftrace
已启用,您可以轻松地按照本文中的示例进行操作。这些命令必须以 root 用户身份运行(sudo
不够)。
# mount | grep tracefs
none on /sys/kernel/tracing type tracefs (rw,relatime,seclabel)
要使用 ftrace
,您首先必须导航到上面 mount 命令中指定的特殊目录,从那里您将运行本文中的其余命令
# cd /sys/kernel/tracing
一般工作流程
首先,您必须了解捕获跟踪和获取输出的一般工作流程。如果您直接使用 ftrace
,则没有任何特殊的 ftrace-
特定命令可以运行。相反,您基本上是使用标准命令行 Linux 实用程序写入一些文件并从一些文件中读取。
一般步骤
- 写入一些特定文件以启用/禁用跟踪。
- 写入一些特定文件以设置/取消设置过滤器以微调跟踪。
- 从基于 1 和 2 的文件中读取生成的跟踪输出。
- 清除文件中较早的输出或缓冲区。
- 缩小到您的特定用例(要跟踪的内核函数)并重复步骤 1、2、3、4。
可用跟踪器类型
有几种不同类型的跟踪器可供您使用。如前所述,在运行任何这些命令之前,您需要位于特定目录中,因为感兴趣的文件位于那里。在我的示例中,我使用相对路径(而不是绝对路径)。
您可以查看 available_tracers
文件的内容以查看所有可用的跟踪器类型。您可以在下面看到一些列出的类型。暂时不用担心所有这些
# pwd
/sys/kernel/tracing
# cat available_tracers
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
在所有给定的跟踪器中,我重点关注三个特定的跟踪器:function
和 function_graph
用于启用跟踪,以及 nop
用于禁用跟踪。
识别当前跟踪器
通常,默认情况下,跟踪器设置为 nop
。也就是说,特殊文件 current_tracer
中的 “No operation”(无操作),这通常意味着跟踪当前已关闭
# pwd
/sys/kernel/tracing
# cat current_tracer
nop
查看跟踪输出
在启用任何跟踪之前,先看一下存储跟踪输出的文件。您可以使用 cat 命令查看名为 trace
的文件的内容
# cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 0/0 #P:8
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
启用 function 跟踪器
您可以通过将 function
写入文件 current_tracer
来启用您的第一个名为 function
的跟踪器(其先前的内容为 nop
,表示跟踪已关闭)。将此操作视为启用跟踪的一种方式
# pwd
/sys/kernel/tracing
# cat current_tracer
nop
# echo function > current_tracer
# cat current_tracer
function
查看 function 跟踪器的更新跟踪输出
现在您已经启用了跟踪,是时候查看输出了。如果您查看 trace
文件的内容,您会看到大量数据不断写入其中。我管道输出了输出,目前只查看前 20 行以保持可管理性。如果您遵循左侧输出中的标题,您可以看到哪个任务和进程 ID 在哪个 CPU 上运行。在输出的右侧,您可以看到正在运行的确切内核函数,后跟其父函数。中心还有时间戳信息
# sudo cat trace | head -20
# tracer: function
#
# entries-in-buffer/entries-written: 409936/4276216 #P:8
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
<idle>-0 [000] d... 2088.841739: tsc_verify_tsc_adjust <-arch_cpu_idle_enter
<idle>-0 [000] d... 2088.841739: local_touch_nmi <-do_idle
<idle>-0 [000] d... 2088.841740: rcu_nocb_flush_deferred_wakeup <-do_idle
<idle>-0 [000] d... 2088.841740: tick_check_broadcast_expired <-do_idle
<idle>-0 [000] d... 2088.841740: cpuidle_get_cpu_driver <-do_idle
<idle>-0 [000] d... 2088.841740: cpuidle_not_available <-do_idle
<idle>-0 [000] d... 2088.841741: cpuidle_select <-do_idle
<idle>-0 [000] d... 2088.841741: menu_select <-do_idle
<idle>-0 [000] d... 2088.841741: cpuidle_governor_latency_req <-menu_select
请记住,跟踪已打开,这意味着跟踪输出将继续写入跟踪文件,直到您关闭跟踪为止。
关闭跟踪
关闭跟踪很简单。您所要做的就是将 current_tracer
文件中的 function
跟踪器替换为 nop
,跟踪就会关闭
# cat current_tracer
function
# echo nop > current_tracer
# cat current_tracer
nop
启用 function_graph 跟踪器
现在尝试第二个跟踪器,称为 function_graph
。您可以按照与之前相同的步骤启用此跟踪器:将 function_graph
写入 current_tracer
文件
# echo function_graph > current_tracer
# cat current_tracer
function_graph
function_graph 跟踪器的跟踪输出
请注意,trace
文件的输出格式已更改。现在,您可以看到 CPU ID 和内核函数执行的持续时间。接下来,您会看到花括号,指示函数的开始以及从中调用的其他函数
# cat trace | head -20
# tracer: function_graph
#
# CPU DURATION FUNCTION CALLS
# | | | | | | |
6) | n_tty_write() {
6) | down_read() {
6) | __cond_resched() {
6) 0.341 us | rcu_all_qs();
6) 1.057 us | }
6) 1.807 us | }
6) 0.402 us | process_echoes();
6) | add_wait_queue() {
6) 0.391 us | _raw_spin_lock_irqsave();
6) 0.359 us | _raw_spin_unlock_irqrestore();
6) 1.757 us | }
6) 0.350 us | tty_hung_up_p();
6) | mutex_lock() {
6) | __cond_resched() {
6) 0.404 us | rcu_all_qs();
6) 1.067 us | }
启用跟踪设置以增加跟踪深度
您可以随时稍微调整跟踪器,以使用以下步骤查看更多函数调用深度。之后,您可以查看 trace
文件的内容,并看到输出稍微详细一些。为了便于阅读,省略了此示例的输出
# cat max_graph_depth
0
# echo 1 > max_graph_depth ## or:
# echo 2 > max_graph_depth
# sudo cat trace
查找要跟踪的函数
上述步骤足以开始进行跟踪。但是,生成的输出量非常庞大,并且在尝试找出感兴趣的项目时,您经常会迷失方向。通常,您希望能够仅跟踪特定函数而忽略其余函数。但是,如果您不知道进程的确切名称,您如何知道要跟踪哪些进程呢?有一个文件可以帮助您—available_filter_functions
为您提供了可用于跟踪的可用函数列表
# wc -l available_filter_functions
63165 available_filter_functions
搜索通用内核函数
现在尝试搜索您知道的简单内核函数。用户空间具有 malloc
来分配内存,而内核具有其 kmalloc
函数,该函数提供类似的功能。以下是所有与 kmalloc
相关的函数
# grep kmalloc available_filter_functions
debug_kmalloc
mempool_kmalloc
kmalloc_slab
kmalloc_order
kmalloc_order_trace
kmalloc_fix_flags
kmalloc_large_node
__kmalloc
__kmalloc_track_caller
__kmalloc_node
__kmalloc_node_track_caller
[...]
搜索内核模块或驱动程序相关函数
从 available_filter_functions
的输出中,您可以看到一些以方括号中的文本结尾的行,例如下面示例中的 [kvm_intel]
。这些函数与当前已加载的内核模块 kvm_intel
相关。您可以运行 lsmod
命令进行验证
# grep kvm available_filter_functions | tail
__pi_post_block [kvm_intel]
vmx_vcpu_pi_load [kvm_intel]
vmx_vcpu_pi_put [kvm_intel]
pi_pre_block [kvm_intel]
pi_post_block [kvm_intel]
pi_wakeup_handler [kvm_intel]
pi_has_pending_interrupt [kvm_intel]
pi_update_irte [kvm_intel]
vmx_dump_dtsel [kvm_intel]
vmx_dump_sel [kvm_intel]
# lsmod | grep -i kvm
kvm_intel 335872 0
kvm 987136 1 kvm_intel
irqbypass 16384 1 kvm
仅跟踪特定函数
要启用特定函数或模式的跟踪,您可以使用 set_ftrace_filter
文件来指定您要跟踪的上述输出中的哪些函数。
此文件还接受 *
模式,该模式扩展为包含具有给定模式的其他函数。例如,我在我的机器上使用 ext4
文件系统。我可以使用以下命令指定 ext4
特定的内核函数进行跟踪
# mount | grep home
/dev/mapper/fedora-home on /home type ext4 (rw,relatime,seclabel)
# pwd
/sys/kernel/tracing
# cat set_ftrace_filter
#### all functions enabled ####
$
$ echo ext4_* > set_ftrace_filter
$
$ cat set_ftrace_filter
ext4_has_free_clusters
ext4_validate_block_bitmap
ext4_get_group_number
ext4_get_group_no_and_offset
ext4_get_group_desc
[...]
现在,当您看到跟踪输出时,您只能看到与您之前设置了过滤器的内核函数相关的 ext4
函数。所有其他输出都被忽略
# cat trace |head -20
## tracer: function
#
# entries-in-buffer/entries-written: 3871/3871 #P:8
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
cupsd-1066 [004] .... 3308.989545: ext4_file_getattr <-vfs_fstat
cupsd-1066 [004] .... 3308.989547: ext4_getattr <-ext4_file_getattr
cupsd-1066 [004] .... 3308.989552: ext4_file_getattr <-vfs_fstat
cupsd-1066 [004] .... 3308.989553: ext4_getattr <-ext4_file_getattr
cupsd-1066 [004] .... 3308.990097: ext4_file_open <-do_dentry_open
cupsd-1066 [004] .... 3308.990111: ext4_file_getattr <-vfs_fstat
cupsd-1066 [004] .... 3308.990111: ext4_getattr <-ext4_file_getattr
cupsd-1066 [004] .... 3308.990122: ext4_llseek <-ksys_lseek
cupsd-1066 [004] .... 3308.990130: ext4_file_read_iter <-new_sync_read
从跟踪中排除函数
您并不总是知道要跟踪什么,但是,您肯定知道您不想跟踪什么。为此,有一个名为 set_ftrace_notrace
的文件—请注意其中的 “no”。您可以在此文件中写入所需的模式并启用跟踪,之后,除了提到的模式之外的所有内容都将被跟踪。这通常有助于删除使我们的输出混乱的常见功能
# cat set_ftrace_notrace
#### no functions disabled ####
目标跟踪
到目前为止,您一直在跟踪内核中发生的所有事情。但是,如果您希望跟踪与特定命令相关的事件,这将无济于事。为实现此目的,您可以按需打开和关闭跟踪,并在它们之间运行我们选择的命令,以便您在跟踪输出中不会获得额外的输出。您可以通过将 1
写入 tracing_on
来启用跟踪,将 0
写入来关闭跟踪
# cat tracing_on
0
# echo 1 > tracing_on
# cat tracing_on
1
### Run some specific command that we wish to trace here ###
# echo 0 > tracing_on
# cat tracing_on
0
跟踪特定 PID
如果要跟踪与已经运行的特定进程相关的活动,您可以将该 PID 写入名为 set_ftrace_pid
的文件,然后启用跟踪。这样,跟踪仅限于此 PID,这在某些情况下非常有用
# echo $PID > set_ftrace_pid
结论
Ftrace
是了解 Linux 内核内部工作原理的好方法。通过一些练习,您可以学习微调 ftrace
并缩小搜索范围。要更详细地了解 ftrace
及其高级用法,请参阅 ftrace
的核心作者 Steven Rostedt 本人撰写的这些优秀文章。
评论已关闭。