📋 本章摘要
在第十八章中,我们使用 ltrace 捕捉库函数的性能热点,定位到正则表达式库(pcre_exec 占用 89%)、加密函数和 JSON 解析成为新的瓶颈。通过函数级别的性能分析,将帧率从 30 FPS 提升到 980 FPS。
但当问题进入内核路径 (调度延迟、软中断拥塞、块 IO 队列、kworker 异常)时,strace、ltrace 等用户态工具就显得无能为力了------它们只能看到系统调用的"入口"和"出口",无法观测内核内部的执行链路。
这时,我们需要进入内核的视角------ftrace。
ftrace(Function Tracer)是 Linux 内核自带的跟踪框架,从 2.6.27 版本开始内置。它能够:
- 函数级别追踪:观测任意内核函数的调用链和耗时
- 事件追踪(tracepoints):监控调度、中断、系统调用等内核事件
- 延迟分析:测量中断延迟、抢占延迟、调度延迟
- 动态探针(kprobes):在任意内核函数插入跟踪点
- 零编译成本 :直接通过
/sys/kernel/debug/tracing接口使用
本章将从实战出发,带你掌握 ftrace 的核心用法,理解 function/function_graph tracer、tracepoints 的区别,学习如何过滤特定进程和函数,并通过两个真实案例(kworker 高负载 和 软中断风暴)完成完整的内核性能排查。
🔗 从用户态到内核态:为什么需要 ftrace
1. 用户态工具的视野盲区
在自动驾驶系统的性能排查中,我们已经掌握了多种用户态工具:
- top/htop:进程级别的 CPU、内存占用
- strace:系统调用追踪,但只能看到调用和返回,无法看到内核内部
- ltrace:库函数调用追踪,仅限用户态
- perf:可以采样内核函数,但无法看到完整的调用链
然而,很多性能瓶颈隐藏在内核执行路径中:
场景 1:传感器数据流的软中断风暴
现象:top 显示 ksoftirqd/3 占用 CPU 85%,但看不到具体在处理什么
原因:激光雷达以 10Hz × 300,000 点/帧的速率产生网络数据包
瓶颈:网络协议栈的软中断处理(__netif_receive_skb → ip_rcv → udp_rcv)
工具盲区:strace 看不到内核函数,perf 只能采样无法看到调用链
场景 2:块 IO 导致进程进入 D 状态
现象:数据录制进程频繁进入 D 状态(不可中断睡眠),load 飙升至 12
原因:SSD 写入队列堵塞,进程在 blk_mq_submit_bio → io_schedule 等待
瓶颈:块 IO 调度器的队列处理逻辑
工具盲区:strace 只能看到 write() 系统调用阻塞,无法深入块设备层
场景 3:kworker 异常占用 CPU
现象:kworker/7:2 进程持续占用 CPU 90%,但不知道在执行什么工作
原因:RCU 回调队列积压,频繁执行 rcu_process_callbacks
瓶颈:某个驱动模块泄漏 RCU 引用,导致对象无法释放
工具盲区:kworker 是内核线程,用户态工具无法追踪
2. ftrace 能回答的关键问题
ftrace 可以深入内核,回答以下问题:
| 问题 | ftrace 工具 | 示例 |
|---|---|---|
| 哪些内核函数被频繁调用? | function tracer | 发现 tcp_sendmsg 每秒调用 50,000 次 |
| 关键调用链的耗时分布? | function_graph tracer | vfs_write 调用链耗时 450μs,其中 ext4_file_write_iter 占 380μs |
| CPU 调度延迟的根因? | sched tracepoint | 高优先级任务被低优先级任务抢占,等待 12ms |
| 网络数据包在哪里丢失? | net tracepoint | netif_receive_skb 收到 10,000 包,ip_rcv 只处理 8,500 包 |
| kworker 在执行什么工作? | workqueue tracepoint | process_one_work 正在执行 blk_mq_run_work_fn |
| 软中断处理时间分布? | irq tracepoint | NET_RX 软中断占用 85%,TASKLET 占用 10% |
3. ftrace vs 其他工具
| 工具 | 作用域 | 开销 | 实时性 | 调用链 | 适用场景 |
|---|---|---|---|---|---|
| strace | 系统调用 | 高(50-100%) | 是 | 否 | 用户态程序的系统调用分析 |
| ltrace | 库函数 | 中(20-50%) | 是 | 否 | 用户态库函数性能分析 |
| perf | 用户态+内核态 | 低(1-5%) | 否(采样) | 有(需 -g) | CPU 热点分析,适合长时间采样 |
| ftrace | 内核函数+事件 | 中(5-20%) | 是 | 是 | 内核路径分析,调度/中断/IO 问题 |
| eBPF | 内核+用户态 | 低(< 5%) | 是 | 可编程 | 复杂的自定义监控,需编写程序 |
ftrace 的优势:
- ✅ 内核自带:无需安装额外工具或编译内核模块
- ✅ 实时输出 :可通过
trace_pipe实时查看,无需等待采样结束 - ✅ 完整调用链:function_graph 可显示函数调用树和耗时
- ✅ 丰富的 tracepoints:调度、中断、网络、块 IO 等子系统预置了大量跟踪点
- ✅ 灵活过滤:可按进程、CPU、函数名、事件类型过滤
ftrace 的限制:
- ⚠️ 性能开销:function tracer 会拖慢系统 10-20%(可通过过滤降低)
- ⚠️ 输出量大:未过滤时每秒可产生数 MB 日志
- ⚠️ 学习曲线:需要理解内核函数名和调用关系
- ⚠️ 缺少编程能力:复杂的条件判断和聚合需要 eBPF
🧠 ftrace 架构与核心概念
1. 整体架构
输出层
存储层
过滤层
数据源
function tracer
(函数调用)
function_graph tracer
(调用链+耗时)
tracepoints
(内核事件)
kprobes
(动态探针)
set_ftrace_filter
(函数名过滤)
set_ftrace_pid
(进程过滤)
set_ftrace_notrace
(排除函数)
events/*/filter
(事件条件过滤)
Ring Buffer
(环形缓冲区)
默认 1.4MB/CPU
trace
(静态快照)
trace_pipe
(实时流)
trace_marker
(用户标记)
2. 核心组件详解
(1) Tracers:追踪器
| Tracer | 功能 | 开销 | 适用场景 |
|---|---|---|---|
| function | 记录每个内核函数的调用 | 高(10-20%) | 查找频繁调用的函数 |
| function_graph | 记录函数调用链和耗时 | 很高(20-50%) | 分析调用链的耗时分布 |
| nop | 无追踪(关闭) | 0 | 停止追踪 |
| irqsoff | 记录关中断时间最长的路径 | 中(5-10%) | 中断延迟分析 |
| preemptoff | 记录关抢占时间最长的路径 | 中(5-10%) | 调度延迟分析 |
| wakeup | 记录高优先级任务的唤醒延迟 | 中(5-10%) | 实时性分析 |
(2) Events(Tracepoints):内核预置的跟踪点
bash
# 查看所有可用 events(通常有 1000+ 个)
ls /sys/kernel/debug/tracing/events/
常用 event 子系统:
| 子系统 | 典型 events | 用途 |
|---|---|---|
| sched | sched_switch、sched_wakeup | 进程调度分析 |
| irq | irq_handler_entry、irq_handler_exit | 中断处理时间 |
| softirq | softirq_entry、softirq_exit | 软中断分析 |
| syscalls | sys_enter_read、sys_exit_write | 系统调用追踪 |
| net | netif_receive_skb、net_dev_xmit | 网络数据包流向 |
| block | block_rq_issue、block_rq_complete | 块 IO 请求分析 |
| workqueue | workqueue_execute_start | kworker 工作追踪 |
| kmem | kmalloc、kfree | 内核内存分配 |
(3) Ring Buffer:环形缓冲区
bash
# 查看缓冲区大小(KB,per CPU)
cat /sys/kernel/debug/tracing/buffer_size_kb
# 输出:1408(即 1.4MB × CPU核数)
# 调整缓冲区大小(避免数据丢失)
echo 4096 > /sys/kernel/debug/tracing/buffer_size_kb # 增加到 4MB
# 查看总缓冲区大小
cat /sys/kernel/debug/tracing/buffer_total_size_kb
# 输出:22528(8 核 × 2816 KB)
缓冲区溢出的影响:
- 当追踪速度 > 读取速度时,旧数据会被覆盖
- 可通过
trace_options中的overwrite控制行为
3. ftrace 接口目录结构
bash
# 主入口(需要 root 权限或 debugfs 挂载)
/sys/kernel/debug/tracing/
# 关键文件详解
├── current_tracer # 当前使用的 tracer(读写)
├── available_tracers # 系统支持的所有 tracers(只读)
├── tracing_on # 总开关(1=开启,0=暂停,写入不清空缓冲区)
│
├── trace # 追踪结果(静态快照,读取时暂停追踪)
├── trace_pipe # 实时流输出(阻塞读取,读取后清空)
├── trace_marker # 用户态程序写入标记(调试用)
│
├── set_ftrace_filter # 只追踪匹配的函数(支持通配符 * 和 ?)
├── set_ftrace_notrace # 排除不追踪的函数
├── set_ftrace_pid # 只追踪指定 PID(支持多个)
├── set_graph_function # function_graph 的函数过滤
│
├── events/ # 所有 tracepoint events
│ ├── enable # 全局 event 开关
│ ├── sched/ # 调度相关 events
│ │ ├── sched_switch/
│ │ │ ├── enable # 单个 event 开关
│ │ │ ├── filter # 事件过滤条件(如 prev_pid == 1234)
│ │ │ └── format # 事件数据格式
│ │ └── sched_wakeup/
│ ├── irq/
│ ├── net/
│ └── ...
│
├── buffer_size_kb # 每 CPU 缓冲区大小
├── buffer_total_size_kb # 总缓冲区大小(只读)
│
├── trace_options # 输出格式选项
└── trace_clock # 时间戳时钟源(local/global/mono)
⚙️ ftrace 基础使用流程
1. 初始化:挂载 debugfs
bash
# 检查 debugfs 是否已挂载
mount | grep debugfs
# 输出:debugfs on /sys/kernel/debug type debugfs (rw,relatime)
# 如果未挂载,手动挂载
sudo mount -t debugfs none /sys/kernel/debug
# 进入 ftrace 目录
cd /sys/kernel/debug/tracing
2. 查看可用的追踪器
bash
cat /sys/kernel/debug/tracing/available_tracers
典型输出:
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
3. 基本操作流程
bash
# (1) 清空之前的追踪数据
echo > /sys/kernel/debug/tracing/trace
# (2) 设置追踪器
echo function > /sys/kernel/debug/tracing/current_tracer
# (3) 设置过滤条件(可选,避免输出过多)
echo vfs_* > /sys/kernel/debug/tracing/set_ftrace_filter
# (4) 开启追踪
echo 1 > /sys/kernel/debug/tracing/tracing_on
# (5) 执行目标操作
ls /tmp > /dev/null
# (6) 停止追踪
echo 0 > /sys/kernel/debug/tracing/tracing_on
# (7) 查看结果
cat /sys/kernel/debug/tracing/trace | head -30
# (8) 关闭追踪器
echo nop > /sys/kernel/debug/tracing/current_tracer
🔍 Function Tracer:函数级别跟踪
1. 基本用法
bash
# 追踪所有内核函数(⚠️ 输出量巨大!)
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
cat /sys/kernel/debug/tracing/trace | head -20
输出示例:
# tracer: function
#
# entries-in-buffer/entries-written: 45720/45720 #P:8
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
<idle>-0 [003] dNh. 1234.567890: __do_softirq <-irq_exit
<idle>-0 [003] dNh. 1234.567892: rcu_process_callbacks <-__do_softirq
<idle>-0 [003] dNs. 1234.567895: run_timer_softirq <-__do_softirq
字段说明:
- CPU#:执行的 CPU 核心
- TIMESTAMP:时间戳(秒.微秒)
- FUNCTION:被调用的函数
- <-:调用者函数
- 标志位 (dNh.):
d:中断禁用N:需要重新调度h:硬中断s:软中断
2. 过滤特定函数
bash
# 只追踪文件操作相关函数
echo > /sys/kernel/debug/tracing/set_ftrace_filter # 清空过滤器
echo vfs_read > /sys/kernel/debug/tracing/set_ftrace_filter
echo vfs_write >> /sys/kernel/debug/tracing/set_ftrace_filter
echo do_sys_open >> /sys/kernel/debug/tracing/set_ftrace_filter
# 使用通配符
echo > /sys/kernel/debug/tracing/set_ftrace_filter
echo 'vfs_*' > /sys/kernel/debug/tracing/set_ftrace_filter
# 查看当前过滤器
cat /sys/kernel/debug/tracing/set_ftrace_filter
输出:
vfs_read
vfs_write
vfs_open
vfs_fsync
vfs_getattr
3. 过滤特定进程
bash
# 只追踪特定 PID
echo 1234 > /sys/kernel/debug/tracing/set_ftrace_pid
# 追踪多个 PID
echo 1234 > /sys/kernel/debug/tracing/set_ftrace_pid
echo 5678 >> /sys/kernel/debug/tracing/set_ftrace_pid
# 清空 PID 过滤
echo > /sys/kernel/debug/tracing/set_ftrace_pid
4. 实战:查找频繁调用的内核函数
场景:自动驾驶感知模块占用 CPU 较高,怀疑内核有频繁调用的函数
bash
# 追踪 5 秒,统计函数调用频率
cd /sys/kernel/debug/tracing
echo function > current_tracer
echo 1234 > set_ftrace_pid # 感知进程的 PID
echo > trace
echo 1 > tracing_on
sleep 5
echo 0 > tracing_on
# 统计调用次数(Top 10)
cat trace | awk '{print $NF}' | sed 's/<-.*//' | sort | uniq -c | sort -nr | head -10
典型输出:
8523 tcp_sendmsg
6712 tcp_write_xmit
5234 ip_queue_xmit
3421 __netif_receive_skb
2890 udp_recvmsg
2456 kmem_cache_alloc
1987 kmem_cache_free
1543 schedule
1201 _raw_spin_lock
1045 _raw_spin_unlock
分析 :tcp_sendmsg 调用 8523 次,网络发送是热点路径。
⏱️ Function Graph Tracer:调用链与耗时分析
1. 基本用法
bash
# 启用 function_graph tracer
echo function_graph > /sys/kernel/debug/tracing/current_tracer
# 过滤特定函数(避免输出过多)
echo vfs_read > /sys/kernel/debug/tracing/set_graph_function
# 追踪
echo 1 > /sys/kernel/debug/tracing/tracing_on
cat /etc/hosts > /dev/null
echo 0 > /sys/kernel/debug/tracing/tracing_on
# 查看结果
cat /sys/kernel/debug/tracing/trace
输出示例:
# tracer: function_graph
#
# CPU DURATION FUNCTION CALLS
# | | | | | | |
0) ==========> |
0) | vfs_read() {
0) 0.234 us | rw_verify_area();
0) | __vfs_read() {
0) | new_sync_read() {
0) | ext4_file_read_iter() {
0) | generic_file_read_iter() {
0) | page_cache_mapping() {
0) 0.123 us | page_cache_get();
0) 0.456 us | }
0) 0.125 us | copy_page_to_iter();
0) 2.456 us | }
0) 3.123 us | }
0) 3.789 us | }
0) 4.234 us | }
0) 5.123 us | }
关键信息:
- DURATION:函数执行耗时(us)
- 缩进:表示调用层级
- { }:函数的进入和退出
- 总耗时计算 :
vfs_read总耗时 5.123us,其中__vfs_read占 4.234us
2. 耗时分析示例
场景 :数据录制过程中 write() 系统调用延迟高
bash
cd /sys/kernel/debug/tracing
echo function_graph > current_tracer
echo ksys_write > set_graph_function # 追踪 write 系统调用入口
echo > trace
echo 1 > tracing_on
# 在另一个终端执行写操作
dd if=/dev/zero of=/tmp/test.dat bs=1M count=10
# 停止追踪
echo 0 > tracing_on
cat trace | grep -A 50 "ksys_write()"
典型输出分析:
ksys_write() {
__fdget_pos() { ... } 0.125 us
vfs_write() {
rw_verify_area() { ... } 0.234 us
__vfs_write() {
new_sync_write() {
ext4_file_write_iter() {
ext4_buffered_write_iter() {
generic_perform_write() {
ext4_da_write_begin() {
grab_cache_page_write_begin() { ... } 2.345 us
ext4_journal_start() { ... } 15.678 us ← 瓶颈!
} 18.234 us
...
} 25.456 us
} 26.123 us
} 26.789 us
} 27.234 us
} 27.890 us
} 28.456 us
} 29.123 us
分析结论 :ext4_journal_start 耗时 15.678us,占总耗时的 54%,日志写入是瓶颈。
优化方向:
- 调整 ext4 日志模式(
data=writeback代替data=ordered) - 增大日志缓冲区大小
- 考虑使用 XFS(日志性能更好)
3. 多层嵌套调用分析
bash
echo function_graph > current_tracer
echo do_sys_open > set_graph_function
echo > trace
echo 1 > tracing_on
ls /tmp > /dev/null
echo 0 > tracing_on
cat trace | head -60
输出:
do_sys_open() {
getname() {
getname_flags() {
kmem_cache_alloc() { ... } 0.234 us
strncpy_from_user() { ... } 0.456 us
} 0.890 us
} 1.123 us
get_unused_fd_flags() { ... } 0.345 us
do_filp_open() {
path_openat() {
link_path_walk() {
walk_component() {
lookup_fast() {
__d_lookup() { ... } 0.567 us
} 0.789 us
} 1.234 us
} 2.456 us
do_last() {
lookup_open() { ... } 1.890 us
vfs_open() {
do_dentry_open() {
ext4_file_open() { ... } 0.678 us
} 1.123 us
} 1.456 us
} 4.567 us
} 7.890 us
} 8.234 us
fd_install() { ... } 0.123 us
} 10.456 us
📌 Tracepoints:事件级别跟踪
1. Tracepoints 的优势
相比 function tracer,tracepoints 具有以下特点:
- ✅ 开销更低:只在预设的事件点记录,不追踪整个函数
- ✅ 结构化数据:提供格式化的事件字段,易于过滤和分析
- ✅ 稳定接口:内核 API 可能变化,但 tracepoints 保持稳定
- ✅ 语义清晰 :事件名称明确表达语义(如
sched_switch、block_rq_issue)
2. 查看可用 events
bash
# 列出所有 events 分类
ls /sys/kernel/debug/tracing/events/
# 常见分类
sched/ # 进程调度
irq/ # 中断
softirq/ # 软中断
syscalls/ # 系统调用
net/ # 网络
block/ # 块 IO
workqueue/ # 工作队列
timer/ # 定时器
kmem/ # 内存分配
bash
# 查看特定分类下的 events
ls /sys/kernel/debug/tracing/events/sched/
sched_kthread_stop/
sched_kthread_stop_ret/
sched_migrate_task/
sched_move_numa/
sched_pi_setprio/
sched_process_exec/
sched_process_exit/
sched_process_fork/
sched_process_free/
sched_process_wait/
sched_stat_blocked/
sched_stat_iowait/
sched_stat_runtime/
sched_stat_sleep/
sched_stat_wait/
sched_stick_numa/
sched_swap_numa/
sched_switch/
sched_wait_task/
sched_wake_idle_without_ipi/
sched_wakeup/
sched_wakeup_new/
sched_waking/
3. 查看 event 格式
bash
cat /sys/kernel/debug/tracing/events/sched/sched_switch/format
输出:
name: sched_switch
ID: 315
format:
field:unsigned short common_type; offset:0; size:2; signed:0;
field:unsigned char common_flags; offset:2; size:1; signed:0;
field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
field:int common_pid; offset:4; size:4; signed:1;
field:char prev_comm[16]; offset:8; size:16; signed:1;
field:pid_t prev_pid; offset:24; size:4; signed:1;
field:int prev_prio; offset:28; size:4; signed:1;
field:long prev_state; offset:32; size:8; signed:1;
field:char next_comm[16]; offset:40; size:16; signed:1;
field:pid_t next_pid; offset:56; size:4; signed:1;
field:int next_prio; offset:60; size:4; signed:1;
print fmt: "prev_comm=%s prev_pid=%d prev_prio=%d prev_state=%s ==> next_comm=%s next_pid=%d next_prio=%d"
关键字段:
prev_comm:切换出去的进程名prev_pid:切换出去的进程 PIDprev_state:进程状态(R=running, S=sleeping, D=uninterruptible)next_comm:切换进来的进程名next_pid:切换进来的进程 PID
4. 启用调度事件
bash
# 启用 sched_switch 事件
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable
# 追踪 3 秒
echo 1 > /sys/kernel/debug/tracing/tracing_on
sleep 3
echo 0 > /sys/kernel/debug/tracing/tracing_on
# 查看结果
cat /sys/kernel/debug/tracing/trace | grep sched_switch | head -10
输出示例:
<idle>-0 [003] d... 1234.567890: sched_switch: prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/3:2 next_pid=1234 next_prio=120
kworker/3:2-1234 [003] d... 1234.568012: sched_switch: prev_comm=kworker/3:2 prev_pid=1234 prev_prio=120 prev_state=S ==> next_comm=perception next_pid=5678 next_prio=110
perception-5678 [003] d... 1234.570543: sched_switch: prev_comm=perception prev_pid=5678 prev_prio=110 prev_state=R ==> next_comm=migration/3 next_pid=23 next_prio=0
5. 过滤事件
bash
# 只显示特定进程的调度事件
echo 'next_pid == 5678 || prev_pid == 5678' > /sys/kernel/debug/tracing/events/sched/sched_switch/filter
# 只显示进入 D 状态的进程
echo 'prev_state == 2' > /sys/kernel/debug/tracing/events/sched/sched_switch/filter
# 清空过滤
echo 0 > /sys/kernel/debug/tracing/events/sched/sched_switch/filter
6. 实战:软中断处理分析
场景:自动驾驶数据采集系统网络延迟高,怀疑软中断处理慢
bash
cd /sys/kernel/debug/tracing
# 启用软中断 events
echo 1 > events/irq/softirq_entry/enable
echo 1 > events/irq/softirq_exit/enable
# 追踪 5 秒
echo > trace
echo 1 > tracing_on
sleep 5
echo 0 > tracing_on
# 查看结果
cat trace | head -50
输出示例:
ksoftirqd/3-25 [003] ..s. 1234.567890: softirq_entry: vec=3 [action=NET_RX]
ksoftirqd/3-25 [003] ..s. 1234.568234: softirq_exit: vec=3 [action=NET_RX]
ksoftirqd/3-25 [003] ..s. 1234.568456: softirq_entry: vec=3 [action=NET_RX]
ksoftirqd/3-25 [003] ..s. 1234.569123: softirq_exit: vec=3 [action=NET_RX]
ksoftirqd/3-25 [003] ..s. 1234.569345: softirq_entry: vec=1 [action=TIMER]
ksoftirqd/3-25 [003] ..s. 1234.569456: softirq_exit: vec=1 [action=TIMER]
统计软中断类型和耗时:
bash
# 计算每种软中断的处理次数
cat trace | grep softirq_entry | awk '{print $NF}' | sort | uniq -c
# 输出:
# 1542 [action=NET_RX]
# 234 [action=TIMER]
# 123 [action=TASKLET]
# 45 [action=SCHED]
计算单次处理耗时:
python
# parse_softirq.py
import re
entry_times = {}
durations = []
with open('/sys/kernel/debug/tracing/trace') as f:
for line in f:
if 'softirq_entry' in line:
m = re.search(r'(\d+\.\d+):.*vec=(\d+)', line)
if m:
ts, vec = float(m.group(1)), m.group(2)
entry_times[vec] = ts
elif 'softirq_exit' in line:
m = re.search(r'(\d+\.\d+):.*vec=(\d+)', line)
if m:
ts, vec = float(m.group(1)), m.group(2)
if vec in entry_times:
duration = (ts - entry_times[vec]) * 1000000 # 转为微秒
durations.append(duration)
del entry_times[vec]
print(f"平均: {sum(durations)/len(durations):.2f} us")
print(f"最大: {max(durations):.2f} us")
print(f"最小: {min(durations):.2f} us")
🧪 实战案例 1:kworker 高负载排查
1. 问题现象
自动驾驶数据录制系统中,发现某个 CPU 核心持续 90% 负载,top 显示 kworker/3:2 占用最高,但看不到具体在执行什么工作。
top 输出:
PID USER PR NI VIRT RES %CPU %MEM TIME+ COMMAND
278 root 20 0 0 0 89.3 0.0 45:32.12 kworker/3:2
5678 user 20 0 4.5g 1.2g 12.5 3.2 12:34.56 perception
vmstat 1:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 3456M 567M 12.3G 0 0 1234 5678 15k 25k 15 85 0 0 0
观察:
- CPU 3 号核心被 kworker/3:2 占用 90%
- 系统态 CPU(sy)占用 85%
- 中断数(in)15k/s,上下文切换(cs)25k/s 都很高
2. 排查思路
发现 kworker 高负载
定位工作类型
启用 workqueue events
查看执行的 work 函数
分析函数耗时
function_graph tracer
定位具体瓶颈
分析对应内核子系统
3. 步骤 1:识别 kworker 的工作内容
bash
cd /sys/kernel/debug/tracing
# 找到 kworker PID
ps -ef | grep "kworker/3:2"
# 输出:root 278 2 89.3 0 ... kworker/3:2
# 启用 workqueue events
echo 1 > events/workqueue/workqueue_execute_start/enable
echo 1 > events/workqueue/workqueue_execute_end/enable
# 过滤特定 kworker
echo 'common_pid == 278' > events/workqueue/workqueue_execute_start/filter
echo 'common_pid == 278' > events/workqueue/workqueue_execute_end/filter
# 追踪 5 秒
echo > trace
echo 1 > tracing_on
sleep 5
echo 0 > tracing_on
# 查看结果
cat trace | head -30
输出分析:
kworker/3:2-278 [003] .... 1234.567890: workqueue_execute_start: work struct ffff88810abcd000: function blk_mq_run_work_fn
kworker/3:2-278 [003] .... 1234.568234: workqueue_execute_end: work struct ffff88810abcd000
kworker/3:2-278 [003] .... 1234.568456: workqueue_execute_start: work struct ffff88810abcd100: function blk_mq_run_work_fn
kworker/3:2-278 [003] .... 1234.568890: workqueue_execute_end: work struct ffff88810abcd100
kworker/3:2-278 [003] .... 1234.569012: workqueue_execute_start: work struct ffff88810abcd200: function blk_mq_run_work_fn
关键发现:
- kworker 一直在执行
blk_mq_run_work_fn函数 - 该函数是块 IO 多队列(blk-mq)的工作处理函数
- 平均每 0.4ms 执行一次(频率 ~2500 Hz)
4. 步骤 2:分析函数调用链和耗时
bash
# 切换到 function_graph tracer
echo nop > current_tracer
echo function_graph > current_tracer
# 过滤 blk_mq_run_work_fn 函数
echo blk_mq_run_work_fn > set_graph_function
# 过滤 kworker PID
echo 278 > set_ftrace_pid
# 追踪 3 秒
echo > trace
echo 1 > tracing_on
sleep 3
echo 0 > tracing_on
# 查看调用链
cat trace | head -80
输出示例:
kworker/3:2-278 [003] .... 1234.567890: blk_mq_run_work_fn() {
blk_mq_run_hw_queue() {
__blk_mq_delay_run_hw_queue() {
__blk_mq_run_hw_queue() {
blk_mq_sched_dispatch_requests() {
__blk_mq_sched_dispatch_requests() {
blk_mq_do_dispatch_sched() {
blk_mq_dispatch_rq_list() {
blk_mq_request_issue_directly() {
nvme_queue_rq() {
nvme_setup_cmd() { ... } 0.234 us
nvme_submit_cmd() {
_raw_spin_lock() { ... } 0.123 us
__nvme_submit_cmd() { ... } 0.456 us
_raw_spin_unlock() { ... } 0.089 us
} 0.890 us
} 1.456 us
} 2.123 us
} 2.789 us
} 3.456 us
} 4.123 us
} 4.789 us
} 5.456 us
} 6.123 us
} 6.789 us
} 7.456 us
耗时分析:
- 单次
blk_mq_run_work_fn耗时 ~7.5us - 主要耗时在
nvme_queue_rq(1.456us)和调度开销(~6us) - 频率 2500 Hz × 7.5us = 18.75ms/秒 1.875% CPU(理论值)
问题:理论 CPU 占用只有 2%,但实际占用 90%,说明有其他因素!
5. 步骤 3:查看块 IO 请求频率
bash
# 启用块 IO events
echo 1 > events/block/block_rq_issue/enable
echo 1 > events/block/block_rq_complete/enable
# 追踪 1 秒
echo > trace
echo 1 > tracing_on
sleep 1
echo 0 > tracing_on
# 统计 IO 请求数
cat trace | grep block_rq_issue | wc -l
# 输出:3456 (每秒 3456 个 IO 请求!)
# 查看 IO 类型
cat trace | grep block_rq_issue | head -10
输出:
kworker/3:2-278 [003] .... 1234.567: block_rq_issue: 259,0 WS 4096 () 1048576 + 8 [kworker/3:2]
kworker/3:2-278 [003] .... 1234.568: block_rq_issue: 259,0 WS 4096 () 1048584 + 8 [kworker/3:2]
kworker/3:2-278 [003] .... 1234.569: block_rq_issue: 259,0 WS 4096 () 1048592 + 8 [kworker/3:2]
关键信息:
- 259,0 :设备号(
/dev/nvme0n1) - WS:Write Sync(同步写)
- 4096:请求大小(4KB)
- 1048576 + 8:起始扇区 + 扇区数
问题根因:
- 每秒 3456 个小 IO 请求(4KB)
- 全部是同步写操作(WS)
- 没有 IO 合并,每个请求独立处理
6. 步骤 4:定位 IO 来源
bash
# 查看是哪个进程产生的 IO
iostat -x 1
# 或使用 iotop
sudo iotop -o
# 或通过 ftrace syscall events
echo 1 > events/syscalls/sys_exit_write/enable
echo 'ret > 0' > events/syscalls/sys_exit_write/filter
echo > trace
echo 1 > tracing_on
sleep 2
echo 0 > tracing_on
cat trace | head -20
发现:
data_recorder-5678 [002] .... 1234.567: sys_exit_write: 0x1000 # 4KB
data_recorder-5678 [002] .... 1234.568: sys_exit_write: 0x1000 # 4KB
data_recorder-5678 [002] .... 1234.569: sys_exit_write: 0x1000 # 4KB
根本原因:
data_recorder进程以 4KB 为单位频繁写入- 每次写入触发一次同步 IO(可能设置了
O_SYNC或频繁fsync()) - 小 IO 导致块设备层无法合并,kworker 频繁调度处理
7. 优化方案
方案 1:增大写入缓冲区
python
# 原代码(慢):
for data in sensor_data:
f.write(data) # 每次写 4KB
f.flush() # 立即刷盘
# 优化后(快):
buffer = []
for data in sensor_data:
buffer.append(data)
if len(buffer) >= 256: # 累积 1MB
f.write(b''.join(buffer))
f.flush()
buffer = []
方案 2:移除不必要的 fsync
python
# 原代码:
f = open('data.log', 'wb', buffering=0) # 无缓冲
f.write(data)
# 优化后:
f = open('data.log', 'wb', buffering=1048576) # 1MB 缓冲
f.write(data)
# 只在关键节点 fsync
效果对比:
| 指标 | 优化前 | 优化后 | 改善 |
|---|---|---|---|
| IO 请求数 | 3456/秒 | 14/秒 | ↓ 99.6% |
| kworker CPU | 89% | 2% | ↓ 97.8% |
| 写入吞吐量 | 13.5 MB/s | 350 MB/s | ↑ 25 倍 |
| 录制延迟 | 450ms | 18ms | ↓ 96% |
🌪️ 实战案例 2:软中断风暴定位
1. 问题现象
自动驾驶感知系统在处理密集点云数据时,某个 CPU 核心的 ksoftirqd 占用 85%,导致感知延迟从 20ms 飙升至 120ms。
top 输出:
PID USER PR NI VIRT RES %CPU %MEM TIME+ COMMAND
25 root 20 0 0 0 85.2 0.0 67:45.23 ksoftirqd/3
6789 user 20 0 8.5g 3.2g 10.5 8.1 23:45.67 perception
mpstat -P ALL 1:
CPU %usr %nice %sys %iowait %irq %soft %idle
0 15.2 0.0 3.4 0.0 0.5 2.1 78.8
1 14.8 0.0 3.2 0.0 0.6 2.3 79.1
2 16.1 0.0 3.5 0.0 0.4 1.9 78.1
3 12.3 0.0 2.8 0.0 1.2 85.2 0.0 ← 异常!
2. 步骤 1:确认软中断类型
bash
watch -n 1 cat /proc/softirqs
输出:
CPU0 CPU1 CPU2 CPU3
HI: 0 0 0 0
TIMER: 123456 124567 125678 126789
NET_TX: 0 0 0 0
NET_RX: 45678 46789 47890 12456789 ← 异常高!
BLOCK: 234 245 256 267
IRQ_POLL: 0 0 0 0
TASKLET: 1234 1245 1256 1267
SCHED: 34567 35678 36789 37890
HRTIMER: 5678 5789 5890 5901
RCU: 78901 79012 80123 81234
发现 :CPU 3 的 NET_RX(网络接收软中断)计数异常高,每秒增加 ~200 万次!
3. 步骤 2:追踪网络软中断处理
bash
cd /sys/kernel/debug/tracing
# 启用软中断和网络 events
echo 1 > events/irq/softirq_entry/enable
echo 1 > events/irq/softirq_exit/enable
echo 1 > events/net/netif_receive_skb/enable
# 过滤 NET_RX 软中断(vec=3)
echo 'vec == 3' > events/irq/softirq_entry/filter
echo 'vec == 3' > events/irq/softirq_exit/filter
# 追踪 1 秒
echo > trace
echo 1 > tracing_on
sleep 1
echo 0 > tracing_on
# 统计软中断触发次数
cat trace | grep softirq_entry | wc -l
# 输出:2145672 (每秒 214 万次!)
# 查看网络数据包数量
cat trace | grep netif_receive_skb | wc -l
# 输出:2145672 (与软中断数一致)
4. 步骤 3:分析数据包来源
bash
# 查看数据包详情
cat trace | grep netif_receive_skb | head -10
输出:
ksoftirqd/3-25 [003] ..s. 1234.567: netif_receive_skb: dev=eth0 napi_id=0x3 queue_mapping=0 skbaddr=ffff888100001000 protocol=8 (IP) ip_summed=0 hash=0x12345678 l4_hash=0 len=98 data_len=0 truesize=768
ksoftirqd/3-25 [003] ..s. 1234.568: netif_receive_skb: dev=eth0 ...
关键信息:
- 网卡:
eth0 - 协议:IP
- 包长:98 字节(小包!)
- 频率:214 万包/秒
5. 步骤 4:使用 tcpdump 确认数据源
bash
# 查看网络流量
sudo tcpdump -i eth0 -n -c 100
# 统计流量来源
sudo tcpdump -i eth0 -n -c 10000 | awk '{print $3}' | sort | uniq -c | sort -nr | head -5
发现:
2145672 192.168.1.100.5201 > 192.168.1.200.12345: UDP, length 64
分析:
- 192.168.1.100 是激光雷达设备
- 以 每秒 214 万包的速率发送 64 字节小包
- 吞吐量:214万 × 64 字节 = 137 MB/s(正常雷达数据应为 ~30 MB/s)
6. 步骤 5:分析网络协议栈耗时
bash
cd /sys/kernel/debug/tracing
# 使用 function_graph 追踪网络接收路径
echo function_graph > current_tracer
echo __netif_receive_skb_core > set_graph_function
# 追踪 0.1 秒(避免输出过多)
echo > trace
echo 1 > tracing_on
sleep 0.1
echo 0 > tracing_on
# 查看调用链
cat trace | head -100
典型调用链:
__netif_receive_skb_core() {
ip_rcv() {
ip_rcv_core() { ... } 0.234 us
ip_rcv_finish() {
ip_local_deliver() {
ip_local_deliver_finish() {
udp_rcv() {
__udp4_lib_rcv() {
udp_queue_rcv_skb() {
__udp_queue_rcv_skb() {
sock_queue_rcv_skb() {
__skb_queue_tail() { ... } 0.123 us
sk_data_ready() {
sock_def_readable() {
__wake_up_common() { ... } 0.456 us
} 0.678 us
} 0.890 us
} 1.234 us
} 1.456 us
} 1.678 us
} 1.890 us
} 2.123 us
} 2.345 us
} 2.567 us
} 2.789 us
} 3.012 us
} 3.456 us
耗时分析:
- 单包处理耗时 ~3.5us
- 214万包/秒 × 3.5us = 7.49秒/秒 → 750% CPU(理论值)
- 但只有 1 个核心,最多 100%,因此会积压
7. 根本原因与优化方案
根本原因:
- 激光雷达配置错误,包大小设置为 64B 而非 1400B
- 小包导致包数量暴增(64B vs 1400B = 21 倍差距)
- 协议栈处理开销主要在固定成本(头部解析、队列操作),小包效率低
优化方案 1:修正雷达配置
bash
# 登录雷达设备,修改 UDP 包大小
ssh lidar@192.168.1.100
# 配置文件:/etc/lidar.conf
packet_size=1400 # 原来是 64
# 重启雷达服务
systemctl restart lidar
优化方案 2:启用 GRO(Generic Receive Offload)
bash
# 启用 GRO(将小包在硬件/驱动层合并)
ethtool -K eth0 gro on
# 查看状态
ethtool -k eth0 | grep generic-receive-offload
# 输出:generic-receive-offload: on
优化方案 3:调整网卡中断亲和性
bash
# 查看网卡中断号
cat /proc/interrupts | grep eth0
# 输出:125: 12345678 ... eth0-TxRx-0
# 将 eth0 中断绑定到 CPU 0-2(避免影响 perception 进程所在的 CPU 3)
echo 07 > /proc/irq/125/smp_affinity # 二进制 0111 = CPU 0,1,2
效果对比:
| 指标 | 优化前 | 优化后 | 改善 |
|---|---|---|---|
| 包频率 | 214 万/秒 | 10 万/秒 | ↓ 95.3% |
| 包大小 | 64B | 1400B | ↑ 21 倍 |
| 吞吐量 | 137 MB/s | 140 MB/s | ↑ 2.2% |
| ksoftirqd CPU | 85% | 4% | ↓ 95.3% |
| 软中断延迟 | 15ms | 0.8ms | ↓ 94.7% |
| 感知处理延迟 | 120ms | 22ms | ↓ 81.7% |
🎯 过滤与性能控制最佳实践
1. 函数过滤技巧
bash
# (1) 通配符过滤
echo 'vfs_*' > set_ftrace_filter # 所有 vfs_ 开头的函数
echo '*_read' > set_ftrace_filter # 所有 _read 结尾的函数
echo '*tcp*' > set_ftrace_filter # 所有包含 tcp 的函数
# (2) 多条件过滤(OR 关系)
echo 'vfs_read' > set_ftrace_filter
echo 'vfs_write' >> set_ftrace_filter # 注意是 >>
echo 'vfs_open' >> set_ftrace_filter
# (3) 模块过滤
echo ':mod:ext4' > set_ftrace_filter # 只追踪 ext4 模块的函数
# (4) 排除函数(NOT)
echo '!schedule' >> set_ftrace_notrace # 不追踪 schedule 函数
echo '!*lock*' >> set_ftrace_notrace # 不追踪锁相关函数
# (5) 清空过滤器
echo > set_ftrace_filter
echo > set_ftrace_notrace
2. PID 过滤技巧
bash
# (1) 单个 PID
echo 1234 > set_ftrace_pid
# (2) 多个 PID
echo 1234 > set_ftrace_pid
echo 5678 >> set_ftrace_pid
# (3) 批量添加(某个进程的所有线程)
pgrep -T perception | while read pid; do
echo $pid >> /sys/kernel/debug/tracing/set_ftrace_pid
done
# (4) 动态追踪新创建的子进程
echo 1 > options/function-fork # 自动追踪 fork 出的子进程
# (5) 清空 PID 过滤
echo > set_ftrace_pid
3. Event 条件过滤
bash
# (1) 数值比较
echo 'prev_prio < 120' > events/sched/sched_switch/filter # 只显示高优先级调度
# (2) 字符串匹配
echo 'prev_comm ~ "kworker*"' > events/sched/sched_switch/filter
# (3) 逻辑运算
echo 'prev_pid == 1234 || next_pid == 1234' > events/sched/sched_switch/filter
echo 'common_pid == 5678 && ret > 4096' > events/syscalls/sys_exit_write/filter
# (4) 支持的操作符
# == != > < >= <= & && || ! ~(正则匹配)
# (5) 查看当前过滤条件
cat events/sched/sched_switch/filter
# (6) 清空过滤
echo 0 > events/sched/sched_switch/filter
4. 性能开销控制
bash
# (1) 减小缓冲区(降低内存占用)
echo 512 > buffer_size_kb # 每 CPU 512KB
# (2) 启用 trace_options 优化
echo nop > current_tracer
echo 0 > options/context-info # 不显示上下文信息
echo 0 > options/graph-time # 不显示函数耗时
echo 0 > options/print-parent # 不显示父函数
# (3) 使用 trace_pipe 代替 trace(避免缓冲积压)
cat trace_pipe | tee output.log
# (4) 限制追踪时间(避免长时间高负载)
echo 1 > tracing_on
sleep 10
echo 0 > tracing_on
# (5) 追踪特定 CPU
echo 1 > tracing_cpumask # 只追踪 CPU 0(二进制掩码)
echo 3 > tracing_cpumask # 只追踪 CPU 0 和 1
5. 实用脚本:ftrace 快速分析
脚本 1:追踪进程的系统调用
bash
#!/bin/bash
# trace_syscalls.sh <PID> <duration>
PID=$1
DURATION=${2:-5}
TRACE_DIR=/sys/kernel/debug/tracing
cd $TRACE_DIR
# 清理
echo nop > current_tracer
echo 0 > tracing_on
echo > trace
# 启用系统调用 events
echo 1 > events/syscalls/enable
echo "common_pid == $PID" > events/syscalls/filter
# 追踪
echo 1 > tracing_on
sleep $DURATION
echo 0 > tracing_on
# 统计调用次数
echo "=== Top 10 System Calls ==="
cat trace | awk '{print $(NF-1)}' | grep sys_ | sort | uniq -c | sort -nr | head -10
# 清理
echo 0 > events/syscalls/enable
echo 0 > events/syscalls/filter
脚本 2:分析函数耗时
bash
#!/bin/bash
# trace_function_latency.sh <function_name> <duration>
FUNC=$1
DURATION=${2:-10}
TRACE_DIR=/sys/kernel/debug/tracing
cd $TRACE_DIR
# 设置
echo nop > current_tracer
echo > trace
echo function_graph > current_tracer
echo $FUNC > set_graph_function
# 追踪
echo 1 > tracing_on
sleep $DURATION
echo 0 > tracing_on
# 提取耗时
cat trace | grep -E '\| *}' | awk '{print $NF}' | sed 's/[^0-9.]//g' | sort -n | awk '
BEGIN {count=0; sum=0}
{
latency[count++] = $1
sum += $1
}
END {
if (count > 0) {
print "Count:", count
print "Min:", latency[0], "us"
print "Max:", latency[count-1], "us"
print "Avg:", sum/count, "us"
print "P50:", latency[int(count*0.5)], "us"
print "P90:", latency[int(count*0.9)], "us"
print "P99:", latency[int(count*0.99)], "us"
}
}'
# 清理
echo nop > current_tracer
🧩 Kprobes 与 Uprobes:动态探针
1. Kprobes:内核动态探针
当内核没有现成的 tracepoint 时,可以通过 kprobes 在任意内核函数插入探针。
示例 1:在 do_sys_open 插入探针
bash
cd /sys/kernel/debug/tracing
# 创建 kprobe
echo 'p:myprobe_open do_sys_open filename=+0(%si):string' > kprobe_events
# 格式:p:探针名 函数名 参数定义
# 启用探针
echo 1 > events/kprobes/myprobe_open/enable
# 追踪
echo 1 > tracing_on
ls /tmp
echo 0 > tracing_on
# 查看结果
cat trace | grep myprobe_open
输出:
ls-1234 [002] .... 1234.567: myprobe_open: (do_sys_open+0x0/0x1a0) filename="/tmp"
ls-1234 [002] .... 1234.568: myprobe_open: (do_sys_open+0x0/0x1a0) filename="/tmp/."
清理:
bash
echo 0 > events/kprobes/myprobe_open/enable
echo '-:myprobe_open' >> kprobe_events # 删除探针
示例 2:获取函数返回值
bash
# 在函数返回处插入探针(r 表示 return probe)
echo 'r:myprobe_read_ret vfs_read ret=$retval' > kprobe_events
echo 1 > events/kprobes/myprobe_read_ret/enable
echo 1 > tracing_on
cat /etc/hosts > /dev/null
echo 0 > tracing_on
cat trace | grep myprobe_read_ret
输出:
cat-2345 [001] .... 1234.567: myprobe_read_ret: (vfs_read+0x120/0x150 <- ksys_read) ret=1024
2. 参数格式
bash
# 寄存器(x86_64)
echo 'p:probe do_sys_open arg1=%di arg2=%si arg3=%dx' > kprobe_events
# 字符串
echo 'p:probe do_sys_open filename=+0(%si):string' > kprobe_events
# 指针解引用
echo 'p:probe do_sys_open mode=+16(%si):u32' > kprobe_events
# 结构体字段
echo 'p:probe tcp_sendmsg sk=+0(%di) size=+8(%si)' > kprobe_events
3. Uprobes:用户态探针
Uprobes 可以在用户态函数插入探针(需要符号表)。
bash
# 在用户态程序的 malloc 函数插入探针
echo 'p:/lib/x86_64-linux-gnu/libc.so.6:malloc size=%di' > uprobe_events
echo 1 > events/uprobes/p_malloc_0/enable
echo 1 > tracing_on
# 运行某个程序
./my_program
echo 0 > tracing_on
cat trace | grep p_malloc_0
4. 性能与注意事项
| 特性 | Tracepoints | Kprobes | Uprobes |
|---|---|---|---|
| 开销 | 低(~50-100ns) | 中(~500ns-1us) | 高(~2-5us) |
| 稳定性 | 稳定(内核 ABI) | 不稳定(函数可能变化) | 中(依赖符号表) |
| 灵活性 | 低(预设点) | 高(任意函数) | 高(任意函数) |
| 使用场景 | 通用监控 | 内核调试 | 应用调试 |
最佳实践:
- ✅ 优先使用 tracepoints(开销低、稳定)
- ✅ Kprobes 用于短期调试(不要长期开启)
- ✅ 避免在热路径函数(如
schedule)插入 kprobes - ⚠️ Kprobes 不要用于生产环境长期监控(改用 eBPF)
✅ 小结与最佳实践
1. 核心要点回顾
- ftrace 是内核自带的强大跟踪框架,无需编译模块或安装工具
- function tracer 用于发现频繁调用的内核函数
- function_graph tracer 用于分析调用链和耗时分布
- tracepoints 提供低开销的结构化事件追踪(sched/irq/net/block 等)
- 过滤机制 是控制输出量和性能开销的关键
- kprobes 可以动态插入探针,但开销较高,适合短期调试
2. 典型分析流程
CPU 占用高
延迟高
调度问题
网络/IO
否
是
发现性能问题
确定问题域
function tracer
查找热点函数
function_graph
分析调用链耗时
sched tracepoints
分析调度行为
net/block tracepoints
追踪数据流
设置函数过滤
设置 graph_function
设置 event 过滤
追踪 5-10 秒
分析输出
找到根因?
扩大追踪范围
或更换工具
实施优化
验证效果
3. 性能开销对比
| 追踪配置 | CPU 开销 | 适用场景 |
|---|---|---|
| 无追踪 | 0% | 正常运行 |
| 单个 tracepoint | < 1% | 生产环境可用 |
| 5-10 个 tracepoints | 1-3% | 生产环境可用 |
| function tracer(过滤 10 个函数) | 3-5% | 短期调试 |
| function tracer(过滤 100 个函数) | 10-15% | 短期调试 |
| function tracer(无过滤) | 30-50% | 不推荐 |
| function_graph(过滤单个函数) | 5-10% | 短期调试 |
| function_graph(过滤 10 个函数) | 20-40% | 仅离线分析 |
4. 常见问题排查清单
| 症状 | 快速检查 | ftrace 工具 |
|---|---|---|
| kworker CPU 高 | `ps aux | grep kworker` |
| ksoftirqd CPU 高 | /proc/softirqs |
softirq tracepoints |
| 进程频繁 D 状态 | `ps aux | awk '$8=="D"'` |
| 网络延迟高 | netstat -s |
net tracepoints + function_graph |
| IO 延迟高 | iostat -x 1 |
block tracepoints + function_graph |
| 调度延迟高 | vmstat 1 |
sched tracepoints |
| 内存分配慢 | /proc/slabinfo |
kmem tracepoints |
5. 实用命令速查
bash
# === 初始化 ===
cd /sys/kernel/debug/tracing
# === 清理之前的追踪 ===
echo 0 > tracing_on
echo nop > current_tracer
echo > trace
echo > set_ftrace_filter
echo > set_ftrace_pid
# === Function Tracer ===
echo function > current_tracer
echo 'vfs_*' > set_ftrace_filter
echo 1234 > set_ftrace_pid
# === Function Graph Tracer ===
echo function_graph > current_tracer
echo sys_write > set_graph_function
# === Tracepoints ===
echo 1 > events/sched/sched_switch/enable
echo 'prev_pid == 1234' > events/sched/sched_switch/filter
# === 追踪与查看 ===
echo 1 > tracing_on
sleep 5
echo 0 > tracing_on
cat trace | less
# === 实时查看 ===
cat trace_pipe
# === 统计分析 ===
cat trace | awk '{print $NF}' | sort | uniq -c | sort -nr | head -10
6. 下一步学习方向
- trace-cmd:ftrace 的命令行封装工具,使用更简便
- kernelshark:ftrace 的图形化分析工具
- eBPF + bpftrace:更强大的内核追踪框架,支持复杂逻辑和聚合
- perf + ftrace 结合:CPU 热点分析(perf)+ 内核路径分析(ftrace)
🔗 参考资料
下一章预告 :第 20 篇:trace-cmd 与 kernelshark 可视化分析
我们将学习如何使用 trace-cmd 简化 ftrace 操作,使用 kernelshark 进行时间线可视化分析,并通过实战案例分析网络中断风暴与 CPU 调度冲突问题。