使用 ftrace 跟蹤內核 | Linux 中國

尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️

加入LINE好友

使用 ftrace 跟蹤內核 | Linux 中國-雪花新聞

ftrace,它是添加到內核中的第一款跟蹤工具,今天我們來看一下它都能做什麼,讓我們從它的一些重要術語開始吧。

— Andrej Yemelianov

致謝

編譯自 | 

Kernel Tracing with Ftrace

作者 | Andrej Yemelianov

譯者 | qhwdw ? 共計翻譯:134篇 貢獻時間:261 天

在內核層面上分析事件有很多的工具:SystemTap[1]ktap[2]Sysdig[3]LTTNG[4]等等,你也可以在網路上找到關於這些工具的大量介紹文章和資料。

而對於使用 Linux 原生機制去跟蹤系統事件以及檢索/分析故障信息的方面的資料卻很少找的到。這就是 ftrace[5],它是添加到內核中的第一款跟蹤工具,今天我們來看一下它都能做什麼,讓我們從它的一些重要術語開始吧。

內核跟蹤和分析

內核分析Kernel profiling可以發現性能「瓶頸」。分析能夠幫我們發現在一個程序中性能損失的準確位置。特定的程序生成一個概述profile — 這是一個事件總結 — 它能夠用於幫我們找出哪個函數占用了大量的運行時間。盡管這些程序並不能識別出為什麼會損失性能。

瓶頸經常發生在無法通過分析來識別的情況下。要推斷出為什麼會發生事件,就必須保存發生事件時的相關上下文,這就需要去跟蹤tracing。

跟蹤可以理解為在一個正常工作的系統上活動的信息收集過程。它使用特定的工具來完成這項工作,就像錄音機來記錄聲音一樣,用它來記錄各種系統事件。

跟蹤程序能夠同時跟蹤應用級和操作系統級的事件。它們收集的信息能夠用於診斷多種系統問題。

有時候會將跟蹤與日志比較。它們兩者確時很相似,但是也有不同的地方。

對於跟蹤,記錄的信息都是些低級別事件。它們的數量是成百上千的,甚至是成千上萬的。對於日志,記錄的信息都是些高級別事件,數量上通常少多了。這些包含用戶登錄系統、應用程序錯誤、數據庫事務等等。

就像日志一樣,跟蹤數據可以被原樣讀取,但是用特定的應用程序提取的信息更有用。所有的跟蹤程序都能這樣做。

在內核跟蹤和分析方面,Linux 內核有三個主要的機制:

◈ 跟蹤點tracepoint:一種基於靜態測試代碼的工作機制

◈ 探針kprobe:一種動態跟蹤機制,用於在任意時刻中斷內核代碼的運行,調用它自己的處理程序,在完成需要的操作之後再返回

◈ perf_events —— 一個訪問 PMU( 性能監視單元Performance Monitoring Unit)的接口

我並不想在這里寫關於這些機制方面的內容,任何對它們感興趣的人可以去訪問 Brendan Gregg 的博客[6]

使用 ftrace,我們可以與這些機制進行交互,並可以從用戶空間直接得到調試信息。下面我們將討論這方面的詳細內容。示例中的所有命令行都是在內核版本為 3.13.0-24 的 Ubuntu 14.04 中運行的。

ftrace:常用信息

ftrace 是 Function Trace 的簡寫,但它能做的遠不止這些:它可以跟蹤上下文切換、測量進程阻塞時間、計算高優先級任務的活動時間等等。

ftrace 是由 Steven Rostedt 開發的,從 2008 年發布的內核 2.6.27 中開始就內置了。這是為記錄數據提供的一個調試 Ring 緩沖區的框架。這些數據由集成到內核中的跟蹤程序來采集。

ftrace 工作在 debugfs 文件系統上,在大多數現代 Linux 發行版中都默認掛載了。要開始使用 ftrace,你將進入到 sys/kernel/debug/tracing 目錄(僅對 root 用戶可用):

  1. # cd /sys/kernel/debug/tracing

這個目錄的內容看起來應該像這樣:

  1. аvailable_filter_functions options stack_trace_filter
  2. available_tracers per_cpu trace
  3. buffer_size_kb printk_formats trace_clock
  4. buffer_total_size_kb README trace_marker
  5. current_tracer saved_cmdlines trace_options
  6. dyn_ftrace_total_info set_event trace_pipe
  7. enabled_functions set_ftrace_filter trace_stat
  8. events set_ftrace_notrace tracing_cpumask
  9. free_buffer set_ftrace_pid tracing_max_latency
  10. function_profile_enabled set_graph_function tracing_on
  11. instances set_graph_notrace tracing_thresh
  12. kprobe_events snapshot uprobe_events
  13. kprobe_profile stack_max_size uprobe_profile

我不想去描述這些文件和子目錄;它們的描述在 官方文檔[5]中已經寫的很詳細了。我只想去詳細介紹與我們這篇文章相關的這幾個文件:

◈ available_tracers —— 可用的跟蹤程序

◈ current_tracer —— 正在運行的跟蹤程序

◈ tracing_on —— 負責啟用或禁用數據寫入到 Ring 緩沖區的系統文件(如果啟用它,數字 1 被添加到文件中,禁用它,數字 0 被添加)

◈ trace —— 以人類友好格式保存跟蹤數據的文件

可用的跟蹤程序

我們可以使用如下的命令去查看可用的跟蹤程序的一個列表:

  1. [email protected]:/sys/kernel/debug/tracing#: cat available_tracers
  2. blk mmiotrace function_graph wakeup_rt wakeup function nop

我們來快速瀏覽一下每個跟蹤程序的特性:

◈ function —— 一個無需參數的函數調用跟蹤程序

◈ function_graph —— 一個使用子調用的函數調用跟蹤程序

◈ blk —— 一個與塊 I/O 跟蹤相關的調用和事件跟蹤程序(它是 blktrace 使用的)

◈ mmiotrace —— 一個記憶體映射 I/O 操作跟蹤程序

◈ nop —— 最簡單的跟蹤程序,就像它的名字所暗示的那樣,它不做任何事情(盡管在某些情況下可能會派上用場,我們將在後文中詳細解釋)

函數跟蹤程序

在開始介紹函數跟蹤程序 ftrace 之前,我們先看一個測試腳本:

  1. #!/bin/sh
  2. dir=/sys/kernel/debug/tracing
  3. sysctl kernel.ftrace_enabled=1
  4. echo function > ${dir}/current_tracer
  5. echo 1 > ${dir}/tracing_on
  6. sleep 1
  7. echo 0 > ${dir}/tracing_on
  8. less ${dir}/trace

這個腳本是非常簡單的,但是還有幾個需要注意的地方。命令 sysctl ftrace.enabled=1 啟用了函數跟蹤程序。然後我們通過寫它的名字到 current_tracer 文件來啟用 current tracer。

接下來,我們寫入一個 1 到 tracing_on,它啟用了 Ring 緩沖區。這些語法都要求在 1和 > 符號前後有一個空格;寫成像 echo 1> tracing_on 這樣將不能工作。一行之後我們禁用它(如果 0 寫入到 tracing_on, 緩沖區不會被清除並且 ftrace 並不會被禁用)。

我們為什麼這樣做呢?在兩個 echo 命令之間,我們看到了命令 sleep 1。我們啟用了緩沖區,運行了這個命令,然後禁用它。這將使跟蹤程序采集了這個命令運行期間發生的所有系統調用的信息。

在腳本的最後一行,我們寫了一個在控制台上顯示跟蹤數據的命令。

一旦腳本運行完成後,我們將看到下列的輸出(這里只列出了一個小片斷):

  1. # tracer: function
  2. #
  3. # entries-in-buffer/entries-written: 29571/29571 #P:2
  4. #
  5. # _—–=> irqs-off
  6. # / _—-=> need-resched
  7. # | / _—=> hardirq/softirq
  8. # || / _–=> preempt-depth
  9. # ||| / delay
  10. # TASK-PID CPU# |||| TIMESTAMP FUNCTION
  11. # | | | |||| | |
  12. trace.sh-1295 [000] …. 90.502874: mutex_unlock <-rb_simple_write
  13. trace.sh-1295 [000] …. 90.502875: __fsnotify_parent <-vfs_write
  14. trace.sh-1295 [000] …. 90.502876: fsnotify <-vfs_write
  15. trace.sh-1295 [000] …. 90.502876: __srcu_read_lock <-fsnotify
  16. trace.sh-1295 [000] …. 90.502876: __srcu_read_unlock <-fsnotify
  17. trace.sh-1295 [000] …. 90.502877: __sb_end_write <-vfs_write
  18. trace.sh-1295 [000] …. 90.502877: syscall_trace_leave <-int_check_syscall_exit_work
  19. trace.sh-1295 [000] …. 90.502878: context_tracking_user_exit <-syscall_trace_leave
  20. trace.sh-1295 [000] …. 90.502878: context_tracking_user_enter <-syscall_trace_leave
  21. trace.sh-1295 [000] d… 90.502878: vtime_user_enter <-context_tracking_user_enter
  22. trace.sh-1295 [000] d… 90.502878: _raw_spin_lock <-vtime_user_enter
  23. trace.sh-1295 [000] d… 90.502878: __vtime_account_system <-vtime_user_enter
  24. trace.sh-1295 [000] d… 90.502878: get_vtime_delta <-__vtime_account_system
  25. trace.sh-1295 [000] d… 90.502879: account_system_time <-__vtime_account_system
  26. trace.sh-1295 [000] d… 90.502879: cpuacct_account_field <-account_system_time
  27. trace.sh-1295 [000] d… 90.502879: acct_account_cputime <-account_system_time
  28. trace.sh-1295 [000] d… 90.502879: __acct_update_integrals <-acct_account_cputime

這個輸出以「緩沖區中的信息條目數量」和「寫入的全部條目數量」開始。這兩者的數據差異是緩沖區中事件的丟失數量(在我們的示例中沒有發生丟失)。


(adsbygoogle = window.adsbygoogle || []).push({});

在這里有一個包含下列信息的函數列表:

◈ 進程標識符(PID)

◈ 運行這個進程的 CPU(CPU#)

◈ 進程開始時間(TIMESTAMP)

◈ 被跟蹤函數的名字以及調用它的父級函數;例如,在我們輸出的第一行, rb_simple_write 調用了 mutex-unlock 函數。

function_graph 跟蹤程序

function_graph 跟蹤程序的工作和函數跟蹤程序一樣,但是它更詳細:它顯示了每個函數的進入和退出點。使用這個跟蹤程序,我們可以跟蹤函數的子調用並且測量每個函數的運行時間。

我們來編輯一下最後一個示例的腳本:

  1. #!/bin/sh
  2. dir=/sys/kernel/debug/tracing
  3. sysctl kernel.ftrace_enabled=1
  4. echo function_graph > ${dir}/current_tracer
  5. echo 1 > ${dir}/tracing_on
  6. sleep 1
  7. echo 0 > ${dir}/tracing_on
  8. less ${dir}/trace

運行這個腳本之後,我們將得到如下的輸出:

  1. # tracer: function_graph
  2. #
  3. # CPU DURATION FUNCTION CALLS
  4. # | | | | | | |
  5. 0) 0.120 us | } /* resched_task */
  6. 0) 1.877 us | } /* check_preempt_curr */
  7. 0) 4.264 us | } /* ttwu_do_wakeup */
  8. 0) + 29.053 us | } /* ttwu_do_activate.constprop.74 */
  9. 0) 0.091 us | _raw_spin_unlock();
  10. 0) 0.260 us | ttwu_stat();
  11. 0) 0.133 us | _raw_spin_unlock_irqrestore();
  12. 0) + 37.785 us | } /* try_to_wake_up */
  13. 0) + 38.478 us | } /* default_wake_function */
  14. 0) + 39.203 us | } /* pollwake */
  15. 0) + 40.793 us | } /* __wake_up_common */
  16. 0) 0.104 us | _raw_spin_unlock_irqrestore();
  17. 0) + 42.920 us | } /* __wake_up_sync_key */
  18. 0) + 44.160 us | } /* sock_def_readable */
  19. 0) ! 192.850 us | } /* tcp_rcv_established */
  20. 0) ! 197.445 us | } /* tcp_v4_do_rcv */
  21. 0) 0.113 us | _raw_spin_unlock();
  22. 0) ! 205.655 us | } /* tcp_v4_rcv */
  23. 0) ! 208.154 us | } /* ip_local_deliver_finish */

在這個圖中,DURATION 展示了花費在每個運行的函數上的時間。注意使用 + 和 ! 符號標記的地方。加號(+)意思是這個函數花費的時間超過 10 毫秒;而感嘆號(!)意思是這個函數花費的時間超過了 100 毫秒。

在 FUNCTION_CALLS 下面,我們可以看到每個函數調用的信息。

和 C 語言一樣使用了花括號({)標記每個函數的邊界,它展示了每個函數的開始和結束,一個用於開始,一個用於結束;不能調用其它任何函數的葉子函數用一個分號(;)標記。

函數過濾器

ftrace 輸出可能會很大,精確找出你所需要的內容可能會非常困難。我們可以使用過濾器去簡化我們的搜尋:輸出中將只顯示與我們感興趣的函數相關的信息。為做到過濾,我們只需要在 set_ftrace_filter 文件中寫入我們需要過濾的函數的名字即可。例如:

  1. [email protected]:/sys/kernel/debug/tracing# echo kfree > set_ftrace_filter

如果禁用過濾器,我們只需要在這個文件中添加一個空白行即可:

  1. [email protected]:/sys/kernel/debug/tracing# echo > set_ftrace_filter

通過運行這個命令:

  1. [email protected]:/sys/kernel/debug/tracing# echo kfree > set_ftrace_notrace

我們將得到相反的結果:輸出將包含除了 kfree() 以外的任何函數的信息。

另一個有用的選項是 set_ftrace_pid。它是為在一個特定的進程運行期間調用跟蹤函數準備的。

ftrace 還有很多過濾選項。對於它們更詳細的介紹,你可以去查看 Steven Rostedt 在 LWN.net[7]上的文章。

跟蹤事件

我們在上面提到到跟蹤點機制。跟蹤點是插入的觸發系統事件的特定代碼。跟蹤點可以是動態的(意味著可能會在它們上面附加幾個檢查),也可以是靜態的(意味著不會附加任何檢查)。

靜態跟蹤點不會對系統有任何影響;它們只是在測試的函數末尾增加幾個字節的函數調用以及在一個獨立的節上增加一個數據結構。

當相關代碼片斷運行時,動態跟蹤點調用一個跟蹤函數。跟蹤數據是寫入到 Ring 緩沖區。

跟蹤點可以設置在代碼的任何位置;事實上,它們確實可以在許多的內核函數中找到。我們來看一下 kmem_cache_alloc 函數(取自 這里[8]):

  1. {
  2. void *ret = slab_alloc(cachep, flags, _RET_IP_);
  3. trace_kmem_cache_alloc(_RET_IP_, ret,
  4. cachep->object_size, cachep->size, flags);
  5. return ret;
  6. }

trace_kmem_cache_alloc 它本身就是一個跟蹤點。我們可以通過查看其它內核函數的源代碼找到這樣無數的例子。

在 Linux 內核中為了從用戶空間使用跟蹤點,它有一個專門的 API。在 /sys/kernel/debug/tracing 目錄中,這里有一個事件目錄,它是為了保存系統事件。這些只是為了跟蹤系統事件。在這個上下文中系統事件可以理解為包含在內核中的跟蹤點。

可以通過運行如下的命令來查看這個事件列表:

  1. [email protected]:/sys/kernel/debug/tracing# cat available_events

這個命令將在控制台中輸出一個很長的列表。這樣看起來很不方便。我們可以使用如下的命令來列出一個結構化的列表:

  1. [email protected]:/sys/kernel/debug/tracing# ls events
  2. block gpio mce random skb vsyscall
  3. btrfs header_event migrate ras sock workqueue
  4. compaction header_page module raw_syscalls spi writeback
  5. context_tracking iommu napi rcu swiotlb xen
  6. enable irq net regmap syscalls xfs
  7. exceptions irq_vectors nmi regulator task xhci-hcd
  8. ext4 jbd2 oom rpm timer
  9. filemap kmem pagemap sched udp
  10. fs kvm power scsi vfs
  11. ftrace kvmmmu printk signal vmscan

所有可能的事件都按子系統分組到子目錄中。在我們開始跟蹤事件之前,我們要先確保啟用了 Ring 緩沖區寫入:

  1. [email protected]:/sys/kernel/debug/tracing# cat tracing_on

如果在控制台中顯示的是數字 0,那麼,我們可以運行如下的命令來啟用它:

  1. [email protected]:/sys/kernel/debug/tracing# echo 1 > tracing_on

在我們上一篇的文章中,我們寫了關於 chroot() 系統調用的內容;我們來跟蹤訪問一下這個系統調用。對於我們的跟蹤程序,我們使用 nop 因為函數跟蹤程序和 function_graph 跟蹤程序記錄的信息太多,它包含了我們不感興趣的事件信息。

  1. [email protected]:/sys/kernel/debug/tracing# echo nop > current_tracer

所有事件相關的系統調用都保存在系統調用目錄下。在這里我們將找到一個進入和退出各種系統調用的目錄。我們需要在相關的文件中通過寫入數字 1 來激活跟蹤點:

  1. [email protected]:/sys/kernel/debug/tracing# echo 1 > events/syscalls/sys_enter_chroot/enable

然後我們使用 chroot 來創建一個獨立的文件系統(更多內容,請查看 之前這篇文章[9])。在我們執行完我們需要的命令之後,我們將禁用跟蹤程序,以便於不需要的信息或者過量信息不會出現在輸出中:

  1. [email protected]:/sys/kernel/debug/tracing# echo 0 > tracing_on

然後,我們去查看 Ring 緩沖區的內容。在輸出的結束部分,我們找到了有關的系統調用信息(這里只是一個節選)。

  1. [email protected]:/sys/kernel/debug/tracing# сat trace
  2. ……
  3. chroot-11321 [000] …. 4606.265208: sys_chroot(filename: 7fff785ae8c2)
  4. chroot-11325 [000] …. 4691.677767: sys_chroot(filename: 7fff242308cc)
  5. bash-11338 [000] …. 4746.971300: sys_chroot(filename: 7fff1efca8cc)
  6. bash-11351 [000] …. 5379.020609: sys_chroot(filename: 7fffbf9918cc)

關於配置事件跟蹤的更的信息可以在 這里[10]找到。

結束語

在這篇文篇中,我們做了一個 ftrace 的功能概述。我們非常感謝你的任何意見或者補充。如果你想深入研究這個主題,我們為你推薦下列的資源:

https://www.kernel.org/doc/Documentation/trace/tracepoints.txt— 一個跟蹤點機制的詳細描述

https://www.kernel.org/doc/Documentation/trace/events.txt— 在 Linux 中跟蹤系統事件的指南

https://www.kernel.org/doc/Documentation/trace/ftrace.txt— ftrace 的官方文檔

https://lttng.org/files/thesis/desnoyers-dissertation-2009-12-v27.pdf— Mathieu Desnoyers(作者是跟蹤點和 LTTNG 的創建者)的關於內核跟蹤和分析的學術論文。

https://lwn.net/Articles/370423/— Steven Rostedt 的關於 ftrace 功能的文章

http://alex.dzyoba.com/linux/profiling-ftrace.html— 用 ftrace 分析實際案例的一個概述

via:https://blog.selectel.com/kernel-tracing-ftrace/

作者:Andrej Yemelianov[15]譯者:qhwdw校對:wxy

本文由 LCTT原創編譯,Linux中國

(adsbygoogle = window.adsbygoogle || []).push({});(adsbygoogle = window.adsbygoogle || []).push({});