Ftrace 简介
最近使用eBPF做可观测性的项目,接触到了一些内核跟踪工具,其中就包括Ftrace。Ftrace 是 Linux 内核自带的跟踪工具,能够追踪内核中的函数调用、上下文切换、中断处理等。通过 Ftrace,开发人员可以详细了解内核的运行状态,从而更好地优化和调试内核模块。Ftrace 的配置和使用非常灵活,可以通过命令行工具和配置文件来控制其行为。Ftrace 提供了多种跟踪模式,其中主要包括 function
和 function_graph
两种模式。
跟踪模式介绍
1. Function 模式
function
模式是 Ftrace 最基本的跟踪模式,用于记录内核函数的调用。每当一个被跟踪的函数被调用或返回时,Ftrace 都会记录一条日志信息。该模式适用于需要详细了解特定函数调用顺序和频率的场景。
2. Function_graph 模式
function_graph
模式是在 function
模式基础上的增强版,不仅记录函数的调用和返回,还会显示每个函数的调用关系和执行时间。通过 function_graph
模式,可以清晰地看到函数调用的层次结构和各函数的执行时间,适用于需要分析函数调用关系和性能的场景。
使用 Ftrace 进行跟踪
配置 Ftrace
首先,需要确保内核支持 Ftrace,并加载必要的内核模块。可以通过以下命令检查 Ftrace 的状态:
ls /sys/kernel/debug/tracing
cd /sys/kernel/debug/tracing
如果目录存在,则说明 Ftrace 已经启用。接下来,cd进入目录,通过以下步骤配置使用Ftrace:
1. 启用 Function 跟踪
首先,停止追踪并清除当前追踪器:
echo 0 > tracing_on
echo nop > current_tracer
启用 function
跟踪:
echo function > current_tracer
添加需要跟踪的函数(把"do_mkdirat"改成自己要追踪的函数):
echo do_mkdirat > set_ftrace_filter
启用跟踪栈:
echo 1 > options/func_stack_trac
开始跟踪:
echo 1 > tracing_on
查看追踪:
cat trace
停止跟踪:
echo 0 > tracing_on
Function跟踪结果解析
[root@localhost tracing]# cat trace
# tracer: function
#
# entries-in-buffer/entries-written: 2/2 #P:1
#
# _-----=> irqs-off/BH-disabled
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / _-=> migrate-disable
# |||| / delay
# TASK-PID CPU# ||||| TIMESTAMP FUNCTION
# | | | ||||| | |
mkdir-65014 [000] ..... 106404.527038: do_mkdirat <-__x64_sys_mkdir
mkdir-65014 [000] ..... 106404.527052: <stack trace>
=> 0xffffffffc0bda097
=> do_mkdirat
=> __x64_sys_mkdir
=> do_syscall_64
=> entry_SYSCALL_64_after_hwframe
可以看到进程 mkdir
(PID: 65014)在 CPU 0 上执行了 do_mkdirat
函数,并且这个函数是通过系统调用 __x64_sys_mkdir
进入的。通过堆栈跟踪信息,我们可以看到调用链,从 entry_SYSCALL_64_after_hwframe
开始,到 do_syscall_64
,再到 __x64_sys_mkdir
,最终调用了 do_mkdirat
函数。
在 function
模式下,Ftrace 只记录顶层函数调用,而不记录该函数内部调用的其他函数。如果需要查看 do_mkdirat
函数内部调用的函数,可以使用 function_graph
模式,它不仅记录函数的调用,还会记录每个函数内部调用的子函数,并显示每个函数的调用关系和执行时间。
2. 启用 Function_graph 跟踪
首先,停止追踪并清除当前追踪器:
echo 0 > tracing_on
echo nop > current_tracer
清空过滤:
echo > set_ftrace_filter
启用 function_graph 跟踪:
echo function_graph > current_tracer
添加需要跟踪的函数:
echo do_mkdirat > set_graph_function
设置追踪调用深度(可选):
echo 4 > max_graph_depth
开始跟踪:
echo 1 > tracing_on
查看追踪:
cat trace
停止跟踪:
echo 0 > tracing_on
Function_graph跟踪结果解析
[root@localhost tracing]# cat trace
# tracer: function_graph
#
# CPU DURATION FUNCTION CALLS
# | | | | | | |
0) ! 618.299 us | do_mkdirat();
------------------------------------------
0) mkdir-65065 => mkdir-65069
------------------------------------------
0) | do_mkdirat() {
0) | filename_create() {
0) | __filename_parentat() {
0) 4.378 us | path_parentat();
0) 5.020 us | }
0) | mnt_want_write() {
0) 0.310 us | __mnt_want_write();
0) 0.922 us | }
0) 0.321 us | down_write();
0) | lookup_one_qstr_excl() {
0) 1.182 us | lookup_dcache();
0) 1.703 us | d_alloc();
0) + 12.033 us | ext4_lookup [ext4]();
0) + 16.351 us | }
0) + 23.995 us | }
0) | security_path_mkdir() {
0) 0.271 us | bpf_lsm_path_mkdir();
0) 0.971 us | }
0) | vfs_mkdir() {
0) 0.290 us | from_vfsuid();
0) 0.270 us | from_vfsgid();
0) | from_kuid() {
0) 0.321 us | map_id_up();
0) 0.841 us | }
0) | from_kgid() {
0) 0.251 us | map_id_up();
0) 0.761 us | }
0) | inode_permission() {
0) 0.251 us | make_vfsuid();
0) 0.301 us | make_vfsgid();
0) 0.381 us | generic_permission();
0) 1.012 us | security_inode_permission();
0) 3.196 us | }
0) 0.271 us | mode_strip_sgid();
0) | security_inode_mkdir() {
0) + 15.079 us | selinux_inode_mkdir();
0) 0.271 us | bpf_lsm_inode_mkdir();
0) + 16.521 us | }
0) | ext4_mkdir [ext4]() {
0) 0.491 us | dquot_initialize();
0) ! 773.775 us | __ext4_new_inode [ext4]();
0) ! 157.675 us | ext4_init_new_dir [ext4]();
0) 4.849 us | __ext4_mark_inode_dirty [ext4]();
0) + 19.497 us | ext4_add_entry [ext4]();
0) 0.552 us | ext4_inc_count [ext4]();
0) 4.839 us | __ext4_mark_inode_dirty [ext4]();
0) 2.344 us | d_instantiate_new();
0) 0.250 us | ext4_fc_track_create [ext4]();
0) 1.393 us | __ext4_journal_stop [ext4]();
0) ! 971.345 us | }
0) 0.491 us | fsnotify();
0) ! 997.414 us | }
0) | dput() {
0) 0.241 us | _raw_spin_lock();
0) | d_lru_add() {
0) 0.821 us | list_lru_add();
0) 1.383 us | }
0) 2.565 us | }
0) 0.231 us | up_write();
0) 0.381 us | mnt_drop_write();
0) 0.250 us | dput();
0) | mntput() {
0) 0.241 us | mntput_no_expire();
0) 0.761 us | }
0) | putname() {
0) 0.361 us | kmem_cache_free();
0) 0.931 us | }
0) # 1032.821 us | }
可以看到do_mkdirat()函数中的执行调用逻辑就被追踪出来了,便于我们进行性能分析。
总结
Ftrace 是一个非常强大的内核调试工具,能够帮助开发人员详细了解内核函数的调用和性能。通过 function
和 function_graph
两种模式,可以灵活地选择适合的跟踪方式,从而更好地优化和调试内核模块。希望本文对你了解和使用 Ftrace 有所帮助。