上一篇【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 合入主线。它既是一个计数工具(“发生了多少次”),也是一个采样工具(“在哪里发生的”)。


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源码解析
所有 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
|
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_list、pmu_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_clock、perf_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 等 |
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 配置复杂,子命令繁多