K3 平台 Perfetto 性能分析
1 平台背景
K3 是 Spacemit(进迭时空)的 RISC-V 64 位 SoC,搭载 Bianbu Linux(基于 Ubuntu 22.04 + 内核 6.x)。本文档记录如何在该平台上从零使能 Perfetto 全系统性能分析框架,并以 V2D/DMA 驱动插桩 为完整示例,演示如何为任意新驱动模块添加 trace 事件。
| 组件 | 版本 / 说明 |
|---|---|
| SoC / ISA | Spacemit K3 | RISC-V rv64gc |
| 操作系统 | Bianbu 4.0.1(Ubuntu 22.04 base) |
| 内核 | 6.18.3(自行编译,含 ftrace + PowerVR 模块化) |
| Perfetto | v49.0-40b529923(源码自编译,RISC-V 原生) |
2 底层准备:编译内核
2.1 必要内核配置项
| 配置项 | 说明 |
|---|---|
CONFIG_FTRACE=y | 基础 ftrace 框架(必须) |
CONFIG_DYNAMIC_FTRACE=y | 动态 ftrace,支持运行时开关(必须) |
CONFIG_TRACE_PRINTK=y | 允许 trace_printk() 写入 ring buffer |
CONFIG_FUNCTION_TRACER=y | 函数级追踪 |
CONFIG_SCHED_TRACER=y | 调度延迟追踪(可选) |
CONFIG_SPACEMIT_V2D=y | V2D 驱动(built-in,tracepoint 随内核加载) |
CONFIG_IMG_POWERVR_ROGUE=m | PowerVR GPU 驱动(可加载模块示例) |
⚠ 若驱动以 =m 编译,其 tracepoint 只在 insmod 后才出现于 tracefs。必须在 modprobe 完成之后再启动 traced_probes,否则事件不会被感知。
2.2 编译与安装
cd /home/perise/source git clone https://gitee.com/bianbu-linux/linux-6.18.git && cd linux-6.18基于平台 defconfig 打开 ftrace
make ARCH=riscv k3_defconfig
scripts/config --enable FTRACE --enable DYNAMIC_FTRACE --enable TRACE_PRINTK编译(原生 RISC-V,无需交叉工具链)
make -j$(nproc)
sudo make install && sudo make modules_install
sudo update-initramfs -u && sudo reboot
3 编译并部署 Perfetto
3.1 编译步骤
cd /home/perise/source git clone https://android.googlesource.com/platform/external/perfetto cd perfetto安装构建依赖
tools/install-build-deps
生成构建文件
tools/gn gen out/linux_riscv64 --args=‘is_debug=false’
只编译必要组件(加快速度)
tools/ninja -C out/linux_riscv64 perfetto traced traced_probes
验证
./out/linux_riscv64/perfetto --version
输出: Perfetto v49.0-40b529923
3.2 安装到系统路径
sudo cp out/linux_riscv64/perfetto /usr/local/bin/
sudo cp out/linux_riscv64/traced /usr/local/bin/
sudo cp out/linux_riscv64/traced_probes /usr/local/bin/
4 Perfetto 架构
4.1 进程角色
| 进程 | 职责 |
|---|---|
traced | 中央协调守护进程;管理 session、ring buffer、IPC |
traced_probes | 内核数据源生产者;操作 tracefs 并读取 ftrace ring buffer |
perfetto (CLI) | 消费者客户端;发送配置、写出 .pftrace 文件 |
4.2 关键规则:何时重启守护进程
每次内核重启、加载新模块(insmod)、或修改了带 tracepoint 的驱动后,必须重新运行 start_traced.sh。旧进程不会自动感知新注册的 tracepoint,这是"采集不到事件"最常见的根因。
4.3 ftrace 数据流
traced_probes tracefs
│
├─ 写 "1" → events/<subsys>/<event>/enable
├─ 写 "1" → tracing_on
│ ↑
│ 内核执行 trace_xxx() ← tracepoint 触发
│
└─ 读 per_cpu/cpuN/trace_pipe_raw → traced → .pftrace
5 启动守护进程
#!/bin/bash export PERFETTO_CONSUMER_SOCK_NAME=/tmp/perfetto-consumer export PERFETTO_PRODUCER_SOCK_NAME=/tmp/perfetto-producer关键:每次都重启,确保识别当前内核的 tracepoint
pkill -f “traced$” 2>/dev/null; pkill -f traced_probes 2>/dev/null
sleep 1; rm -f /tmp/perfetto-consumer /tmp/perfetto-producer
nohup setsid /home/perise/source/perfetto/out/linux_riscv64/traced
–producer-socket=/tmp/perfetto-producer
–consumer-socket=/tmp/perfetto-consumer
> /tmp/traced.log 2>&1 &
sleep 0.5
nohup setsid /home/perise/source/perfetto/out/linux_riscv64/traced_probes
> /tmp/traced_probes.log 2>&1 &
sleep 2
echo “[OK] traced=$(pgrep -f ‘traced$’) probes=$(pgrep -f traced_probes)”
6 采集配置格式
buffers: { size_kb: 65536 fill_policy: RING_BUFFER }data_sources: { config {
name: “linux.ftrace”
ftrace_config {
# 格式: “<subsystem>/<event_name>”
ftrace_events: “sched/sched_switch”
ftrace_events: “sched/sched_wakeup”
# 自定义事件(示例:V2D)
ftrace_events: “v2d/v2d_job_submit”
ftrace_events: “v2d/v2d_dma_map_start”
buffer_size_kb: 8192
}
}}data_sources: { config {
name: “linux.process_stats”
process_stats_config { scan_all_processes_on_start: true }
}}
duration_ms: 15000
export PERFETTO_CONSUMER_SOCK_NAME=/tmp/perfetto-consumer
export PERFETTO_PRODUCER_SOCK_NAME=/tmp/perfetto-producer
sudo perfetto --txt --config config.cfg --out /tmp/trace.pftrace
7 为新驱动添加 Trace 事件(V2D/DMA 完整示例)
V2D 是 K3 平台内置的 2D 图形加速器,通过 DMA-buf 进行内存传输。本章以完整可编译的代码演示如何为任意驱动添加 TRACE_EVENT 插桩。
7.1 接入步骤总览
- 创建
<drv>_trace.h——声明 TRACE_EVENT 宏 - 修改 Makefile——添加
ccflags-y += -I$(src) - 修改
<drv>.c——CREATE_TRACE_POINTS+ 调用trace_xxx() - 编译 & 安装内核,重启
- 验证 tracefs 中事件可见,手动 ftrace 测试
- 重启 traced_probes,Perfetto 采集
7.2 第一步:创建 v2d_trace.h
在 drivers/soc/spacemit/v2d/ 目录下创建 v2d_trace.h:
/* SPDX-License-Identifier: GPL-2.0 */ #undef TRACE_SYSTEM #define TRACE_SYSTEM v2d /* 决定 tracefs 子目录名: events/v2d/ */#if !defined(_TRACE_V2D_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_V2D_H
#include <linux/tracepoint.h>/* ── Job 生命周期 ───────────────────────────────────────── */
TRACE_EVENT(v2d_job_submit,
TP_PROTO(u32 seqno, u32 w, u32 h, int acq_fence_fd),
TP_ARGS(seqno, w, h, acq_fence_fd),
TP_STRUCT__entry(
__field(u32, seqno) __field(u32, w) __field(u32, h)
__field(int, acq_fence_fd)
),
TP_fast_assign(
__entry->seqno=seqno; __entry->w=w; __entry->h=h;
__entry->acq_fence_fd=acq_fence_fd;
),
TP_printk(“seqno=%u dst=%ux%u acq_fence=%d”,
__entry->seqno, __entry->w, __entry->h, __entry->acq_fence_fd));TRACE_EVENT(v2d_job_start,
TP_PROTO(u32 seqno, u32 w, u32 h), TP_ARGS(seqno, w, h),
TP_STRUCT__entry(__field(u32,seqno) __field(u32,w) __field(u32,h)),
TP_fast_assign(__entry->seqno=seqno; __entry->w=w; __entry->h=h;),
TP_printk(“seqno=%u dst=%ux%u”, __entry->seqno, __entry->w, __entry->h));TRACE_EVENT(v2d_irq_eof,
TP_PROTO(u32 seqno, u32 irqstatus), TP_ARGS(seqno, irqstatus),
TP_STRUCT__entry(__field(u32,seqno) __field(u32,irqstatus)),
TP_fast_assign(__entry->seqno=seqno; __entry->irqstatus=irqstatus;),
TP_printk(“seqno=%u irqstatus=0x%x”, __entry->seqno, __entry->irqstatus));TRACE_EVENT(v2d_job_done,
TP_PROTO(u32 seqno, int reset), TP_ARGS(seqno, reset),
TP_STRUCT__entry(__field(u32,seqno) __field(int,reset)),
TP_fast_assign(__entry->seqno=seqno; __entry->reset=reset;),
TP_printk(“seqno=%u reset=%d”, __entry->seqno, __entry->reset));/* ── DMA-buf map/unmap ─────────────────────────────────── */
TRACE_EVENT(v2d_dma_map_start,
TP_PROTO(int tbu_id, unsigned long size), TP_ARGS(tbu_id, size),
TP_STRUCT__entry(__field(int,tbu_id) __field(unsigned long,size)),
TP_fast_assign(__entry->tbu_id=tbu_id; __entry->size=size;),
TP_printk(“tbu_id=%d size=%lu”, __entry->tbu_id, __entry->size));TRACE_EVENT(v2d_dma_map_end,
TP_PROTO(int tbu_id, u64 dma_addr), TP_ARGS(tbu_id, dma_addr),
TP_STRUCT__entry(__field(int,tbu_id) __field(u64,dma_addr)),
TP_fast_assign(__entry->tbu_id=tbu_id; __entry->dma_addr=dma_addr;),
TP_printk(“tbu_id=%d dma_addr=0x%llx”, __entry->tbu_id, __entry->dma_addr));TRACE_EVENT(v2d_dma_unmap_start,
TP_PROTO(u32 seqno), TP_ARGS(seqno),
TP_STRUCT__entry(__field(u32,seqno)),
TP_fast_assign(__entry->seqno=seqno;),
TP_printk(“seqno=%u”, __entry->seqno));TRACE_EVENT(v2d_dma_unmap_end,
TP_PROTO(u32 seqno), TP_ARGS(seqno),
TP_STRUCT__entry(__field(u32,seqno)),
TP_fast_assign(__entry->seqno=seqno;),
TP_printk(“seqno=%u”, __entry->seqno));#endif /* _TRACE_V2D_H */
/* 必须放在所有 #endif 之后 /
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH . / 相对于驱动源码目录 */
#define TRACE_INCLUDE_FILE v2d_trace
#include <trace/define_trace.h>
7.3 第二步:修改 Makefile
ccflags-y += -I$(src) # 关键!$(src) 指向驱动源码目录
obj-$(CONFIG_SPACEMIT_V2D) += v2d.o
v2d-y := v2d_drv.o v2d_hw.o v2d_iommu.o
⚠ 不加 -I$(src) 时,编译报 v2d_trace.h: No such file or directory。原因:include <trace/define_trace.h> 触发二次包含,编译器当前目录是内核根目录,找不到驱动目录下的 v2d_trace.h。
7.4 第三步:在 v2d_drv.c 中插桩
/* 在所有 #include 之后,仅此一个 .c 文件添加 */ #define CREATE_TRACE_POINTS #include "v2d_trace.h"/* ── 插桩点 1:v2d_job_submit() ── 任务提交入口 /
trace_v2d_job_submit(
atomic_read(&info->seqno) + 1,
pTask->param.dst.w, / V2D_SURFACE_S 字段是 w/h,不是 width/height */
pTask->param.dst.h,
pTask->acquireFencefd);/* ── 插桩点 2:v2d_post_work_func() ── HW 开始前 */
trace_v2d_job_start(atomic_read(&info->seqno),
post->pTask->param.dst.w, post->pTask->param.dst.h);/* ── 插桩点 3:v2d_irq_handler() ── EOF 中断 */
trace_v2d_irq_eof(atomic_read(&info->seqno), irqstatus);/* ── 插桩点 4:v2d_work_done() ── fence signal 前 /
/ 注意:seqno 是 dma_fence 结构体字段,不是函数 */
trace_v2d_job_done((u32)pCompleteFence->seqno, info->do_reset);/* ── 插桩点 5/6:get_addr_from_dmabuf() ── DMA map /
trace_v2d_dma_map_start(pInfo->tbu_id, pInfo->dmabuf->size);
/ … dma_buf_attach / dma_buf_map_attachment … */
trace_v2d_dma_map_end(pInfo->tbu_id, (u64)addr);
/* ── 插桩点 7/8:v2d_put_dmabuf() ── DMA unmap /
u32 _seqno = cfg->pCompleteFence ?
(u32)cfg->pCompleteFence->seqno : 0;
trace_v2d_dma_unmap_start(_seqno);
/ … cleanup loop … */
trace_v2d_dma_unmap_end(_seqno);
7.5 已知编译陷阱
| 错误信息 | 原因与修复 |
|---|---|
has no member 'width'/'height' | V2D_SURFACE_S 字段是 w / h(uint16_t),不是 width / height |
implicit declaration of 'dma_fence_seqno' | seqno 是 struct dma_fence 直接字段,改写 fence->seqno |
| Module version mismatch (6.18.3+) | 未提交的 git 改动使版本追加 + 后缀;ln -sfn /lib/modules/6.18.3+ /lib/modules/6.18.3-pvr |
v2d_trace.h: No such file or directory | Makefile 缺少 ccflags-y += -I$(src) |
7.6 手动 ftrace 验证
# 内核重启后,确认 event 目录存在 ls /sys/kernel/tracing/events/v2d/ # 预期输出: # enable filter v2d_dma_map_end v2d_dma_map_start # v2d_dma_unmap_end v2d_dma_unmap_start v2d_irq_eof # v2d_job_done v2d_job_start v2d_job_submit手动打开事件,运行负载
echo 1 > /sys/kernel/tracing/events/v2d/enable
echo 1 > /sys/kernel/tracing/tracing_on
/usr/local/bin/v2d_test --fill
cat /sys/kernel/tracing/trace | grep v2d实测输出:
v2d_test-1768 [004] 2920.816178: v2d_job_submit: seqno=15 dst=320x240 acq_fence=-1
v2d-176 [007] 2920.816227: v2d_dma_map_start: tbu_id=2 size=307200
v2d-176 [007] 2920.816283: v2d_dma_map_end: tbu_id=2 dma_addr=0xa0000000
v2d-176 [007] 2920.816284: v2d_job_start: seqno=15 dst=320x240
<idle>-0 [004] 2920.816397: v2d_irq_eof: seqno=15 irqstatus=0x1
清理
echo 0 > /sys/kernel/tracing/events/v2d/enable
echo 0 > /sys/kernel/tracing/tracing_on
echo > /sys/kernel/tracing/trace
8 Perfetto 采集 V2D 事件
8.1 采集脚本 capture_v2d.sh
#!/bin/bash # 用法: sudo bash /home/perise/capture_v2d.sh [时长秒] DURATION=${1:-15} OUT=/tmp/v2d_trace_$(date +%Y%m%d_%H%M%S).pftrace export PERFETTO_CONSUMER_SOCK_NAME=/tmp/perfetto-consumer export PERFETTO_PRODUCER_SOCK_NAME=/tmp/perfetto-producer每次重启以识别最新 tracepoint
pkill -f “traced$” 2>/dev/null; pkill -f traced_probes 2>/dev/null; sleep 1
nohup setsid /home/perise/source/perfetto/out/linux_riscv64/traced
–producer-socket=/tmp/perfetto-producer
–consumer-socket=/tmp/perfetto-consumer
> /tmp/traced.log 2>&1 &
sleep 0.5
nohup setsid /home/perise/source/perfetto/out/linux_riscv64/traced_probes
> /tmp/traced_probes.log 2>&1 &
sleep 2
echo 0 > /proc/sys/kernel/kptr_restrictcat > /tmp/v2d.cfg << CFG
buffers: { size_kb: 65536 fill_policy: RING_BUFFER }
data_sources: { config {
name: “linux.ftrace”
ftrace_config {
ftrace_events: “sched/sched_switch”
ftrace_events: “sched/sched_wakeup”
ftrace_events: “v2d/v2d_job_submit”
ftrace_events: “v2d/v2d_job_start”
ftrace_events: “v2d/v2d_irq_eof”
ftrace_events: “v2d/v2d_job_done”
ftrace_events: “v2d/v2d_dma_map_start”
ftrace_events: “v2d/v2d_dma_map_end”
ftrace_events: “v2d/v2d_dma_unmap_start”
ftrace_events: “v2d/v2d_dma_unmap_end”
buffer_size_kb: 8192
}
}}
data_sources: { config {
name: “linux.process_stats”
process_stats_config { scan_all_processes_on_start: true }
}}
duration_ms: $((DURATION * 1000))
CFG
echo “[] 采集 ${DURATION}s,请在另一终端运行 v2d_test…"
/home/perise/source/perfetto/out/linux_riscv64/perfetto
–txt --config /tmp/v2d.cfg --out “$OUT”
chmod 644 “$OUT” && echo "[] 完成: $(ls -lh $OUT)”
8.2 两终端操作流程
| 终端 1(采集) | 终端 2(负载) |
|---|---|
sudo bash /home/perise/capture_v2d.sh 15 | 等提示出现后运行 |
| 采集中…… | sudo /usr/local/bin/v2d_test --fill |
sudo /usr/local/bin/v2d_test --blit | |
| 15s 后自动完成 | |
scp perise@192.168.31.9:/tmp/v2d_trace_*.pftrace ~/ | |
诊断提示:采集期间执行以下命令确认事件已 enable(值必须为 1):
cat /sys/kernel/tracing/events/v2d/v2d_job_submit/enable # → 1
cat /sys/kernel/tracing/tracing_on # → 1
若值为 0,说明 traced_probes 未正确启动,重新运行 start_traced.sh。
8.3 Perfetto UI 查看
- 访问 https://ui.perfetto.dev,上传 .pftrace 文件
- 左侧搜索框输入
v2d,可筛出全部 8 个 V2D 事件 v2d_job_submit→v2d_job_start→v2d_irq_eof→v2d_job_done构成完整 Job 时序v2d_dma_map_start/end显示 DMA-buf 映射耗时- 配合
sched_switch可分析 V2D 线程在各 CPU 核的调度情况
9 通用新模块 Trace 接入 SOP
| 步骤 | 操作 |
|---|---|
| 1. 头文件 | 创建 <drv>_trace.h,声明 TRACE_SYSTEM 和所有 TRACE_EVENT 宏,末尾写 TRACE_INCLUDE_PATH / TRACE_INCLUDE_FILE |
| 2. Makefile | 添加 ccflags-y += -I$(src) |
| 3. 驱动 .c | 一处 #define CREATE_TRACE_POINTS + #include "<drv>_trace.h",关键路径调用 trace_xxx() |
| 4. 编译安装 | make modules_install + reboot(或 rmmod/insmod) |
| 5. 验证 tracefs | ls /sys/kernel/tracing/events/<TRACE_SYSTEM>/ 确认 event 目录 |
| 6. ftrace 测试 | echo 1 > events/<sys>/enable;运行负载;cat trace |
| 7. 重启 probes | sudo bash /home/perise/start_traced.sh(每次内核/模块变化后必须) |
| 8. 配置采集 | ftrace_events: "<TRACE_SYSTEM>/<event_name>" |
| 9. 可视化 | 下载 .pftrace,上传 ui.perfetto.dev |
10 rvtrace:RISC-V 硬件指令 Trace
rvtrace 是 K3 平台的 RISC-V Nexus 协议硬件 trace,通过 CoreSight TMC ETF 将 CPU 取指记录写入 AUX buffer。与 ftrace 软件插桩不同,rvtrace 零侵入、极低开销,可捕获精确到指令级的执行轨迹。
# 前置条件 echo -1 > /proc/sys/kernel/perf_event_paranoid echo 0 > /proc/sys/kernel/kptr_restrict采集(sinkid=1 对应 tmc_etf0)
sudo perf record -e rvtrace/sinkid=1/ -a
–clockid CLOCK_MONOTONIC
–vmlinux=/home/perise/source/linux-6.18/vmlinux
-o rvtrace.data – sleep 5解码为 branch 记录
sudo perf script -i rvtrace.data
–vmlinux=/home/perise/source/linux-6.18/vmlinux
–itrace=b > branches.txt转换为 Chrome Trace JSON(可导入 Perfetto UI)
python3 /home/perise/rvtrace_to_json.py branches.txt > rvtrace.json
上传 rvtrace.json 到 ui.perfetto.dev
11 常见问题排查
| 现象 | 原因与解决 |
|---|---|
events/v2d/ 目录不存在 | CONFIG_SPACEMIT_V2D 未打开,或模块未 insmod |
| Perfetto 采集不到 v2d 事件 | traced_probes 在事件注册前启动;重启 traced_probes(执行 start_traced.sh) |
| 采集期间 enable=0 | traced_probes 启动失败或 socket 权限问题;以 root 重启 |
| "EnableTracing IPC request rejected" | traced 以非 root 启动,perfetto 以 root 运行;重启 traced 为 root |
| traced 在 SSH 断开后被杀 | 缺少 nohup setsid;start_traced.sh 已包含此修复 |
| rvtrace 采集失败(permission denied) | perf_event_paranoid 未设为 -1;需以 root 运行 |
| rvtrace "failed to get insn info" | kptr_restrict 未设为 0;或 --vmlinux 路径错误 |
| 内核模块版本不匹配(6.18.3+) | 未提交 git 改动追加 + 后缀;ln -sfn /lib/modules/6.18.3+ /lib/modules/6.18.3-pvr |
12 附录:关键路径速查
echo "=== traced ===" && pgrep -af "traced$"
echo "=== traced_probes ===" && pgrep -af traced_probes
echo "=== v2d events ===" && ls /sys/kernel/tracing/events/v2d/ 2>/dev/null || echo "NOT FOUND"
echo "=== tracing_on ===" && cat /sys/kernel/tracing/tracing_on
echo "=== Perfetto ===" && perfetto --version
| 文件 / 路径 | 说明 |
|---|---|
/home/perise/source/perfetto/out/linux_riscv64/ | 编译产物目录 |
/home/perise/start_traced.sh | 启动守护进程(每次重启后运行) |
/home/perise/capture_v2d.sh | V2D 专项采集脚本 |
/home/perise/capture_combined.sh | Perfetto + rvtrace 联合采集脚本 |
/home/perise/rvtrace_to_json.py | rvtrace → Chrome Trace JSON 转换器 |
/tmp/traced_probes.log | traced_probes 日志(含 Ftrace setup 记录) |
/sys/kernel/tracing/events/v2d/ | V2D tracepoint 在 tracefs 中的目录 |