认识 Ftrace 机制

认识 Ftrace 机制

1 说明背景

从做工作的角度,我能体会到的是trace event工具能带来工作效率的提升。从学习的角度,我相信使用ftrace工具能更加方便观测内核

文档作为会议的记录和补充,会议主题是《使用Ftrace研究Linux内核》,主讲人是谢欢

回放链接:阅码场录播

2 基本内容

2.1 Ftrace整体框架

根据官方文档的定义,Ftrace是一个内部跟踪器,旨在帮助系统的开发者和设计者去观测内核的运行。它可以被用于调试或分析发生在用户空间之外的延迟和性能问题。随着发展,目前已经演变成为一个基础的调试框架(支持多个不同类型的trace功能)。直观理解有两层含义:一是提供函数钩子的基础设施,二是基于tracefs文件系统的trace框架。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FhsAbBjK-1671863034979)(images/202208210954.png)]

  • function tracer:在函数头挂钩子函数
  • function graph tracer:可以带时间戳函数执行流打印
  • kprobe:一般是挂在函数入口点,用于获取参数
  • trace event:函数执行时调用静态的钩子函数
  • kretprobe:通常是函数出口点,执行钩子函数
mount -t tracefs nodev /sys/kernel/tracing # 挂载tracefs
ls -l /sys/kernel/tracing # 查看挂载点

2.2 function tracer使用

2.2.1 case01: 过滤"vfs_open"函数
cd /sys/kernel/debug/tracing
echo vfs_open > ./set_ftrace_filter # 过滤要跟踪的函数
echo function > ./current_tracer
cp trace /test.txt && cat /test.txt

# 可配置功能
cat ./set_ftrace_filter
cat ./set_ftrace_pid
cat ./options/func_stack_trace
# tracer: function
#
# entries-in-buffer/entries-written: 61/61   #P:2
#
#                                _-----=> irqs-off
#                               / _----=> need-resched
#                              | / _---=> hardirq/softirq
#                              || / _--=> preempt-depth
#                              ||| /     delay
#           TASK-PID     CPU#  ||||   TIMESTAMP  FUNCTION
#              | |         |   ||||      |         |
         systemd-1       [000] .... 549571.137111: vfs_open <-do_open
         systemd-1       [000] .... 549571.137177: vfs_open <-do_open
      multipathd-496     [001] .... 549572.062027: vfs_open <-do_open
      multipathd-496     [001] .... 549572.062797: vfs_open <-do_open
      multipathd-496     [001] .... 549572.064078: vfs_open <-do_open
 systemd-journal-358     [001] .... 549572.064461: vfs_open <-do_open
 systemd-journal-358     [001] .... 549572.064552: vfs_open <-do_open
 systemd-journal-358     [001] .... 549572.064603: vfs_open <-do_open
 systemd-journal-358     [001] .... 549572.064642: vfs_open <-do_open
 systemd-journal-358     [001] .... 549572.064807: vfs_open <-do_open

2.3 function graph tracer使用

2.3.1 case01: 函数"vfs_open"的执行时间
echo vfs_open > ./set_ftrace_filter
echo function_graph > ./current_tracer
echo 1 > ./options/funcgraph-proc # 打印进程TASK/PID
cp trace /test.txt && cat /test.txt
# tracer: function_graph
#
# CPU  TASK/PID         DURATION                  FUNCTION CALLS
# |     |    |           |   |                     |   |   |   |
 1)  multipa-496   | + 14.596 us   |  vfs_open();
 0)  irqbala-637   |   7.785 us    |  vfs_open();
 0)  irqbala-637   |   3.493 us    |  vfs_open();
 0)  irqbala-637   |   1.775 us    |  vfs_open();
 0)  irqbala-637   |   1.189 us    |  vfs_open();
 0)  irqbala-637   |   1.146 us    |  vfs_open();
 0)  irqbala-637   |   1.261 us    |  vfs_open();
 0)  irqbala-637   |   1.101 us    |  vfs_open();
 0)  irqbala-637   |   1.177 us    |  vfs_open();
 0)  irqbala-637   |   1.056 us    |  vfs_open();
 0)  irqbala-637   |   1.169 us    |  vfs_open();
 0)  irqbala-637   |   1.117 us    |  vfs_open();
 1)  multipa-496   | + 16.365 us   |  vfs_open();
 1)  multipa-496   |   9.509 us    |  vfs_open();
 1)  multipa-496   | + 15.136 us   |  vfs_open();
 ------------------------------------------
 1)  multipa-496   =>  systemd-358  
 ------------------------------------------
.....
2.3.2 case02: 函数"vfs_open"向下执行流
echo > ./set_ftrace_filter # 不使用过滤!!!
echo vfs_open > ./set_graph_function #  使用函数图表
echo function_graph > ./current_tracer
echo 1 > ./options/funcgraph-proc # 打印进程TASK/PID
echo 1 > ./options/funcgraph-tail # 尾部注释(方便观察)
cp trace /test.txt && cat /test.txt
# tracer: function_graph
#
# CPU  TASK/PID         DURATION                  FUNCTION CALLS
# |     |    |           |   |                     |   |   |   |
 0)  systemd-358   | + 38.350 us   |            } /* single_release */
 0)  systemd-358   |   0.504 us    |            module_put();
 0)  systemd-358   |   0.500 us    |            put_pid();
 0)  systemd-358   |               |            dput() {
 0)  systemd-358   |               |              __cond_resched() {
 0)  systemd-358   |   0.478 us    |                rcu_all_qs();
 0)  systemd-358   |   1.433 us    |              } /* __cond_resched */
 0)  systemd-358   |   0.480 us    |              _raw_spin_lock();
 0)  systemd-358   |   0.459 us    |              rcu_read_unlock_strict();
 0)  systemd-358   |   0.553 us    |              pid_delete_dentry();
 0)  systemd-358   |   5.900 us    |            } /* dput */
 0)  systemd-358   |               |            mntput() {
 0)  systemd-358   |               |              mntput_no_expire() {
 0)  systemd-358   |   0.493 us    |                rcu_read_unlock_strict();
 0)  systemd-358   |   1.592 us    |              } /* mntput_no_expire */
 0)  systemd-358   |   2.607 us    |            } /* mntput */
 0)  systemd-358   |               |            security_file_free() {
 0)  systemd-358   |   0.607 us    |              apparmor_file_free_security();
 0)  systemd-358   |   1.038 us    |              kmem_cache_free();
 0)  systemd-358   |   3.355 us    |            } /* security_file_free */
....

2.4 kprobe event

2.4.1 case01: 查看"vfs_open"当前打开文件名
# 理论计算:
# $arg1, 第一个参数
# +0x8($arg1), 地址偏移+0x8
# +0x70(+0x8($arg1)), 相当与C语言的 *(*($arg1 + 0x8) + 0x70)
echo 'p vfs_open name=+0x70(+0x8($arg1)):string namep=+0(+0x60(+0x8($arg1))):string' > ./kprobe_events
echo 1 > ./events/kprobes/p_vfs_open_0/enable
echo > trace && cat /test.txt
cp trace /test.txt && cat /test.txt
cat-1317 [003] ..... 14640.951607: p_vfs_open_0: (vfs_open+0x0/0x30) name="cat" namep="cat"
cat-1317 [003] ..... 14640.953115: p_vfs_open_0: (vfs_open+0x0/0x30) name="ld-2.31.so" namep="ld-2.31.so"
cat-1317 [003] ..... 14640.954505: p_vfs_open_0: (vfs_open+0x0/0x30) name="ld.so.cache" namep="ld.so.cache"
cat-1317 [003] ..... 14640.954947: p_vfs_open_0: (vfs_open+0x0/0x30) name="libc-2.31.so" namep="libc-2.31.so"
cat-1317 [003] ..... 14640.955536: p_vfs_open_0: (vfs_open+0x0/0x30) name="locale-archive" namep="locale-archive"
cat-1317 [003] ..... 14640.955655: p_vfs_open_0: (vfs_open+0x0/0x30) name="test.txt" namep="test.txt"
cat-1318 [003] ..... 14643.884073: p_vfs_open_0: (vfs_open+0x0/0x30) name="cat" namep="cat"
2.4.2 case02: 捕获"vfs_open"查看指定文件的信息的事件
# 功能: 利用filter和trigger文件
root@debian:/sys/kernel/debug/tracing# ls ./events/kprobes/p_vfs_open_0/
enable filter format hist id inject trigger

# 格式: kprobe event
root@debian:/sys/kernel/debug/tracing# cat ./events/kprobes/p_vfs_open_0/format

echo 'p vfs_open name=+0x70(+0x8($arg1)):string namep=+0(+0x60(+0x8($arg1))):string' > ./kprobe_events
echo 1 > ./events/kprobes/p_vfs_open_0/enable # 过滤包含"test"字段的文件的事件
echo 'name ~ "*test*"' > ./events/kprobes/p_vfs_open_0/filter
echo > trace && cat /test.txt
echo 'stacktrace if name ~ "*test*"' > ./events/kprobes/p_vfs_open_0/trigger  # 包含"test"字段的文件的事件会触发"stacktrace"堆栈打印

2.5 trace event

cat /sys/kernel/debug/tracing/available_events # 查看当前支持的跟踪事件列表
2.5.1 case01: 打开"gsgpu"驱动跟踪节点
echo 1 > /sys/kernel/debug/tracing/events/gsgpu/enable && \
echo 0 > /sys/kernel/debug/tracing/trace && \
/root/run_test.sh ; cp /sys/kernel/debug/tracing/trace /test.txt && \
echo 0 > /sys/kernel/debug/tracing/events/gsgpu/enable
cat /test.txt
2.5.2 case02: 通过filter过滤事件
echo 0 > /sys/kernel/debug/tracing/events/gsgpu/enable && \
echo 1 > /sys/kernel/debug/tracing/events/gsgpu/gsgpu_bo_move/enable && \
echo 0 > /sys/kernel/debug/tracing/trace && \
echo "bo_size >= 50000" > /sys/kernel/debug/tracing/events/gsgpu/gsgpu_bo_move/filter && \
/root/run_test.sh ; cp /sys/kernel/debug/tracing/trace /test.txt && \
echo 0 > /sys/kernel/debug/tracing/events/gsgpu/gsgpu_bo_move/filter
cat /test.txt
2.5.3 case03: 通过trigger查看栈回溯
echo 0 > /sys/kernel/debug/tracing/events/gsgpu/enable && \
echo 1 > /sys/kernel/debug/tracing/events/gsgpu/gsgpu_bo_move/enable && \
echo 0 > /sys/kernel/debug/tracing/trace && \
echo 'stacktrace' > /sys/kernel/debug/tracing/events/gsgpu/gsgpu_bo_move/trigger && \
/root/run_test.sh ; cp /sys/kernel/debug/tracing/trace /test.txt && \
echo '!stacktrace' > /sys/kernel/debug/tracing/events/gsgpu/gsgpu_bo_move/trigger && \
cat /test.txt

2.6 objtrace

2.6.1 case01:观察对象数据在函数中流动
cd /sys/kernel/debug/tracing/
echo 'p bio_add_page arg1=$arg1 arg2=$arg2' > ./kprobe_events
echo 'objtrace:add:arg1,0x28:u32:5 if comm == "cat"' > ./events/kprobes/p_bio_add_page_0/trigger
# du -sh /test.txt // 12K
cat /test.txt > /dev/null

源码位置:https://github.com/x-lugoo/linux/tree/objtrace-v9

3 一些观点

1、ftrace很多功能在国内使用不充分,比如tracer网上资料少,ftrace功能很强大,可挖掘的潜力大。像是大家对shell的使用,如果大家能积累更多的案例,这样能更好的普及和使用。
2、ftrace和正常的日志环形缓冲区不同,如果大量日志向同一个缓冲区输入,一会导致信息混乱,二是容易覆盖有效数据。使用ftrace的过滤等功能,可以更好的解决此类问题(适用更加复杂的业务场景)。补充ftrace其他功能:自动保存结果输出到文件;生成直方图;触发其他事件;等等。
3、ftrace对内核的通用性还是比较强的,相比ebpf来说ftrace对于低版本的内核更加友好。
4、ftrace的用户群体大,但是名声没有ebpf的功能大。
5、ftrace的tracer在linux中使用shell脚本来实现,如果想要观察和定位,使用这种手段方便;如果转发类或者做业务相关的,推荐使用ebpf比较多。可以根据各自优势应用到不同的场景,也可以两者结合使用。

4 一些问答

0、perf/ftrace/ebpf关系
基于kprobe的ebpf通过ID找到字节码程序,当perf使用相同功能的时候,可以依据ID来找到这个字节码程序。ebpf程序本质上是基于ftrace,在挂载点通过ftrace框架来调用字节码程序

# 使用bpftrace工具将ebpf程序挂载到"kprobe:do_nanosleep"
bpftrace -e 'kprobe:do_nanosleep { printf("PID %d sleeping...\n", pid); }'

1、kprobe和function trace的钩子有什么区别?
基本相同,区别在于kprobe钩子函数会将寄存器信息放入堆栈,可以用于取出函数参数,而function trace没有这个功能。

2、什么是no trace函数?
如果函数本身参与ftrace功能,不能用于trace(避免递归) 这样的函数一般都是no trace。

3、uprobe是用什么实现的?
uprobe在用户层,基于断点指令来实现。

4、ftrace对性能影响多大?
看如何使用?如果对所有函数使用function tracer,如果只使用一个性能事件性能消耗很小。

5、如果从学习内核的角度来讲,怎样把ftrace作为一个辅助的工具来上手内核?
在调试内核的时候,通常使用printk/printf来使用,使用ftrace的前提要戒掉这个习惯,然后使用ftrace工具来调试。

6、对于可靠性和安全性比较高的领域,对于ftrace是不是要慎用一些?
是的,对原理理解要求比较高一些。对原理比较清楚的话,能很好的缩小ftrace的使用范围,来进一步降低对系统的性能消耗。

7、如何评估这些调试工具的开销?
正确对待ftrace是一个辅助工具,前提还是要对代码比较熟悉,ftrace辅助对代码的观察。

8、ftrace这样的工具好处?
使用在不破坏内核的情况下,提供一个对内核可观测手段;提供基础的tracer功能,灵活运用好tracer功能对分析问题帮助;perf工具将各种类型的挂载点收入囊中,一统江湖。

9、linux内核中有这么多钩子?都有什么局限,如何选择
看具体想使用哪些功能,比如查看函数怎么执行,选择function tracer;比如查看某一个函数的参数,使用kprobe挂载点对应的钩子函数不一样的。

10、ftrace的tracer在linux中使用shell脚本来实现,如果想要观察和定位,使用这种手段方便;如果转发类或者做业务相关的,推荐使用ebpf比较多
各有优势,应用到不同的场景,也可以两者结合使用。

11、在嵌入式场景,内存资源比较紧张的时候适用么?
内存消耗比较小。也可以设置,buffer可以调小一些。

12、ftrace在性能消耗比ebpf更小么?
也不一定,看使用那部分功能。

13、有没有推荐的日志化性能分析的图形工具?
tracecmd和KernelShark。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hinzer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值