Linux perf

上一篇【linux trace – ptrace strace ltrace ftrace 】 简要学习了ptrace strace ltrace ftrace, 本篇学习一下perf

perf(1) — Linux manual page
Linux perf Examples—-brendangregg
https://elixir.bootlin.com/linux/v5.15/source/tools/perf 内核源码: tools/perf/ (用户态工具) + kernel/events/ (内核子系统)
perf-简介
深入探索 perf CPU Profiling 实现原理
一文看懂Linux性能分析|perf源码实现
perfwiki

概述

perf 全称 performance counters.Performance counters for Linux are a new kernel-based subsystem that provide a framework for all things performance analysis. It covers hardware level (CPU/PMU, Performance Monitoring Unit) features and software features (software counters, tracepoints) as well. 源码位于Linux 源码中:包括用户态工具(tools/perf)和 内核子系统(kernel/events/),还有arch/arm64/kernel/perf_xxx.c 和driver/perf/

perf 是 Linux 内核性能事件子系统(perf_event)的用户态工具集,利用 PMU(Performance Monitoring Unit)硬件计数器软件事件内核 tracepoint 来量化系统行为。与 ptrace 的"停止-读取-继续"模式不同,perf 在内核侧通过环形缓冲区(ring buffer)将采样数据批量上传到用户态,不会阻塞被观测进程。perf 最初由 Thomas Gleixner 和 Ingo Molnar 等人开发,2009 年随 Linux 2.6.31 合入主线。它既是一个计数工具(“发生了多少次”),也是一个采样工具(“在哪里发生的”)。

Performance Event

perf是一个面向event的可观测性工具,能够帮助您实现高级性能分析和故障排查功能。它可以回答的问题包括:

  • 为什么内核在 CPU 上运行时间这么长?具体是哪些代码路径导致的?
  • 哪些代码路径导致了 CPU 二级缓存缺失?
  • CPU 是否因内存 I/O 而停滞?
  • 哪些代码路径在分配内存,分配了多少?
  • 是什么触发了 TCP 重传?
  • 某个内核函数是否被调用,调用频率是多少?
  • 线程离开 CPU 的原因是什么?

perf 的使用依赖我们前面所说的 event(事件)。event 是不同内核工具框架的统一接口,上面的图片说明了 event 来源:

  • Hardware Events: CPU性能监视计数器 PMCs
  • Software Events: 这些是基于内核计数器的低级事件。例如,CPU迁移、主次缺页异常等等。
  • Kernel Tracepoint Events: 硬编码在内核中的静态内核级的检测点,即静态探针
  • User Statically-Defined Tracing (USDT): 这些是用户级程序和应用程序的静态跟踪点。
  • Dynamic Tracing: 可以被放置在任何地方的动态探针。对于内核软件,它使用kprobes框架。对于用户级软件,uprobes。
  • Timed Profiling: 使用perf -FHz选项以指定频率收集的快照。这通常用于CPU使用情况分析,其工作原理是周期性的产生时钟中断事件。

perf源码解析

tools/perf/

所有 perf 功能都建立在同一个系统调用perf_event_open之上

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$grep -rni sys_perf_event_open tools/perf/

util/cloexec.c:53:              fd = sys_perf_event_open(&attr, pid, cpu, -1,
util/cloexec.c:74:              fd = sys_perf_event_open(&attr, pid, cpu, -1, 0);
util/evlist.c:1252:      * as sys_perf_event_open(cpu = -1, thread = -1) is EINVAL
util/evsel.c:1546:              pr_debug2("sys_perf_event_open: pid %d  cpu %d  group_fd %d  flags %#lx",
util/evsel.c:1549:              fd = sys_perf_event_open(&evsel->core.attr, pid, cpu, group_fd, flags);
util/evsel.c:1566:              pr_debug2("\nsys_perf_event_open failed, error %d\n", -ENOTSUP);
util/evsel.c:1687:                              pr_debug2("\nsys_perf_event_open failed, error %d\n",
util/evsel.c:2512:      "The sys_perf_event_open() syscall returned with %d (%s) for event (%s).\n"
util/record.c:36:               fd = sys_perf_event_open(&evsel->core.attr, pid, cpu, -1, flags);
util/record.c:50:       fd = sys_perf_event_open(&evsel->core.attr, pid, cpu, -1, flags);
util/record.c:131:      fd = sys_perf_event_open(&attr, -1, cpu, -1, 0);
util/record.c:291:              fd = sys_perf_event_open(&evsel->core.attr, pid, cpu, -1,
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// tools/perf/perf-sys.h
static inline int
sys_perf_event_open(struct perf_event_attr *attr,
		      pid_t pid, int cpu, int group_fd,
		      unsigned long flags)
{
	int fd;

	fd = syscall(__NR_perf_event_open, attr, pid, cpu,
		     group_fd, flags);

#if HAVE_ATTR_TEST
	if (unlikely(test_attr__enabled))
		test_attr__open(attr, pid, cpu, fd, group_fd, flags);
#endif
	return fd;
}

// kernel/events/core.c:11902
SYSCALL_DEFINE5(perf_event_open,
    struct perf_event_attr __user *, attr_uptr,
    pid_t, pid, int, cpu, int, group_fd, unsigned long, flags)

struct perf_event_attr:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
struct perf_event_attr {
    __u32 type;               // PERF_TYPE_HARDWARE / SOFTWARE / TRACEPOINT / ...
    __u32 size;               // sizeof(struct perf_event_attr)
    __u64 config;             // 事件具体配置(如 PERF_COUNT_HW_CPU_CYCLES)
    union {
        __u64 sample_period;  // 采样周期(模式)
        __u64 sample_freq;    // 采样频率(模式)
    };
    __u64 sample_type;        // 采样数据包含哪些字段(IP, TID, TIME, ADDR, ...)
    __u64 read_format;        // 读取格式
    // ... 更多标志位
    __u64 disabled       : 1; // 初始是否禁用
    __u64 inherit        : 1; // 是否跟踪子进程
    __u64 pinned         : 1; // 是否独占 PMU
    __u64 exclusive      : 1;
    __u64 exclude_user   : 1; // 排除用户态
    __u64 exclude_kernel : 1; // 排除内核态
    __u64 exclude_hv     : 1; // 排除虚拟机管理程序
    __u64 precise_ip     : 2; // 精确指令指针(PEBS 精度)instruction pointer(ip)
    // ...
};
  sequenceDiagram
    participant Perf as perf tool
    participant Kernel as perf_event 内核
    participant PMU as PMU 硬件
    participant RB as Ring Buffer

    Perf->>Kernel: perf_event_open(type=HARDWARE, config=CPU_CYCLES)
    Kernel->>PMU: MSR 编程,启用计数器
    PMU-->>Kernel: 计数器溢出 → NMI
    Kernel->>RB: 写入 sample (IP, PID, time, ...)
    Perf->>Kernel: read(perf_fd, buf, size)
    Kernel-->>Perf: 批量样本数据
    Note over Perf,RB: mmap ring buffer,避免 read 系统调用

perf 支持两种核心工作模式:

模式 机制 数据量 典型命令 用途
计数(Counting) PMU 计数器持续累加,按需读取 极低 perf stat “发生了多少次 / 多少周期”
采样(Sampling) 计数器溢出或定时触发,记录现场(IP、栈等) 较高 perf record “CPU 在哪里消耗时间”

perf stat

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
charles@ubuntu22:~$ perf stat -d   hackbench
Running in process mode with 10 groups using 40 file descriptors each (== 400 tasks)
Each sender will pass 100 messages of 100 bytes
Time: 0.785

 Performance counter stats for 'hackbench':

           1600.33 msec task-clock                #    1.891 CPUs utilized
              9696      context-switches          #    6.059 K/sec
               592      cpu-migrations            #  369.924 /sec
             11558      page-faults               #    7.222 K/sec
   <not supported>      cycles
   <not supported>      instructions          
   <not supported>      branches
   <not supported>      branch-misses
   <not supported>      L1-dcache-loads
   <not supported>      L1-dcache-load-misses
   <not supported>      LLC-loads
   <not supported>      LLC-load-misses

       0.846441059 seconds time elapsed

       0.733235000 seconds user
       0.920203000 seconds sys

cycles 我用的是virtualbox ,可能不支持PMU ,硬件信息统计不了

  • 指标解读
    指标 (Event) 含义 (Description) 解读与分析 (Interpretation & Analysis)
    task-clock 任务占用的 CPU 时间(毫秒) CPU 利用率:该值高说明程序是 CPU 密集型(CPU-bound),反之则可能受 I/O 或调度等待影响。事件名带 :u 后缀表示仅统计用户态。
    context-switches 上下文切换次数 调度开销:频繁切换会带来性能开销。如果数值过高,可能暗示线程/进程数过多或锁争用严重。
    cpu-migrations CPU 迁移次数 CPU 亲和性:进程被调度器在不同 CPU 核心间迁移的次数。频繁迁移会降低缓存命中率。
    page-faults 缺页异常次数 内存访问:包括次要缺页(内存已映射)和主要缺页(需磁盘 I/O)。主要缺页对性能影响很大。
    cycles CPU 时钟周期数 执行时长:指示程序运行消耗的 CPU 周期。可结合 task-clock 计算 CPU 频率。
    instructions 执行的指令数 指令量:程序执行的机器指令总数。是衡量代码复杂度的指标之一。
    branches 分支指令数 分支操作:程序中条件跳转、循环等分支指令的总数。
    branch-misses 分支预测失败次数 预测惩罚:分支预测失败会导致流水线清空。该值比例(如 4.96% of all branches)越低越好。
指标 含义 详细说明
seconds time elapsed 程序总运行时间 (墙上时钟时间) 从程序启动到结束的实际耗时,包含 CPU 计算、I/O 等待、睡眠、调度延迟等所有时间。这是用户体验到的“真实时间”。
seconds user 用户态 CPU 时间 程序在用户空间执行代码所消耗的 CPU 时间总和(多核累加)。反映应用程序自身逻辑的计算开销。
seconds sys 内核态 CPU 时间 程序在内核空间执行代码所消耗的 CPU 时间总和(多核累加)。反映系统调用、文件 I/O、内存分配、网络收发等内核服务的开销。
  • 选择特定事件:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 列出所有可用事件
perf list

# perf stat help
perf stat -h

# 只统计特定事件
perf stat -e cycles,instructions,branch-misses,cache-misses hackbench

# 统计多个事件组(硬件限制:同时可编程的 PMC 数量有限)
perf stat -e '{cycles,instructions},{L1-dcache-loads,L1-dcache-misses}' hackbench

# 每秒输出一次(interval mode)
perf stat -I 1000 -e cycles,instructions -p $(pgrep app)

# 统计整个系统所有 CPU
perf stat -a sleep 5
  • perf stat 精度
1
2
3
4
5
# 使用 PEBS(Precise Event-Based Sampling)提高精度 见struct perf_event_attr中的precise_ip
perf stat -e instructions:u,cycles:u ./myapp   # 只统计用户态

# 多次运行取平均值 + 标准差
perf stat -r 5 ./myapp

perf record / perf report — 采样模式

采样模式会以固定频率(或事件周期)打断 CPU,记录当前的指令指针(IP)和调用栈,然后聚合生成热点分布图。

1
2
3
4
5
# 以 99 Hz 频率采样整个系统(99 Hz 避免与常见周期共振)
perf record -F 99 -a -g -- sleep 10

# 生成报告
perf report

perf report 交互界面

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Samples: 1K of event 'cpu-clock:pppH', Event count (approx.): 19999999800
  Children      Self  Command          Shared Object      Symbol
+   99.85%     0.00%  swapper          [kernel.kallsyms]  [k] secondary_startup_64_no_verify
+   99.85%     0.00%  swapper          [kernel.kallsyms]  [k] cpu_startup_entry
+   99.85%     0.00%  swapper          [kernel.kallsyms]  [k] do_idle
+   99.85%     0.00%  swapper          [kernel.kallsyms]  [k] cpuidle_idle_call
+   99.85%     0.00%  swapper          [kernel.kallsyms]  [k] default_idle_call
+   99.85%     0.00%  swapper          [kernel.kallsyms]  [k] arch_cpu_idle
+   99.85%    98.64%  swapper          [kernel.kallsyms]  [k] native_safe_halt
+   49.95%     0.00%  swapper          [kernel.kallsyms]  [k] start_secondary
+   49.90%     0.00%  swapper          [kernel.kallsyms]  [k] x86_64_start_kernel
+   49.90%     0.00%  swapper          [kernel.kallsyms]  [k] x86_64_start_reservations
+   49.90%     0.00%  swapper          [kernel.kallsyms]  [k] start_kernel
+   49.90%     0.00%  swapper          [kernel.kallsyms]  [k] arch_call_rest_init
+   49.90%     0.00%  swapper          [kernel.kallsyms]  [k] rest_init
+    1.21%     0.40%  swapper          [kernel.kallsyms]  [k] handle_softirqs
+    1.21%     0.00%  swapper          [kernel.kallsyms]  [k] asm_sysvec_apic_timer_interrupt
+    1.21%     0.00%  swapper          [kernel.kallsyms]  [k] sysvec_apic_timer_interrupt
+    1.21%     0.00%  swapper          [kernel.kallsyms]  [k] irq_exit_rcu
+    0.76%     0.00%  swapper          [kernel.kallsyms]  [k] run_timer_softirq
+    0.76%     0.00%  swapper          [kernel.kallsyms]  [k] __run_timers.part.0

火焰图生成:

1
2
3
4
5
6
7
# 生成折叠调用栈
perf script > out.perf
# 使用 FlameGraph 工具集
git clone https://github.com/brendangregg/FlameGraph
cd FlameGraph
./stackcollapse-perf.pl ../out.perf > out.folded
./flamegraph.pl out.folded > flamegraph.svg

采集调用栈的注意事项:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#  -g enables call-graph recording,记录完整的调用栈(Call Stack)
perf record -F 99 -a -g ./myapp

# 使用 DWARF 展开调用栈(更精确但更慢)
perf record -F 99 --call-graph dwarf ./myapp

# 使用 LBR(Last Branch Record,硬件辅助,开销最低)
perf record -F 99 --call-graph lbr ./myapp

# -C 可指定cpu ; -a   system-wide collection from all CPUs
perf record -C 0 -a -o /tmp/perf_run_cpu0.data -- sleep 700&  #CPU 0 上运行的所有进程的系统级采样

记录特定 PID:

1
perf record -F 99 -p $(pgrep myapp) -g -- sleep 30

perf top — 实时热点

类似 top 但按 CPU 采样热点排序,实时显示最热的函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 实时查看 CPU 热点(类似 htop 但显示函数级)
perf top

# 只查看用户态
perf top -u

# 指定 PID
perf top -p $(pgrep myapp)

# 显示调用链
perf top -g

perf probe — 动态插桩

perf probe 利用 kprobe/uprobe 机制动态插入探测点,无需重新编译内核或程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 在内核函数入口添加探针
perf probe --add 'tcp_sendmsg size'

# 在内核函数返回添加探针(返回值)
perf probe --add 'tcp_sendmsg%return $retval'

# 查看探针记录的事件
perf record -e probe:tcp_sendmsg -a -g -- sleep 5
perf script

# 删除探针
perf probe --del probe:tcp_sendmsg

# 探测内核函数局部变量
perf probe --add 'tcp_v4_rcv skb->len'

# 探测用户态函数(需要调试符号)
perf probe -x /usr/lib/libc.so.6 --add 'malloc size'

# 列出所有已注册探针
perf probe --list
 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
perf probe --add 'tcp_sendmsg size'
curl http://www.baidu.com
perf record -e probe:tcp_sendmsg -a -g -- sleep 5
perf report
Samples: 3  of event 'probe:tcp_sendmsg', Event count (approx.): 3
  Children      Self  Command  Shared Object      Symbol
+   66.67%    66.67%  sshd     [kernel.kallsyms]  [k] tcp_sendmsg
+   66.67%     0.00%  sshd     [unknown]          [k] 0000000000000000
+   66.67%     0.00%  sshd     libc.so.6          [.] write
+   66.67%     0.00%  sshd     [kernel.kallsyms]  [k] entry_SYSCALL_64_after_hwframe
+   66.67%     0.00%  sshd     [kernel.kallsyms]  [k] do_syscall_64
+   66.67%     0.00%  sshd     [kernel.kallsyms]  [k] x64_sys_call
+   66.67%     0.00%  sshd     [kernel.kallsyms]  [k] __x64_sys_write
+   66.67%     0.00%  sshd     [kernel.kallsyms]  [k] ksys_write
+   66.67%     0.00%  sshd     [kernel.kallsyms]  [k] vfs_write
+   66.67%     0.00%  sshd     [kernel.kallsyms]  [k] new_sync_write
+   66.67%     0.00%  sshd     [kernel.kallsyms]  [k] sock_write_iter
+   66.67%     0.00%  sshd     [kernel.kallsyms]  [k] __sock_sendmsg
+   33.33%    33.33%  curl     [kernel.kallsyms]  [k] tcp_sendmsg
+   33.33%     0.00%  curl     [unknown]          [k] 0000000000000000
+   33.33%     0.00%  curl     libc.so.6          [.] __send
+   33.33%     0.00%  curl     [kernel.kallsyms]  [k] entry_SYSCALL_64_after_hwframe
+   33.33%     0.00%  curl     [kernel.kallsyms]  [k] do_syscall_64
+   33.33%     0.00%  curl     [kernel.kallsyms]  [k] x64_sys_call
+   33.33%     0.00%  curl     [kernel.kallsyms]  [k] __x64_sys_sendto
+   33.33%     0.00%  curl     [kernel.kallsyms]  [k] __sys_sendto
+   33.33%     0.00%  curl     [kernel.kallsyms]  [k] __sock_sendmsg

perf trace — 类 strace 系统调用追踪

perf trace 利用内核 tracepoint 实现系统调用追踪,开销远低于 strace:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 追踪指定进程的系统调用(效果类似 strace,但快得多)
perf trace -p $(pgrep myapp)

# 追踪特定系统调用
perf trace -e openat,read,write -p $(pgrep myapp)

# 追踪所有进程的特定系统调用
perf trace -e openat -a

# 显示系统调用耗时
perf trace --duration 0.1 -e poll -p $(pgrep myapp)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
root@ubuntu22:~# ls 11
11
root@ubuntu22:~# ls
11
root@ubuntu22:~# perf trace -e read ls
     0.000 ( 0.218 ms): ls/10609 read(fd: 3, buf: 0x7fff90b79428, count: 832)                          = 832
     0.456 ( 0.155 ms): ls/10609 read(fd: 3, buf: 0x7fff90b79408, count: 832)                          = 832
     0.834 ( 0.590 ms): ls/10609 read(fd: 3, buf: 0x7fff90b793e8, count: 832)                          = 832
     3.198 ( 0.325 ms): ls/10609 read(fd: 3, buf: 0x5605a08b1500, count: 1024)                         = 400
     4.035 ( 0.223 ms): ls/10609 read(fd: 3, buf: 0x5605a08b1500, count: 1024)                         = 0
     4.491 ( 0.616 ms): ls/10609 read(fd: 3, buf: 0x5605a08b1930, count: 4096)                         = 2996
     5.492 ( 0.725 ms): ls/10609 read(fd: 3, buf: 0x5605a08b1930, count: 4096)                         = 0
11

perf trace vs strace 性能对比

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# strace: 10~100x 降速
time strace -e openat find /usr -name "*.txt" 2>/dev/null
eal    1m15.565s
user    0m1.170s
sys     0m32.853s

# perf trace: 几乎无开销
time perf trace -e openat find /usr -name "*.txt" 2>/dev/null
real    0m5.467s
user    0m0.389s
sys     0m0.279s

perf sched — 调度器分析

专门用于分析 Linux 调度器行为的子命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 记录调度事件
perf sched record -- sleep 5

# 生成调度延迟报告
perf sched latency

# 可视化调度时间线
perf sched timehist

# 显示每个任务的调度统计
perf sched map
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

root@ubuntu22:~# perf sched record -- sleep 5
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.281 MB perf.data (916 samples) ]
root@ubuntu22:~# perf sched latency
 -------------------------------------------------------------------------------------------------------------------------------------------
  Task                  |   Runtime ms  | Switches | Avg delay ms    | Max delay ms    | Max delay start           | Max delay end          |
 -------------------------------------------------------------------------------------------------------------------------------------------
  multipathd:(2)        |      0.000 ms |        2 | avg:   0.000 ms | max:   0.000 ms | max start:     0.000000 s | max end:     0.000000 s
  kworker/0:2-eve:10236 |     14.261 ms |        1 | avg:   0.000 ms | max:   0.000 ms | max start:     0.000000 s | max end:     0.000000 s
  kworker/u4:2-ev:10491 |      4.524 ms |        1 | avg:   0.000 ms | max:   0.000 ms | max start:     0.000000 s | max end:     0.000000 s
  rcu_sched:14          |      4.352 ms |        1 | avg:   0.000 ms | max:   0.000 ms | max start:     0.000000 s | max end:     0.000000 s
  sleep:10660           |      3.476 ms |        1 | avg:   0.000 ms | max:   0.000 ms | max start:     0.000000 s | max end:     0.000000 s
  kworker/0:1H-kb:90    |      3.087 ms |        1 | avg:   0.000 ms | max:   0.000 ms | max start:     0.000000 s | max end:     0.000000 s
  systemd-resolve:607   |      1.958 ms |        1 | avg:   0.000 ms | max:   0.000 ms | max start:     0.000000 s | max end:     0.000000 s
  systemd-network:605   |      1.601 ms |        1 | avg:   0.000 ms | max:   0.000 ms | max start:     0.000000 s | max end:     0.000000 s

kernel/events

perf_event 子系统初始化

 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
┌─────────────────────────────────────────────────────────────────┐
│  用户空间:  perf stat / perf record / perf top                  │
│             调用 perf_event_open(2) syscall                     │
│             通过 mmap'd ring buffer 读取采样数据                  │
├─────────────────────────────────────────────────────────────────┤
│  内核 perf 核心 (kernel/events/core.c)                          │
│  ┌──────────┬──────────┬──────────┬──────────┬─────────────┐   │
│  │ 事件分配  │ 上下文调度│ 溢出处理  │ Ring     │ 继承/分组    │   │
│  │ alloc     │ ctx sched│ overflow │ Buffer   │ inherit     │   │
│  └──────────┴──────────┴──────────┴──────────┴─────────────┘   │
├─────────────────────────────────────────────────────────────────┤
│  PMU 抽象层 — struct pmu (vtable 多态接口)                       │
│  ┌──────────┬──────────┬──────────┬──────────────┬──────────┐  │
│  │ perf_    │ perf_trace│ perf_    │ perf_        │ perf_    │  │
│  │ swevent  │ point    │ cpu_clock│ kprobe/uprobe│ breakpoint│  │
│  │ (软件)    │ (静态探针)│ (hrtimer) │ (动态探针)    │ (硬件断点) │  │
│  └──────────┴──────────┴──────────┴──────────────┴──────────┘  │
├─────────────────────────────────────────────────────────────────┤
│  硬件 PMU 驱动 (ARM64)                                          │
│  ┌──────────────────────────────────────────────────────┐       │
│  │  struct arm_pmu  →  struct pmu (core)                │       │
│  │  armv8pmu_handle_irq / armv8pmu_enable_event / ...   │       │
│  │  PMCR / PMOVSR / PMEVCNTR / PMEVTYPER 寄存器操作       │       │
│  └──────────────────────────────────────────────────────┘       │
└─────────────────────────────────────────────────────────────────┘
 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
31
32
33
34
35
36
37
38
39
40
// init/main.c 
asmlinkage __visible void __init start_kernel(void)
{
...
    random_init(command_line);
    boot_init_stack_canary();

    perf_event_init();        // ← perf_event 子系统初始化
    profile_init();
    call_function_init();
    WARN(!irqs_disabled(), "Interrupts were enabled early\n");
...
    arch_call_rest_init(); //-->rest_init()-->kernel_thread(kernel_init)->kernel_init_freeable();run_init_process(/sbin/init)
    //kernel_init_freeable()->do_basic_setup()->driver_init();do_initcalls();
    //drivers/base/init.c:driver_init()
    //do_initcalls()->include/kernel/init.h:#define device_initcall(fn)		__define_initcall(fn, 6)-->do_initcall_level()   device_initcall 编译时注册机制
    //drivers\perf\arm_pmu_acpi.cdevice_initcall(armv8_pmu_driver_init)-->platform_driver_register()->(&armv8_pmu_driver)->.probe = armv8_pmu_device_probe()若没有PMU 硬件,则用不了
    //\tools\perf\design.txt 中提到, not have hardware performance metrics, you can still use the generic software counters based on hrtimers for sampling。没有硬件PMU ,基本的软件PMU 可以用(在perf_event_init时已启用)
...
}
// kernel/events/core.c
void __init perf_event_init(void)
{
    int ret;

    idr_init(&pmu_idr);                                          // ① ID分配器

    perf_event_init_all_cpus();                                  // ② per-CPU数据结构
    init_srcu_struct(&pmus_srcu);                                // ③ SRCU锁

    perf_pmu_register(&perf_swevent, "software", PERF_TYPE_SOFTWARE); // ④ 注册软件PMU
    perf_pmu_register(&perf_cpu_clock, NULL, -1);                     // ⑤ cpu-clock PMU
    perf_pmu_register(&perf_task_clock, NULL, -1);                    // ⑥ task-clock PMU
    perf_tp_register();                                          // ⑦ tracepoint/kprobe/uprobe PMU
    perf_event_init_cpu(smp_processor_id());                     // ⑧ 初始化boot CPU
    register_reboot_notifier(&perf_reboot_notifier);             // ⑨ 重启通知

    ret = init_hw_breakpoint();                                  // ⑩ 硬件断点PMU
    WARN(ret, "hw_breakpoint initialization failed with: %d", ret);
}

perf_event_init详解:

步骤 函数 作用
idr_init(&pmu_idr) 初始化 PMU 类型 ID 分配器,PERF_TYPE_*struct pmu * 的映射
perf_event_init_all_cpus() 为每个 possible CPU 初始化:swevent_htable(软件事件哈希表)、active_ctx_listpmu_sb_events(side-band 事件列表)
init_srcu_struct(&pmus_srcu) 初始化保护 PMU 链表和 IDR 的 SRCU(Sleepable RCU)
④⑤⑥ perf_pmu_register() ×3 注册三个内置软件 PMU:perf_swevent(类型=PERF_TYPE_SOFTWARE,负责 page-fault/context-switch 等)、perf_cpu_clockperf_task_clock
perf_tp_register() 注册 perf_tracepoint PMU(类型=PERF_TYPE_TRACEPOINT),以及可选的 perf_kprobe/perf_uprobe PMU
perf_event_init_cpu(boot_cpu) 初始化启动 CPU 的 swevent hlist、标记已注册 PMU 的 CPU 上下文 online
register_reboot_notifier() 注册优先级 INT_MIN 的重启通知(最后执行,确保 watchdog 尽可能久运行)
init_hw_breakpoint() 查询硬件断点寄存器数量、分配 per-CPU 数据、注册 perf_breakpoint PMU

关键结构体关系总览

  classDiagram
    class perf_event_attr {
        +u32 type
        +u64 config
        +u64 sample_period / sample_freq
        +u64 sample_type
        +bits: disabled, inherit, pinned, exclude_user, exclude_kernel...
        +u64 branch_sample_type
        +u64 sample_regs_user
    }

    class perf_event {
        +struct pmu *pmu
        +struct perf_event_context *ctx
        +struct hw_perf_event hw
        +struct perf_buffer *rb
        +perf_overflow_handler_t overflow_handler
        +struct irq_work pending
        +local64_t count
        +struct perf_event_attr attr
        +struct perf_event *group_leader, *parent
        +struct list_head sibling_list, child_list
        +u64 id
        +int state
    }

    class pmu {
        +event_init()
        +add() / del()
        +start() / stop()
        +read()
        +pmu_enable() / pmu_disable()
        +sched_task()
        +attr_groups
        +int capabilities
    }

    class hw_perf_event {
        +local64_t period_left
        +u64 sample_period
        +u64 last_period
        +u64 interrupts
        +int state
        +int idx
        +struct task_struct *target
        +local64_t prev_count
    }

    class perf_event_context {
        +struct pmu *pmu
        +struct task_struct *task
        +struct rb_root pinned_groups
        +struct rb_root flexible_groups
        +struct list_head event_list
        +u64 time, timestamp
    }

    class perf_buffer {
        +int nr_pages
        +int overwrite
        +local_t head
        +unsigned int nest
        +local_t lost
        +atomic_t mmap_count
        +struct perf_event_mmap_page *user_page
        +void *data_pages[]
    }

    class perf_output_handle {
        +struct perf_buffer *rb
        +struct perf_event *event
        +void *addr
        +unsigned long size
        +int page
    }

    class perf_sample_data {
        +u64 ip
        +u32 pid, tid
        +u64 time, addr, id
        +u64 period
        +struct perf_callchain_entry *callchain
        +struct perf_raw_record *raw
        +struct perf_branch_stack *br_stack
        +struct perf_regs regs_user
        +u64 stack_user_size
        +u64 weight, phys_addr
    }

    perf_event_attr --> perf_event : embedded attr
    perf_event --> pmu : points to
    perf_event --> hw_perf_event : embedded hw
    perf_event --> perf_event_context : ctx
    perf_event --> perf_buffer : rb (RCU)
    perf_output_handle --> perf_buffer : rb
    perf_output_handle --> perf_event : event
    perf_sample_data --> perf_event : sampled from
结构体 定义位置 作用
perf_event_attr include/uapi/linux/perf_event.h:320 用户态配置:type, config, sample_period/freq, sample_type 等
perf_event include/linux/perf_event.h 核心事件对象:绑定 PMU → ctx → ring buffer,持有事件状态、计数
pmu include/linux/perf_event.h PMU 驱动虚函数表:event_init/add/del/start/stop/read,多态核心
hw_perf_event include/linux/perf_event.h 硬件计数器状态:period_left, idx, prev_count, interrupts(节流用)
perf_event_context include/linux/perf_event.h 事件调度容器:per-task 或 per-CPU,管理 pinned/flexible 两组红黑树
perf_buffer kernel/events/internal.h:13 mmap Ring Buffer:head/tail 游标、data_pages[]、user_page(控制页)
perf_output_handle include/linux/perf_event.h 写入游标:addr/size/page,封装跨页写入
perf_sample_data include/linux/perf_event.h 采样数据暂存区:IP/TID/TIME/CALLCHAIN/REGS 等
  • 系统调用入口:perf_event_open
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// kernel/events/core.c
SYSCALL_DEFINE5(perf_event_open,
        struct perf_event_attr __user *, attr_uptr,
        pid_t, pid, int, cpu, int, group_fd, unsigned long, flags)
{
    // 1. 复制用户态 attr
    err = perf_copy_attr(attr_uptr, &attr);
    // 2. LSM 安全检查
    err = security_perf_event_open(&attr, PERF_SECURITY_OPEN);
    // 3. exclude_kernel 需要 CAP_PERFMON
    if (!attr.exclude_kernel) { err = perf_allow_kernel(&attr); }
    // 4. 分配 core 结构
    event = perf_event_alloc(&attr, cpu, task, group_leader, ...);
    // 5. 获取/创建上下文
    ctx  = find_get_context(task, event, &err);
    // 6. 创建匿名文件(fd 最终关联此文件)
    event_file = anon_inode_getfile("[perf_event]", &perf_fops, event, f_flags);
    // 7. 安装事件到上下文(通过 IPI 跨 CPU 通知)
    perf_install_in_context(ctx, event, event->cpu);
    // 8. 返回 fd
    fd_install(event_fd, event_file);
    return event_fd;
}
  • struct perf_event — 事件对象(Central Object) struct perf_event 是整个 perf 子系统的核心对象,定义在 include/linux/perf_event.h 中。每个 perf_event_open() 系统调用创建一个 perf_event 实例,贯穿事件从创建到销毁的整个生命周期。
 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// include/linux/perf_event.h — Linux v5.10
// 关键字段按逻辑分组展示
struct perf_event {
    /* --- 1. PMU 后端绑定 --- */
    struct pmu               *pmu;           // PMU 后端(perf_init_event 选择)
    local64_t                 count;         // 累计计数值(用户最终读到)
    atomic64_t                child_count;   // 子事件累计(inherit 场景求和)

    /* --- 2. 状态与标识 --- */
    enum perf_event_state     state;         // DEAD/EXIT/ERROR/OFF/INACTIVE/ACTIVE
    unsigned int              attach_state;  // PERF_ATTACH_TASK 等
    local64_t                 total_time_enabled;  // 累计启用时间
    local64_t                 total_time_running;  // 累计运行时间(不含 multiplexing)
    u64                       tstamp;             // 时间戳(用于核算)

    /* --- 3. CPU 与上下文关联 --- */
    int                       cpu;           // 目标 CPU(-1 = 不限)
    int                       oncpu;         // 当前运行的 CPU(ACTIVE 时有效)
    struct perf_event_context *ctx;          // 所属上下文(task or per-CPU)

    /* --- 4. 事件关系(分组 + 继承) --- */
    struct perf_event         *parent;       // 父事件(inherit 场景的 clone 来源)
    struct perf_event         *group_leader; // 组 leader(单事件以自身为 leader)
    struct list_head          sibling_list;  // 组内兄弟链表
    struct list_head          child_list;    // 子事件链表
    struct perf_event         *next_sibling; // 组内下一个兄弟
    int                       group_caps;    // 组能力位

    /* --- 5. 调度与激活 --- */
    struct list_head          active_list;   // ctx->pinned_active 或 flexible_active
    struct list_head          event_entry;   // ctx->event_list
    struct rb_node            group_node;    // pinned/flexible RB-tree 节点
    u64                       group_index;   // RB-tree 排序键

    /* --- 6. 硬件状态 --- */
    struct hw_perf_event      hw;            // 硬件细节(见下方展开)
    int                       event_caps;    // PERF_EV_CAP_SOFTWARE 等

    /* --- 7. 采样与 ring buffer --- */
    struct perf_buffer        *rb;           // ring buffer(RCU 指针)
    struct list_head          rb_entry;      // rb->event_list
    struct perf_event         *aux_event;    // 关联的 AUX 事件

    /* --- 8. 溢出处理 --- */
    perf_overflow_handler_t   overflow_handler;        // 溢出回调函数
    void                      *overflow_handler_context;
    int                       pending_disable;         // 延期关闭标志
    struct irq_work           pending;                 // 延期执行(irq_work)
    atomic_t                  event_limit;             // 触发次数上限

    /* --- 9. fd 与生命期管理 --- */
    struct file               *filp;         // 关联的文件对象
    atomic_t                  mmap_count;    // mmap 引用计数
    atomic_long_t             refcount;      // 引用计数(put_event 释放)
    struct rcu_head           rcu_head;      // RCU 延迟释放

    /* --- 10. 地址过滤 --- */
    struct perf_addr_filters_head addr_filters;
    struct perf_addr_filter_range *addr_filter_ranges;
    unsigned long             addr_filters_gen;

    /* --- 11. 其他 --- */
    struct list_head          sb_list;       // 侧带宽事件
    struct hlist_node         hlist_entry;   // PMU 的 owner 链表
    wait_queue_head_t         waitq;         // poll 等待队列
    struct fasync_struct      *fasync;       // 异步通知
    struct mutex              mmap_mutex;    // mmap 操作互斥锁
    /* ... */
};
  • struct hw_perf_event — 硬件状态展开:
 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
struct hw_perf_event {
    union {
        struct { /* 硬件 PMU 计数器 */
            u64               config;          // 事件编码
            unsigned long     config_base;     // 控制寄存器地址(如 PMXEVTYPER)
            unsigned long     event_base;      // 计数寄存器地址(如 PMEVCNTRn)
            int               event_base_rdpmc;// RDPMC 索引
            int               idx;             // 硬件计数器槽位索引
            int               last_cpu;
            int               flags;
            struct hw_perf_event_extra extra_reg, branch_reg;
        };
        struct { /* 软件事件 */
            struct hrtimer    hrtimer;         // 高精度定时器(采样周期)
        };
        struct { /* tracepoint 事件 */
            struct list_head  tp_list;
        };
    };
    struct task_struct        *target;         // per-task 指向目标 task
    int                       state;           // PERF_HES_STOPPED/UPTODATE/ARCH
    local64_t                 prev_count;      // 上一次读取的硬件计数值
    u64                       sample_period;   // 采样周期
    u64                       last_period;     // 本采样周期的起始值
    local64_t                 period_left;     // 周期剩余值
    u64                       interrupts_seq;  // 中断序列号(throttle 跟踪)
    u64                       interrupts;      // 中断计数
    u64                       freq_time_stamp; // 频率调整时间戳
    u64                       freq_count_stamp;// 频率调整计数值
};

perf_event 各大字段组详解

PMU 绑定 — pmu / count
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// kernel/events/core.c — perf_event_alloc() 
pmu = perf_init_event(event);  // 遍历已注册 PMU,调用 pmu->event_init()
// 返回后:event->pmu = pmu

// 读取计数值
static inline u64 perf_event_count(struct perf_event *event)
{
    return local64_read(&event->count) + atomic64_read(&event->child_count);
}
// child_count 用于 inherit 事件:子进程结束时将计数合并到父事件
状态机 — state
 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
// include/linux/perf_event.h — 状态枚举
enum perf_event_state {
    PERF_EVENT_STATE_DEAD     = -4,    // 已销毁
    PERF_EVENT_STATE_EXIT     = -3,    // 进程退出
    PERF_EVENT_STATE_ERROR    = -2,    // 硬件错误
    PERF_EVENT_STATE_OFF      = -1,    // 初始/关闭
    PERF_EVENT_STATE_INACTIVE =  0,    // 已安装但未调度到 PMU
    PERF_EVENT_STATE_ACTIVE   =  1,    // 正在 PMU 上运行
};

// 状态转换
static inline void perf_event_set_state(struct perf_event *event,
                                        enum perf_event_state state)
{
    event->state = state;
}

// 有效状态的复合判断
static inline enum perf_event_state
__perf_effective_state(struct perf_event *event)
{
    if (event->state == PERF_EVENT_STATE_OFF &&
        event->attr.enable_on_exec)
        return PERF_EVENT_STATE_INACTIVE;
    return event->state;
}
// 关键逻辑:OFF 但设置了 enable_on_exec 的 event
// 在 fork/exec 时会自动变成 INACTIVE
上下文关联 — cpu / oncpu / ctx
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 写入 oncpu(event_sched_in 的开头)
event->oncpu = smp_processor_id();   // 写入当前 CPU
smp_wmb();                           // 保证 oncpu 对其他 CPU 可见
event->state = PERF_EVENT_STATE_ACTIVE;

// 当跨 CPU 读取时( __perf_event_read_cpu)
static int __perf_event_read_cpu(struct perf_event *event, int event_cpu)
{
    if (event->oncpu == event_cpu)   // 事件是否还在这个 CPU 上
        return event_cpu;
    return smp_processor_id();       // 已迁移 → 在本 CPU 读取
}
事件关系 — parent / group_leader / sibling_list
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 分组(perf_group_attach)
static void perf_group_attach(struct perf_event *event)
{
    struct perf_event *group_leader = event->group_leader;

    if (group_leader == event)  // 单事件 = 自己的 leader
        return;

    if (group_leader->nr_siblings >= group_leader->attr.wakeup_events)
        return;                 // 触发了 watermark

    list_add_tail(&event->sibling_list, &group_leader->sibling_list);
    group_leader->nr_siblings++;
}

// 继承( — perf_event_alloc)
event->parent = parent_event;   // 父事件指针
// 子 event 共享 parent 的 ring buffer 和配置
// 子进程 exit 时,子 event 的计数合并到 parent->child_count
调度 — active_list / event_entry / group_node / group_index
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 加入上下文(add_event_to_ctx)
static void add_event_to_ctx(struct perf_event *event,
                              struct perf_event_context *ctx)
{
    list_add_event(event, ctx);              // → ctx->event_list
    perf_event_groups_insert(event, ctx);    // → RB-tree (pinned/flexible)
}

// RB-tree 插入( perf_event_groups_insert)
// 按 {event->cpu, event->group_index} 排序
// pinned_groups 中所有事件必须被容纳
// flexible_groups 中事件可轮转(multiplexing)

// 轮转(rotate_ctx)
static void rotate_ctx(struct perf_event_context *ctx,
                        struct perf_event *event)
{
    perf_event_groups_delete(&ctx->flexible_groups, event);
    event->group_index++;          // 增大排序键 → 下轮排到末尾
    perf_event_groups_insert(&ctx->flexible_groups, event);
    // 每次定时器中断调用 rotate_ctx,实现 round-robin
}
硬件状态 — hw

在 Arm64 上的具体用法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// armv8pmu_enable_event(arch/arm64/kernel/perf_event.c)
static void armv8pmu_enable_event(struct perf_event *event)
{
    struct hw_perf_event *hwc = &event->hw;
    // hwc->config = 事件编码(如 0x11 = CPU_CYCLES)
    // hwc->config_base = 写入 PMXEVTYPER_EL0 的值
    // hwc->idx = 分配的计数器槽位索引

    armv8pmu_write_evtype(idx, hwc->config);  // 编程事件类型
    armv8pmu_enable_counter(idx);              // 启用计数器
    armv8pmu_enable_intens(idx);               // 启用中断
}

// armv8pmu_read_counter
static inline u64 armv8pmu_read_counter(struct perf_event *event)
{
    struct hw_perf_event *hwc = &event->hw;
    int idx = hwc->idx;

    if (idx == ARMV8_IDX_CYCLE_COUNTER)
        return read_sysreg(pmccntr_el0);       // 读取周期计数器
    else
        return armv8pmu_read_evcntr(idx);      // 读取 PMEVCNTRn_EL0
}
Ring Buffer — rb
 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
// kernel/events/core.c — 获取 ring buffer
struct perf_buffer *ring_buffer_get(struct perf_event *event)
{
    struct perf_buffer *rb;

    rcu_read_lock();
    rb = rcu_dereference(event->rb);    // RCU 保护
    if (rb) {
        if (!refcount_inc_not_zero(&rb->refcount))
            rb = NULL;
    }
    rcu_read_unlock();
    return rb;
}

// 溢出时写 sample( __perf_event_output)
static void __perf_event_output(struct perf_event *event, ...)
{
    struct perf_output_handle handle;
    struct perf_sample_data data;

    perf_prepare_sample(&data, event, regs);     // 填充 IP/TID/TIME/...
    if (output_begin(&handle, &data, event, header.size))  // 预留 rb 空间
        return;

    perf_output_sample(&handle, &header, &data, event);    // 写入 ring buffer
    perf_output_end(&handle);                              // 提交 + 唤醒 poll
}
溢出处理 — overflow_handler / pending
 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
// 内核默认溢出回调( perf_event_output_forward)
void perf_event_output_forward(struct perf_event *event, ...)
{
    __perf_event_output(event, data, regs);  // 写入 ring buffer
}

// 延期关闭(perf_event_disable_inatomic)
void perf_event_disable_inatomic(struct perf_event *event)
{
    event->pending_disable = 1;              // 置标志
    irq_work_queue(&event->pending);         // 调度 irq_work
    // 在 NMI/IRQ 上下文中不能直接调用关闭函数
    // 通过 irq_work 延期到 softirq 执行
}

// irq_work 处理函数(perf_pending_event)
static void perf_pending_event(struct irq_work *entry)
{
    struct perf_event *event = container_of(entry,
                                struct perf_event, pending);

    if (event->pending_disable) {
        event->pending_disable = 0;
        perf_event_disable_local(event);      // 关闭事件
    }
    if (event->pending_wakeup) {
        event->pending_wakeup = 0;
        perf_event_wakeup(event);             // 唤醒 poll
    }
}
生命周期 — refcount / filp
 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
// 引用计数管理(put_event)
static void put_event(struct perf_event *event)
{
    if (!atomic_long_dec_and_test(&event->refcount))
        return;
    // refcount == 0 → 真正释放
    free_event(event);
}

// 释放路径(free_event)
static void free_event(struct perf_event *event)
{
    // 1. 关闭所有子事件
    perf_event_free_child(event);
    // 2. 关闭地址过滤
    perf_addr_filters_splice(event, NULL);
    // 3. 关闭 BPF 程序
    perf_event_free_bpf_prog(event);
    // 4. PMU 释放
    event->pmu->read(event);
    event->pmu->del(event, 0);
    // 5. 释放 ring buffer
    ring_buffer_put(event->rb);
    // 6. 释放 task 引用
    put_task_struct(event->hw.target);
    // 7. RCU 释放
    call_rcu(&event->rcu_head, free_event_rcu);
}

perf_event_open 系统调用流程

文件: kernel/events/core.c

调用流程图
  flowchart TD
    A["用户态: perf_event_open(attr, pid, cpu, group_fd, flags)"] --> B["perf_copy_attr() 复制用户attr"]
    B --> C["security_perf_event_open() LSM检查"]
    C --> D["perf_init_event() 查找匹配PMU"]
    D --> E{"attr.type 是什么?"}
    E -->|"PERF_TYPE_HARDWARE / HW_CACHE"| F["改写 type = PERF_TYPE_RAW"]
    E -->|"其他已知type"| G["idr_find(&pmu_idr, type) 查IDR"]
    E -->|"动态PMU (kprobe/uprobe)"| H["遍历pmus链表 match"]
    F --> G
    G --> I{"perf_try_init_event(pmu, event)"}
    H --> I
    I -->|"成功"| J["pmu->event_init(event)"]
    I -->|"ENOENT"| H
    J --> K["perf_event_alloc() 分配perf_event"]
    K --> L["find_get_context() 获取per-task或per-CPU ctx"]
    L --> M["perf_install_in_context() 将事件加入ctx"]
    M --> N["返回fd给用户态"]
PMU 匹配逻辑 (perf_init_event, core.c)
 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
static struct pmu *perf_init_event(struct perf_event *event)
{
    // ① 先尝试父事件的 PMU(用于继承场景)
    if (event->parent && event->parent->pmu) {
        pmu = event->parent->pmu;
        ret = perf_try_init_event(pmu, event);
        if (!ret) goto unlock;
    }

    // ② HARDWARE/HW_CACHE 改写为 RAW
    type = event->attr.type;
    if (type == PERF_TYPE_HARDWARE || type == PERF_TYPE_HW_CACHE)
        type = PERF_TYPE_RAW;

    // ③ IDR 快速查找(固定 type)
    pmu = idr_find(&pmu_idr, type);
    if (pmu) {
        ret = perf_try_init_event(pmu, event);  // 调用 pmu->event_init
        // 若 -ENOENT 且 type 被改写了,用原始 type 重试
    }

    // ④ 遍历 PMU 链表(动态 PMU:kprobe/uprobe/raw 找不到时)
    list_for_each_entry_rcu(pmu, &pmus, entry) {
        ret = perf_try_init_event(pmu, event);
        if (!ret) goto unlock;
    }
}

perf stat — 计数模式

计数原理

perf stat 不采样(不设 sample_period),只关心事件的累计计数。关键路径:

1
2
3
4
5
6
7
perf_event_open() → event被加到ctx → pmu->add() 将事件分配到硬件计数器
                    → pmu->start() 启动计数

用户态 read(fd)  →  perf_read()  →  __perf_event_read()
                    →  IPI到目标CPU →  __perf_event_read() (在目标CPU上执行)
                    →  pmu->read(event)  读取硬件计数器
                    →  perf_event_count()  返回 event->count
读取路径核心代码
 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
// kernel/events/core.c
static void __perf_event_read(void *info)
{
    // 在目标 CPU 上运行
    // ① 更新上下文时间(用于 multiplexing 缩放)
    perf_event_update_time(event);

    // ② 调用 PMU 驱动的 read 方法
    //    ARM64: armpmu_read() → armpmu_event_update()
    //    计算 delta = 当前硬件计数器 - prev_count
    //    累加到 event->count
    pmu->read(event);
}

// kernel/events/core.c
static u64 __perf_event_read_value(struct perf_event *event, ...)
{
    (void)perf_event_read(event, false);
    total += perf_event_count(event);     // local64_read(&event->count)

    // 累加子事件的计数(inherit)
    *enabled += event->total_time_enabled;
    *running += event->total_time_running;
    // 多路复用缩放: scaled = count * enabled / running
}
ARM64 上的计数器读取
1
2
3
4
5
6
// arch/arm64/kernel/perf_event.c
static u64 armv8pmu_read_counter(struct perf_event *event)
{
    // 通过 PMEVCNTR<n>_EL0 或 PMCCNTR_EL0(cycle counter)读取
    // 若为 32-bit 计数器且需要 64-bit,则读取相邻的 chain 计数器
}

perf record — 采样模式

采样完整路径
  flowchart TD
    A["硬件计数器溢出 (PMU overflow)"] --> B["armv8pmu_handle_irq()"]
    B --> C["armv8pmu_getreset_flags()<br/>读取 PMOVSR 获取溢出位"]
    C --> D{"有溢出?"}
    D -->|"否"| E["return IRQ_NONE"]
    D -->|"是"| F["armv8pmu_stop()<br/>停止PMU防止组内偏差"]
    F --> G["遍历每个计数器"]
    G --> H{"该计数器溢出?"}
    H -->|"是"| I["armpmu_event_update()<br/>读取硬件计数器,计算delta"]
    I --> J["armpmu_event_set_period()<br/>重新设定采样周期"]
    J --> K["perf_event_overflow(event, data, regs)"]
    K --> L["__perf_event_overflow()"]
    L --> M["节流检查<br/>__perf_event_account_interrupt()"]
    M --> N["event->overflow_handler()"]
    N --> O["perf_event_output_forward()"]
    O --> P["perf_output_begin()<br/>在ring buffer中分配空间"]
    P --> Q["perf_output_sample()<br/>写入IP/TID/TIME/CALLCHAIN等"]
    Q --> R["perf_output_end()<br/>更新user_page->data_head"]
    R --> S["armv8pmu_start() 重启PMU"]
ARM64 中断处理 (arch/arm64/kernel/perf_event.c:749)
 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
31
32
33
34
35
36
37
38
39
40
static irqreturn_t armv8pmu_handle_irq(struct arm_pmu *cpu_pmu)
{
    u32 pmovsr;
    struct pmu_hw_events *cpuc = this_cpu_ptr(cpu_pmu->hw_events);

    // ① 读取并清除溢出状态寄存器 (PMOVSR)
    pmovsr = armv8pmu_getreset_flags();

    // ② 无溢出 → IRQ_NONE
    if (!armv8pmu_has_overflowed(pmovsr))
        return IRQ_NONE;

    regs = get_irq_regs();

    // ③ 停止 PMU(清 PMCR.E),防止组内计数器偏差
    armv8pmu_stop(cpu_pmu);

    // ④ 遍历所有计数器
    for (idx = 0; idx < cpu_pmu->num_events; ++idx) {
        struct perf_event *event = cpuc->events[idx];

        if (!event) continue;
        if (!armv8pmu_counter_has_overflowed(pmovsr, idx)) continue;

        hwc = &event->hw;
        armpmu_event_update(event);                     // 读计数器, 更新event->count
        perf_sample_data_init(&data, 0, hwc->last_period);

        if (!armpmu_event_set_period(event))            // 重设采样周期
            continue;

        // ⑤ 调用 perf 核心溢出处理 → 写入 ring buffer
        if (perf_event_overflow(event, &data, regs))
            cpu_pmu->disable(event);                    // 节流:关闭事件
    }

    // ⑥ 重新启动 PMU
    armv8pmu_start(cpu_pmu);
    return IRQ_HANDLED;
}
溢出处理 → Ring Buffer 写入
 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
// core.c
static int __perf_event_overflow(struct perf_event *event,
                   int throttle, struct perf_sample_data *data,
                   struct pt_regs *regs)
{
    // 非采样事件不处理
    if (unlikely(!is_sampling_event(event)))
        return 0;

    // 节流:如果中断速率过高,禁用事件
    ret = __perf_event_account_interrupt(event, throttle);

    event->pending_kill = POLL_IN;
    if (events && atomic_dec_and_test(&event->event_limit)) {
        ret = 1;
        event->pending_kill = POLL_HUP;
        perf_event_disable_inatomic(event);    // 强制关闭
    }

    // 调用 overflow_handler → 默认 perf_event_output_forward()
    READ_ONCE(event->overflow_handler)(event, data, regs);

    // 如果有 fasync 等待者,排队 irq_work 唤醒
    if (*perf_event_fasync(event) && event->pending_kill) {
        event->pending_wakeup = 1;
        irq_work_queue(&event->pending);
    }
    return ret;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ring_buffer.c — Ring Buffer 空间分配
static int __perf_output_begin(struct perf_output_handle *handle, ...)
{
    rb = rcu_dereference(event->rb);

    // 计算 tail(用户态读位置) vs head(内核写位置)
    do {
        tail = READ_ONCE(rb->user_page->data_tail);
        head = local_read(&rb->head);

        if (!rb->overwrite) {
            // 空间不足 → 记录 lost,返回 -ENOSPC
            if (!ring_buffer_has_space(head, tail, ...))
                goto fail;
        }

        // CAS 原子前进 head
    } while (local_cmpxchg(&rb->head, offset, head) != offset);

    // 计算写入地址
    handle->addr = rb->data_pages[handle->page] + offset;
    handle->size = (1UL << page_shift) - offset;
}

perf event 总览

所有事件类型及 PMU 对照表
类型 perf_type_id 对应 PMU event_init 触发机制 典型用法
硬件事件 PERF_TYPE_HARDWARE (0) arm_pmu.pmu (ARM64) armpmu_event_init() 硬件计数器溢出中断 (PMU IRQ) perf stat -e cycles, perf record -e cache-misses
软件事件 PERF_TYPE_SOFTWARE (1) perf_swevent perf_swevent_init() 内核代码路径主动调用 perf_sw_event() perf stat -e page-faults,context-switches
Tracepoint PERF_TYPE_TRACEPOINT (2) perf_tracepoint perf_tp_event_init() 静态埋点 trace_xxx() 宏展开 perf record -e sched:sched_switch
HW Cache PERF_TYPE_HW_CACHE (3) 改写为 RAW 同硬件 PMU 同硬件事件 perf stat -e L1-dcache-loads
Raw PERF_TYPE_RAW (4) 遍历 PMU 链表 各 PMU 自行处理 取决于匹配到的 PMU perf stat -e r11 (ARM64 raw event)
Breakpoint PERF_TYPE_BREAKPOINT (5) perf_breakpoint hw_breakpoint_event_init() 调试异常 (debug exception) perf record -e mem:0xaddr
Kprobe 动态分配 perf_kprobe perf_kprobe_event_init() kprobe 断点 → kprobe_dispatcher() perf probe --add func
Uprobe 动态分配 perf_uprobe perf_uprobe_event_init() uprobe 断点 → uprobe_dispatcher() perf probe -x /bin/bash func
cpu-clock 动态分配 perf_cpu_clock cpu_clock_event_init() hrtimer 周期触发 perf record -e cpu-clock
task-clock 动态分配 perf_task_clock task_clock_event_init() hrtimer 周期触发 perf record -e task-clock
软件事件触发点
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// core.c — 所有软件类事件最终汇聚此函数
static void perf_swevent_event(struct perf_event *event, u64 nr,
                               struct perf_sample_data *data,
                               struct pt_regs *regs)
{
    local64_add(nr, &event->count);   // ① 累加计数

    if (!regs) return;
    if (!is_sampling_event(event))    // ② 非采样事件只计数
        return;

    // ③ 采样模式:检查采样周期
    if (nr == 1 && hwc->sample_period == 1 && !event->attr.freq)
        return perf_swevent_overflow(event, 1, data, regs);

    if (local64_add_negative(nr, &hwc->period_left))
        return;

    perf_swevent_overflow(event, 0, data, regs);  // ④ 溢出→写入ring buffer
}
Tracepoint 事件流转
  flowchart TD
    A["内核代码: trace_sched_switch(prev, next)"] --> B["tracepoint 宏展开"]
    B --> C["RCU遍历 tp_funcs 链表"]
    C --> D["perf_trace_##name() 即 class->perf_probe"]
    D --> E["perf_trace_buf_alloc()<br/>分配临时缓冲区填数据"]
    E --> F["perf_trace_run_bpf_submit()"]
    F --> G{"有BPF程序?"}
    G -->|"是"| H["trace_call_bpf() 执行BPF"]
    G -->|"否/BPF通过"| I["perf_tp_event()<br/>遍历per-CPU hlist"]
    I --> J["perf_swevent_event()<br/>累加计数 + overflow检查"]
    J --> K["perf_swevent_overflow()<br/>→ overflow_handler → ring buffer"]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// core.c
void perf_trace_run_bpf_submit(void *raw_data, int size, int rctx,
                               struct trace_event_call *call, u64 count,
                               struct pt_regs *regs, struct hlist_head *head,
                               struct task_struct *task)
{
    if (bpf_prog_array_valid(call)) {
        *(struct pt_regs **)raw_data = regs;
        if (!trace_call_bpf(call, raw_data) || hlist_empty(head)) {
            perf_swevent_put_recursion_context(rctx);
            return;                          // BPF 过滤掉了该事件
        }
    }
    perf_tp_event(call->event.type, count, raw_data, size, regs, head,
                  rctx, task);
}
Kprobe/Uprobe 动态探针
  flowchart TD
    subgraph 注册阶段
        A["perf_event_open(type=kprobe, config=func_name)"] --> B["perf_kprobe_event_init()"]
        B --> C["perf_kprobe_init() → create_local_trace_kprobe()"]
        C --> D["register_kprobe() 在目标函数入口安装断点"]
    end
    subgraph 触发阶段
        E["目标函数被调用"] --> F["kprobe 断点触发"]
        F --> G["kprobe_dispatcher()"]
        G --> H["kprobe_perf_func()"]
        H --> I["perf_trace_buf_submit()"]
        I --> J["perf_trace_run_bpf_submit()"]
        J --> K["perf_tp_event() → perf_swevent_event() → ring buffer"]
    end

Kprobe/Uprobe 的 PMU 结构(core.c):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static struct pmu perf_kprobe = {
    .task_ctx_nr   = perf_sw_context,
    .event_init    = perf_kprobe_event_init,   // 创建 trace_kprobe
    .add           = perf_trace_add,           // 加入 per-CPU hlist
    .del           = perf_trace_del,
    .start         = perf_swevent_start,
    .stop          = perf_swevent_stop,
    .read          = perf_swevent_read,
    .attr_groups   = kprobe_attr_groups,
};
tracepoint PMU (核心)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// core.
static struct pmu perf_tracepoint = {
    .task_ctx_nr   = perf_sw_context,

    .event_init    = perf_tp_event_init,   // 匹配 trace_event_call
    .add           = perf_trace_add,       // 加入 per-CPU hlist
    .del           = perf_trace_del,
    .start         = perf_swevent_start,
    .stop          = perf_swevent_stop,
    .read          = perf_swevent_read,
};
perf_tp_register() 注册
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// core.c
static inline void perf_tp_register(void)
{
    perf_pmu_register(&perf_tracepoint, "tracepoint", PERF_TYPE_TRACEPOINT);
#ifdef CONFIG_KPROBE_EVENTS
    perf_pmu_register(&perf_kprobe, "kprobe", -1);
#endif
#ifdef CONFIG_UPROBE_EVENTS
    perf_pmu_register(&perf_uprobe, "uprobe", -1);
#endif
}

事件调度与 Multiplexing

Context 调度

每个 perf_event_context 维护两组红黑树:

说明
pinned_groups 钉住组:必须在 PMU 上始终运行,失败则 events 返回 0
flexible_groups 弹性组:硬件计数器不够时,按时间片轮转 (round-robin multiplexing)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// perf_event_context 结构(概念)
struct perf_event_context {
    struct pmu *pmu;
    struct rb_root pinned_groups;      // 红黑树: 固定事件组
    struct rb_root flexible_groups;    // 红黑树: 弹性事件组
    struct list_head event_list;       // 所有事件链表
    struct list_head pinned_active;    // 正在硬件上跑的固定组
    struct list_head flexible_active;  // 正在硬件上跑的弹性组
    u64 time;                          // 上下文累计使能时间
    u64 timestamp;                     // 上次更新时间戳
};
Multiplexing 缩放公式

当硬件计数器不够时,弹性组事件被轮转调度。用户态需要用时间比例缩放读数:

1
scaled_count = raw_count × (time_enabled / time_running)
调度流程
  flowchart TD
    A["需要调度 ctx (add/del/enable/disable/sched_in)"] --> B["ctx_sched_out() 切出旧ctx"]
    B --> C["遍历pinned_active: pmu->del() 从硬件移除"]
    C --> D["遍历flexible_active: pmu->del() 从硬件移除"]
    D --> E["ctx_sched_in() 切入新ctx"]
    E --> F["遍历pinned_groups: pmu->add() 尝试分配硬件计数器"]
    F --> G{"pinned 全部分配成功?"}
    G -->|"否"| H["pinned事件返回0计数"]
    G -->|"是"| I["遍历flexible_groups: pmu->add() 剩余计数器"]
    I --> J{"有事件未分配?"}
    J -->|"是"| K["按时间片轮转 (multiplexing)<br/>记录time_enabled/time_running"]
    J -->|"否"| L["所有事件在硬件上运行"]

数据流总结图

  flowchart TD
    subgraph 计数模式
        C1["pmu->start()"] --> C2["硬件计数器累加"]
        C2 --> C3["read(): pmu->read()"]
        C3 --> C4["armpmu_event_update()<br/>delta = curr - prev_count"]
        C4 --> C5["local64_add(delta, &event->count)"]
        C5 --> C6["返回给用户态"]
    end

    subgraph 采样模式
        S1["硬件计数器溢出"] --> S2["PMU IRQ"]
        S2 --> S3["armv8pmu_handle_irq()"]
        S3 --> S4["perf_event_overflow()"]
        S4 --> S5["overflow_handler()"]
        S5 --> S6["perf_output_begin()<br/>CAS 争夺 ring buffer 空间"]
        S6 --> S7["perf_output_sample()<br/>写入 PERF_RECORD_SAMPLE"]
        S7 --> S8["perf_output_end()<br/>更新 data_head 唤醒 poll"]
    end

模式对比表

维度 perf stat (计数) perf record (采样) Tracepoint Kprobe/Uprobe
事件来源 硬件PMC或软件计数器 硬件PMC溢出或hrtimer 内核静态埋点 动态断点
采样? 可计数/可采样 可计数/可采样
触发机制 read() 读取累计值 溢出中断 → overflow_handler tracepoint回调 kprobe/uprobe回调
Ring Buffer 不用 (或只用于metadata) 用 (写入PERF_RECORD_SAMPLE) 可选用 可选用
开销 极低 (一次read) 中等 (每次溢出) 低 (tracepoint原生) 高 (断点陷入)
Overhead类型 rdpmc/read syscall PMU中断+ring buffer写入 函数调用+遍历hlist int3/uprobe异常
ARM64寄存器 PMEVCNTR, PMCCNTR PMOVSR→溢出IRQ — (软件路径) — (软件路径)
PMU type HARDWARE/SOFTWARE HARDWARE/SOFTWARE TRACEPOINT 动态分配
初始化路径 perf_event_init 注册PMU 同左 perf_tp_register() perf_tp_register()

关键文件索引

文件 内容
init/main.c:980 perf_event_init() 调用点
kernel/events/core.c:13258 perf_event_init() 定义
kernel/events/core.c:11902 perf_event_open() syscall
kernel/events/core.c:11210 perf_init_event() PMU匹配
kernel/events/core.c:9204 perf_swevent_event() 软件事件汇聚
kernel/events/core.c:9087 __perf_event_overflow() 溢出处理
kernel/events/core.c:9610 perf_trace_run_bpf_submit() tracepoint→BPF→perf
kernel/events/core.c:9709 perf_tracepoint PMU 定义
kernel/events/core.c:9761 perf_kprobe PMU 定义
kernel/events/core.c:9820 perf_uprobe PMU 定义
kernel/events/core.c:9861 perf_tp_register()
kernel/events/core.c:4354 __perf_event_read() 计数器读取
kernel/events/core.c:5227 __perf_event_read_value() 读值(含继承)
kernel/events/ring_buffer.c:149 __perf_output_begin() ring buffer 空间分配
kernel/events/internal.h:13 perf_buffer 结构体定义
arch/arm64/kernel/perf_event.c:749 armv8pmu_handle_irq() ARM64 PMU中断处理
arch/arm64/kernel/perf_event.c:1021 __armv8pmu_probe_pmu() ARM64 PMU探测
arch/arm64/kernel/perf_event.c:1253 armv8_pmu_driver_init() ARM64 PMU驱动入口
drivers/perf/arm_pmu.c ARM PMU 通用框架(alloc/register/irq)
include/uapi/linux/perf_event.h:320 perf_event_attr UAPI 定义
tools/include/uapi/linux/perf_event.h:320 UAPI 副本

perf命令

子命令 用途 示例
perf stat 事件计数统计 perf stat -e cycles,instructions ./myapp
perf record 采样记录 + perf.data perf record -F 99 -g ./myapp
perf report 分析 perf.data perf report -g graph
perf top 实时热点 perf top -e cache-misses
perf trace 系统调用追踪 perf trace ls
perf sched 调度器分析 perf sched latency
perf annotate 源码/汇编标注 perf annotate hot_func
perf c2c 缓存一致性分析 perf c2c record ./myapp
perf kmem slab 分配器追踪 perf kmem stat
perf script 原始转储 perf script > out.perf
perf diff perf.data 差分 perf diff perf.data.old perf.data.new

perf 的优势与局限

优势:

  • 硬件计数器直读——利用 PMU 零开销统计 CPU 周期、cache miss、分支预测等
  • 低采样开销——硬件中断驱动,支持 precise_ip 精确采样
  • 支持双端(用户态 + 内核态)——同时采样用户态和内核态调用链
  • 内核子系统架构清晰——struct perf_event 为中心,PMU 通过 struct pmu 回调抽象,架构扩展性强
  • 生产环境友好——低 overhead,可在负载下运行
  • 上下文切换深度集成——perf 事件调度与 CPU 调度器天然协同

局限:

  • PMU 计数器有限——Arm64 通常只有 4-7 个通用计数器和 1 个周期计数器
  • 硬件依赖——某些事件需要架构支持,不同 SoC 实现差异大
  • 采样精度 skid——即使 precise_ip=3,PMU 溢出到记录 PC 之间仍有延迟(SPE 可弥补)
  • 学习曲线——perf_event_attr 配置复杂,子命令繁多

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计