AkiraZheng's Time.

linux内核驱动debug方法

Word count: 5.6kReading time: 22 min
2025/12/24

当我们需要查看内核中函数是否调用、入参值是否符合预期、函数返回值、函数调用栈等问题时,我们可以在启动机器后,通过trace方法来动态获取这些信息。

ftrace是最常用的工具,其中kprobetracepoint是其中的两个子工具。

通用操作

过滤

如果要过滤的话,可以查看该函数的format才能知道怎么过滤:

1
2
3
cat events/目录/函数名/format
# eg:
cat events/irq/irq_handler_entry/format

然后就可以根据format中的字段来过滤:

1
echo 'irq==123' > events/irq/irq_handler_entry/filter

查看有哪些可用的 trace events

查看的是包括 tracepoint 和 kprobe 在内的所有 trace 事件,说白了,kprobe 事件也是 trace event 的一种。所以我们| grep kprobe就能看到所有的 kprobe 事件,跟我们平时| grep kvm看到的某个子模块 (kvm) 事件是一样的。

1
2
cat available_events | grep kprobe
cat available_events | grep kvm

set_event 设置跟踪的事件

1
2
3
echo 'kvm:kvm_exit' > set_event
echo 'kvm:kvm_entry' > set_event
cat set_event

kprobe

内核中几乎所有函数都可以被kprobe拦截,包括内核初始化时的函数。kprobe可以用来跟踪函数的调用、入参值和返回值,且不需要手动写trace代码

cd /sys/kernel/debug/tracing

查看

查看所有kprobe可用的函数(几乎所有内核函数都在available_filter_functions中):

1
cat available_filter_functions | grep __setup_irq

当前已经添加跟踪的事件:

1
cat kprobe_events

禁用所有已存在的 kprobe 事件

1
2
3
# 禁用所有已存在的 kprobe 事件(关键步骤)
# 这会关闭所有 events/kprobes/ 下的 enable 开关
echo 0 > events/kprobes/enable 2>/dev/null

进行一些清除操作

1
2
3
echo nop > current_tracer
echo 0 > tracing_on
echo > trace

添加 kprobe 事件到 kprobe_events

原生tracing目录下是没有events/kprobes/目录的,但是为了方便管理 kprobe 事件,我们可以把 kprobe 事件都添加到events/kprobes/目录下进行管理。(echo 命令里带不带 kprobes 都会被定向到kprobes文件夹中)

添加的事项可以有 p 标志的和 r标志的,其中 r标志的表示跟踪的是这个函数的返回值

1
2
3
4
echo 'p:kprobes/p___setup_irq __setup_irq irq=%x0 desc=%x1 new=%x2' >> kprobe_events
echo 'r:kprobes/r___setup_irq __setup_irq ret=$retval' >> kprobe_events
cat kprobe_events # 此时就能看到刚刚添加的 kprobe 事件
ls events/kprobes/ | grep __setup_irq # 此时能看到 __setup_irq 事件了

注意:如果用>写入的话,是覆盖,最后只能存在一个事件;所以要用>>追加的方式写入。

其中可以根据参数类型来选择不同的格式化输出:

1
2
echo 'p:vfio_pci_set_msi_trigger vfio_pci_set_msi_trigger type=%x1:u32 count=%x3:u32 flags=%x4:u32' > kprobe_events
echo 'p:nic_open hns3_nic_net_up name=+0(%x0):string state=+64(%x0):x64' > kprobe_events

参数类型:

  • :x8-64:表示 8-64 位十六进制数
  • :s8-64:表示 8-64 位有符号十进制数
  • :u8-64:表示 8-64 位无符号十
  • :string:表示字符串

寄存器参数:寄存器一般是通用寄存器,在 ARM64 架构中,参数传递通常使用 x0 ~ x7 寄存器,按照传入参数顺序依次为 - 第一个参数:%x0 - 第二个参数:%x1 - ...

所以我们一般要求函数参数控制在 5 个以内,如果函数参数超过了寄存器的数量,额外的参数通常会通过 栈 或 其他寄存器 传递。

查找对应偏移量的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# way 1
cat /proc/kallsyms | grep hns3_nic_net_up # /proc/kallsyms 中包含了内核中所有符号的地址,查看函数地址

# way 2
objdump -d /path/to/vmlinux | grep hns3_nic_net_up -A 20 # 反汇编查看函数实现

# way 3:gdb 可以调试内核并查看寄存器的使用(结构体偏移量推荐用gdb查看,可以参考下面的文章)
gdb /path/to/vmlinux
(gdb) disassemble hns3_nic_net_up

# way 4:
# pahole 是查看内核结构体布局的最佳工具,它直接解析 DWARF 调试信息。
# 假设我们要看:vfio_pci_core_device的入参结构体布局:
# dnf install -y dwarves
pahole -C vfio_pci_core_device

偏移量的获取参考Kernel调试追踪技术之 Kprobe on ARM64

过滤、禁止输出调用栈

1
2
3
4
5
echo 'name=="eth2"' > events/kprobes/filter  # 过滤入参 name==eth2 的事件
# echo name!="eth2" > events/kprobes/nic_open/filter # 过滤入参 name!=eth2 的事件
echo 'pid==1234 || pid==1235' > events/kprobes/filter # 过滤特定 pid 的事件
echo nostacktrace > trace_options # 关闭调用栈的打印,只看 __setup_irq 的入参显示
echo nostacktrace > events/kprobes/p___setup_irq/trigger # 关闭特定 kprobe 事件的调用栈打印

启动追踪

1
2
3
echo 1 > events/kprobes/enable # 启用所有 kprobe 事件
# echo 1 > events/kprobes/nic_open/enable # 单独启用某个 kprobe 事件
echo 1 > tracing_on

查看输出

1
2
3
cat trace # 可以看到所有的 trace,包括耗时
cat trace_pipe # 实时采集管道,退出后就看不到了
cat trace_pipe | tee /tmp/trace.log # 使用tee同时输出到屏幕和文件

停止追踪

1
2
3
4
echo '-:p___setup_irq' > kprobe_events  # 删除某个 kprobe 事件
echo 0 > events/kprobes/enable
# echo 0 > events/kprobes/p___setup_irq/enable # 单独禁用某个 kprobe 事件
echo 0 > tracing_on

清空缓存

1
echo > trace

示例:使用 kprobe 跟踪__setup_irq

用 kprobe 查看__setup_irq

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cd /sys/kernel/debug/tracing

# 禁用所有已存在的 kprobe 事件(关键步骤)
# 这会关闭所有 events/kprobes/ 下的 enable 开关
echo 0 > events/kprobes/enable 2>/dev/null

# 1. 查看当前已经添加跟踪的事件
cat kprobe_events

# 2. 使用简单ftrace查看
echo 'p:kprobes/__setup_irq __setup_irq irq=%x0 desc=%x1 new=%x2' > kprobe_events
# 刚开始 ./events/kprobes/路径 查不到有 __setup_irq
# 事件 echo 进 kprobe_events 后,在./events/kprobes/路径下就有这个 __setup_irq
echo nostacktrace > trace_options # 关闭调用栈的打印,只看 __setup_irq 的入参显示
echo 1 > events/kprobes/enable
echo 1 > tracing_on

# 3. 查看输出
# cat trace # 可以看到所有的 trace,包括耗时
cat trace_pipe # 实时采集管道,退出后就看不到了
cat trace_pipe | tee /tmp/trace.log # 使用tee同时输出到屏幕和文件

# 4. 停止追踪
echo 0 > tracing_on

# 5. 清空缓存
echo > trace

tracepoint 内核插桩

其优点是可以按照我们预期地输出信息,且可以替代printk。缺点是需要内核源码中明确定义 tracepoint 事件,且无法像 kprobe 通过echo 'name=="eth2"' > events/kprobes/filter来过滤特定 pid 的 tracepoint 事件,需要在桩函数代码中自己实现过滤。

trace point 是内核中预定义的跟踪点,通常用于跟踪内核事件。与 kprobe 不同,tracepoint 需要内核源码中明确定义。并显式调用 tracepoint 事件。

有时我们需要printk来输出信息,但printk的输出量很大,且无法控制输出格式。而 tracepoint 可以通过 ftrace 来控制输出,且输出格式可以自定义。

tracepoint都有一个 name、一个 enable 开关和一系列桩函数。

tracepoint 结构体

./include/linux/tracepoint-defs.h中提供了tracepoint struct结构体来定义 tracepoint 的信息。

1
2
3
4
5
6
7
8
9
10
struct tracepoint {
const char *name; /* Tracepoint name */
struct static_key_false key;
struct static_call_key *static_call_key;
void *static_call_tramp;
void *iterator;
void *probestub;
struct tracepoint_func __rcu *funcs;
struct tracepoint_ext *ext;
};

创建 tracepoint

内核里已经自带实现了许多 tracepoint 桩函数,可以在/sys/kernel/debug/tracing/events/目录下看到。

1
2
root@akira:/sys/kernel/tracing# ls ./events/irq
enable filter irq_handler_entry irq_handler_exit softirq_entry softirq_exit softirq_raise

如果想实现添加自己的 tracepoint,可以参考下面的步骤:

首先要在内核中插件入 tracepoint,需要在内核源码中添加 tracepoint 的定义和实现。一般基于模块粒度创建一个trace头文件,本例中创建./include/trace/events/irq.

比如对于irq,可以在./include/trace/events/irq.h中添加 tracepoint 定义:

1
2
3
4
5
6
7
8
/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM irq

#if !defined(_TRACE_IRQ_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_IRQ_H

#include <linux/tracepoint.h>

然后可以在./include/trace/events/irq.h中添加 tracepoint 的定义和实现,一个 tracepoint 文件可以包含多个事件,这里以irq_handler_exit为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 /**
* irq_handler_exit - called immediately after the irq action handler returns
* @irq: irq number
* @action: pointer to struct irqaction
* @ret: return value
*
* If the @ret value is set to IRQ_HANDLED, then we know that the corresponding
* @action->handler successfully handled this irq. Otherwise, the irq might be
* a shared irq line, or the irq was not handled successfully. Can be used in
* conjunction with the irq_handler_entry to understand irq handler latencies.
*/
TRACE_EVENT(irq_handler_exit, // tracepoint name 可自定义,后面使用时用 trace_irq_handler_exit 来调用

TP_PROTO(int irq, struct irqaction *action, int ret),

TP_ARGS(irq, action, ret), // 函数参数

TP_STRUCT__entry( // 表示当前 trace 函数定义了这些变量
__field( int, irq )
__field( int, ret )
),

TP_fast_assign( // 对前面定义的 trace 函数变量进行赋值
__entry->irq = irq;
__entry->ret = ret;
),

TP_printk("irq=%d ret=%s",
__entry->irq, __entry->ret ? "handled" : "unhandled") // tracepoint 的输出格式,也就是前面说的可以替代printk的地方
);

代码中调用 tracepoint

在内核代码中,可以通过trace_irq_handler_exit来调用 tracepoint。比如在./kernel/irq/handle.c中要调用这个 tracepoint 的话,添加对应的头文件:

1
#include <trace/events/irq.h>

注意!!如果是自己添加的 tracepoint 头文件,需要在第一次调用的文件里面加上:

1
#define CREATE_TRACE_POINTS

且整个工程中关于同一个tracepoint 文件只能有一个文件定义这个宏,否则编译会不通过。正常情况下如果你没有重新自己创建一个 tracepoint 文件,只是在现有的 tracepoint 文件中添加事件的话,是不需要在调用处定义这个宏的。

然后使用的时候直接调用:

1
trace_irq_handler_exit(irq, action, res);

内核中动态查看相关桩函数的打印

查看可用的 tracepoint 以及它们的输出格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看所有 irq 相关的 tracepoints
ls events/irq/

# 查看特定 tracepoint 的格式
cat events/irq/irq_handler_exit/format

# 输出示例:
name: irq_handler_exit
ID: 1234
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:int irq; offset:8; size:4; signed:1;
field:int ret; offset:12; size:4; signed:1;

启用和查看 tracepoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 方法1:单个启用
echo 1 > events/irq/irq_handler_exit/enable

# 方法2:启用所有 irq 相关的 tracepoints
echo 1 > events/irq/enable

# 开启跟踪
echo 1 > tracing_on

# 查看输出
cat trace
# 或实时查看
cat trace_pipe

# 停止跟踪
echo 0 > tracing_on
echo 0 > events/irq/irq_handler_exit/enable

也可以使用 perf 工具来查看 tracepoint 的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看所有可用的 tracepoints
sudo perf list | grep irq:

# 记录 tracepoint 事件,一般用于性能分析,当有性能问题,观察耗时分布
sudo perf record -e irq:irq_handler_exit -a sleep 10

# 筛选 tracepoint,一般用于debug调试,当已经定位到某个具体函数或者代码路径的时候,想深入分析它的**行为、参数和延时**
sudo perf record -e "probe:irq:irq_handler_exit" -a -- sleep 10

# 实时查看
sudo perf trace -e irq:irq_handler_exit

# 统计事件计数
sudo perf stat -e irq:irq_handler_exit -a sleep 10

trace_printk

在代码中可以直接通过trace_printk来输出信息,输出的信息会被 ftrace 捕获并显示在 trace 中。相比于printktrace_printk的输出量更小,用法跟普通的printf类似,可以输出格式化字符串和变量值。

1
trace_printk("This is a trace message with value: %d\n", value);

但他不会输出到内核日志中,所以不会对系统性能产生太大影响,且可以通过 ftrace 来控制输出的格式和内容:

1
2
echo 1 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace_pipe

perf 性能分析工具

perf 是基于 Linux 内核提供的 tracepoint 性能事件 perf_events 来进行性能分析的工具。它可以用于分析 CPU 性能、内存性能、I/O 性能等方面的问题。

  • perf stat:用于统计性能事件的计数,例如 CPU 周期数、指令数、缓存命中率等。简单的屏幕输出,支持的指令可以用perf stat -h查看。

    perf stat -a sleep 10:统计全系统在 10 秒内的性能事件计数。

    假设有一个测试的可执行文件要分析,可以使用以下命令: perf stat -e cycles,instructions,cache-references,cache-misses ./test_program

    • -a:显示所有 CPU 上的统计信息。
    • -C:显示指定 CPU 上的统计信息。
    • -e:指定要显示的事件。
    • -i:禁止子任务继承父任务的性能计数器。
    • -r:重复执行 n 次目标程序,并给出性能指标在 n 次执行中的变化范围。
    • -p:指定要显示的进程的 ID。
    • -t:指定要显示的线程的 ID。
  • perf record:用于记录性能事件的采样数据,可以生成性能分析报告。可以与-e选项指定要记录的事件类型。通过perf record -h查看指令参数

    比如针对 irq :perf record -e "irq:irq_handler_exit,irq:irq_handler_entry" -a -- sleep 2

    • -a:分析整个系统的性能
    • -A:以 append 的方式写输出文件
    • -c:事件的采样周期
    • -C:只采集指定 CPU 数据
    • -e:选择性能事件,可以是硬件事件也可以是软件事件
    • -f:以 OverWrite 的方式写输出文件
    • -g:记录函数间的调用关系
    • -o:指定输出文件,默认为 perf.data
    • -p:指定一个进程的 ID 来采集特定进程的数据
    • -t:指定一个线程的 ID 来采集特定线程的数据
  • perf report:针对perf record生成的采样数据进行分析和报告。通过perf report -h查看指令参数

    比如:perf report

    • -c:指定采样周期
    • -C:只显示指定 CPU 的信息
    • -d:只显示指定 dos 的符号
    • -g:生成函数调用关系图,具体等同于 perf top 命令中的 -g
    • -i:导入的数据文件的名称,默认为 perf.data
    • -M:以指定汇编指令风格显示
    • –sort:分类统计信息,如 PID、COMM、CPU 等
    • -S:只考虑指定符号
    • -U:只显示已解析的符号
    • -v:显示每个符号的地址
  • perf annotate:用于查看特定函数或代码段的汇编代码,并采集显示每行代码的耗时,可以帮助我们找到代码、函数的执行瓶颈。通过perf annotate -h查看指令参数

    比如:perf annotate -i perf.data

    • -C:指定某个 CPU 事件
    • -d:只解析指定文件中符号
    • -i:指定输入文件
    • -k:指定内核文件
    • -s:指定符号定位

  1. 第一列:Children

    这列表示当前函数或符号所占的执行时间占比。

    例如,77.14% 表示该项在整个分析的执行时间中占用了 99.95%。

  2. 第三列:Command

    这列显示了执行命令的名称。例如,swapper 是 Linux 内核中的一个常见进程,负责交换内存页。

  3. 第四列:Shared Object

    这列显示符号所属的共享对象或库。

    在这个例子中,[kernel.kallsysms] 表示这是内核的符号。

    [k] 表示该符号或函数是在内核模式下执行的。

  4. 第五列:Symbol

    这列列出了对应的符号或函数名称。例如:default_idle_call。

查看可用的性能事件

1
2
3
perf -h
perf list # 查看所有可用的性能事件
perf list | grep kvm # 查看某个子模块的性能事件

生成火焰图

要生成火焰图必须有调用栈信息,所以需要在perf record时加上-g选项。

perf record -g记录采样数据后,通过perf script > out.perf生成脚本文件,该脚本数据可以用于生成火焰图。

生成的 out.perf可以放到其他机器上生成火焰图,如果只有perf.data的话,需要把/proc/kallsyms文件也一起复制过去,因为生成火焰图需要解析符号表。

1
2
perf record -g ./test
perf script > out.perf

然后使用FlameGraph工具生成火焰图。

1
2
git clone https://github.com/brendangregg/FlameGraph.git
cd FlameGraph

生成火焰图:

1
2
./stackcollapse-perf.pl ../out.perf > out.folded
./flamegraph.pl out.folded > flamegraph.svg

将生成的flamegraph.svg文件用浏览器打开即可看到火焰图。

实战

代码

写一个简单的测试程序 test.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <unistd.h>

int main(void)
{
int times_s = 10;//10s
int num = 1;
for (int i = 0; i < times_s * 1000; ++i) {
num += 1;
usleep(1000);//睡眠1ms
}
return 0;
}

编译并运行(运行的时候加上 perf record):

1
2
gcc -o test test.c
perf record ./test # 只记录函数调用,没有调用关系

  • 22.22% test libc-2.31.so clock_nanosleep@GLIBC_2.17:

    这表示 test 程序在执行时,clock_nanosleep 函数占用了 22.22% 的 CPU 时间。

    clock_nanosleep 来自 libc-2.31.so 库,表示程序在执行睡眠操作(或者等待)时占用了大量 CPU 时间。

  • 13.68% test [kernel.kallsysms] __arm64_sys_clock_nanosleep:

    这个符号表示内核调用 __arm64_sys_clock_nanosleep,也就是说内核在处理 clock_nanosleep 系统调用时占用了 13.68% 的 CPU 时间。

    __arm64_sys_clock_nanosleep 是 ARM64 平台下的系统调用处理函数。

  • 13.25% test [kernel.kallsysms] el0_svc_common.constprop.0:

    该符号与内核中系统调用的处理有关,尤其是与特定的服务调用(SVC)相关。

    SVC(Supervisor Call) 是一种特权指令,用于从用户模式切换到内核模式。

  • 8.55% test [kernel.kallsysms] common_nsleep:

    这个符号表示内核中的 common_nsleep,与常见的睡眠操作有关。它占用了 8.55% 的 CPU 时间。

耗时排查

下面针对这个测试程序通过annotate查看具体的汇编代码及耗时百分比:

1
2
perf record -g ./test
perf annotate -i perf.data

火焰图

下面针对这个测试程序生成火焰图:

1
2
3
4
5
perf record -g ./test  # -g: 记录完整的调用栈,有完整的调用栈才能生成火焰图
perf script > out.perf
cd FlameGraph
./stackcollapse-perf.pl ../out.perf > out.folded
./flamegraph.pl out.folded > flamegraph.svg

生成火焰图的效果图:

这个火焰图主要显示了程序在执行 usleep() 导致的系统调用 nanosleep() 时花费了大量时间。您提供的代码正是导致此性能特征的原因。

  • Y轴(垂直方向): 表示函数调用栈的深度。底部的函数是调用者(父函数),其上方的函数是被调用者(子函数)。
  • X轴(水平方向): 表示该函数在性能分析采样期间占用的 CPU 时间比例,宽度越宽,表示占用 CPU 时间越长。X轴上的顺序没有特定的时间含义,仅用于最大化地合并相同调用栈的矩形块。
  • 颜色: 通常没有特定含义,仅用于区分不同的函数或表示新旧版本对比等。

结合代码中有一个循环,每次循环都会调用 usleep(1000)(睡眠 1 毫秒)。

1
2
3
4
for (int i = 0; i < times_s * 1000; ++i) {
num += 1;
usleep(1000);//睡眠1us
}

这个火焰图显示:

  1. 最底层的 test(您的程序二进制文件,或 main 函数)是程序的起点。
  2. main 函数调用了 libc_start_main,然后调用 usleep。
  3. usleep 最终导致了 nanosleep 或类似的系统调用,例如 hrtimer_nanosleep 和 do_nanosleep,这些调用在图中形成了最宽的“火焰尖”。

结论: - 图中最宽的区域是与 nanosleep 相关的系统调用栈。这表明您的程序大部分时间都花在了“睡眠”(等待)状态,而不是在执行计算任务。火焰图有效地指出了性能瓶颈在于频繁且耗时的睡眠操作。

Linux 性能分析工具 perf 的使用指南

qemu trace

qemu起虚机时使用已有的 trace

1. 查看支持哪些 trace

1
2
3
4
5
6
# 方法1:起虚机前在服务器的host中查看:
qemu-system-aarch -help | grep -i mem

# 方法2:在虚机中查看:
# 进入qemu monitor
(qemu) info trace-events

我们可以看到有一个kvm_memory_faultkvm_vm_ioctl的trace,我们以这个为例来介绍使用方法:

在启动虚机后面,加上这个 trace:

1
2
-trace kvm_memory_fault \
-trace kvm_vm_ioctl

但是这样会有个问题,如果我们直接启动虚机的话,会有大量的打印信息(默认直接在启qemu虚机的串口中打印)

因此,在起虚机的指令中加上:

1
-trace file=qemu.log

然后如果要实时查看日志信息的话,可以在host上查看:

1
tail -f qemu.log

代码中添加 trace

qemu里增加trace的方法

qemu ioctl invalid argument

一般是 ioctl 的参数错误,可以通过strace命令查看ioctl的参数,然后根据参数的错误信息进行修改:

1
strace -f -e trace=ioctl -o ioctl.log qemu-system-aarch64 <其他启虚机指令>

tmp

ftrace主要用于跟踪时延和行为

perf的原理是这样的:每隔一个固定的时间,就在CPU上(每个核上都有)产生一个中断,在中断上看看,当前是哪个pid,哪个函数,然后给对应的pid和函数加一个统计值,这样,我们就知道CPU有百分几的时间在某个pid,或者某个函数上了。 很明显可以看出,这是一种采样的模式,我们预期,运行时间越多的函数,被时钟中断击中的机会越大,从而推测,那个函数(或者pid等)的CPU占用率就越高。(所以不是完整样本,具有概率性) 参考:https://mysummary.readthedocs.io/zh/latest/软件构架设计/在Linux下做性能分析.html#id1

sudo perf record -e 'cycles' -- myapplication arg1 arg2 sudo perf report

另外,我们要清楚,现代CPU基本上已经不用忙等的方式进入等待了,所以,如果CPU在idle(就是没有任务调度,这种情况只要你的CPU占用率不是100%,必然要发生的),击中任务也会停止,所以,在Idle上是没有点的(你看到Idle函数本身的点并非CPU Idle的点,而是准备进入Idle前后花的时间),所以,perf的统计不能用来让你分析CPU占用率的。ftrace和top等工具才能看CPU占用率,perf是不行的。

qemu guest host hypervisor的陷入陷出流程

从这个图上我们可以看到,除了掌握更多的资源(IO资源),Host的地位和Guest地位几乎是对等的。这造成一个很有趣的现象:如果我们在host上用top来看进程的CPU的占用率,Guest占掉的CPU是算在qemu头上的,因为从时间上来说,host确实看到CPU进入qemu后,就没有出来了。但如果用perf top来看,你却看不见qemu占用CPU,因为PMU的中断打进来后,如果调度到Guest中,Host是看不到这个打的点的。所以perf top的报告是qemu占用并不高。

反过来,如果我们在Guest看,top看不见Host抢去的CPU,Guest从这个角度是看不到Host或者其他Guest抢去的CPU的时间的。Perf top同样看不到,因为Guest的中断基本上都是Host种进去的,它只能统计它自己看到的点。

kvm_entry是进入guet,kvm_exit是离开guest进入host/kvm

CATALOG
  1. 通用操作
    1. 过滤
    2. 查看有哪些可用的 trace events
    3. set_event 设置跟踪的事件
  2. kprobe
    1. 查看
    2. 禁用所有已存在的 kprobe 事件
    3. 添加 kprobe 事件到 kprobe_events
    4. 过滤、禁止输出调用栈
    5. 启动追踪
    6. 查看输出
    7. 停止追踪
    8. 清空缓存
    9. 示例:使用 kprobe 跟踪__setup_irq
  3. tracepoint 内核插桩
    1. tracepoint 结构体
    2. 创建 tracepoint
    3. 代码中调用 tracepoint
    4. 内核中动态查看相关桩函数的打印
    5. trace_printk
  4. perf 性能分析工具
    1. 查看可用的性能事件
    2. 生成火焰图
    3. 实战
      1. 代码
      2. 耗时排查
      3. 火焰图
  5. qemu trace
    1. qemu起虚机时使用已有的 trace
    2. 代码中添加 trace
    3. qemu ioctl invalid argument
  6. tmp