Linux 内核鲨鱼(KernelShark)实战培训讲解

目录

培训说明

一、KernelShark 核心认知

1. 工具定位与价值

2. 核心原理

二、工具链部署(宿主机 + 嵌入式目标板)

安装依赖库

安装KernelShark与trace-cmd(宿主机用于分析)

验证安装

依赖准备

交叉编译步骤

编译libtracefs(静态库)

编译trace-cmd(目标板可执行文件)

目标板部署

内核配置要求(目标板)

三、核心操作:数据采集与可视化分析

1. 目标板数据采集(trace-cmd 命令)

1、全量事件采集

2、跟踪特定进程

3、跟踪特定事件

4、跟踪文件系统事件

5、嵌入式采集注意事项

四、嵌入式场景实战案例

案例1:系统启动过程分析

案例 2:实时任务调度延迟超标

五、参考资料


培训说明

适用人群:嵌入式 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分析内核任务执行过程-CSDN博客

KernelShark 官方文档:https://www.kernelshark.org/Documentation.html

trace-cmd 命令手册:man trace-cmd

Linux 内核ftrace文档:/usr/share/doc/linux-doc/Documentation/trace/ftrace.rst

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值