Linux 系统工具功能与底层实现
目录
1. 进程与线程调试工具
1.1 gdb
功能:GNU 调试器,支持断点、单步、变量查看、内存检查、远程调试等。
底层实现:
gdb 的核心机制是 ptrace 系统调用:
gdb 启动调试目标:
fork() → 子进程调用 ptrace(PTRACE_TRACEME) → exec() 加载目标程序
父进程(gdb)通过 ptrace 控制子进程
gdb 附加到已有进程:
ptrace(PTRACE_ATTACH, pid) → 目标进程收到 SIGSTOP 暂停
关键 ptrace 操作:
┌──────────────────────────┬───────────────────────────────────────┐
│ ptrace 请求 │ 作用 │
├──────────────────────────┼───────────────────────────────────────┤
│ PTRACE_TRACEME │ 标记自己被追踪(子进程调用) │
│ PTRACE_ATTACH │ 附加到指定进程 │
│ PTRACE_CONT │ 让被追踪进程继续执行 │
│ PTRACE_SINGLESTEP │ 单步执行一条指令 │
│ PTRACE_PEEKTEXT/DATA │ 读取目标进程内存 │
│ PTRACE_POKETEXT/DATA │ 写入目标进程内存 │
│ PTRACE_GETREGS │ 读取寄存器 │
│ PTRACE_SETREGS │ 写入寄存器 │
│ PTRACE_SET_SYSCALL │ 修改系统调用号 │
└──────────────────────────┴───────────────────────────────────────┘
断点实现:
软件断点:
1. gdb 读取目标地址的原始指令字节,保存到内部表
2. 将目标地址的第一个字节替换为 INT 3(0xCC)
3. 进程执行到 INT 3 → 触发 SIGTRAP → gdb 收到信号
4. gdb 恢复原始指令字节,回退 PC,等待用户命令
硬件断点:
1. 使用 CPU 的调试寄存器(DR0-DR3 存地址,DR7 存控制位)
2. CPU 执行到对应地址时自动触发调试异常
3. 最多 4 个硬件断点(DR0-DR3)
4. 优势:不修改代码段,可用于 ROM/只读内存
单步实现:
指令级单步:
设置 EFLAGS 的 TF(Trap Flag)位 → CPU 每执行一条指令后触发调试异常
源码级单步:
gdb 内部计算下一行源码对应的地址,在该地址设临时断点,然后 CONT
如果下一行有函数调用,step-into 会在函数入口设断点,step-over 会在返回点设断点
1.2 gcore
功能:在不停止进程的情况下生成 core dump 文件。
底层实现:
gcore 的执行流程:
1. ptrace(PTRACE_ATTACH, pid)
→ 目标进程暂停(发送 SIGSTOP)
2. 读取进程状态
→ /proc/pid/maps → 内存映射布局
→ /proc/pid/stat → 进程状态信息
→ ptrace(PTRACE_GETREGS) → 寄存器
3. 生成 ELF core 文件
→ 写入 ELF Header(类型 ET_CORE)
→ 写入 Program Headers(每个内存段一个 LOAD 段)
→ 遍历 /proc/pid/maps 中的每个内存区域
→ 通过 ptrace(PTRACE_PEEKDATA) 或 /proc/pid/mem 读取内存内容
→ 写入 Note 段(寄存器、信号、文件描述符等辅助信息)
4. ptrace(PTRACE_DETACH, pid)
→ 目标进程恢复执行
ELF Core 文件结构:
┌──────────────────────────────┐
│ ELF Header (ET_CORE) │
├──────────────────────────────┤
│ Program Headers │
│ ├─ NOTE segment (prstatus) │ ← 寄存器、信号信息
│ ├─ NOTE segment (prpsinfo) │ ← 进程信息
│ ├─ NOTE segment (auxv) │ ← 辅助向量
│ ├─ LOAD segment (代码段) │ ← 对应 /proc/pid/maps 中的可读段
│ ├─ LOAD segment (数据段) │
│ ├─ LOAD segment (堆) │
│ ├─ LOAD segment (栈) │
│ └─ ... │
├──────────────────────────────┤
│ Note Data │
├──────────────────────────────┤
│ Memory Segments Data │ ← 实际内存内容
└──────────────────────────────┘
与 gdb 配合:
# 生成 core dump
gcore <pid>
# 事后分析
gdb ./executable core.<pid>
1.3 pstack
功能:打印进程的线程调用栈。
底层实现:
pstack 的执行流程:
1. ptrace(PTRACE_ATTACH, pid) → 暂停目标进程
2. 读取 /proc/pid/task/ 获取所有线程 tid
3. 对每个线程:
a. ptrace(PTRACE_GETREGS) → 获取 PC、SP、FP 等寄存器
b. 从 FP 开始,沿栈帧链回溯:
当前 FP → [上一级 FP, 返回地址]
读取下一级 FP → [再上一级 FP, 返回地址]
... 直到 FP 为 0 或无效
c. 查找 ELF 的 .eh_frame / .debug_frame 段获取 DWARF unwind 信息
(如果可用,DWARF 信息比 FP 链更准确)
d. 通过返回地址查 ELF 符号表(.symtab / .dynsym)得到函数名
4. ptrace(PTRACE_DETACH, pid) → 恢复目标进程
栈回溯的两种方式:
方式一:帧指针链(Frame Pointer Chaining)
栈布局(x86-64):
┌──────────────┐ ← 高地址
│ ... │
│ arg n │
│ arg 1 │
│ return addr │ ← 调用者返回地址
│ saved RBP │ ← 指向调用者的栈帧
├──────────────┤ ← 当前 RBP
│ local var 1 │
│ local var 2 │
├──────────────┤ ← 当前 RSP
│ ... │
└──────────────┘ ← 低地址
回溯:RBP → [saved_RBP, ret_addr] → saved_RBP → [saved_RBP', ret_addr'] → ...
方式二:DWARF Unwind Info(-fomit-frame-pointer 编译时)
.eh_frame 段记录了每个 PC 范围内的栈回溯规则:
→ 如何恢复上一级的 CFA(Canonical Frame Address)
→ 各个寄存器保存在栈的哪个偏移
→ 基于当前 PC 查表,按规则计算上一级栈帧位置
1.4 strace
功能:追踪进程的系统调用和信号。
底层实现:
strace 的核心机制:ptrace + PTRACE_SYSCALL
执行流程:
1. ptrace(PTRACE_ATTACH, pid) 或 fork + PTRACE_TRACEME
2. ptrace(PTRACE_SYSCALL, pid) → 让进程继续运行直到下一个系统调用
3. 进程进入内核(syscall entry)→ 触发 SIGTRAP → strace 被通知
4. strace 读取寄存器获取系统调用号和参数
→ x86-64: RAX=系统调用号, RDI/RSI/RDX/R10/R8/R9=参数
5. ptrace(PTRACE_SYSCALL, pid) → 继续运行
6. 进程从内核返回(syscall exit)→ 再次触发 SIGTRAP
7. strace 读取 RAX 获取返回值
8. 重复 2-7
每个系统调用被拦截两次:entry 和 exit
开销来源:
每次系统调用:
1. 进程暂停(SIGTRAP)
2. 上下文切换到 strace
3. strace 通过 ptrace 读取寄存器
4. 上下文切换回目标进程
开销:每个系统调用增加约 5-20μs
对频繁系统调用的程序,strace 可导致 10-100x 性能下降
新内核的替代方案:
seccomp-bpf 过滤:
→ 不需要 ptrace,在内核中用 BPF 程序过滤系统调用
→ 开销极低,但只能过滤不能读取参数
perf trace:
→ 基于 tracepoint 而非 ptrace
→ 开销更低,但信息不如 strace 完整
2. 系统级监控工具
2.1 top
功能:实时显示系统中各进程的 CPU、内存使用情况。
底层实现:
top 的数据来源全部来自 /proc 文件系统:
┌─────────────────────┬──────────────────────────────────────────┐
│ 数据 │ 来源 │
├─────────────────────┼──────────────────────────────────────────┤
│ CPU 整体使用率 │ /proc/stat │
│ │ user, nice, system, idle, iowait, │
│ │ irq, softirq, steal, guest │
├─────────────────────┼──────────────────────────────────────────┤
│ 内存使用 │ /proc/meminfo │
│ │ MemTotal, MemFree, Buffers, Cached, │
│ │ Slab, SReclaimable │
├─────────────────────┼──────────────────────────────────────────┤
│ 交换分区 │ /proc/meminfo │
│ │ SwapTotal, SwapFree │
├─────────────────────┼──────────────────────────────────────────┤
│ 进程信息 │ /proc/[pid]/stat │
│ │ pid, state, utime, stime, rss, ... │
├─────────────────────┼──────────────────────────────────────────┤
│ 进程命令行 │ /proc/[pid]/cmdline │
├─────────────────────┼──────────────────────────────────────────┤
│ 进程内存映射 │ /proc/[pid]/statm │
└─────────────────────┴──────────────────────────────────────────┘
CPU 使用率计算:
/proc/stat 格式:
cpu user nice system idle iowait irq softirq steal guest
两次采样间:
d_user = user[t2] - user[t1]
d_nice = nice[t2] - nice[t1]
d_system = system[t2] - system[t1]
d_idle = idle[t2] - idle[t1]
d_total = d_user + d_nice + d_system + d_idle + d_iowait + ...
CPU% = (d_total - d_idle) / d_total * 100
进程 CPU%:
/proc/[pid]/stat 中的 utime + stime(单位:clock ticks)
进程CPU% = (d_utime + d_stime) / (d_total / ncpu) / interval
进程状态:
/proc/[pid]/stat 第 3 个字段:
R Running → 正在运行或就绪等待 CPU
S Sleeping → 可中断睡眠(等待事件)
D Disk Sleep → 不可中断睡眠(通常是 I/O 等待)
Z Zombie → 已退出但父进程未 wait
T Stopped → 被信号停止
t Tracing Stop → 被追踪器停止
X Dead → 已死亡(不应看到)
2.2 htop
功能:top 的增强版,支持鼠标、树状视图、快捷杀进程等。
底层实现:
与 top 相同的数据来源(/proc),差异在于:
1. 使用 ncurses 库实现交互式 TUI
2. 读取 /proc/[pid]/io 获取 I/O 信息(top 不显示)
3. 读取 /proc/[pid]/fd 统计打开的文件描述符数
4. 通过 libc 的 getpwuid() / getgrgid() 解析用户名/组名
5. 杀进程:kill(pid, signal) 系统调用
6. CPU 栏的彩色分段:
→ 蓝色 = low-priority (nice > 0)
→ 绿色 = normal (user)
→ 红色 = kernel (system)
→ 青色 = steal (虚拟化环境被其他 VM 偷走的时间)
→ 品红 = IRQ / soft IRQ
2.3 uptime
功能:显示系统运行时间、登录用户数、1/5/15 分钟平均负载。
底层实现:
数据来源:
运行时间:/proc/uptime
第一个字段:系统启动后的秒数(含小数)
第二个字段:系统空闲的累计秒数
平均负载:/proc/loadavg
前三个数字:1分钟、5分钟、15分钟平均负载
第四个数字:当前运行进程数 / 总进程数
第五个数字:最近创建的进程 PID
平均负载的含义:
load average = 运行队列中的进程数 + 不可中断睡眠(D状态)的进程数
→ 包含 CPU 等待 + I/O 等待
→ 单核:1.0 表示满载;4核:4.0 表示满载
→ 计算方式:指数衰减移动平均
load1 = load1 * exp(-1/60) + n * (1 - exp(-1/60))
load5 = load5 * exp(-1/300) + n * (1 - exp(-1/300))
load15 = load15 * exp(-1/900) + n * (1 - exp(-1/900))
其中 n = 当前运行队列 + D状态进程数
3. 进程级监控工具
3.1 pidstat
功能:按进程显示 CPU、内存、I/O、线程、上下文切换等统计。
底层实现:
pidstat 是 sysstat 套件的一部分,数据来源:
┌──────────────────┬──────────────────────────────────────────────┐
│ 选项 │ 数据来源 │
├──────────────────┼──────────────────────────────────────────────┤
│ 默认(CPU统计) │ /proc/[pid]/stat │
│ │ utime, stime, cutime, cstime, processor │
├──────────────────┼──────────────────────────────────────────────┤
│ -r(内存统计) │ /proc/[pid]/statm + /proc/[pid]/status │
│ │ VSS, RSS, %MEM, minflt, majflt │
├──────────────────┼──────────────────────────────────────────────┤
│ -d(I/O统计) │ /proc/[pid]/io │
│ │ read_bytes, write_bytes, cancelled_write_bytes│
├──────────────────┼──────────────────────────────────────────────┤
│ -w(上下文切换) │ /proc/[pid]/status │
│ │ voluntary_ctxt_switches, nonvoluntary_ctxt... │
├──────────────────┼──────────────────────────────────────────────┤
│ -t(线程统计) │ /proc/[pid]/task/[tid]/stat │
└──────────────────┴──────────────────────────────────────────────┘
/proc/[pid]/io 的内核实现:
内核为每个 task_struct 维护 I/O 计数器:
struct task_struct {
...
u64 acct_rss_mem1; // RSS 增量
u64 acct_vm_mem1; // 虚拟内存增量
u64 acct_timexpd; // I/O 等待时间
...
};
struct io_accounting {
u64 rchar; // 读字节数(含缓存命中)
u64 wchar; // 写字节数(含缓存命中)
u64 syscr; // 读系统调用次数
u64 syscw; // 写系统调用次数
u64 read_bytes; // 实际从块设备读取的字节数
u64 write_bytes; // 实际写入块设备的字节数
u64 cancelled_write_bytes; // 取消写入的字节数
};
read_bytes 在以下时机递增:
→ submit_bio() 提交读请求时
→ 仅统计实际到达块设备的 I/O,不包含页缓存命中
write_bytes 在以下时机递增:
→ 页面被标记为脏页时(mark_buffer_dirty())
→ 或直接 I/O 提交时
3.2 mpstat
功能:按 CPU 核心显示统计信息。
底层实现:
数据来源:/proc/stat
逐行解析:
cpu : user nice system idle iowait irq softirq steal guest guest_nice
cpu0 : ...
cpu1 : ...
cpuN : ...
mpstat 读取两次 /proc/stat,计算差值得到各核的使用率
关键指标计算:
%usr = d_user / d_total * 100
%sys = d_system / d_total * 100
%iowait = d_iowait / d_total * 100
%irq = d_irq / d_total * 100
%soft = d_softirq / d_total * 100
%steal = d_steal / d_total * 100
%idle = d_idle / d_total * 100
内核更新 /proc/stat 的时机:
每次 tick(通常 1ms,CONFIG_HZ=1000)中断时
account_process_tick() → 更新当前进程的 utime/stime
account_idle_ticks() → 更新全局 idle 计数
4. I/O 与存储工具
4.1 iostat
功能:显示 CPU 使用率和磁盘 I/O 统计。
底层实现:
CPU 统计:/proc/stat(同 top/mpstat)
磁盘 I/O 统计:/proc/diskstats 或 /sys/block/<dev>/stat
/proc/diskstats 格式(每个设备一行):
Field 1: 主设备号
Field 2: 次设备号
Field 3: 设备名
Field 4: 读完成次数
Field 5: 读合并次数
Field 6: 读扇区数
Field 7: 读花费时间(ms)
Field 8: 写完成次数
Field 9: 写合并次数
Field 10: 写扇区数
Field 11: 写花费时间(ms)
Field 12: 正在进行的 I/O 数
Field 13: I/O 总花费时间(ms)
Field 14: 加权 I/O 时间(ms)
关键指标计算:
两次采样间:
tps = (d_reads + d_writes) / interval
每秒传输次数(含合并后的)
kB_read/s = d_sectors_read * 512 / 1024 / interval
kB_wrtn/s = d_sectors_written * 512 / 1024 / interval
await = (d_read_ms + d_write_ms) / (d_reads + d_writes)
平均 I/O 响应时间(ms),含排队时间
svctm = d_io_time / (d_reads + d_writes)
平均服务时间(ms),不含排队
%util = d_io_time / (interval * 1000) * 100
设备利用率(>80% 通常表示饱和)
aqu-sz = d_weighted_io_time / (interval * 1000)
平均队列深度
内核如何收集 diskstats:
内核 I/O 栈中的统计点:
应用层 → VFS → 块层 → 设备驱动
块层(block layer)关键函数:
→ blk_account_io_start() : I/O 开始,递增 in_flight,记录开始时间
→ blk_account_io_completion() : I/O 完成,更新完成次数、扇区数、耗时
每个 request_queue 维护统计计数器:
struct request_queue {
struct disk_stats stats;
...
};
/proc/diskstats 由 diskstats_show() 函数生成
→ 遍历所有块设备的 request_queue
→ 读取 stats 计数器
4.2 iotop
功能:按进程显示实时磁盘 I/O 使用率。
底层实现:
iotop 使用两种机制获取数据:
机制一:/proc/[pid]/io(I/O 字节数)
→ read_bytes, write_bytes(实际块设备 I/O)
→ 两次采样差值计算速率
机制二:/proc/task/[tid]/io(线程级 I/O)
→ 遍历 /proc/[pid]/task/ 获取每个线程的 I/O
I/O 百分比计算:
→ 内核通过 taskstats 接口提供延迟统计
→ netlink 接口:TASKSTATS_CMD_GET
→ 返回 struct taskstats 中的 delay accounting 数据
.blkio_delay_total : 等待块 I/O 的累计时间(纳秒)
.swapin_delay_total : 等待换入的累计时间(纳秒)
I/O% = d_blkio_delay / interval / ncpu * 100
Swap% = d_swapin_delay / interval / ncpu * 100
taskstats 的内核实现:
内核延迟统计(Delay Accounting):
在以下节点记录时间戳:
→ schedule() 切换到进程时:记开始时间
→ 从 I/O 等待唤醒时:记结束时间,累加到 blkio_delay_total
→ 页面换入完成时:累加到 swapin_delay_total
通过 netlink 接口导出:
→ 用户空间发送 TASKSTATS_CMD_GET + pid
→ 内核填充 struct taskstats 返回
→ iotop 解析 blkio_delay_total 计算百分比
注意:需要内核编译时开启 CONFIG_TASK_DELAY_ACCT=y
否则 iotop 无法显示 I/O 百分比
4.3 blktrace
功能:追踪块层 I/O 事件的详细过程,配合 blkparse 分析。
底层实现:
blktrace 的架构:
内核模块(blktrace.ko)→ 在块层关键点插入 tracepoint
用户空间(blktrace 命令)→ 通过 relayfs 读取 trace 数据
内核侧 tracepoint:
块层 I/O 路径:
应用 → VFS → __generic_file_aio_write / generic_file_read
→ 块层 submit_bio → 请求调度 → 设备驱动 → 中断完成
blktrace 捕获的事件:
┌──────────┬──────────────────────────────────────────┐
│ 事件标识 │ 含义 │
├──────────┼──────────────────────────────────────────┤
│ Q (Queue)│ I/O 请求进入块层(submit_bio) │
│ G (Get) │ 请求从调度器获取 │
│ I (Issue)│ 请求发送给设备驱动 │
│ D (Done) │ 设备完成 I/O(中断处理) │
│ C (Comp) │ 请求完成回调 │
│ M (Merge)│ 请求被合并 │
│ P (Plug) │ 队列被插入(蓄流) │
│ U (Unplug)│ 队列被拔出(发送蓄积的请求) │
│ S (Sleep)│ 等待 I/O 完成 │
│ R (Remap)│ 请求被映射到其他设备(DM/MD) │
└──────────┴──────────────────────────────────────────┘
数据传输机制:
relayfs(relay filesystem):
内核侧:
1. blktrace 在块层注册 tracepoint 回调
2. 回调函数将事件写入 per-CPU relay 缓冲区
→ 每个 CPU 一个独立的环形缓冲区
→ 无锁写入(per-CPU),零拷贝
用户空间侧:
1. blktrace 打开 /sys/kernel/debug/block/<dev>/trace
2. 通过 relayfs 读取每个 CPU 的缓冲区
3. 写入磁盘文件(<dev>.blktrace.<cpu>)
4. blkparse 解析二进制数据输出可读格式
blkparse 输出示例:
8,0 3 1171 0.000000000 1171 Q WS 3492848 + 8 [kworker]
8,0 3 1171 0.000001234 1171 G WS 3492848 + 8 [kworker]
8,0 3 1171 0.000002345 1171 I WS 3492848 + 8 [kworker]
8,0 3 1171 0.000543210 1171 D WS 3492848 + 8 [kworker]
8,0 3 1171 0.000654321 1171 C WS 3492848 + 8 [0]
字段含义:
8,0 → 主:次设备号
3 → CPU 编号
1171 → 序列号
0.000... → 时间戳(秒)
1171 → 进程 PID
Q/G/I/D/C → 事件类型
WS → 写同步(Write Sync)
3492848 + 8 → 起始扇区 + 扇区数
[kworker] → 进程名
[0] → 完成时的错误码
I/O 延迟分析:
Q → I:排队延迟(scheduler latency)
I → D:设备服务时间(device service time)
Q → C:端到端 I/O 延迟(total latency)
blkparse -w 10 -d /dev/sda 输出统计:
→ 按事件类型汇总平均延迟
→ 识别 I/O 调度器瓶颈(Q→I 过长)vs 设备瓶颈(I→D 过长)
4.4 dstat
功能:综合系统资源统计工具,替代 vmstat/iostat/ifstat 等。
底层实现:
dstat 的数据来源汇总:
┌──────────────────┬──────────────────────────────────────┐
│ 插件 │ 数据来源 │
├──────────────────┼──────────────────────────────────────┤
│ cpu │ /proc/stat │
│ cpu-adv │ /proc/stat(含 steal/guest) │
│ disk │ /proc/diskstats │
│ net │ /proc/net/dev │
│ page │ /proc/vmstat │
│ swap │ /proc/vmstat │
│ sys │ /proc/stat + /proc/interrupts │
│ proc │ /proc/stat(进程创建数) │
│ mem │ /proc/meminfo │
│ vm │ /proc/vmstat │
│ inode │ /proc/sys/fs/inode-state │
│ socket │ /proc/net/sockstat │
│ tcp │ /proc/net/snmp │
│ udp │ /proc/net/snmp │
│ unix │ /proc/net/unix │
│ ipc │ ipcs 命令输出 │
│ top-cpu │ /proc/[pid]/stat │
│ top-bio │ /proc/[pid]/io │
│ top-io │ /proc/[pid]/io │
│ top-oom │ /proc/[pid]/oom_score │
│ latency │ /proc/interrupts + 计算中断延迟 │
└──────────────────┴──────────────────────────────────────┘
dstat 的核心循环:
1. 读取所有启用的插件的数据源
2. 计算与上一次采样的差值
3. 格式化输出一行
4. sleep(delay) → 默认 1 秒
5. 重复
5. 网络工具
5.1 sar
功能:System Activity Reporter,sysstat 套件核心工具,采集和报告系统活动。
底层实现:
sar 的工作架构:
1. 后台守护进程 sadc(System Activity Data Collector)
→ 定期(默认 10 分钟)采集数据写入 /var/log/sa/saDD
→ 由 cron 任务 /etc/cron.d/sysstat 触发
→ 数据源同 dstat(全部来自 /proc)
2. sar 命令读取二进制日志文件并格式化输出
→ sar -u → CPU
→ sar -r → 内存
→ sar -b → I/O
→ sar -n DEV → 网络
→ sar -W → 交换
→ sar -q → 队列长度和平均负载
→ sar -w → 上下文切换
→ sar -x → 指定进程
sadc 内核数据采集路径:
→ 打开 /proc/stat, /proc/vmstat, /proc/diskstats, /proc/net/dev 等
→ 读取计数器快照
→ 写入二进制格式的日志文件
→ 文件格式:struct sa_file_header + sa_record 数组
5.2 nicstat
功能:网卡流量统计,显示每秒读写字节数、包数、利用率。
底层实现:
数据来源:/proc/net/dev
格式:
Inter-| Receive | Transmit
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
eth0: 12345 200 0 0 0 0 0 0 54321 150 0 0 0 0 0 0
利用率计算:
%util = (d_bytes / interval) / interface_speed * 100
→ 对全双工网卡,分别计算读/写利用率
→ 需要知道网卡速率(从 /sys/class/net/<dev>/speed 读取,单位 Mbps)
关键指标:
rKB/s : 每秒接收 KB
wKB/s : 每秒发送 KB
rPk/s : 每秒接收包数
wPk/s : 每秒发送包数
rAvs : 平均接收包大小(字节)
wAvs : 平均发送包大小(字节)
%Util : 网卡利用率
Sat : 饱和度(基于丢包/错误)
6. 内存工具
6.1 vmstat
功能:报告虚拟内存统计,包含进程、内存、交换、I/O、系统、CPU。
底层实现:
数据来源:
┌──────────────┬────────────────────────────────────────────┐
│ vmstat 字段 │ 来源 │
├──────────────┼────────────────────────────────────────────┤
│ procs-r │ /proc/stat 中的 running 进程数 │
│ procs-b │ /proc/stat 中的 blocked (D state) 进程数 │
├──────────────┼────────────────────────────────────────────┤
│ memory-swpd │ /proc/meminfo SwapUsed │
│ memory-free │ /proc/meminfo MemFree │
│ memory-buff │ /proc/meminfo Buffers │
│ memory-cache │ /proc/meminfo Cached │
├──────────────┼────────────────────────────────────────────┤
│ swap-si │ /proc/vmstat pswpin(换入页数/s) │
│ swap-so │ /proc/vmstat pswpout(换出页数/s) │
├──────────────┼────────────────────────────────────────────┤
│ io-bi │ /proc/vmstat pgpgin(读入页数/s) │
│ io-bo │ /proc/vmstat pgpgout(写出页数/s) │
├──────────────┼────────────────────────────────────────────┤
│ system-in │ /proc/stat intr(中断数/s) │
│ system-cs │ /proc/stat ctxt(上下文切换数/s) │
├──────────────┼────────────────────────────────────────────┤
│ cpu-us/sy/id/wa/st │ /proc/stat 同 top │
└──────────────┴────────────────────────────────────────────┘
内核如何统计 pswpin/pswpout:
→ do_swap_page() 完成换入时递增 pswpin
→ shrink_page_list() 将页面写回交换区时递增 pswpout
→ 单位:页(通常 4KB)
6.2 free
功能:显示系统内存使用和交换分区使用。
底层实现:
数据来源:/proc/meminfo
total used free shared buff/cache available
Mem: 16384000 8192000 2048000 512000 6144000 7168000
Swap: 4096000 512000 3584000
计算公式:
total = MemTotal
free = MemFree
buff/cache= Buffers + Cached + SReclaimable
shared = Shmem(tmpfs + 共享内存段)
available = MemAvailable(内核估算的可用于启动新进程的内存)
used = total - free - buff/cache
MemAvailable 的内核计算(mm/page_alloc.c: si_mem_available()):
→ 可用内存 = Free + PageCache - 不可回收部分 + 可回收 slab
→ 考虑了水位线(watermark)限制
→ 比 free 更准确反映"实际可用"内存
6.3 pmap
功能:显示进程的内存映射详情。
底层实现:
数据来源:/proc/[pid]/maps + /proc/[pid]/smaps
/proc/[pid]/maps 格式:
地址范围 权限 偏移 设备 inode 路径
00400000-0040d000 r-xp 00000000 fd:00 12345 /usr/bin/ls
0060c000-0060d000 r--p 0000c000 fd:00 12345 /usr/bin/ls
0060d000-0060e000 rw-p 0000d000 fd:00 12345 /usr/bin/ls
7f000000000-7f000200000 rw-p 00000000 00:00 0 [heap]
7fff1234000-7fff1255000 rw-p 00000000 00:00 0 [stack]
/proc/[pid]/smaps 额外信息(每个映射区域):
Size: 812 kB ← 虚拟内存大小
Rss: 460 kB ← 实际物理内存
Pss: 230 kB ← 比例分摊(共享页按进程数均分)
Shared_Clean: 200 kB ← 共享的干净页
Shared_Dirty: 0 kB ← 共享的脏页
Private_Clean: 60 kB ← 私有的干净页
Private_Dirty: 200 kB ← 私有的脏页
Referenced: 460 kB ← 最近被访问的页
Anonymous: 200 kB ← 匿名页(无文件后端)
AnonHugePages: 2048 kB ← 透明大页
Swap: 0 kB ← 被换出的页
KernelPageSize: 4 kB ← 内核页大小
MMUPageSize: 4 kB ← MMU 页大小
Locked: 0 kB ← mlock 锁定的页
pmap -x <pid> 读取 smaps 汇总输出
pmap -XX <pid> 显示最详细信息
7. 性能分析工具
7.1 perf
功能:Linux 性能分析框架,支持 CPU profiling、硬件计数器、tracepoint、kprobe 等。
底层实现:
perf 的架构:
用户空间:perf 命令
内核空间:perf_event 子系统
┌───────────────────────────────────────────────────┐
│ perf 命令 │
│ stat / record / top / report / annotate │
└────────────────────┬──────────────────────────────┘
│ perf_event_open() 系统调用
┌────────────────────▼──────────────────────────────┐
│ perf_event 子系统 │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ 硬件计数器 │ │ tracepoint │ │ kprobe/ │ │
│ │ (PMC/MSR) │ │ (ftrace) │ │ uprobe │ │
│ └──────┬──────┘ └──────┬───────┘ └─────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ perf_event_context │ │
│ │ per-task 或 per-CPU 的事件上下文 │ │
│ │ 管理 PMC 寄存器分配、采样、溢出中断 │ │
│ └──────────────────┬───────────────────────────┘ │
│ │ │
│ ┌──────────────────▼───────────────────────────┐ │
│ │ ring buffer (mmap) │ │
│ │ perf_mmap → 用户空间直接读取采样数据 │ │
│ └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
perf_event_open 系统调用:
int perf_event_open(struct perf_event_attr *attr,
pid_t pid, int cpu, int group_fd, unsigned long flags);
参数含义:
pid : 监控的进程(-1 = 当前,0 = 所有)
cpu : 监控的 CPU 核心(-1 = 所有)
group_fd : 事件组(-1 = 新组)
attr : 事件配置
.type : PERF_TYPE_HARDWARE/SOFTWARE/TRACEPOINT/HW_CACHE/RAW
.config : 具体事件编号
.sample_period/freq : 采样周期/频率
.mmap : 是否记录 mmap 事件
.task : 是否记录 task 事件
.precise_ip : 采样精度(0-3,越高越精确)
采样模式(perf record)的实现:
1. 用户调用 perf_event_open() 创建事件
2. 内核配置 PMC 寄存器:
→ 写入 PERF_EVTSEL MSR(事件选择 + 配置)
→ 设置计数器初始值(2^48 - sample_period)
3. 进程运行,PMC 递增
4. PMC 溢出 → 触发 PMI(Performance Monitoring Interrupt)
5. PMI 处理程序(intel_pmu_handle_irq):
a. 读取触发溢出的 PMC
b. 采集样本:
→ IP(指令指针)
→ TID(线程 ID)
→ 时间戳
→ 调用栈(如果启用 -g,通过遍历栈帧或使用 DWARF)
c. 写入 per-CPU ring buffer
d. 重置计数器
6. 用户空间通过 mmap 读取 ring buffer
7. perf record 将样本写入 perf.data 文件
硬件计数器(PMC)的内核驱动:
x86 架构:
Intel CPU: intel_pmu 驱动
→ x86_pmu.enable = intel_pmu_enable_event() → 写 MSR
→ x86_pmu.disable = intel_pmu_disable_event() → 写 MSR
→ x86_pmu.read = intel_pmu_read_event() → 读 MSR
AMD CPU: amd_pmu 驱动
→ 类似结构,操作 AMD 的 PERF_CTL/PERF_CTR MSR
ARM 架构:
→ arm_pmu 驱动
→ 操作 PMU 系统寄存器(PMXEVTYPER_EL0, PMXEVCNTR_EL0)
→ 溢出中断由 PMU IRQ 处理
7.2 perf 常用子命令
┌──────────────────┬──────────────────────────────────────────┐
│ 子命令 │ 功能与底层实现 │
├──────────────────┼──────────────────────────────────────────┤
│ perf stat │ 计数模式:读取 PMC 差值,不采样 │
│ → perf_event_open(COUNT_MODE) │
│ → ioctl(PERF_EVENT_IOC_ENABLE) │
│ → ioctl(PERF_EVENT_IOC_DISABLE) │
│ → read(fd) 读取计数器值 │
├──────────────────┼──────────────────────────────────────────┤
│ perf record │ 采样模式:PMC 溢出中断采集样本 │
│ → perf_event_open(SAMPLE_MODE) │
│ → mmap() 映射 ring buffer │
│ → PMC 溢出 → PMI → 写入 ring buffer │
│ → 用户空间 poll ring buffer 写入文件 │
├──────────────────┼──────────────────────────────────────────┤
│ perf top │ 实时采样:类似 record 但实时显示热点 │
│ → 采样机制同 record │
│ → 每 2-3 秒刷新直方图 │
├──────────────────┼──────────────────────────────────────────┤
│ perf annotate │ 反汇编 + 样本分布标注 │
│ → 读取 perf.data 中的样本 │
│ → 解析 ELF 的 .text 段 │
│ → 用 objdump 反汇编 │
│ → 将样本数映射到每条指令 │
├──────────────────┼──────────────────────────────────────────┤
│ perf trace │ 追踪系统调用,类似 strace │
│ → 基于 tracepoint 而非 ptrace │
│ → 开销远低于 strace │
└──────────────────┴──────────────────────────────────────────┘
8. 追踪工具
8.1 ftrace
功能:内核函数追踪框架,支持函数追踪、函数图谱、事件追踪等。
底层实现:
ftrace 的架构:
┌───────────────────────────────────────────┐
│ 用户接口 │
│ /sys/kernel/debug/tracing/ │
│ trace │
│ set_ftrace_filter │
│ available_tracers │
│ current_tracer │
├───────────────────────────────────────────┤
│ 追踪器 │
│ function → 函数调用追踪 │
│ function_graph → 函数调用图谱 │
│ wakeup → 唤醒延迟追踪 │
│ irqsoff → 中断关闭时间追踪 │
│ preemptoff → 抢占关闭时间追踪 │
├───────────────────────────────────────────┤
│ 基础设施 │
│ mcount / __fentry__ → 编译器插入的钩子 │
│ tracepoint → 静态插桩点 │
│ ring buffer → 追踪数据存储 │
└───────────────────────────────────────────┘
函数追踪的编译时插桩:
GCC 的 -pg 选项会在每个函数入口插入调用:
x86: call mcount (旧版)
x86: call __fentry__ (新版,CONFIG_FUNCTION_TRACER)
原始代码:
void my_func(void) {
do_something();
}
编译后:
my_func:
call __fentry__ ← 编译器自动插入
... (函数体)
ret
未启用 ftrace 时:
__fentry__ 的实现是简单的 ret 指令(1 条指令开销 ≈ 0)
启用 ftrace 时:
内核将 __fentry__ 的入口替换为跳转到追踪处理函数
→ 使用 text_poke_bp() 修改内核代码段
→ 替换为 jmp ftrace_caller 或 call ftrace_caller
动态过滤:
set_ftrace_filter 的实现:
→ 内核维护一个 ftrace_func_hash 哈希表
→ 写入函数名时,查找对应的符号地址
→ 将该地址对应的 ftrace_rec 标记为 enabled
→ ftrace_caller 执行时检查当前函数是否在 hash 中
→ 只追踪匹配的函数,减少开销
8.2 eBPF / bpftrace
功能:在内核中安全运行用户编写的程序,实现自定义追踪和监控。
底层实现:
eBPF 的架构:
┌───────────────────────────────────────────────┐
│ 用户空间 │
│ bpftrace / BCC / libbpf │
│ → 编写 eBPF 程序(类 C 语法) │
│ → 编译为 BPF 字节码 │
│ → bpf() 系统调用加载到内核 │
├───────────────────────────────────────────────┤
│ 内核验证器 │
│ verifier.c │
│ → 确保程序安全(不会崩溃内核) │
│ → 检查:无无限循环、无越界访问、栈大小限制 │
│ → 验证通过后 JIT 编译为本机指令 │
├───────────────────────────────────────────────┤
│ 挂载点 │
│ kprobe → 内核函数入口/返回 │
│ tracepoint → 静态追踪点 │
│ perf_event → PMC 溢出 │
│ XDP → 网卡收包路径 │
│ tc → 流量控制 │
│ cgroup → cgroup 事件 │
│ socket → socket 操作 │
├───────────────────────────────────────────────┤
│ 数据传递 │
│ BPF_MAP → 内核-用户空间共享数据结构 │
│ perf_event → 采样数据传递 │
└───────────────────────────────────────────────┘
eBPF 程序的执行流程:
1. 用户编写 eBPF 程序
bpftrace -e 'kprobe:do_sys_open { @opens = count(); }'
2. 编译为 BPF 字节码
→ bpftrace 内部使用 LLVM 将脚本编译为 BPF 字节码
3. bpf(BPF_PROG_LOAD) 系统调用加载到内核
4. 内核验证器检查:
→ DAG 验证(无环)
→ 寄存器状态追踪(每个分支的所有可能值)
→ 内存访问边界检查
→ 最大指令数限制(1M 条)
→ 最大栈深度(512 字节)
5. JIT 编译为本机指令
→ bpf_int_jit_compile() 将 BPF 字节码翻译为 x86/ARM 机器码
→ 替换 BPF 指令为原生 CPU 指令
→ 性能接近原生内核代码
6. 挂载到 kprobe/tracepoint
→ 当 do_sys_open 被调用时,JIT 编译后的 BPF 程序执行
→ 更新 BPF_MAP 中的计数器
7. 用户空间通过 bpf(BPF_MAP_LOOKUP_ELEM) 读取结果
BPF Map 类型:
┌──────────────────┬──────────────────────────────────────┐
│ Map 类型 │ 用途 │
├──────────────────┼──────────────────────────────────────┤
│ BPF_MAP_TYPE_HASH│ 通用哈希表 │
│ BPF_MAP_TYPE_ARRAY│ 通用数组 │
│ BPF_MAP_TYPE_PERF_EVENT_ARRAY │ per-CPU 事件缓冲区 │
│ BPF_MAP_TYPE_PERCPU_HASH │ per-CPU 哈希表(无锁) │
│ BPF_MAP_TYPE_RINGBUF │ 可变大小环形缓冲区 │
│ BPF_MAP_TYPE_STACK_TRACE │ 调用栈追踪 │
│ BPF_MAP_TYPE_LRU_HASH │ 带 LRU 淘汰的哈希表 │
└──────────────────┴──────────────────────────────────────┘
8.3 SystemTap
功能:内核动态追踪框架,用脚本语言编写探测点。
底层实现:
SystemTap 的执行流程:
1. 用户编写 .stp 脚本
probe syscall.open { printf("%s opened %s\n", execname(), user_string($filename)); }
2. stap 编译器将脚本翻译为 C 代码
→ 解析脚本语法
→ 生成内核模块的 C 源码
→ 插入 probe handler 函数
3. 调用 GCC 编译为内核模块(.ko)
4. insmod 加载内核模块
→ 注册 kprobe 探测点
→ kprobe 在目标函数入口插入断点(INT 3 或 ftrace 钩子)
5. 探测点触发时:
→ kprobe 回调执行 probe handler
→ handler 读取参数、收集数据
→ 数据写入 relayfs 缓冲区
6. 用户空间 stap 进程从 relayfs 读取并输出
7. Ctrl+C 时 rmmod 卸载模块
与 eBPF 的对比:
┌──────────────┬──────────────────┬──────────────────┐
│ │ SystemTap │ eBPF │
├──────────────┼──────────────────┼──────────────────┤
│ 安全性 │ 需要加载 .ko │ 验证器保证安全 │
│ │ 可能崩溃内核 │ 不会崩溃内核 │
├──────────────┼──────────────────┼──────────────────┤
│ 权限 │ root │ root / CAP_BPF │
├──────────────┼──────────────────┼──────────────────┤
│ 编译 │ GCC 编译 .ko │ LLVM → BPF 字节码│
│ │ 需要内核头文件 │ 不需要内核头文件 │
├──────────────┼──────────────────┼──────────────────┤
│ 性能 │ 中等(kprobe) │ 高(JIT 编译) │
├──────────────┼──────────────────┼──────────────────┤
│ 功能范围 │ 更灵活 │ 受验证器限制 │
├──────────────┼──────────────────┼──────────────────┤
│ 生产可用性 │ 风险较高 │ 适合生产环境 │
└──────────────┴──────────────────┴──────────────────┘
8.4 bcc 工具集
功能:基于 eBPF 的性能分析工具集,提供数十个现成工具。
常用工具与底层实现:
┌──────────────────┬──────────────────────────────────────────────────┐
│ 工具 │ 功能 + 底层 eBPF 挂载点 │
├──────────────────┼──────────────────────────────────────────────────┤
│ execsnoop │ 追踪新进程执行 │
│ → kprobe:do_execve / tracepoint:sched:sched_process_exec│
├──────────────────┼──────────────────────────────────────────────────┤
│ opensnoop │ 追踪文件打开 │
│ → kprobe:do_sys_open / tracepoint:syscalls:sys_enter_open│
├──────────────────┼──────────────────────────────────────────────────┤
│ biolatency │ 块 I/O 延迟直方图 │
│ → tracepoint:block:block_rq_issue (记录开始时间) │
│ → tracepoint:block:block_rq_complete (计算延迟) │
│ → BPF_MAP_TYPE_HASH 存储进行中的请求 │
│ → BPF_MAP_TYPE_PERCPU_ARRAY/LRU_HASH 存直方图 │
├──────────────────┼──────────────────────────────────────────────────┤
│ biosnoop │ 追踪每个块 I/O 请求 │
│ → 同 biolatency 的 tracepoint │
│ → perf_event 输出每条 I/O 详情 │
├──────────────────┼──────────────────────────────────────────────────┤
│ cachestat │ 页缓存统计 │
│ → kprobe:mark_page_accessed (缓存命中) │
│ → kprobe:mark_buffer_dirty (脏页) │
│ → kprobe:add_to_page_cache_lru (缓存添加) │
│ → kprobe:__remove_mapping (缓存移除) │
├──────────────────┼──────────────────────────────────────────────────┤
│ tcpconnect │ 追踪 TCP 主动连接 │
│ → kprobe:tcp_v4_connect / tcp_v6_connect │
├──────────────────┼──────────────────────────────────────────────────┤
│ tcpaccept │ 追踪 TCP 被动接受 │
│ → kprobe:inet_csk_accept │
├──────────────────┼──────────────────────────────────────────────────┤
│ offcputime │ 追踪 CPU 外等待时间 │
│ → kprobe:schedule (记录离开 CPU 时间) │
│ → kprobe:finish_task_switch (记录回到 CPU 时间) │
│ → BPF_MAP_TYPE_STACK_TRACE 采集调用栈 │
├──────────────────┼──────────────────────────────────────────────────┤
│ memleak │ 内存泄漏检测 │
│ → kprobe:kmalloc / kmem_cache_alloc (记录分配) │
│ → kprobe:kfree / kmem_cache_free (记录释放) │
│ → BPF_MAP_TYPE_HASH 存储未释放的分配 │
│ → 定期扫描,长时间未释放的视为泄漏 │
├──────────────────┼──────────────────────────────────────────────────┤
│ profile │ CPU profiling(基于定时器采样) │
│ → perf_event (CPU 周期采样) │
│ → BPF_MAP_TYPE_STACK_TRACE 采集调用栈 │
│ → BPF_MAP_TYPE_PERCPU_HASH 统计栈出现次数 │
└──────────────────┴──────────────────────────────────────────────────┘
8.5 kprobe / uprobe
功能:内核/用户空间动态插桩机制,是 ftrace、eBPF、SystemTap 的底层基础设施。
kprobe 底层实现:
x86 上 kprobe 的实现:
1. 注册 kprobe:
→ 用户指定目标函数地址
→ 保存目标地址的原始指令
→ 将目标地址第一个字节替换为 INT 3(0xCC,断点指令)
2. 执行到 INT 3:
→ CPU 触发 #DB 异常 → 进入 kprobe_handler()
→ 单步执行原始指令(设置 TF 位)
→ 执行 pre_handler 回调
→ 执行后恢复执行
优化:kprobe 跳转优化
→ 如果目标函数开头 ≥ 5 字节,直接替换为 JMP 指令
→ 跳转到 kprobe 的处理代码
→ 避免 INT 3 的异常处理开销
→ 需要启用 CONFIG_OPTPROBES
uprobe 底层实现:
用户空间插桩:
1. 注册 uprobe:
→ 指定可执行文件 + 偏移量
→ 内核在目标文件的 page cache 中找到对应页
→ 将目标地址替换为 INT 3(写时拷贝 COW)
2. 进程执行到 INT 3:
→ 触发 #DB 异常
→ 内核检查是否是 uprobe 断点
→ 执行 uprobe handler
→ 单步执行原始指令
→ 恢复执行
3. uprobe 与 kprobe 的区别:
→ kprobe 修改内核代码段(所有进程共享)
→ uprobe 修改用户空间页(通过 COW,只影响目标进程)
→ uprobe 需要处理进程 fork 时的断点继承
8.6 tracepoint
功能:内核中预定义的静态追踪点,比 kprobe 开销更低、更稳定。
底层实现:
tracepoint 的定义(内核源码中):
TRACE_EVENT(sched_switch,
TP_PROTO(struct task_struct *prev, struct task_struct *next),
TP_ARGS(prev, next),
TP_STRUCT__entry(
__field(pid_t, prev_pid)
__field(pid_t, next_pid)
__field(unsigned int, prev_prio)
__field(unsigned int, next_prio)
),
TP_fast_assign(
__entry->prev_pid = prev->pid;
__entry->next_pid = next->pid;
__entry->prev_prio = prev->prio;
__entry->next_prio = next->prio;
),
TP_printk("prev_pid=%d next_pid=%d prev_prio=%d next_prio=%d",
__entry->prev_pid, __entry->next_pid,
__entry->prev_prio, __entry->next_prio)
);
编译后展开为:
// 函数入口的钩子
static inline void trace_sched_switch(struct task_struct *prev,
struct task_struct *next) {
if (trace_sched_switch_enabled()) { ← 快速检查(分支预测优化)
__trace_sched_switch(prev, next); ← 慢路径:写入 ring buffer
}
}
// 未启用时:trace_sched_switch_enabled() 返回 false
// 开销:一次条件判断 + 分支预测正确 ≈ 0
tracepoint vs kprobe:
┌──────────────┬─────────────────────┬──────────────────────┐
│ │ tracepoint │ kprobe │
├──────────────┼─────────────────────┼──────────────────────┤
│ 插桩方式 │ 编译时静态插入 │ 运行时动态替换指令 │
│ 稳定性 │ ABI 稳定 │ 依赖内核内部实现 │
│ 未启用开销 │ ≈ 0(条件跳转) │ ≈ 0(ftrace nop) │
│ 启用开销 │ 低(直接调用) │ 中(INT 3 → 异常) │
│ 可追踪位置 │ 仅预定义位置 │ 任意函数入口/返回 │
│ 参数访问 │ 结构化(有类型) │ 需要知道寄存器/栈布局 │
└──────────────┴─────────────────────┴──────────────────────┘
8.7 常用 tracepoint 列表
┌──────────────────────┬───────────────────────────────────────────┐
│ 子系统 │ 关键 tracepoint │
├──────────────────────┼───────────────────────────────────────────┤
│ sched(调度) │ sched_switch → 进程切换 │
│ │ sched_wakeup → 进程唤醒 │
│ │ sched_process_fork → 进程创建 │
│ │ sched_process_exit → 进程退出 │
│ │ sched_migrate_task → 进程迁移 │
├──────────────────────┼───────────────────────────────────────────┤
│ block(块层) │ block_rq_issue → I/O 请求提交 │
│ │ block_rq_complete → I/O 请求完成 │
│ │ block_rq_requeue → I/O 请求重入队列 │
│ │ block_bio_merge → bio 合并 │
├──────────────────────┼───────────────────────────────────────────┤
│ syscalls(系统调用) │ sys_enter_openat → open 系统调用入口 │
│ │ sys_exit_openat → open 系统调用返回 │
│ │ sys_enter_read / write / ... │
├──────────────────────┼───────────────────────────────────────────┤
│ net(网络) │ net_dev_xmit → 网络包发送 │
│ │ netif_receive_skb → 网络包接收 │
│ │ napi_poll → NAPI 轮询 │
├──────────────────────┼───────────────────────────────────────────┤
│ irq(中断) │ irq_handler_entry → 中断处理入口 │
│ │ irq_handler_exit → 中断处理退出 │
│ │ softirq_entry/exit → 软中断处理 │
├──────────────────────┼───────────────────────────────────────────┤
│ rpm(电源管理) │ rpm_idle/suspend/resume → 运行时电源管理 │
├──────────────────────┼───────────────────────────────────────────┤
│ writeback(回写) │ writeback_start → 回写开始 │
│ │ writeback_written → 回写完成 │
│ │ writeback_wait → 回写等待 │
└──────────────────────┴───────────────────────────────────────────┘
查看所有可用 tracepoint:
ls /sys/kernel/debug/tracing/available_events
或 perf list tracepoint