此文章用来汇总通过ftrace查看函数调用栈的方法。
1、查看指定函数向上调用栈
bash
# cat function.sh
#
cd /sys/kernel/debug/tracing/
echo 0 > trace
echo function > current_tracer
# filter vfs_open function
echo vfs_open > ./set_ftrace_filter
echo 1 > ./options/func_stack_trace
echo 1 > tracing_on
cat /mnt/ftrace_script/function/test.file
sleep 1
echo 0 > tracing_on
cat trace
echo nop > ./current_tracer
echo 0 > ./options/func_stack_trace
echo !vfs_open >> ./set_ftrace_filter
可通过 cat /sys/kernel/debug/tracing/available_filter_functions | grep vfs_open
查看是否支持要filter的函数。
ini
# ./function.sh
# tracer: function
#
# entries-in-buffer/entries-written: 328/328 #P:4
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
modprobe-1563 [000] .... 21050.687018: vfs_open <-do_last
modprobe-1563 [000] .... 21050.694614: <stack trace>
=> vfs_open
=> path_openat
=> do_filp_open
=> do_open_execat
=> __do_execve_file
=> do_execveat_common
=> do_execve
=> call_usermodehelper_exec_async
modprobe-1563 [000] .... 21050.700709: vfs_open <-do_last
modprobe-1563 [000] .... 21050.700768: <stack trace>
=> vfs_open
=> path_openat
=> do_filp_open
=> do_open_execat
=> open_exec
=> load_elf_binary
=> search_binary_handler
=> exec_binprm
=> __do_execve_file
=> do_execveat_common
=> do_execve
=> call_usermodehelper_exec_async
"set_ftrace_notrace"文件阻止这些函数被跟踪:
bash
echo '*preempt*' '*lock*' > set_ftrace_notrace
2、查看函数向下的调用栈与执行时间
function_graph tracer与function tracer类似,不同之处在于它探测函数的入口和出口。
在函数的两端进行探测会导致一些特殊的特性,例如:
- 测量函数的执行时间
- 有一个可靠的调用堆栈来绘制函数调用图
这个tracer在以下几种情况是很有用的:
- 想要找到一个奇怪的内核行为的原因,并需要看到在任何区域(或特定区域)发生了什么细节
- 你正好遇到了奇怪的延迟,但很难找到它的起源
- 想要快速找到特定函数的路径
- 只想看看一个正在运行的内核里面发生了什么
c
cd /sys/kernel/debug/tracing/
echo 0 > tracing_on
echo 0 > trace
echo function_graph > current_tracer
echo 1 > options/funcgraph-proc
echo vfs_read > set_graph_function
echo 1 > options/funcgraph-tail
echo 1 > tracing_on
cat /mnt/ftrace_script/function/test.file
echo 0 > tracing_on
cat trace
echo 0 > trace
echo nop > ./current_tracer
echo 0 > options/funcgraph-proc
echo !vfs_read >> set_graph_function
echo 0 > options/funcgraph-tail
输出结果:
scss
ftrace test file
# tracer: function_graph
#
# CPU TASK/PID DURATION FUNCTION CALLS
# | | | | | | | | |
2) exe-227 | | vfs_read() {
2) exe-227 | | rw_verify_area() {
2) exe-227 | | security_file_permission() {
2) exe-227 | + 15.040 us | __fsnotify_parent();
2) exe-227 | + 15.696 us | fsnotify();
2) exe-227 | + 78.768 us | } /* security_file_permission */
2) exe-227 | ! 109.552 us | } /* rw_verify_area */
2) exe-227 | | __vfs_read() {
2) exe-227 | | new_sync_read() {
2) exe-227 | | shmem_file_read_iter() {
2) exe-227 | | shmem_getpage_gfp.isra.47() {
2) exe-227 | | find_lock_entry() {
2) exe-227 | | find_get_entry() {
2) exe-227 | + 14.128 us | __rcu_read_lock();
2) exe-227 | + 15.232 us | PageHuge();
2) exe-227 | + 14.592 us | __rcu_read_unlock();
2) exe-227 | ! 107.856 us | } /* find_get_entry */
2) exe-227 | + 14.688 us | page_mapping();
2) exe-227 | ! 301.456 us | } /* find_lock_entry */
2) exe-227 | ! 336.768 us | } /* shmem_getpage_gfp.isra.47 */
2) exe-227 | | set_page_dirty() {
2) exe-227 | + 14.608 us | page_mapping();
2) exe-227 | + 15.760 us | __set_page_dirty_no_writeback();
2) exe-227 | + 77.168 us | } /* set_page_dirty */
2) exe-227 | + 14.928 us | unlock_page();
2) exe-227 | + 15.920 us | preempt_count_add();
2) exe-227 | + 16.208 us | preempt_count_sub();
2) exe-227 | | touch_atime() {
2) exe-227 | | atime_needs_update() {
2) exe-227 | | current_time() {
2) exe-227 | + 14.560 us | ktime_get_coarse_real_ts64();
2) exe-227 | + 14.560 us | timestamp_truncate();
2) exe-227 | + 72.240 us | } /* current_time */
2) exe-227 | ! 104.432 us | } /* atime_needs_update */
2) exe-227 | ! 134.544 us | } /* touch_atime */
2) exe-227 | ! 714.336 us | } /* shmem_file_read_iter */
2) exe-227 | ! 745.280 us | } /* new_sync_read */
2) exe-227 | ! 775.344 us | } /* __vfs_read */
2) exe-227 | + 14.496 us | __fsnotify_parent();
2) exe-227 | + 14.864 us | fsnotify();
2) exe-227 | # 1000.880 us | } /* vfs_read */
有几列可以被动态地启用和禁用。可以根据自己的需要,使用任何想要的选项组合:
执行该功能的cpu编号默认为启用状态。有时候只跟踪一个cpu更好(参见"tracing_cpu_mask"文件),或者有时在cpu跟踪开关时可能会看到无序的函数调用。
bash
隐藏:echo nofuncgraph-cpu > trace_options
显示:echo funcgraph-cpu > trace_options
持续时间(该函数的执行时间)显示在函数的右括号行上,如果是第一个叶节点,则显示在与当前函数相同的行上。默认是启用的。
bash
隐藏:echo nofuncgraph-duration > trace_options
显示:echo funcgraph-duration > trace_options
当达到持续时间阈值时,开销字段在持续时间字段之前。
bash
隐藏:echo nofuncgraph-overhead > trace_options
显示:echo funcgraph-overhead > trace_options
依赖于选项:funcgraph-duration
flags:
- '+' 表示函数超过10个usec。
- '!' 表示函数超过100个usec。
- '#' 表示函数超过1000个usec。
- '*' 表示函数超过10毫秒。
- '@' 表示函数超过100毫秒。
- '$' 表示函数超过1秒。
task/pid字段显示执行该函数的线程cmdline和pid。默认是禁用的。
bash
隐藏:echo nofuncgraph-proc > trace_options
显示:echo funcgraph-proc > trace_options
绝对时间字段是系统时钟自启动以来给出的绝对时间戳。在函数的每次进入/退出时都会给出这个时间的快照。
bash
隐藏:echo nofuncgraph-abstime > trace_options
显示:echo funcgraph-abstime > trace_options
如果函数的开头不在trace buffer中,则函数名总是显示在函数的右括号之后。
对于开头在跟踪缓冲区中的函数,可以启用在结束括号后显示函数名,以便使用grep更容易地搜索函数持续时间。默认是禁用的。
bash
隐藏:echo nofuncgraph-tail > trace_options
显示:echo funcgraph-tail > trace_options
如果你想在__might_sleep()函数中添加注释,你只需要包含<linux/ftrace. h>;并在__might_sleep()::中调用trace_printk()
css
trace_printk("I'm a comment!\n")
将会产生::
1) | __might_sleep() {
1) | /* I'm a comment! */
1) 1.449 us | }