使用 ftrace 调试 Linux 内核,第 2 部分
ftrace 操作概述
使用 ftrace 提供的跟蹤器來調試或者分析內核時需要如下操作:
- 切換到目錄 /sys/kernel/debug/tracing/ 下
- 查看 available_tracers 文件,獲取當前內核支持的跟蹤器列表
- 關閉 ftrace 跟蹤,即將 0 寫入文件 tracing_enabled
- 激活 ftrace_enabled ,否則 function 跟蹤器的行為類似于 nop;另外,激活該選項還可以讓一些跟蹤器比如 irqsoff 獲取更豐富的信息。建議使用 ftrace 時將其激活。要激活 ftrace_enabled ,可以通過 proc 文件系統接口來設置:
- 將所選擇的跟蹤器的名字寫入文件 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 跟蹤器使用示例
[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 # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | <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_timertrace 文件給出的信息格式很清晰。首先,字段“tracer:”給出了當前所使用的跟蹤器的名字,這里為 function 跟蹤器。然后是跟蹤信息記錄的格式,TASK 字段對應任務的名字,PID 字段則給出了任務的進程 ID,字段 CPU# 表示運行被跟蹤函數的 CPU 號,這里可以看到 idle 進程運行在 0 號 CPU 上,其進程 ID 是 0 ;字段 TIMESTAMP 是時間戳,其格式為“<secs>.<usecs>”,表示執行該函數時對應的時間戳;FUNCTION 一列則給出了被跟蹤的函數,函數的調用者通過符號 “<-” 標明,這樣可以觀察到函數的調用關系。
回頁首
function_graph 跟蹤器
在 function 跟蹤器給出的信息中,可以通過 FUNCTION 列中的符號 “<-” 來查看函數調用關系,但是由于中間會混合不同函數的調用,導致看起來非常混亂,十分不方便。function_graph 跟蹤器則可以提供類似 C 代碼的函數調用關系信息。通過寫文件 set_graph_function 可以顯示指定要生成調用關系的函數,缺省會對所有可跟蹤的內核函數生成函數調用關系圖。清單 2 給出了使用 function_grapch 跟蹤器的示例,示例中將內核函數 __do_fault 作為觀察對象,該函數在內核運作過程中會被頻繁調用。
清單 2. function_graph 跟蹤器使用示例
[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 [root@linux tracing]# echo __do_fault > set_graph_function [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 -20 # tracer: function_graph # # CPU DURATION FUNCTION CALLS # | | | | | | | 1) 9.936 us | } 1) 0.714 us | put_prev_task_fair(); 1) | pick_next_task_fair() { 1) | set_next_entity() { 1) 0.647 us | update_stats_wait_end(); 1) 0.699 us | __dequeue_entity(); 1) 3.322 us | } 1) 0.865 us | hrtick_start_fair(); 1) 6.313 us | } 1) | __switch_to_xtra() { 1) 1.601 us | memcpy(); 1) 3.938 us | } [root@linux tracing]# echo > set_graph_function在文件 trace 的輸出信息中,首先給出的也是當前跟蹤器的名字,這里是 function_graph 。接下來是詳細的跟蹤信息,可以看到,函數的調用關系以類似 C 代碼的形式組織。
CPU 字段給出了執行函數的 CPU 號,本例中都為 1 號 CPU。DURATION 字段給出了函數執行的時間長度,以 us 為單位。FUNCTION CALLS 則給出了調用的函數,并顯示了調用流程。注意,對于不調用其它函數的函數,其對應行以“;”結尾,而且對應的 DURATION 字段給出其運行時長;對于調用其它函數的函數,則在其“}”對應行給出了運行時長,該時間是一個累加值,包括了其內部調用的函數的執行時長。DURATION 字段給出的時長并不是精確的,它還包含了執行 ftrace 自身的代碼所耗費的時間,所以示例中將內部函數時長累加得到的結果會與對應的外圍調用函數的執行時長并不一致;不過通過該字段還是可以大致了解函數在時間上的運行開銷的。清單 2 中最后通過 echo 命令重置了文件 set_graph_function 。
回頁首
sched_switch 跟蹤器
sched_switch 跟蹤器可以對進程的調度切換以及之間的喚醒操作進行跟蹤,如清單 3 所示。
清單 3. sched_switch 跟蹤器使用示例
[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 # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | 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-terminalevents/0-9 [000] 26208.817088: 9:120:S ==> [000] 1377:120:R gnome-terminal在 sched_swich 跟蹤器獲取的跟蹤信息中記錄了進程間的喚醒操作和調度切換信息,可以通過符號‘ + ’和‘ ==> ’區分;喚醒操作記錄給出了當前進程喚醒運行的進程,進程調度切換記錄中顯示了接替當前進程運行的后續進程。
描述進程狀態的格式為“Task-PID:Priority:Task-State”。以示例跟蹤信息中的第一條跟蹤記錄為例,可以看到進程 bash 的 PID 為 1408 ,其對應的內核態優先級為 120 ,當前狀態為 S(可中斷睡眠狀態),當前 bash 并沒有喚醒其它進程;從第 3 條記錄可以看到,進程 bash 將進程 events/0 喚醒,而在第 4 條記錄中發生了進程調度,進程 bash 切換到進程 events/0 執行。
在 Linux 內核中,進程的狀態在內核頭文件 include/linux/sched.h 中定義,包括可運行狀態 TASK_RUNNING(對應跟蹤信息中的符號‘ R ’)、可中斷阻塞狀態 TASK_INTERRUPTIBLE(對應跟蹤信息中的符號‘ S ’)等。同時該頭文件也定義了用戶態進程所使用的優先級的范圍,最小值為 MAX_USER_RT_PRIO(值為 100 ),最大值為 MAX_PRIO - 1(對應值為 139 ),缺省為 DEFAULT_PRIO(值為 120 );在本例中,進程優先級都是缺省值 120 。
回頁首
irqsoff 跟蹤器
當關閉中斷時,CPU 會延遲對設備的狀態變化做出反應,有時候這樣做會對系統性能造成比較大的影響。irqsoff 跟蹤器可以對中斷被關閉的狀況進行跟蹤,有助于發現導致較大延遲的代碼;當出現最大延遲時,跟蹤器會記錄導致延遲的跟蹤信息,文件 tracing_max_latency 則記錄中斷被關閉的最大延時。
清單 4. irqsoff 跟蹤器使用示例
[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 # # irqsoff latency trace v1.1.5 on 2.6.33.1 # -------------------------------------------------------------------- # latency: 34380 us, #6/6, CPU#1 | (M:desktop VP:0, KP:0, SP:0 HP:0 #P:2) # ----------------- # | task: -0 (uid:0 nice:0 policy:0 rt_prio:0) # ----------------- # => started at: reschedule_interrupt # => ended at: restore_all_notrace # # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| /_--=> lock-depth # |||||/ delay # cmd pid |||||| time | caller # \ / |||||| \ | / <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從清單 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 中,這個可以從最大延遲時間所在的記錄項看到,也可以從延遲記錄信息中最后的“=>”標識所對應的記錄行知道這一點。
在輸出信息中,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 字段是與大內核鎖相關的,而最近的內核開發工作中有一部分是與移除大內核鎖相關的,這里對該字段不再加以描述。
另外,還有用于跟蹤禁止進程搶占的跟蹤器 preemptoff 和跟蹤中斷 / 搶占禁止的跟蹤器 preemptirqsoff,它們的使用方式與輸出信息格式與 irqsoff 跟蹤器類似,這里不再贅述。
回頁首
跟蹤指定模塊中的函數
前面提過,通過文件 set_ftrace_filter 可以指定要跟蹤的函數,缺省目標為所有可跟蹤的內核函數;可以將感興趣的函數通過 echo 寫入該文件。為了方便使用,set_ftrace_filter 文件還支持簡單格式的通配符。
- begin*選擇所有名字以 begin 字串開頭的函數
- *middle*選擇所有名字中包含 middle 字串的函數
- *end選擇所有名字以 end 字串結尾的函數
需要注意的是,這三種形式不能組合使用,比如“begin*middle*end”實際的效果與“begin”相同。另外,使用通配符表達式時,需要用單引號將其括起來,如果使用雙引號,shell 可能會對字符‘ * ’進行擴展,這樣最終跟蹤的對象可能與目標函數不一樣。
通過該文件還可以指定屬于特定模塊的函數,這要用到 mod 指令。指定模塊的格式為:
echo ':mod:[module_name]' > set_ftrace_filter下面給出了一個指定跟蹤模塊 ipv6 中的函數的例子。可以看到,指定跟蹤模塊 ipv6 中的函數會將文件 set_ftrace_filter 的內容設置為只包含該模塊中的函數。
清單 5. 指定跟蹤 ipv6 模塊中的函數
[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回頁首
小結
本系列文章對 ftrace 的配置和使用進行了介紹。本文是其中的第二部分,介紹了 ftrace 所提供的跟蹤器的使用,重點對不同格式的跟蹤信息進行了描述,并對如何跟蹤指定模塊中的函數進行了介紹。當前 Linux 內核中 ftrace 支持多個跟蹤器,由于篇幅限制,這里不能對其全部加以描述。讀者可參照參考資料中給出的信息對 ftrace 跟蹤器進行更深入的了解。下一篇文章將對如何在代碼中通過 ftrace 提供的工具函數與 ftrace 交互進行描述。
轉載于:https://www.cnblogs.com/jefree/p/4439010.html
總結
以上是生活随笔為你收集整理的使用 ftrace 调试 Linux 内核,第 2 部分的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vs2012 发布网站时出现 sgen.
- 下一篇: ssas 为绑定指定的大小太小,导致一个