使用 ftrace 分析 Linux 内核

Ftrace 是了解 Linux 内核内部工作原理的好方法。
60 位读者喜欢这篇文章。
Linux keys on the keyboard for a desktop computer

操作系统的内核是目前最难以捉摸的软件之一。从您的系统启动时起,它始终在后台运行。每个用户都在内核的帮助下完成他们的计算工作,但他们从不直接与内核交互。与内核的交互是通过发出系统调用,或者由各种库或应用程序代表用户发出这些调用来发生的,这些库或应用程序是他们日常使用的。

在之前的文章中,我介绍了如何使用 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. 写入一些特定文件以设置/取消设置过滤器以微调跟踪。
  3. 从基于 1 和 2 的文件中读取生成的跟踪输出。
  4. 清除文件中较早的输出或缓冲区。
  5. 缩小到您的特定用例(要跟踪的内核函数)并重复步骤 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

在所有给定的跟踪器中,我重点关注三个特定的跟踪器:functionfunction_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 本人撰写的这些优秀文章。

接下来阅读什么
标签
User profile image.
经验丰富的软件工程专业人士。主要兴趣是安全、Linux、恶意软件。喜欢在命令行上工作。对底层软件和了解事物的工作原理感兴趣。此处表达的观点仅代表我个人,不代表我的雇主

评论已关闭。

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.