目录
安装KernelShark与trace-cmd(宿主机用于分析)
培训说明
适用人群:嵌入式 Linux 开发工程师、内核调优工程师、系统性能优化工程师
课程目标:掌握 KernelShark 工具链的交叉编译、数据采集、可视化分析能力,能独立定位嵌入式系统内核级性能瓶颈(调度延迟、IO 阻塞、中断冲突等)
前置知识:熟悉Linux内核基础(进程调度、中断、tracefs)、嵌入式交叉编译、Shell 命令行操作
一、KernelShark 核心认知
1. 工具定位与价值
KernelShark 是 Linux 内核跟踪工具ftrace的图形化前端,与命令行工具trace-cmd配套使用,被称为 “内核性能分析鲨鱼”。
核心价值:将内核运行的二进制跟踪数据转化为可视化时间线,直观呈现 CPU调度、进程切换、中断响应、IO 操作等行为,解决 “内核黑盒” 问题。
嵌入式场景适配:轻量级采集(trace-cmd仅占少量资源)、支持 ARM/RISC-V 等架构交叉编译,适配资源受限的嵌入式设备。
2. 核心原理
底层依赖:基于ftrace框架,通过内核内置的跟踪点(tracepoint)、函数钩子捕获内核事件(如sched_switch、irq_handler_entry)。
工作流程:trace-cmd在目标板采集事件→生成trace.dat数据文件→KernelShark 在宿主机加载文件→可视化分析(时间线 + 事件列表)。
数据特点:毫秒级时间戳、低侵入性(采集时系统负载 < 5%)、事件类型全覆盖(调度、中断、IO、内存、函数调用等)。
二、工具链部署(宿主机 + 嵌入式目标板)
1. 宿主机环境准备(Ubuntu 20.04/22.04)
安装依赖库
| sudo apt update && sudo apt install -y libtraceevent-dev libtracefs-dev qt5-default qttools5-dev-tools |
安装KernelShark与trace-cmd(宿主机用于分析)
| sudo apt install -y kernelshark trace-cmd |
验证安装
| kernelshark --version trace-cmd --version |
2. 嵌入式目标板交叉编译(重点)
嵌入式设备(ARM/RISC-V 架构)需交叉编译trace-cmd(目标板仅需采集工具,无需图形化界面)。
依赖准备
交叉编译工具链:获取目标板对应的工具链(如arm-linux-gnueabihf-、riscv64-linux-gnu-),配置环境变量:
| export PATH=/opt/toolchain/bin:$PATH # 工具链路径 export CC=arm-linux-gnueabihf-gcc export CXX=arm-linux-gnueabihf-g++ export AR=arm-linux-gnueabihf-ar |
依赖库:下载libtraceevent和libtracefs源码(静态编译,避免目标板缺库):
| git clone https://git.kernel.org/pub/scm/linux/kernel/git/rostedt/libtraceevent.git git clone https://git.kernel.org/pub/scm/linux/kernel/git/rostedt/libtracefs.git |
交叉编译步骤
编译libtraceevent(静态库)
| cd libtraceevent ./configure --host=arm-linux-gnueabihf --enable-static --disable-shared make -j4 sudo make install prefix=/opt/toolchain/sysroot/usr |
编译libtracefs(静态库)
| cd ../libtracefs ./configure --host=arm-linux-gnueabihf --enable-static --disable-shared --with-libtraceevent=/opt/toolchain/sysroot/usr make -j4 sudo make install prefix=/opt/toolchain/sysroot/usr |
编译trace-cmd(目标板可执行文件)
| git clone https://git.kernel.org/pub/scm/linux/kernel/git/rostedt/trace-cmd.git cd trace-cmd make CC=$CC AR=$AR CFLAGS="-static" LDFLAGS="-static" |
目标板部署
将编译生成的trace-cmd可执行文件拷贝到目标板(通过 scp/TF 卡):
| scp trace-cmd root@192.168.1.100:/usr/bin/ |
目标板验证
| chmod +x /usr/bin/trace-cmd && trace-cmd --version |
内核配置要求(目标板)
确保目标板内核开启ftrace相关配置(编译内核时设置):
内核配置选项(.config文件)
| CONFIG_FTRACE=y CONFIG_FUNCTION_TRACER=y CONFIG_FUNCTION_GRAPH_TRACER=y CONFIG_TRACEPOINTS=y CONFIG_TRACING=y CONFIG_DEBUG_FS=y |
启动后验证:目标板存在/sys/kernel/debug/tracing目录,说明配置生效。
三、核心操作:数据采集与可视化分析
1. 目标板数据采集(trace-cmd 命令)
1、全量事件采集
| //设置 buffer 大小(8192KB),采集 10 秒内所有内核事件 trace-cmd record -m 8192 -o /tmp/trace.dat sleep 10 -m |
2、跟踪特定进程
| //跟踪 PID=1234 的进程,记录函数调用图(function_graph) trace-cmd record -p function_graph -P 1234 -o task_trace.dat |
3、跟踪特定事件
| //仅采集进程切换(sched_switch)和中断入口(irq_handler_entry)事件 trace-cmd record -e sched_switch -e irq_handler_entry -o irq_trace.dat |
4、跟踪文件系统事件
| //跟踪 ext4 文件系统和块设备 IO 事件,运行测试程序 test_app trace-cmd record -e ext4 -e block -o fs_trace.dat ./test_app |
5、嵌入式采集注意事项
1、控制采集时长:避免数据量过大(嵌入式设备存储有限),建议单次采集≤30 秒。
2、静态链接:确保trace-cmd静态编译,避免目标板缺少依赖库。
3、清理缓冲区:采集前执行trace-cmd clear,避免旧数据干扰。
4、 数据导出,将目标板的trace.dat拷贝到宿主机:
| scp root@192.168.1.100:/tmp/trace.dat ~/kernelshark_analysis/ |
5、启动与加载数据,启动KernelShark并加载数据
| kernelshark ~/kernelshark_analysis/trace.dat |
四、嵌入式场景实战案例
案例1:系统启动过程分析
Linux内核kernelshark不同于分析运行时热点的火焰图,它主要用于分析某个过程的时序问题。
Linux界有一个杀手级分析Linux本身启动慢的工具,叫做bootchart,它其实把启动过程中进程的IO,CPU占用情况进行了描述。注意这类图都有一个共同特点,横轴是时间,纵轴是CPU、线程等的状态(运行、睡眠、IO什么的)。
目前,在较新的 Ubuntu 版本(如 24.04)中,bootchart 和 pybootchartgui 已经从官方软件源中移除了(主要因为它们依赖于旧的 upstart 初始化系统,而现代 Ubuntu 已全面使用 systemd,相关工具被 systemd-analyze 替代)。
以下是重启系统后,对各个服务进程进行分析

生成启动时间轴图表(类似 bootchart 的可视化)
| systemd-analyze plot > boot_analysis.svg |
结果展示:


其中,各颜色表示的含义如下:
红色(Activating):表示服务正在启动过程中,处于激活的过渡状态。
深红色(Active):表示服务已成功启动并处于活跃、正常运行的状态。
浅红色(Deactivating):表示服务正在停止过程中,处于停用的过渡状态。
绿色(Setting up security module):表示正在配置安全模块,属于服务初始化或配置的一个环节。
青绿色(Generating):表示正在生成相关资源或配置,例如生成配置文件、临时数据等。
蓝色(Loading unit file):表示正在加载单元文件,是系统服务启动前的准备步骤,用于读取服务的配置信息。
案例 2:实时任务调度延迟超标
systemd-analyze确实有利于分析开机过程,但开发人员写的程序在系统里面是怎么在运行,几个线程怎么在跑?我们则需要使用perf的timechart来画出它的时序图,如以下2个线程周期循环做事情和睡眠的代码。其中:
线程 1:每轮执行 1 秒 CPU 密集型工作(循环计数),然后睡眠 2 秒。
线程 2:每轮执行 0.5 秒 CPU 密集型工作,然后睡眠 1 秒(周期更短,方便在perf timechart中区分)。
主线程等待 30 秒后退出,足够用perf timechart记录线程的周期行为。
| #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> #include <time.h> // 线程1:工作1秒,睡眠2秒 void *thread_func1(void *arg) { volatile int count = 0; // volatile 防止编译器优化掉"工作"逻辑 while (1) { // 模拟"工作":消耗CPU时间(循环计数) printf("线程1:开始工作...\n"); clock_t start = clock(); while ((clock() - start) < CLOCKS_PER_SEC * 1); // 工作1秒 count++; // 简单计数,避免空循环被优化 // 模拟"睡眠" printf("线程1:工作结束,开始睡眠2秒...\n"); sleep(2); // 睡眠2秒 } return NULL; } // 线程2:工作0.5秒,睡眠1秒(周期更短) void *thread_func2(void *arg) { volatile int count = 0; while (1) { // 模拟"工作":消耗CPU时间 printf("线程2:开始工作...\n"); clock_t start = clock(); while ((clock() - start) < CLOCKS_PER_SEC * 0.5); // 工作0.5秒 count++; // 模拟"睡眠" printf("线程2:工作结束,开始睡眠1秒...\n"); sleep(1); // 睡眠1秒 } return NULL; } int main() { pthread_t tid1, tid2; int ret; // 创建线程1 ret = pthread_create(&tid1, NULL, thread_func1, NULL); if (ret != 0) { perror("pthread_create tid1 failed"); exit(1); } // 创建线程2 ret = pthread_create(&tid2, NULL, thread_func2, NULL); if (ret != 0) { perror("pthread_create tid2 failed"); exit(1); } // 主线程等待30秒后退出(给足够时间用perf记录) printf("主线程:等待30秒后退出...\n"); sleep(30); // (实际中应调用pthread_cancel终止线程,此处简化) return 0; } |
编译运行程序

使用perf timechart工具分析

使用trace-cmd来录制sched相关的trace点

(1)CPU 调度时序图(上半部分)
每一行对应一个 CPU 核心(CPU 0~3),彩色竖条 / 块代表不同进程的调度活动:
黑色:通常表示内核空闲线程(idle 进程),即 CPU 无任务可执行时的状态。
其他颜色:对应不同用户 / 内核进程(如 a.out、gnome-terminal、kworker 等),颜色用于区分进程。
块的长度反映进程在该 CPU 上的执行时长,密集的竖条表示进程频繁切换。
(2)事件列表(下半部分)
表格列出了具体的内核事件,包含以下关键列:
CPU:事件发生的 CPU 核心。
Time Stamp:事件发生的时间戳。
Task/PID:涉及的进程名称和进程 ID(如 a.out、sudo、nautilus 等)。
Event:内核事件类型,这里主要是 调度类事件:
sched/sched_switch:进程切换事件(如进程 A 切换到进程 B)。
sched/sched_wakeup:进程唤醒事件(如将睡眠的进程标记为可运行)。
Info:事件的详细信息(如进程状态变化、目标 CPU 等)。
3. 关键进程与行为分析
a.out(PID 7683):
这是一个用户自定义程序(通常是编译后的可执行文件),从事件列表中可见它被频繁调度(sched_wakeup、sched_switch),说明该程序在活跃地执行任务。
系统进程(如 nautilus、gnome-terminal、kworker):
这些是桌面环境和系统后台的进程,kworker 是内核工作线程(处理异步任务),它们的调度行为反映了系统日常的后台活动。
idle 进程:
当 CPU 无任务时会运行 idle 进程(PID 0),从图中可见 CPU 0 有较多绿色空闲块,说明该核心有时处于空闲状态,而 CPU 1~3 则更繁忙。
4. 性能与调度结论
系统存在多进程并发调度:多个 CPU 核心同时运行不同进程,说明系统负载较均衡。
a.out 是当前分析的重点进程:其频繁的调度事件表明它在持续占用 CPU 资源,可进一步分析其代码逻辑(如是否存在循环、高计算量任务)来优化性能。
内核调度器正常工作:进程切换和唤醒事件的时序符合预期,无明显异常延迟(可通过 “Latency” 列或时间戳差分析延迟情况)。
拓展篇
场景描述
嵌入式工业控制器中,实时任务(PID=5678)要求调度延迟≤1ms,但实际运行中偶尔出现延迟超 3ms 的情况。
分析步骤
目标板采集数据:
trace-cmd record -e sched_switch -e sched_wakeup -P 5678 -m 4096 -o rt_trace.dat sleep 20
宿主机加载数据:kernelshark rt_trace.dat。
筛选事件:过滤sched_switch和sched_wakeup事件,聚焦 PID=5678。
分析时间线:
查看sched_wakeup(进程唤醒)到sched_switch(进程切换运行)的时间差,即为调度延迟。
若延迟超标时段存在高优先级进程占用 CPU,可定位为 “优先级抢占导致延迟”。
优化方案:调整实时任务优先级(chrt -f 99 5678),或限制高优先级后台进程的 CPU 占用。
案例 2:嵌入式系统启动慢(启动耗时 > 60 秒)
分析步骤
目标板采集启动过程:
# 启动时执行(可通过rc.local自动运行)
trace-cmd record -e sched_switch -e syscalls -e block -m 16384 -o boot_trace.dat -- boot
可视化分析:
顶部时间线:查看各 CPU 核心的 Idle 状态占比,若某时段 CPU 长期 Idle,可能是等待 IO。
事件列表:筛选block_rq_issue(块设备 IO 请求)事件,查看是否有长时间未完成的磁盘 IO。
进程时间线:定位启动过程中耗时最长的服务进程(如 udevd、mount),分析其阻塞原因。
优化方案:简化启动脚本、优化文件系统挂载顺序、减少启动时的磁盘读写操作。
案例 3:中断冲突导致 IO 响应缓慢
分析步骤
采集中断相关事件:
trace-cmd record -e irq_handler_entry -e irq_handler_exit -e block -o irq_trace.dat ./io_test_app
分析中断响应:
查看irq_handler_entry(中断入口)到irq_handler_exit(中断出口)的时间差,若某中断处理时间 > 100ms,即为瓶颈。
若 IO 设备中断(如 SPI、I2C)与高优先级中断(如定时器)频繁抢占,可调整中断优先级。
优化方案:通过echo 50 > /proc/irq/12/smp_affinity_list绑定中断到特定 CPU,或优化中断处理函数(减少耗时操作)。
五、进阶技巧与优化
1. 自定义事件跟踪
内核态自定义跟踪点:在驱动或内核模块中添加 tracepoint,跟踪特定业务逻辑(如设备初始化、数据传输)。
示例:在驱动中添加跟踪点:
运行
#include <linux/tracepoint.h>
DECLARE_TRACEPOINT(my_driver_init); // 声明跟踪点
trace_event(my_driver_init, TP_PROTO(int dev_id), TP_ARGS(dev_id)); // 定义参数
// 在驱动初始化函数中触发跟踪点
trace_my_driver_init(dev->id);
采集命令:trace-cmd record -e my_driver_init -o custom_trace.dat。
2. 轻量化采集优化(嵌入式场景)
限制事件类型:仅采集与问题相关的事件(如仅跟踪调度或中断),减少数据量。
调整 buffer 大小:根据设备内存调整-m参数(内存充足时设为 8192KB,内存紧张时设为 2048KB)。
后台采集:nohup trace-cmd record -e sched_switch -o bg_trace.dat &,避免终端断开影响采集。
3. 报告导出与分享
导出事件列表:KernelShark 中执行File → Export → Export Events,保存为 CSV 文件,用于离线分析。
截图分享:通过Edit → Screenshot保存当前分析视图,便于团队协作排查问题。
六、常见问题排查
1. 采集失败
问题 1:trace-cmd: error: Could not open tracefs → 内核未开启CONFIG_DEBUG_FS,重新编译内核并开启配置。
问题 2:trace-cmd: error: No such file or directory → 目标板缺少/sys/kernel/debug/tracing目录,检查内核配置。
问题 3:采集时设备卡死 → buffer 设置过大,减少-m参数值(如设为 2048KB)。
2. 可视化乱码或无数据
问题 1:trace.dat文件损坏 → 采集时避免强制断电,确保数据完整导出。
问题 2:KernelShark 版本不兼容 → 升级宿主机 KernelShark(sudo apt upgrade kernelshark)。
问题 3:事件类型未识别 → 确保目标板内核与宿主机 KernelShark 支持的事件类型一致。
3. 嵌入式设备资源占用过高
解决方案:使用静态编译的trace-cmd、缩短采集时长、限制事件类型,采集完成后立即停止trace-cmd进程。
练习作业
在 ARM 嵌入式板上交叉编译trace-cmd,采集 10 秒内的进程调度事件,导出trace.dat并在宿主机分析。
模拟实时任务调度延迟场景,使用 KernelShark 定位延迟原因并给出优化方案。
自定义一个内核跟踪点,采集驱动初始化事件并可视化分析。
五、参考资料
KernelShark 官方文档:https://www.kernelshark.org/Documentation.html
trace-cmd 命令手册:man trace-cmd
Linux 内核ftrace文档:/usr/share/doc/linux-doc/Documentation/trace/ftrace.rst
实战培训讲解&spm=1001.2101.3001.5002&articleId=153721309&d=1&t=3&u=d5f23906c74f463492a72c24bc7a02a6)
1752

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



