深入探索事件追踪工具:Ftrace、LTTng、Valgrind与strace
在软件开发和系统调试过程中,了解事件的顺序和关联对于排查问题、优化性能至关重要。本文将详细介绍几种常用的事件追踪工具,包括Ftrace、LTTng、Valgrind和strace,帮助你更好地理解和使用这些工具来进行系统分析和调试。
1. 事件追踪概述
目前我们所见到的工具大多使用统计采样。但在很多情况下,我们希望更深入地了解事件的顺序,以便观察它们之间的关系。函数追踪通过在代码中插入追踪点来捕获事件信息,这些信息可能包括:
- 时间戳
- 上下文,如当前进程ID(PID)
- 函数参数和返回值
- 调用栈
与统计分析相比,函数追踪的侵入性更强,并且会生成大量数据。不过,我们可以在采样时和查看追踪数据时应用过滤器来减少数据量。
本文将重点介绍两种内核函数追踪工具:Ftrace和LTTng。
2. Ftrace介绍
Ftrace是一种内核函数追踪器,它源于Steven Rostedt等人在追踪实时应用中高调度延迟原因时的工作。Ftrace首次出现在Linux 2.6.27版本中,并在此后得到了积极的开发。在内核源代码的
Documentation/trace
目录下有许多描述内核追踪的文档。
2.1 Ftrace的组成
Ftrace由多个追踪器组成,可以记录内核中各种类型的活动。这里主要介绍函数追踪器(function)、函数图追踪器(function_graph)和事件追踪点(event tracepoints)。
-
函数追踪器(function)
:对每个内核函数进行插桩,记录函数调用并添加时间戳。它在编译内核时使用
-pg开关插入插桩代码,但与gprof的相似之处仅此而已。 - 函数图追踪器(function_graph) :不仅记录函数的调用,还记录函数的退出,从而可以创建调用图。
- 事件追踪点(event tracepoints) :记录与函数调用相关的参数。
2.2 Ftrace的用户界面
Ftrace具有非常适合嵌入式系统的用户界面,它完全通过debugfs文件系统中的虚拟文件实现。这意味着你无需在目标设备上安装任何工具即可使用它。不过,如果你有其他需求,还有其他用户界面可供选择:
-
trace-cmd
:一个命令行工具,用于记录和查看追踪数据,在Buildroot(
BR2_PACKAGE_TRACE_CMD
)和Yocto Project(
trace-cmd
)中均可使用。
-
KernelShark
:一个图形化的追踪查看器,可作为Yocto Project的一个软件包使用。
2.3 准备使用Ftrace
在使用Ftrace之前,需要在内核配置菜单中进行一些配置。至少需要开启以下选项:
-
CONFIG_FUNCTION_TRACER
:位于
Kernel hacking | Tracers | Kernel Function Tracer
菜单中。
为了获得更好的使用体验,建议同时开启以下选项:
-
CONFIG_FUNCTION_GRAPH_TRACER
:位于
Kernel hacking | Tracers | Kernel Function Graph Tracer
菜单中。
-
CONFIG_DYNAMIC_FTRACE
:位于
Kernel hacking | Tracers | enable/disable function tracing dynamically
菜单中。
由于Ftrace完全在内核中运行,因此无需进行用户空间的配置。
2.4 使用Ftrace
在使用Ftrace之前,需要挂载debugfs文件系统,通常挂载到
/sys/kernel/debug
目录下:
# mount -t debugfs none /sys/kernel/debug
Ftrace的所有控制文件都位于
/sys/kernel/debug/tracing
目录下,该目录下的
README
文件中甚至还有一个简单的使用指南。
查看内核中可用的追踪器列表:
# cat /sys/kernel/debug/tracing/available_tracers
blk function_graph function nop
当前活动的追踪器由
current_tracer
文件显示,初始时为
nop
(空追踪器)。
下面是一个简单的示例,展示如何捕获追踪数据:
# echo function > /sys/kernel/debug/tracing/current_tracer
# echo 1 > /sys/kernel/debug/tracing/tracing_on
# sleep 1
# echo 0 > /sys/kernel/debug/tracing/tracing_on
在这一秒内,追踪缓冲区将被内核调用的每个函数的详细信息填满。追踪缓冲区的格式为纯文本,具体格式可参考
Documentation/trace/ftrace.txt
文件。可以通过以下命令查看追踪缓冲区的内容:
# cat /sys/kernel/debug/tracing/trace
输出示例如下:
# tracer: function
#
# entries-in-buffer/entries-written: 40051/40051 #P:1
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
sh-361 [000] ...1 992.990646: mutex_unlock <-rb_simple_write
sh-361 [000] ...1 992.990658: __fsnotify_parent <-vfs_write
sh-361 [000] ...1 992.990661: fsnotify <-vfs_write
sh-361 [000] ...1 992.990663: __srcu_read_lock <-fsnotify
sh-361 [000] ...1 992.990666: preempt_count_add <-__srcu_read_lock
sh-361 [000] ...2 992.990668: preempt_count_sub <-__srcu_read_lock
sh-361 [000] ...1 992.990670: __srcu_read_unlock <-fsnotify
sh-361 [000] ...1 992.990672: __sb_end_write <-vfs_write
sh-361 [000] ...1 992.990674: preempt_count_add <-__sb_end_write
可以看到,在一秒内可以捕获大量的数据点,这里超过了40000个。
如果选择
function_graph
追踪器,Ftrace将捕获调用图,示例如下:
# tracer: function_graph
#
# CPU DURATION FUNCTION CALLS
# | | | | | | |
0) + 63.167 us | } /* cpdma_ctlr_int_ctrl */
0) + 73.417 us | } /* cpsw_intr_disable */
0) | disable_irq_nosync() {
0) | __disable_irq_nosync() {
0) | __irq_get_desc_lock() {
0) 0.541 us | irq_to_desc();
0) 0.500 us | preempt_count_add();
0) + 16.000 us | }
0) | __disable_irq() {
0) 0.500 us | irq_disable();
0) 8.208 us | }
0) | __irq_put_desc_unlock() {
0) 0.459 us | preempt_count_sub();
0) 8.000 us | }
0) + 55.625 us | }
0) + 63.375 us | }
通过调用图,我们可以看到函数调用的嵌套关系,用花括号
{
和
}
表示。在结束的花括号处,会显示函数执行所花费的时间,如果时间超过10µs,会用加号
+
标注;如果超过100µs,会用感叹号
!
标注。
如果你只对单个进程或线程引起的内核活动感兴趣,可以通过将线程ID写入
set_ftrace_pid
文件来限制追踪范围。
2.5 动态Ftrace和追踪过滤器
启用
CONFIG_DYNAMIC_FTRACE
选项后,Ftrace可以在运行时修改函数追踪点,这带来了两个好处:
-
减少性能开销
:在构建时对追踪函数探针进行额外处理,使得Ftrace子系统在启动时能够定位这些探针,并将其替换为NOP指令,从而将函数追踪代码的开销几乎降为零。这样,你可以在生产环境或接近生产环境的内核中启用Ftrace,而不会对性能产生影响。
-
选择性追踪
:可以选择性地启用函数追踪点,而不是追踪所有函数。函数列表存储在
available_filter_functions
文件中,其中包含数万个函数。你可以根据需要从
available_filter_functions
文件中复制函数名到
set_ftrace_filter
文件来启用特定函数的追踪,通过将函数名写入
set_ftrace_notrace
文件来停止追踪该函数。你还可以使用通配符并将函数名追加到列表中。
例如,如果你对TCP处理感兴趣,可以使用以下命令:
# cd /sys/kernel/debug/tracing
# echo "tcp*" > set_ftrace_filter
# echo function > current_tracer
# echo 1 > tracing_on
运行一些测试后,查看追踪数据:
# cat trace
2.6 追踪事件
前面介绍的函数追踪器和函数图追踪器只记录函数执行的时间,而追踪事件功能还会记录与函数调用相关的参数,使追踪数据更具可读性和信息量。
例如,对于
kmalloc
函数,追踪事件不仅会记录函数被调用,还会记录请求的字节数和返回的指针。追踪事件在perf、LTTng和Ftrace中都有应用,其开发受到了LTTng项目的推动。
每个追踪事件由内核开发者使用
TRACE_EVENT
宏在源代码中定义,目前有超过一千个追踪事件。你可以在运行时通过
/sys/kernel/debug/tracing/available_events
文件查看可用的事件列表,事件名称的格式为
子系统:函数
,例如
kmem:kmalloc
。每个事件在
tracing/events/[子系统]/[函数]
目录下都有一个对应的子目录,包含以下文件:
-
enable
:写入
1
以启用该事件。
-
filter
:一个表达式,只有当该表达式计算结果为真时,才会追踪该事件。
-
format
:事件和参数的格式。
-
id
:事件的数字标识符。
-
trigger
:当事件发生时执行的命令,其语法在
Documentation/trace/ftrace.txt
的
Filter commands
部分定义。
下面是一个简单的示例,展示如何追踪
kmalloc
和
kfree
事件:
# echo nop > current_tracer
# echo 1 > events/kmem/kmalloc/enable
# echo 1 > events/kmem/kfree/enable
# echo "kmem:kmalloc kmem:kfree" > set_event
现在,当你查看追踪数据时,可以看到函数及其参数:
# cat trace
输出示例如下:
# tracer: nop
#
# entries-in-buffer/entries-written: 359/359 #P:1
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
cat-382 [000] ...1 2935.586706: kmalloc:call_site=c0554644 ptr=de515a00
bytes_req=384 bytes_alloc=512
gfp_flags=GFP_ATOMIC|GFP_NOWARN|GFP_NOMEMALLOC
cat-382 [000] ...1 2935.586718: kfree: call_site=c059c2d8 ptr=(null)
3. LTTng介绍
Linux Trace Toolkit(LTT)项目由Karim Yaghmour发起,是最早用于Linux内核的追踪工具之一。后来,Mathieu Desnoyers对其进行了重新实现,推出了下一代追踪工具LTTng,并将其扩展到涵盖用户空间追踪。LTTng的项目网站为http://lttng.org/,其中包含了全面的用户手册。
3.1 LTTng的组成
LTTng由三个组件组成:
-
核心会话管理器
:负责管理追踪会话。
-
内核追踪器
:以一组内核模块的形式实现。
-
用户空间追踪器
:以库的形式实现。
此外,你还需要一个追踪查看器,如Babeltrace(http://www.efficios.com/babeltrace)或Eclipse Trace Compass插件,用于在主机或目标设备上显示和过滤原始追踪数据。
3.2 LTTng的配置
LTTng需要内核配置
CONFIG_TRACEPOINTS
选项,当你选择
Kernel hacking | Tracers | Kernel Function Tracer
时,该选项会被启用。
3.3 LTTng与Yocto Project和Buildroot的集成
3.3.1 Yocto Project
在
conf/local.conf
文件中,需要将以下包添加到目标设备的依赖项中:
IMAGE_INSTALL_append = " lttng-tools lttng-modules lttng-ust"
如果你想在目标设备上运行Babeltrace,还需要追加
babeltrace
包。
3.3.2 Buildroot
需要启用以下选项:
-
BR2_PACKAGE_LTTNG_MODULES
:位于
Target packages | Debugging, profiling and benchmark | lttng-modules
菜单中。
-
BR2_PACKAGE_LTTNG_TOOLS
:位于
Target packages | Debugging, profiling and benchmark | lttng-tools
菜单中。
-
BR2_PACKAGE_LTTNG_LIBUST
:位于
Target packages | Libraries | Other, enable lttng-libust
菜单中,用于用户空间追踪。
Buildroot会自动构建主机版的Babeltrace,并将其放置在
output/host/usr/bin/babeltrace
目录下。
3.4 使用LTTng进行内核追踪
LTTng可以使用前面介绍的Ftrace事件作为潜在的追踪点,初始时这些事件是禁用的。
LTTng的控制接口是
lttng
命令。你可以使用以下命令列出内核探针:
# lttng list --kernel
输出示例如下:
Kernel events:
-------------
writeback_nothread (loglevel: TRACE_EMERG (0)) (type: tracepoint)
writeback_queue (loglevel: TRACE_EMERG (0)) (type: tracepoint)
writeback_exec (loglevel: TRACE_EMERG (0)) (type: tracepoint)
追踪数据是在会话的上下文中捕获的,下面是一个示例:
# lttng create test
Session test created.
Traces will be written in /home/root/lttng-traces/test-20150824-140942
# lttng list
Available tracing sessions:
1) test (/home/root/lttng-traces/test-20150824-140942) [inactive]
现在,在当前会话中启用一些事件。你可以使用
--all
选项启用所有内核追踪点,但要注意会生成大量追踪数据。这里我们从几个与调度器相关的追踪事件开始:
# lttng enable-event --kernel sched_switch,sched_process_fork
检查配置是否正确:
# lttng list test
输出示例如下:
Tracing session test: [inactive]
Trace path: /home/root/lttng-traces/test-20150824-140942
Live timer interval (usec): 0
=== Domain: Kernel ===
Channels:
-------------
- channel0: [enabled]
Attributes:
overwrite mode: 0
subbufers size: 26214
number of subbufers: 4
switch timer interval: 0
read timer interval: 200000
trace file count: 0
trace file size (bytes): 0
output: splice()
Events:
sched_process_fork (loglevel: TRACE_EMERG (0)) (type: tracepoint) [enabled]
sched_switch (loglevel: TRACE_EMERG (0)) (type: tracepoint) [enabled]
启动追踪:
# lttng start
运行测试负载后,停止追踪:
# lttng stop
会话的追踪数据会写入会话目录的
lttng-traces/<会话名>/kernel
目录下。
你可以使用Babeltrace查看器将原始追踪数据以文本格式输出:
$ babeltrace lttng-traces/test-20150824-140942/kernel
Babeltrace的文本输出便于使用
grep
等命令进行字符串搜索。
对于图形化的追踪查看器,Eclipse的Trace Compass插件是一个不错的选择。将追踪数据导入Eclipse的步骤如下:
1. 打开追踪视角。
2. 选择
File | New | Tracing project
创建一个新项目。
3. 输入项目名称并点击
Finish
。
4. 在项目资源管理器菜单中右键点击新项目选项,选择
Import
。
5. 展开
Tracing
并选择
Trace Import
。
6. 浏览到包含追踪数据的目录(例如
test-20150824-140942
),勾选你想要的子目录(可能是
kernel
),然后点击
Finish
。
7. 展开项目,再展开
Traces[1]
,双击
kernel
,你应该能看到追踪数据。
3.5 总结
通过本文的介绍,我们详细了解了Ftrace和LTTng这两种内核函数追踪工具的使用方法和特点。Ftrace适合嵌入式系统,具有低开销和灵活的追踪选项;LTTng则提供了更全面的追踪功能,包括用户空间追踪。在实际应用中,你可以根据具体需求选择合适的工具来进行系统分析和调试。
接下来,我们将继续介绍Valgrind和strace这两种工具,它们在应用程序分析和系统调用追踪方面具有独特的优势。
4. 使用Valgrind进行应用程序分析
Valgrind是一个强大的工具,在第13章中我们介绍过它的memcheck工具,用于识别内存问题。除此之外,Valgrind还有其他有用的工具用于应用程序分析,这里主要介绍Callgrind和Helgrind。
4.1 Callgrind
Callgrind是一个生成调用图的分析器,它还可以收集处理器缓存命中率和分支预测的信息。Callgrind仅在瓶颈是CPU限制时有用,如果涉及大量I/O或多个进程,它的作用就不大了。
Valgrind不需要内核配置,但需要调试符号。它在Yocto Project和Buildroot中都作为目标包可用(
BR2_PACKAGE_VALGRIND
)。
在目标设备上使用Valgrind运行Callgrind的命令如下:
# valgrind --tool=callgrind <程序>
这将生成一个名为
callgrind.out.<PID>
的文件,你可以将其复制到主机上,使用
callgrind_annotate
进行分析。
默认情况下,Callgrind会将所有线程的数据收集到一个文件中。如果你在捕获数据时添加
--separate-threads=yes
选项,每个线程的分析数据将分别存储在名为
callgrind.out.<PID>-<线程ID>
的文件中,例如
callgrind.out.122-01
和
callgrind.out.122-02
。
Callgrind可以模拟处理器的L1/L2缓存,并报告缓存未命中情况。使用
--simulate-cache=yes
选项捕获追踪数据。由于L2缓存未命中的代价远高于L1缓存未命中,因此要特别关注D2mr或D2mw计数较高的代码。
4.2 Helgrind
Helgrind是一个线程错误检测器,用于检测包含POSIX线程的C、C++和Fortran程序中的同步错误。
Helgrind可以检测三类错误:
-
API使用错误
:例如,解锁已经解锁的互斥锁、解锁由不同线程锁定的互斥锁或未检查某些pthread函数的返回值。
-
潜在死锁
:通过监控线程获取锁的顺序,检测由于锁的循环形成而可能导致的死锁,也称为“致命拥抱”。
-
数据竞争
:当两个线程在没有使用适当的锁或其他同步机制的情况下访问共享内存位置时,可能会发生数据竞争。
使用Helgrind非常简单,只需运行以下命令:
# valgrind --tool=helgrind <程序>
Helgrind会在发现问题时打印问题和潜在问题。你可以通过添加
--log-file=<文件名>
选项将这些消息定向到一个文件中。
5. 使用strace进行系统调用追踪
strace是一个非常简单的追踪工具,用于捕获程序及其子进程(可选)进行的系统调用。你可以使用它来完成以下任务:
- 了解程序进行了哪些系统调用。
- 找出失败的系统调用及其错误代码,这在程序无法启动但不打印错误消息或消息过于笼统时非常有用。
- 找出程序尝试打开的文件。
- 了解正在运行的程序正在进行哪些系统调用,例如检查程序是否陷入循环。
5.1 strace的基本使用
strace使用
ptrace(2)
函数来拦截从用户空间到内核的系统调用。如果你想了解
ptrace
的工作原理,可以查看详细且易懂的手册页。
获取追踪数据的最简单方法是将命令作为参数传递给strace,示例如下:
# strace ./helloworld
输出示例如下:
execve("./helloworld", ["./helloworld"], [/* 14 vars */]) = 0
brk(0) = 0x11000
uname({sys="Linux", node="beaglebone", ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6f40000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=8100, ...}) = 0
mmap2(NULL, 8100, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb6f3e000
close(3) = 0
open("/lib/tls/v7l/neon/vfp/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0$`\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1291884, ...}) = 0
mmap2(NULL, 1328520, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6df9000
mprotect(0xb6f30000, 32768, PROT_NONE) = 0
mmap2(0xb6f38000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x137000) = 0xb6f38000
mmap2(0xb6f3b000, 9608, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb6f3b000
close(3)
write(1, "Hello, world!\n", 14) = 14
exit_group(0) = ?
+++ exited with 0 +++
大部分追踪数据显示了运行时环境的创建过程,特别是可以看到库加载器如何搜索
libc.so.6
,最终在
/lib
目录下找到它。最后,程序的
main()
函数开始运行,打印消息并退出。
5.2 追踪子进程和线程
如果你希望strace跟踪原始进程创建的任何子进程或线程,可以添加
-f
选项。如果要跟踪创建线程的程序,几乎肯定需要使用
-f
选项。更好的做法是使用
-ff
和
-o <文件名>
选项,这样每个子进程或线程的输出将被写入一个单独的文件,文件名格式为
<文件名>.<PID | TID>
。
5.3 限制追踪的系统调用
strace的一个常见用途是发现程序在启动时尝试打开哪些文件。你可以通过
-e
选项限制要追踪的系统调用,使用
-o
选项将追踪数据写入文件而不是标准输出。例如:
# strace -e open -o ssh-strace.txt ssh localhost
这将显示
ssh
在建立连接时打开的库和配置文件。
5.4 使用strace作为基本的分析工具
你甚至可以将strace用作基本的分析工具。如果使用
-c
选项,它会累积系统调用所花费的时间,并输出一个摘要,示例如下:
# strace -c grep linux /usr/lib/* > /dev/null
输出示例如下:
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------
78.68 0.012825 1 11098 18 read
11.03 0.001798 1 3551 write
10.02 0.001634 8 216 15 open
0.26 0.000043 0 202 fstat64
0.00 0.000000 0 201 close
0.00 0.000000 0 1 execve
0.00 0.000000 0 1 1 access
0.00 0.000000 0 3 brk
0.00 0.000000 0 199 munmap
0.00 0.000000 0 1 uname
0.00 0.000000 0 5 mprotect
0.00 0.000000 0 207 mmap2
0.00 0.000000 0 15 15 stat64
0.00 0.000000 0 1 getuid32
0.00 0.000000 0 1 set_tls
------ ----------- ----------- --------- --------- -----------
100.00 0.016300 15702 49 total
6. 总结
本文详细介绍了Ftrace、LTTng、Valgrind和strace这几种常用的事件追踪工具。Ftrace和LTTng主要用于内核函数追踪,Ftrace适合嵌入式系统,LTTng功能更全面;Valgrind的Callgrind和Helgrind工具可用于应用程序分析,分别用于生成调用图和检测线程错误;strace则是一个简单而强大的系统调用追踪工具,可帮助我们了解程序的行为和性能瓶颈。
在实际应用中,你可以根据具体需求选择合适的工具进行系统分析和调试。希望本文能帮助你更好地掌握这些工具的使用方法,提高系统分析和调试的效率。
7. 工具对比总结
为了更清晰地了解这些工具的特点和适用场景,下面通过表格进行对比:
| 工具名称 | 主要功能 | 适用场景 | 优点 | 缺点 |
| — | — | — | — | — |
| Ftrace | 内核函数追踪,记录函数调用、时间戳、参数等 | 嵌入式系统内核分析,实时应用性能调试 | 低开销,可选择性追踪,适合嵌入式环境 | 数据量大时需合理使用过滤器 |
| LTTng | 内核和用户空间追踪 | 需要全面追踪系统活动的场景 | 功能全面,支持用户空间追踪 | 配置相对复杂,可能产生大量数据 |
| Valgrind - Callgrind | 生成调用图,收集处理器缓存和分支预测信息 | CPU 瓶颈的应用程序分析 | 提供详细的调用图和缓存信息 | 不适用于大量 I/O 或多进程场景 |
| Valgrind - Helgrind | 检测线程同步错误 | 包含 POSIX 线程的 C、C++ 和 Fortran 程序调试 | 能有效检测多种线程错误 | 对非线程相关问题作用不大 |
| strace | 捕获系统调用 | 了解程序系统调用行为,排查启动问题 | 简单易用,可限制追踪范围 | 主要关注系统调用,对程序内部逻辑分析有限 |
7.1 选择合适工具的流程图
graph TD;
A[问题类型] --> B{是否是内核相关问题};
B -- 是 --> C{是否是嵌入式系统};
C -- 是 --> D[选择 Ftrace];
C -- 否 --> E{是否需要用户空间追踪};
E -- 是 --> F[选择 LTTng];
E -- 否 --> D;
B -- 否 --> G{是否是应用程序性能问题};
G -- 是 --> H{是否是 CPU 瓶颈};
H -- 是 --> I[选择 Valgrind - Callgrind];
H -- 否 --> J{是否涉及线程同步};
J -- 是 --> K[选择 Valgrind - Helgrind];
J -- 否 --> L[选择 strace 分析系统调用];
G -- 否 --> L;
8. 实际案例分析
8.1 案例一:嵌入式系统性能优化
假设我们有一个嵌入式系统,运行过程中出现了性能问题,怀疑是内核函数调用导致的。我们可以使用 Ftrace 进行分析。
1.
配置内核
:开启
CONFIG_FUNCTION_TRACER
、
CONFIG_FUNCTION_GRAPH_TRACER
和
CONFIG_DYNAMIC_FTRACE
选项。
2.
挂载 debugfs
:
# mount -t debugfs none /sys/kernel/debug
- 选择追踪器并开始追踪 :
# echo function_graph > /sys/kernel/debug/tracing/current_tracer
# echo 1 > /sys/kernel/debug/tracing/tracing_on
# sleep 5
# echo 0 > /sys/kernel/debug/tracing/tracing_on
- 查看追踪数据 :
# cat /sys/kernel/debug/tracing/trace
通过分析调用图,我们可以找出执行时间较长的函数,对这些函数进行优化,从而提高系统性能。
8.2 案例二:多线程程序调试
对于一个包含 POSIX 线程的 C++ 程序,运行过程中出现了崩溃现象,怀疑是线程同步问题。我们可以使用 Valgrind 的 Helgrind 工具进行检测。
# valgrind --tool=helgrind ./your_program
Helgrind 会输出程序中存在的线程同步错误,如互斥锁使用不当、潜在死锁和数据竞争等问题。根据这些信息,我们可以对代码进行修改,修复线程同步问题。
8.3 案例三:程序启动问题排查
如果一个程序无法正常启动,且没有明确的错误信息,我们可以使用 strace 来找出问题所在。
# strace -o startup_trace.txt ./your_program
查看
startup_trace.txt
文件,找出返回错误的系统调用,根据错误代码定位问题。例如,如果发现
open
系统调用返回
ENOENT
错误,说明程序尝试打开的文件不存在,我们可以检查文件路径或文件权限是否正确。
9. 注意事项和技巧
9.1 Ftrace 注意事项
- 数据量控制 :Ftrace 可能会生成大量数据,尤其是在长时间追踪或不使用过滤器的情况下。因此,要合理设置追踪时间和使用过滤器,避免数据过多导致分析困难。
-
动态追踪
:启用
CONFIG_DYNAMIC_FTRACE可以减少性能开销,但在使用时要注意正确配置追踪点,避免遗漏重要信息。
9.2 LTTng 注意事项
- 配置复杂性 :LTTng 的配置相对复杂,需要正确设置内核选项和相关包。在使用前要仔细阅读文档,确保配置正确。
- 数据管理 :由于 LTTng 可以全面追踪系统活动,可能会产生大量数据。要合理规划存储和分析这些数据,避免磁盘空间不足。
9.3 Valgrind 注意事项
- 性能影响 :Valgrind 会对程序的运行性能产生较大影响,尤其是在使用 Callgrind 模拟缓存时。因此,在测试时要注意测试环境和测试时间的安排。
- 调试符号 :确保程序包含调试符号,否则 Valgrind 可能无法提供准确的信息。
9.4 strace 注意事项
-
输出管理
:strace 的输出可能会很长,尤其是在追踪复杂程序时。可以使用
-o选项将输出保存到文件中,方便后续分析。 -
子进程追踪
:如果程序会创建子进程或线程,要根据需要使用
-f、-ff选项进行追踪。
9.5 通用技巧
- 结合使用工具 :在实际分析中,往往需要结合多种工具来全面了解系统问题。例如,先用 strace 了解程序的系统调用行为,再用 Valgrind 分析程序内部的性能和线程问题。
- 记录测试环境 :在进行测试和分析时,要记录测试环境的相关信息,如系统版本、程序版本、硬件配置等,以便后续复现问题和对比分析。
10. 总结与展望
本文详细介绍了 Ftrace、LTTng、Valgrind 和 strace 这几种常用的事件追踪工具,包括它们的功能、使用方法、适用场景和注意事项。通过合理使用这些工具,可以帮助我们更好地理解系统行为,排查问题,优化性能。
随着软件系统的不断发展,对调试和分析工具的要求也越来越高。未来,这些工具可能会在以下方面得到进一步发展:
-
智能化分析
:工具能够自动分析大量数据,提供更准确的问题定位和解决方案建议。
-
跨平台支持
:更好地支持不同操作系统和硬件平台,方便在多样化的环境中使用。
-
可视化增强
:提供更直观、丰富的可视化界面,帮助用户更轻松地理解和分析数据。
希望读者通过本文的介绍,能够掌握这些工具的使用方法,并在实际工作中灵活运用,提高系统分析和调试的效率。同时,也期待这些工具在未来能够不断发展和完善,为软件开发和系统维护提供更强大的支持。
超级会员免费看
2010

被折叠的 条评论
为什么被折叠?



