Linux 系統(tǒng)工具功能與底層實(shí)現(xiàn)
目錄
- 1. 進(jìn)程與線程調(diào)試工具
- 2. 系統(tǒng)級(jí)監(jiān)控工具
- 3. 進(jìn)程級(jí)監(jiān)控工具
- 4. I/O 與存儲(chǔ)工具
- 5. 網(wǎng)絡(luò)工具
- 6. 內(nèi)存工具
- 7. 性能分析工具
- 8. 追蹤工具
1. 進(jìn)程與線程調(diào)試工具
1.1 gdb
功能:GNU 調(diào)試器,支持?jǐn)帱c(diǎn)、單步、變量查看、內(nèi)存檢查、遠(yuǎn)程調(diào)試等。
底層實(shí)現(xiàn):
gdb 的核心機(jī)制是 ptrace 系統(tǒng)調(diào)用:
gdb 啟動(dòng)調(diào)試目標(biāo):
fork() → 子進(jìn)程調(diào)用 ptrace(PTRACE_TRACEME) → exec() 加載目標(biāo)程序
父進(jìn)程(gdb)通過 ptrace 控制子進(jìn)程
gdb 附加到已有進(jìn)程:
ptrace(PTRACE_ATTACH, pid) → 目標(biāo)進(jìn)程收到 SIGSTOP 暫停
關(guān)鍵 ptrace 操作:
┌──────────────────────────┬───────────────────────────────────────┐
│ ptrace 請(qǐng)求 │ 作用 │
├──────────────────────────┼───────────────────────────────────────┤
│ PTRACE_TRACEME │ 標(biāo)記自己被追蹤(子進(jìn)程調(diào)用) │
│ PTRACE_ATTACH │ 附加到指定進(jìn)程 │
│ PTRACE_CONT │ 讓被追蹤進(jìn)程繼續(xù)執(zhí)行 │
│ PTRACE_SINGLESTEP │ 單步執(zhí)行一條指令 │
│ PTRACE_PEEKTEXT/DATA │ 讀取目標(biāo)進(jìn)程內(nèi)存 │
│ PTRACE_POKETEXT/DATA │ 寫入目標(biāo)進(jìn)程內(nèi)存 │
│ PTRACE_GETREGS │ 讀取寄存器 │
│ PTRACE_SETREGS │ 寫入寄存器 │
│ PTRACE_SET_SYSCALL │ 修改系統(tǒng)調(diào)用號(hào) │
└──────────────────────────┴───────────────────────────────────────┘
斷點(diǎn)實(shí)現(xiàn):
軟件斷點(diǎn):
1. gdb 讀取目標(biāo)地址的原始指令字節(jié),保存到內(nèi)部表
2. 將目標(biāo)地址的第一個(gè)字節(jié)替換為 INT 3(0xCC)
3. 進(jìn)程執(zhí)行到 INT 3 → 觸發(fā) SIGTRAP → gdb 收到信號(hào)
4. gdb 恢復(fù)原始指令字節(jié),回退 PC,等待用戶命令
硬件斷點(diǎn):
1. 使用 CPU 的調(diào)試寄存器(DR0-DR3 存地址,DR7 存控制位)
2. CPU 執(zhí)行到對(duì)應(yīng)地址時(shí)自動(dòng)觸發(fā)調(diào)試異常
3. 最多 4 個(gè)硬件斷點(diǎn)(DR0-DR3)
4. 優(yōu)勢(shì):不修改代碼段,可用于 ROM/只讀內(nèi)存
單步實(shí)現(xiàn):
指令級(jí)單步:
設(shè)置 EFLAGS 的 TF(Trap Flag)位 → CPU 每執(zhí)行一條指令后觸發(fā)調(diào)試異常
源碼級(jí)單步:
gdb 內(nèi)部計(jì)算下一行源碼對(duì)應(yīng)的地址,在該地址設(shè)臨時(shí)斷點(diǎn),然后 CONT
如果下一行有函數(shù)調(diào)用,step-into 會(huì)在函數(shù)入口設(shè)斷點(diǎn),step-over 會(huì)在返回點(diǎn)設(shè)斷點(diǎn)
1.2 gcore
功能:在不停止進(jìn)程的情況下生成 core dump 文件。
底層實(shí)現(xiàn):
gcore 的執(zhí)行流程:
1. ptrace(PTRACE_ATTACH, pid)
→ 目標(biāo)進(jìn)程暫停(發(fā)送 SIGSTOP)
2. 讀取進(jìn)程狀態(tài)
→ /proc/pid/maps → 內(nèi)存映射布局
→ /proc/pid/stat → 進(jìn)程狀態(tài)信息
→ ptrace(PTRACE_GETREGS) → 寄存器
3. 生成 ELF core 文件
→ 寫入 ELF Header(類型 ET_CORE)
→ 寫入 Program Headers(每個(gè)內(nèi)存段一個(gè) LOAD 段)
→ 遍歷 /proc/pid/maps 中的每個(gè)內(nèi)存區(qū)域
→ 通過 ptrace(PTRACE_PEEKDATA) 或 /proc/pid/mem 讀取內(nèi)存內(nèi)容
→ 寫入 Note 段(寄存器、信號(hào)、文件描述符等輔助信息)
4. ptrace(PTRACE_DETACH, pid)
→ 目標(biāo)進(jìn)程恢復(fù)執(zhí)行
ELF Core 文件結(jié)構(gòu):
┌──────────────────────────────┐
│ ELF Header (ET_CORE) │
├──────────────────────────────┤
│ Program Headers │
│ ├─ NOTE segment (prstatus) │ ← 寄存器、信號(hào)信息
│ ├─ NOTE segment (prpsinfo) │ ← 進(jìn)程信息
│ ├─ NOTE segment (auxv) │ ← 輔助向量
│ ├─ LOAD segment (代碼段) │ ← 對(duì)應(yīng) /proc/pid/maps 中的可讀段
│ ├─ LOAD segment (數(shù)據(jù)段) │
│ ├─ LOAD segment (堆) │
│ ├─ LOAD segment (棧) │
│ └─ ... │
├──────────────────────────────┤
│ Note Data │
├──────────────────────────────┤
│ Memory Segments Data │ ← 實(shí)際內(nèi)存內(nèi)容
└──────────────────────────────┘
與 gdb 配合:
# 生成 core dump
gcore <pid>
# 事后分析
gdb ./executable core.<pid>
1.3 pstack
功能:打印進(jìn)程的線程調(diào)用棧。
底層實(shí)現(xiàn):
pstack 的執(zhí)行流程:
1. ptrace(PTRACE_ATTACH, pid) → 暫停目標(biāo)進(jìn)程
2. 讀取 /proc/pid/task/ 獲取所有線程 tid
3. 對(duì)每個(gè)線程:
a. ptrace(PTRACE_GETREGS) → 獲取 PC、SP、FP 等寄存器
b. 從 FP 開始,沿棧幀鏈回溯:
當(dāng)前 FP → [上一級(jí) FP, 返回地址]
讀取下一級(jí) FP → [再上一級(jí) FP, 返回地址]
... 直到 FP 為 0 或無效
c. 查找 ELF 的 .eh_frame / .debug_frame 段獲取 DWARF unwind 信息
(如果可用,DWARF 信息比 FP 鏈更準(zhǔn)確)
d. 通過返回地址查 ELF 符號(hào)表(.symtab / .dynsym)得到函數(shù)名
4. ptrace(PTRACE_DETACH, pid) → 恢復(fù)目標(biāo)進(jìn)程
?;厮莸膬煞N方式:
方式一:幀指針鏈(Frame Pointer Chaining)
棧布局(x86-64):
┌──────────────┐ ← 高地址
│ ... │
│ arg n │
│ arg 1 │
│ return addr │ ← 調(diào)用者返回地址
│ saved RBP │ ← 指向調(diào)用者的棧幀
├──────────────┤ ← 當(dāng)前 RBP
│ local var 1 │
│ local var 2 │
├──────────────┤ ← 當(dāng)前 RSP
│ ... │
└──────────────┘ ← 低地址
回溯:RBP → [saved_RBP, ret_addr] → saved_RBP → [saved_RBP', ret_addr'] → ...
方式二:DWARF Unwind Info(-fomit-frame-pointer 編譯時(shí))
.eh_frame 段記錄了每個(gè) PC 范圍內(nèi)的?;厮菀?guī)則:
→ 如何恢復(fù)上一級(jí)的 CFA(Canonical Frame Address)
→ 各個(gè)寄存器保存在棧的哪個(gè)偏移
→ 基于當(dāng)前 PC 查表,按規(guī)則計(jì)算上一級(jí)棧幀位置
1.4 strace
功能:追蹤進(jìn)程的系統(tǒng)調(diào)用和信號(hào)。
底層實(shí)現(xiàn):
strace 的核心機(jī)制:ptrace + PTRACE_SYSCALL
執(zhí)行流程:
1. ptrace(PTRACE_ATTACH, pid) 或 fork + PTRACE_TRACEME
2. ptrace(PTRACE_SYSCALL, pid) → 讓進(jìn)程繼續(xù)運(yùn)行直到下一個(gè)系統(tǒng)調(diào)用
3. 進(jìn)程進(jìn)入內(nèi)核(syscall entry)→ 觸發(fā) SIGTRAP → strace 被通知
4. strace 讀取寄存器獲取系統(tǒng)調(diào)用號(hào)和參數(shù)
→ x86-64: RAX=系統(tǒng)調(diào)用號(hào), RDI/RSI/RDX/R10/R8/R9=參數(shù)
5. ptrace(PTRACE_SYSCALL, pid) → 繼續(xù)運(yùn)行
6. 進(jìn)程從內(nèi)核返回(syscall exit)→ 再次觸發(fā) SIGTRAP
7. strace 讀取 RAX 獲取返回值
8. 重復(fù) 2-7
每個(gè)系統(tǒng)調(diào)用被攔截兩次:entry 和 exit
開銷來源:
每次系統(tǒng)調(diào)用:
1. 進(jìn)程暫停(SIGTRAP)
2. 上下文切換到 strace
3. strace 通過 ptrace 讀取寄存器
4. 上下文切換回目標(biāo)進(jìn)程
開銷:每個(gè)系統(tǒng)調(diào)用增加約 5-20μs
對(duì)頻繁系統(tǒng)調(diào)用的程序,strace 可導(dǎo)致 10-100x 性能下降
新內(nèi)核的替代方案:
seccomp-bpf 過濾:
→ 不需要 ptrace,在內(nèi)核中用 BPF 程序過濾系統(tǒng)調(diào)用
→ 開銷極低,但只能過濾不能讀取參數(shù)
perf trace:
→ 基于 tracepoint 而非 ptrace
→ 開銷更低,但信息不如 strace 完整
2. 系統(tǒng)級(jí)監(jiān)控工具
2.1 top
功能:實(shí)時(shí)顯示系統(tǒng)中各進(jìn)程的 CPU、內(nèi)存使用情況。
底層實(shí)現(xiàn):
top 的數(shù)據(jù)來源全部來自 /proc 文件系統(tǒng):
┌─────────────────────┬──────────────────────────────────────────┐
│ 數(shù)據(jù) │ 來源 │
├─────────────────────┼──────────────────────────────────────────┤
│ CPU 整體使用率 │ /proc/stat │
│ │ user, nice, system, idle, iowait, │
│ │ irq, softirq, steal, guest │
├─────────────────────┼──────────────────────────────────────────┤
│ 內(nèi)存使用 │ /proc/meminfo │
│ │ MemTotal, MemFree, Buffers, Cached, │
│ │ Slab, SReclaimable │
├─────────────────────┼──────────────────────────────────────────┤
│ 交換分區(qū) │ /proc/meminfo │
│ │ SwapTotal, SwapFree │
├─────────────────────┼──────────────────────────────────────────┤
│ 進(jìn)程信息 │ /proc/[pid]/stat │
│ │ pid, state, utime, stime, rss, ... │
├─────────────────────┼──────────────────────────────────────────┤
│ 進(jìn)程命令行 │ /proc/[pid]/cmdline │
├─────────────────────┼──────────────────────────────────────────┤
│ 進(jìn)程內(nèi)存映射 │ /proc/[pid]/statm │
└─────────────────────┴──────────────────────────────────────────┘
CPU 使用率計(jì)算:
/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
進(jìn)程 CPU%:
/proc/[pid]/stat 中的 utime + stime(單位:clock ticks)
進(jìn)程CPU% = (d_utime + d_stime) / (d_total / ncpu) / interval
進(jìn)程狀態(tài):
/proc/[pid]/stat 第 3 個(gè)字段:
R Running → 正在運(yùn)行或就緒等待 CPU
S Sleeping → 可中斷睡眠(等待事件)
D Disk Sleep → 不可中斷睡眠(通常是 I/O 等待)
Z Zombie → 已退出但父進(jìn)程未 wait
T Stopped → 被信號(hào)停止
t Tracing Stop → 被追蹤器停止
X Dead → 已死亡(不應(yīng)看到)
2.2 htop
功能:top 的增強(qiáng)版,支持鼠標(biāo)、樹狀視圖、快捷殺進(jìn)程等。
底層實(shí)現(xiàn):
與 top 相同的數(shù)據(jù)來源(/proc),差異在于:
1. 使用 ncurses 庫實(shí)現(xiàn)交互式 TUI
2. 讀取 /proc/[pid]/io 獲取 I/O 信息(top 不顯示)
3. 讀取 /proc/[pid]/fd 統(tǒng)計(jì)打開的文件描述符數(shù)
4. 通過 libc 的 getpwuid() / getgrgid() 解析用戶名/組名
5. 殺進(jìn)程:kill(pid, signal) 系統(tǒng)調(diào)用
6. CPU 欄的彩色分段:
→ 藍(lán)色 = low-priority (nice > 0)
→ 綠色 = normal (user)
→ 紅色 = kernel (system)
→ 青色 = steal (虛擬化環(huán)境被其他 VM 偷走的時(shí)間)
→ 品紅 = IRQ / soft IRQ
2.3 uptime
功能:顯示系統(tǒng)運(yùn)行時(shí)間、登錄用戶數(shù)、1/5/15 分鐘平均負(fù)載。
底層實(shí)現(xiàn):
數(shù)據(jù)來源:
運(yùn)行時(shí)間:/proc/uptime
第一個(gè)字段:系統(tǒng)啟動(dòng)后的秒數(shù)(含小數(shù))
第二個(gè)字段:系統(tǒng)空閑的累計(jì)秒數(shù)
平均負(fù)載:/proc/loadavg
前三個(gè)數(shù)字:1分鐘、5分鐘、15分鐘平均負(fù)載
第四個(gè)數(shù)字:當(dāng)前運(yùn)行進(jìn)程數(shù) / 總進(jìn)程數(shù)
第五個(gè)數(shù)字:最近創(chuàng)建的進(jìn)程 PID
平均負(fù)載的含義:
load average = 運(yùn)行隊(duì)列中的進(jìn)程數(shù) + 不可中斷睡眠(D狀態(tài))的進(jìn)程數(shù)
→ 包含 CPU 等待 + I/O 等待
→ 單核:1.0 表示滿載;4核:4.0 表示滿載
→ 計(jì)算方式:指數(shù)衰減移動(dòng)平均
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āng)前運(yùn)行隊(duì)列 + D狀態(tài)進(jìn)程數(shù)
3. 進(jìn)程級(jí)監(jiān)控工具
3.1 pidstat
功能:按進(jìn)程顯示 CPU、內(nèi)存、I/O、線程、上下文切換等統(tǒng)計(jì)。
底層實(shí)現(xiàn):
pidstat 是 sysstat 套件的一部分,數(shù)據(jù)來源:
┌──────────────────┬──────────────────────────────────────────────┐
│ 選項(xiàng) │ 數(shù)據(jù)來源 │
├──────────────────┼──────────────────────────────────────────────┤
│ 默認(rèn)(CPU統(tǒng)計(jì)) │ /proc/[pid]/stat │
│ │ utime, stime, cutime, cstime, processor │
├──────────────────┼──────────────────────────────────────────────┤
│ -r(內(nèi)存統(tǒng)計(jì)) │ /proc/[pid]/statm + /proc/[pid]/status │
│ │ VSS, RSS, %MEM, minflt, majflt │
├──────────────────┼──────────────────────────────────────────────┤
│ -d(I/O統(tǒng)計(jì)) │ /proc/[pid]/io │
│ │ read_bytes, write_bytes, cancelled_write_bytes│
├──────────────────┼──────────────────────────────────────────────┤
│ -w(上下文切換) │ /proc/[pid]/status │
│ │ voluntary_ctxt_switches, nonvoluntary_ctxt... │
├──────────────────┼──────────────────────────────────────────────┤
│ -t(線程統(tǒng)計(jì)) │ /proc/[pid]/task/[tid]/stat │
└──────────────────┴──────────────────────────────────────────────┘
/proc/[pid]/io 的內(nèi)核實(shí)現(xiàn):
內(nèi)核為每個(gè) task_struct 維護(hù) I/O 計(jì)數(shù)器:
struct task_struct {
...
u64 acct_rss_mem1; // RSS 增量
u64 acct_vm_mem1; // 虛擬內(nèi)存增量
u64 acct_timexpd; // I/O 等待時(shí)間
...
};
struct io_accounting {
u64 rchar; // 讀字節(jié)數(shù)(含緩存命中)
u64 wchar; // 寫字節(jié)數(shù)(含緩存命中)
u64 syscr; // 讀系統(tǒng)調(diào)用次數(shù)
u64 syscw; // 寫系統(tǒng)調(diào)用次數(shù)
u64 read_bytes; // 實(shí)際從塊設(shè)備讀取的字節(jié)數(shù)
u64 write_bytes; // 實(shí)際寫入塊設(shè)備的字節(jié)數(shù)
u64 cancelled_write_bytes; // 取消寫入的字節(jié)數(shù)
};
read_bytes 在以下時(shí)機(jī)遞增:
→ submit_bio() 提交讀請(qǐng)求時(shí)
→ 僅統(tǒng)計(jì)實(shí)際到達(dá)塊設(shè)備的 I/O,不包含頁緩存命中
write_bytes 在以下時(shí)機(jī)遞增:
→ 頁面被標(biāo)記為臟頁時(shí)(mark_buffer_dirty())
→ 或直接 I/O 提交時(shí)
3.2 mpstat
功能:按 CPU 核心顯示統(tǒng)計(jì)信息。
底層實(shí)現(xiàn):
數(shù)據(jù)來源:/proc/stat
逐行解析:
cpu : user nice system idle iowait irq softirq steal guest guest_nice
cpu0 : ...
cpu1 : ...
cpuN : ...
mpstat 讀取兩次 /proc/stat,計(jì)算差值得到各核的使用率
關(guān)鍵指標(biāo)計(jì)算:
%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
內(nèi)核更新 /proc/stat 的時(shí)機(jī):
每次 tick(通常 1ms,CONFIG_HZ=1000)中斷時(shí)
account_process_tick() → 更新當(dāng)前進(jìn)程的 utime/stime
account_idle_ticks() → 更新全局 idle 計(jì)數(shù)
4. I/O 與存儲(chǔ)工具
4.1 iostat
功能:顯示 CPU 使用率和磁盤 I/O 統(tǒng)計(jì)。
底層實(shí)現(xiàn):
CPU 統(tǒng)計(jì):/proc/stat(同 top/mpstat)
磁盤 I/O 統(tǒng)計(jì):/proc/diskstats 或 /sys/block/<dev>/stat
/proc/diskstats 格式(每個(gè)設(shè)備一行):
Field 1: 主設(shè)備號(hào)
Field 2: 次設(shè)備號(hào)
Field 3: 設(shè)備名
Field 4: 讀完成次數(shù)
Field 5: 讀合并次數(shù)
Field 6: 讀扇區(qū)數(shù)
Field 7: 讀花費(fèi)時(shí)間(ms)
Field 8: 寫完成次數(shù)
Field 9: 寫合并次數(shù)
Field 10: 寫扇區(qū)數(shù)
Field 11: 寫花費(fèi)時(shí)間(ms)
Field 12: 正在進(jìn)行的 I/O 數(shù)
Field 13: I/O 總花費(fèi)時(shí)間(ms)
Field 14: 加權(quán) I/O 時(shí)間(ms)
關(guān)鍵指標(biāo)計(jì)算:
兩次采樣間:
tps = (d_reads + d_writes) / interval
每秒傳輸次數(shù)(含合并后的)
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 響應(yīng)時(shí)間(ms),含排隊(duì)時(shí)間
svctm = d_io_time / (d_reads + d_writes)
平均服務(wù)時(shí)間(ms),不含排隊(duì)
%util = d_io_time / (interval * 1000) * 100
設(shè)備利用率(>80% 通常表示飽和)
aqu-sz = d_weighted_io_time / (interval * 1000)
平均隊(duì)列深度
內(nèi)核如何收集 diskstats:
內(nèi)核 I/O 棧中的統(tǒng)計(jì)點(diǎn):
應(yīng)用層 → VFS → 塊層 → 設(shè)備驅(qū)動(dòng)
塊層(block layer)關(guān)鍵函數(shù):
→ blk_account_io_start() : I/O 開始,遞增 in_flight,記錄開始時(shí)間
→ blk_account_io_completion() : I/O 完成,更新完成次數(shù)、扇區(qū)數(shù)、耗時(shí)
每個(gè) request_queue 維護(hù)統(tǒng)計(jì)計(jì)數(shù)器:
struct request_queue {
struct disk_stats stats;
...
};
/proc/diskstats 由 diskstats_show() 函數(shù)生成
→ 遍歷所有塊設(shè)備的 request_queue
→ 讀取 stats 計(jì)數(shù)器
4.2 iotop
功能:按進(jìn)程顯示實(shí)時(shí)磁盤 I/O 使用率。
底層實(shí)現(xiàn):
iotop 使用兩種機(jī)制獲取數(shù)據(jù):
機(jī)制一:/proc/[pid]/io(I/O 字節(jié)數(shù))
→ read_bytes, write_bytes(實(shí)際塊設(shè)備 I/O)
→ 兩次采樣差值計(jì)算速率
機(jī)制二:/proc/task/[tid]/io(線程級(jí) I/O)
→ 遍歷 /proc/[pid]/task/ 獲取每個(gè)線程的 I/O
I/O 百分比計(jì)算:
→ 內(nèi)核通過 taskstats 接口提供延遲統(tǒng)計(jì)
→ netlink 接口:TASKSTATS_CMD_GET
→ 返回 struct taskstats 中的 delay accounting 數(shù)據(jù)
.blkio_delay_total : 等待塊 I/O 的累計(jì)時(shí)間(納秒)
.swapin_delay_total : 等待換入的累計(jì)時(shí)間(納秒)
I/O% = d_blkio_delay / interval / ncpu * 100
Swap% = d_swapin_delay / interval / ncpu * 100
taskstats 的內(nèi)核實(shí)現(xiàn):
內(nèi)核延遲統(tǒng)計(jì)(Delay Accounting):
在以下節(jié)點(diǎn)記錄時(shí)間戳:
→ schedule() 切換到進(jìn)程時(shí):記開始時(shí)間
→ 從 I/O 等待喚醒時(shí):記結(jié)束時(shí)間,累加到 blkio_delay_total
→ 頁面換入完成時(shí):累加到 swapin_delay_total
通過 netlink 接口導(dǎo)出:
→ 用戶空間發(fā)送 TASKSTATS_CMD_GET + pid
→ 內(nèi)核填充 struct taskstats 返回
→ iotop 解析 blkio_delay_total 計(jì)算百分比
注意:需要內(nèi)核編譯時(shí)開啟 CONFIG_TASK_DELAY_ACCT=y
否則 iotop 無法顯示 I/O 百分比
4.3 blktrace
功能:追蹤塊層 I/O 事件的詳細(xì)過程,配合 blkparse 分析。
底層實(shí)現(xiàn):
blktrace 的架構(gòu):
內(nèi)核模塊(blktrace.ko)→ 在塊層關(guān)鍵點(diǎn)插入 tracepoint
用戶空間(blktrace 命令)→ 通過 relayfs 讀取 trace 數(shù)據(jù)
內(nèi)核側(cè) tracepoint:
塊層 I/O 路徑:
應(yīng)用 → VFS → __generic_file_aio_write / generic_file_read
→ 塊層 submit_bio → 請(qǐng)求調(diào)度 → 設(shè)備驅(qū)動(dòng) → 中斷完成
blktrace 捕獲的事件:
┌──────────┬──────────────────────────────────────────┐
│ 事件標(biāo)識(shí) │ 含義 │
├──────────┼──────────────────────────────────────────┤
│ Q (Queue)│ I/O 請(qǐng)求進(jìn)入塊層(submit_bio) │
│ G (Get) │ 請(qǐng)求從調(diào)度器獲取 │
│ I (Issue)│ 請(qǐng)求發(fā)送給設(shè)備驅(qū)動(dòng) │
│ D (Done) │ 設(shè)備完成 I/O(中斷處理) │
│ C (Comp) │ 請(qǐng)求完成回調(diào) │
│ M (Merge)│ 請(qǐng)求被合并 │
│ P (Plug) │ 隊(duì)列被插入(蓄流) │
│ U (Unplug)│ 隊(duì)列被拔出(發(fā)送蓄積的請(qǐng)求) │
│ S (Sleep)│ 等待 I/O 完成 │
│ R (Remap)│ 請(qǐng)求被映射到其他設(shè)備(DM/MD) │
└──────────┴──────────────────────────────────────────┘
數(shù)據(jù)傳輸機(jī)制:
relayfs(relay filesystem):
內(nèi)核側(cè):
1. blktrace 在塊層注冊(cè) tracepoint 回調(diào)
2. 回調(diào)函數(shù)將事件寫入 per-CPU relay 緩沖區(qū)
→ 每個(gè) CPU 一個(gè)獨(dú)立的環(huán)形緩沖區(qū)
→ 無鎖寫入(per-CPU),零拷貝
用戶空間側(cè):
1. blktrace 打開 /sys/kernel/debug/block/<dev>/trace
2. 通過 relayfs 讀取每個(gè) CPU 的緩沖區(qū)
3. 寫入磁盤文件(<dev>.blktrace.<cpu>)
4. blkparse 解析二進(jìn)制數(shù)據(jù)輸出可讀格式
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 → 主:次設(shè)備號(hào)
3 → CPU 編號(hào)
1171 → 序列號(hào)
0.000... → 時(shí)間戳(秒)
1171 → 進(jìn)程 PID
Q/G/I/D/C → 事件類型
WS → 寫同步(Write Sync)
3492848 + 8 → 起始扇區(qū) + 扇區(qū)數(shù)
[kworker] → 進(jìn)程名
[0] → 完成時(shí)的錯(cuò)誤碼
I/O 延遲分析:
Q → I:排隊(duì)延遲(scheduler latency)
I → D:設(shè)備服務(wù)時(shí)間(device service time)
Q → C:端到端 I/O 延遲(total latency)
blkparse -w 10 -d /dev/sda 輸出統(tǒng)計(jì):
→ 按事件類型匯總平均延遲
→ 識(shí)別 I/O 調(diào)度器瓶頸(Q→I 過長(zhǎng))vs 設(shè)備瓶頸(I→D 過長(zhǎng))
4.4 dstat
功能:綜合系統(tǒng)資源統(tǒng)計(jì)工具,替代 vmstat/iostat/ifstat 等。
底層實(shí)現(xiàn):
dstat 的數(shù)據(jù)來源匯總:
┌──────────────────┬──────────────────────────────────────┐
│ 插件 │ 數(shù)據(jù)來源 │
├──────────────────┼──────────────────────────────────────┤
│ 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(進(jìn)程創(chuàng)建數(shù)) │
│ 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 + 計(jì)算中斷延遲 │
└──────────────────┴──────────────────────────────────────┘
dstat 的核心循環(huán):
1. 讀取所有啟用的插件的數(shù)據(jù)源
2. 計(jì)算與上一次采樣的差值
3. 格式化輸出一行
4. sleep(delay) → 默認(rèn) 1 秒
5. 重復(fù)
5. 網(wǎng)絡(luò)工具
5.1 sar
功能:System Activity Reporter,sysstat 套件核心工具,采集和報(bào)告系統(tǒng)活動(dòng)。
底層實(shí)現(xiàn):
sar 的工作架構(gòu):
1. 后臺(tái)守護(hù)進(jìn)程 sadc(System Activity Data Collector)
→ 定期(默認(rèn) 10 分鐘)采集數(shù)據(jù)寫入 /var/log/sa/saDD
→ 由 cron 任務(wù) /etc/cron.d/sysstat 觸發(fā)
→ 數(shù)據(jù)源同 dstat(全部來自 /proc)
2. sar 命令讀取二進(jìn)制日志文件并格式化輸出
→ sar -u → CPU
→ sar -r → 內(nèi)存
→ sar -b → I/O
→ sar -n DEV → 網(wǎng)絡(luò)
→ sar -W → 交換
→ sar -q → 隊(duì)列長(zhǎng)度和平均負(fù)載
→ sar -w → 上下文切換
→ sar -x → 指定進(jìn)程
sadc 內(nèi)核數(shù)據(jù)采集路徑:
→ 打開 /proc/stat, /proc/vmstat, /proc/diskstats, /proc/net/dev 等
→ 讀取計(jì)數(shù)器快照
→ 寫入二進(jìn)制格式的日志文件
→ 文件格式:struct sa_file_header + sa_record 數(shù)組
5.2 nicstat
功能:網(wǎng)卡流量統(tǒng)計(jì),顯示每秒讀寫字節(jié)數(shù)、包數(shù)、利用率。
底層實(shí)現(xiàn):
數(shù)據(jù)來源:/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
利用率計(jì)算:
%util = (d_bytes / interval) / interface_speed * 100
→ 對(duì)全雙工網(wǎng)卡,分別計(jì)算讀/寫利用率
→ 需要知道網(wǎng)卡速率(從 /sys/class/net/<dev>/speed 讀取,單位 Mbps)
關(guān)鍵指標(biāo):
rKB/s : 每秒接收 KB
wKB/s : 每秒發(fā)送 KB
rPk/s : 每秒接收包數(shù)
wPk/s : 每秒發(fā)送包數(shù)
rAvs : 平均接收包大?。ㄗ止?jié))
wAvs : 平均發(fā)送包大?。ㄗ止?jié))
%Util : 網(wǎng)卡利用率
Sat : 飽和度(基于丟包/錯(cuò)誤)
6. 內(nèi)存工具
6.1 vmstat
功能:報(bào)告虛擬內(nèi)存統(tǒng)計(jì),包含進(jìn)程、內(nèi)存、交換、I/O、系統(tǒng)、CPU。
底層實(shí)現(xiàn):
數(shù)據(jù)來源:
┌──────────────┬────────────────────────────────────────────┐
│ vmstat 字段 │ 來源 │
├──────────────┼────────────────────────────────────────────┤
│ procs-r │ /proc/stat 中的 running 進(jìn)程數(shù) │
│ procs-b │ /proc/stat 中的 blocked (D state) 進(jìn)程數(shù) │
├──────────────┼────────────────────────────────────────────┤
│ 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(換入頁數(shù)/s) │
│ swap-so │ /proc/vmstat pswpout(換出頁數(shù)/s) │
├──────────────┼────────────────────────────────────────────┤
│ io-bi │ /proc/vmstat pgpgin(讀入頁數(shù)/s) │
│ io-bo │ /proc/vmstat pgpgout(寫出頁數(shù)/s) │
├──────────────┼────────────────────────────────────────────┤
│ system-in │ /proc/stat intr(中斷數(shù)/s) │
│ system-cs │ /proc/stat ctxt(上下文切換數(shù)/s) │
├──────────────┼────────────────────────────────────────────┤
│ cpu-us/sy/id/wa/st │ /proc/stat 同 top │
└──────────────┴────────────────────────────────────────────┘
內(nèi)核如何統(tǒng)計(jì) pswpin/pswpout:
→ do_swap_page() 完成換入時(shí)遞增 pswpin
→ shrink_page_list() 將頁面寫回交換區(qū)時(shí)遞增 pswpout
→ 單位:頁(通常 4KB)
6.2 free
功能:顯示系統(tǒng)內(nèi)存使用和交換分區(qū)使用。
底層實(shí)現(xiàn):
數(shù)據(jù)來源:/proc/meminfo
total used free shared buff/cache available
Mem: 16384000 8192000 2048000 512000 6144000 7168000
Swap: 4096000 512000 3584000
計(jì)算公式:
total = MemTotal
free = MemFree
buff/cache= Buffers + Cached + SReclaimable
shared = Shmem(tmpfs + 共享內(nèi)存段)
available = MemAvailable(內(nèi)核估算的可用于啟動(dòng)新進(jìn)程的內(nèi)存)
used = total - free - buff/cache
MemAvailable 的內(nèi)核計(jì)算(mm/page_alloc.c: si_mem_available()):
→ 可用內(nèi)存 = Free + PageCache - 不可回收部分 + 可回收 slab
→ 考慮了水位線(watermark)限制
→ 比 free 更準(zhǔn)確反映"實(shí)際可用"內(nèi)存
6.3 pmap
功能:顯示進(jìn)程的內(nèi)存映射詳情。
底層實(shí)現(xiàn):
數(shù)據(jù)來源:/proc/[pid]/maps + /proc/[pid]/smaps
/proc/[pid]/maps 格式:
地址范圍 權(quán)限 偏移 設(shè)備 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 額外信息(每個(gè)映射區(qū)域):
Size: 812 kB ← 虛擬內(nèi)存大小
Rss: 460 kB ← 實(shí)際物理內(nèi)存
Pss: 230 kB ← 比例分?jǐn)偅ü蚕眄摪催M(jìn)程數(shù)均分)
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 ← 內(nèi)核頁大小
MMUPageSize: 4 kB ← MMU 頁大小
Locked: 0 kB ← mlock 鎖定的頁
pmap -x <pid> 讀取 smaps 匯總輸出
pmap -XX <pid> 顯示最詳細(xì)信息
7. 性能分析工具
7.1 perf
功能:Linux 性能分析框架,支持 CPU profiling、硬件計(jì)數(shù)器、tracepoint、kprobe 等。
底層實(shí)現(xiàn):
perf 的架構(gòu):
用戶空間:perf 命令
內(nèi)核空間:perf_event 子系統(tǒng)
┌───────────────────────────────────────────────────┐
│ perf 命令 │
│ stat / record / top / report / annotate │
└────────────────────┬──────────────────────────────┘
│ perf_event_open() 系統(tǒng)調(diào)用
┌────────────────────▼──────────────────────────────┐
│ perf_event 子系統(tǒng) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ 硬件計(jì)數(shù)器 │ │ tracepoint │ │ kprobe/ │ │
│ │ (PMC/MSR) │ │ (ftrace) │ │ uprobe │ │
│ └──────┬──────┘ └──────┬───────┘ └─────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ perf_event_context │ │
│ │ per-task 或 per-CPU 的事件上下文 │ │
│ │ 管理 PMC 寄存器分配、采樣、溢出中斷 │ │
│ └──────────────────┬───────────────────────────┘ │
│ │ │
│ ┌──────────────────▼───────────────────────────┐ │
│ │ ring buffer (mmap) │ │
│ │ perf_mmap → 用戶空間直接讀取采樣數(shù)據(jù) │ │
│ └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
perf_event_open 系統(tǒng)調(diào)用:
int perf_event_open(struct perf_event_attr *attr,
pid_t pid, int cpu, int group_fd, unsigned long flags);
參數(shù)含義:
pid : 監(jiān)控的進(jìn)程(-1 = 當(dāng)前,0 = 所有)
cpu : 監(jiān)控的 CPU 核心(-1 = 所有)
group_fd : 事件組(-1 = 新組)
attr : 事件配置
.type : PERF_TYPE_HARDWARE/SOFTWARE/TRACEPOINT/HW_CACHE/RAW
.config : 具體事件編號(hào)
.sample_period/freq : 采樣周期/頻率
.mmap : 是否記錄 mmap 事件
.task : 是否記錄 task 事件
.precise_ip : 采樣精度(0-3,越高越精確)
采樣模式(perf record)的實(shí)現(xiàn):
1. 用戶調(diào)用 perf_event_open() 創(chuàng)建事件
2. 內(nèi)核配置 PMC 寄存器:
→ 寫入 PERF_EVTSEL MSR(事件選擇 + 配置)
→ 設(shè)置計(jì)數(shù)器初始值(2^48 - sample_period)
3. 進(jìn)程運(yùn)行,PMC 遞增
4. PMC 溢出 → 觸發(fā) PMI(Performance Monitoring Interrupt)
5. PMI 處理程序(intel_pmu_handle_irq):
a. 讀取觸發(fā)溢出的 PMC
b. 采集樣本:
→ IP(指令指針)
→ TID(線程 ID)
→ 時(shí)間戳
→ 調(diào)用棧(如果啟用 -g,通過遍歷棧幀或使用 DWARF)
c. 寫入 per-CPU ring buffer
d. 重置計(jì)數(shù)器
6. 用戶空間通過 mmap 讀取 ring buffer
7. perf record 將樣本寫入 perf.data 文件
硬件計(jì)數(shù)器(PMC)的內(nèi)核驅(qū)動(dòng):
x86 架構(gòu):
Intel CPU: intel_pmu 驅(qū)動(dòng)
→ 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 驅(qū)動(dòng)
→ 類似結(jié)構(gòu),操作 AMD 的 PERF_CTL/PERF_CTR MSR
ARM 架構(gòu):
→ arm_pmu 驅(qū)動(dòng)
→ 操作 PMU 系統(tǒng)寄存器(PMXEVTYPER_EL0, PMXEVCNTR_EL0)
→ 溢出中斷由 PMU IRQ 處理
7.2 perf 常用子命令
┌──────────────────┬──────────────────────────────────────────┐
│ 子命令 │ 功能與底層實(shí)現(xiàn) │
├──────────────────┼──────────────────────────────────────────┤
│ perf stat │ 計(jì)數(shù)模式:讀取 PMC 差值,不采樣 │
│ → perf_event_open(COUNT_MODE) │
│ → ioctl(PERF_EVENT_IOC_ENABLE) │
│ → ioctl(PERF_EVENT_IOC_DISABLE) │
│ → read(fd) 讀取計(jì)數(shù)器值 │
├──────────────────┼──────────────────────────────────────────┤
│ perf record │ 采樣模式:PMC 溢出中斷采集樣本 │
│ → perf_event_open(SAMPLE_MODE) │
│ → mmap() 映射 ring buffer │
│ → PMC 溢出 → PMI → 寫入 ring buffer │
│ → 用戶空間 poll ring buffer 寫入文件 │
├──────────────────┼──────────────────────────────────────────┤
│ perf top │ 實(shí)時(shí)采樣:類似 record 但實(shí)時(shí)顯示熱點(diǎn) │
│ → 采樣機(jī)制同 record │
│ → 每 2-3 秒刷新直方圖 │
├──────────────────┼──────────────────────────────────────────┤
│ perf annotate │ 反匯編 + 樣本分布標(biāo)注 │
│ → 讀取 perf.data 中的樣本 │
│ → 解析 ELF 的 .text 段 │
│ → 用 objdump 反匯編 │
│ → 將樣本數(shù)映射到每條指令 │
├──────────────────┼──────────────────────────────────────────┤
│ perf trace │ 追蹤系統(tǒng)調(diào)用,類似 strace │
│ → 基于 tracepoint 而非 ptrace │
│ → 開銷遠(yuǎn)低于 strace │
└──────────────────┴──────────────────────────────────────────┘
8. 追蹤工具
8.1 ftrace
功能:內(nèi)核函數(shù)追蹤框架,支持函數(shù)追蹤、函數(shù)圖譜、事件追蹤等。
底層實(shí)現(xiàn):
ftrace 的架構(gòu):
┌───────────────────────────────────────────┐
│ 用戶接口 │
│ /sys/kernel/debug/tracing/ │
│ trace │
│ set_ftrace_filter │
│ available_tracers │
│ current_tracer │
├───────────────────────────────────────────┤
│ 追蹤器 │
│ function → 函數(shù)調(diào)用追蹤 │
│ function_graph → 函數(shù)調(diào)用圖譜 │
│ wakeup → 喚醒延遲追蹤 │
│ irqsoff → 中斷關(guān)閉時(shí)間追蹤 │
│ preemptoff → 搶占關(guān)閉時(shí)間追蹤 │
├───────────────────────────────────────────┤
│ 基礎(chǔ)設(shè)施 │
│ mcount / __fentry__ → 編譯器插入的鉤子 │
│ tracepoint → 靜態(tài)插樁點(diǎn) │
│ ring buffer → 追蹤數(shù)據(jù)存儲(chǔ) │
└───────────────────────────────────────────┘
函數(shù)追蹤的編譯時(shí)插樁:
GCC 的 -pg 選項(xiàng)會(huì)在每個(gè)函數(shù)入口插入調(diào)用:
x86: call mcount (舊版)
x86: call __fentry__ (新版,CONFIG_FUNCTION_TRACER)
原始代碼:
void my_func(void) {
do_something();
}
編譯后:
my_func:
call __fentry__ ← 編譯器自動(dòng)插入
... (函數(shù)體)
ret
未啟用 ftrace 時(shí):
__fentry__ 的實(shí)現(xiàn)是簡(jiǎn)單的 ret 指令(1 條指令開銷 ≈ 0)
啟用 ftrace 時(shí):
內(nèi)核將 __fentry__ 的入口替換為跳轉(zhuǎn)到追蹤處理函數(shù)
→ 使用 text_poke_bp() 修改內(nèi)核代碼段
→ 替換為 jmp ftrace_caller 或 call ftrace_caller
動(dòng)態(tài)過濾:
set_ftrace_filter 的實(shí)現(xiàn):
→ 內(nèi)核維護(hù)一個(gè) ftrace_func_hash 哈希表
→ 寫入函數(shù)名時(shí),查找對(duì)應(yīng)的符號(hào)地址
→ 將該地址對(duì)應(yīng)的 ftrace_rec 標(biāo)記為 enabled
→ ftrace_caller 執(zhí)行時(shí)檢查當(dāng)前函數(shù)是否在 hash 中
→ 只追蹤匹配的函數(shù),減少開銷
8.2 eBPF / bpftrace
功能:在內(nèi)核中安全運(yùn)行用戶編寫的程序,實(shí)現(xiàn)自定義追蹤和監(jiān)控。
底層實(shí)現(xiàn):
eBPF 的架構(gòu):
┌───────────────────────────────────────────────┐
│ 用戶空間 │
│ bpftrace / BCC / libbpf │
│ → 編寫 eBPF 程序(類 C 語法) │
│ → 編譯為 BPF 字節(jié)碼 │
│ → bpf() 系統(tǒng)調(diào)用加載到內(nèi)核 │
├───────────────────────────────────────────────┤
│ 內(nèi)核驗(yàn)證器 │
│ verifier.c │
│ → 確保程序安全(不會(huì)崩潰內(nèi)核) │
│ → 檢查:無無限循環(huán)、無越界訪問、棧大小限制 │
│ → 驗(yàn)證通過后 JIT 編譯為本機(jī)指令 │
├───────────────────────────────────────────────┤
│ 掛載點(diǎn) │
│ kprobe → 內(nèi)核函數(shù)入口/返回 │
│ tracepoint → 靜態(tài)追蹤點(diǎn) │
│ perf_event → PMC 溢出 │
│ XDP → 網(wǎng)卡收包路徑 │
│ tc → 流量控制 │
│ cgroup → cgroup 事件 │
│ socket → socket 操作 │
├───────────────────────────────────────────────┤
│ 數(shù)據(jù)傳遞 │
│ BPF_MAP → 內(nèi)核-用戶空間共享數(shù)據(jù)結(jié)構(gòu) │
│ perf_event → 采樣數(shù)據(jù)傳遞 │
└───────────────────────────────────────────────┘
eBPF 程序的執(zhí)行流程:
1. 用戶編寫 eBPF 程序
bpftrace -e 'kprobe:do_sys_open { @opens = count(); }'
2. 編譯為 BPF 字節(jié)碼
→ bpftrace 內(nèi)部使用 LLVM 將腳本編譯為 BPF 字節(jié)碼
3. bpf(BPF_PROG_LOAD) 系統(tǒng)調(diào)用加載到內(nèi)核
4. 內(nèi)核驗(yàn)證器檢查:
→ DAG 驗(yàn)證(無環(huán))
→ 寄存器狀態(tài)追蹤(每個(gè)分支的所有可能值)
→ 內(nèi)存訪問邊界檢查
→ 最大指令數(shù)限制(1M 條)
→ 最大棧深度(512 字節(jié))
5. JIT 編譯為本機(jī)指令
→ bpf_int_jit_compile() 將 BPF 字節(jié)碼翻譯為 x86/ARM 機(jī)器碼
→ 替換 BPF 指令為原生 CPU 指令
→ 性能接近原生內(nèi)核代碼
6. 掛載到 kprobe/tracepoint
→ 當(dāng) do_sys_open 被調(diào)用時(shí),JIT 編譯后的 BPF 程序執(zhí)行
→ 更新 BPF_MAP 中的計(jì)數(shù)器
7. 用戶空間通過 bpf(BPF_MAP_LOOKUP_ELEM) 讀取結(jié)果
BPF Map 類型:
┌──────────────────┬──────────────────────────────────────┐
│ Map 類型 │ 用途 │
├──────────────────┼──────────────────────────────────────┤
│ BPF_MAP_TYPE_HASH│ 通用哈希表 │
│ BPF_MAP_TYPE_ARRAY│ 通用數(shù)組 │
│ BPF_MAP_TYPE_PERF_EVENT_ARRAY │ per-CPU 事件緩沖區(qū) │
│ BPF_MAP_TYPE_PERCPU_HASH │ per-CPU 哈希表(無鎖) │
│ BPF_MAP_TYPE_RINGBUF │ 可變大小環(huán)形緩沖區(qū) │
│ BPF_MAP_TYPE_STACK_TRACE │ 調(diào)用棧追蹤 │
│ BPF_MAP_TYPE_LRU_HASH │ 帶 LRU 淘汰的哈希表 │
└──────────────────┴──────────────────────────────────────┘
8.3 SystemTap
功能:內(nèi)核動(dòng)態(tài)追蹤框架,用腳本語言編寫探測(cè)點(diǎn)。
底層實(shí)現(xiàn):
SystemTap 的執(zhí)行流程:
1. 用戶編寫 .stp 腳本
probe syscall.open { printf("%s opened %s\n", execname(), user_string($filename)); }
2. stap 編譯器將腳本翻譯為 C 代碼
→ 解析腳本語法
→ 生成內(nèi)核模塊的 C 源碼
→ 插入 probe handler 函數(shù)
3. 調(diào)用 GCC 編譯為內(nèi)核模塊(.ko)
4. insmod 加載內(nèi)核模塊
→ 注冊(cè) kprobe 探測(cè)點(diǎn)
→ kprobe 在目標(biāo)函數(shù)入口插入斷點(diǎn)(INT 3 或 ftrace 鉤子)
5. 探測(cè)點(diǎn)觸發(fā)時(shí):
→ kprobe 回調(diào)執(zhí)行 probe handler
→ handler 讀取參數(shù)、收集數(shù)據(jù)
→ 數(shù)據(jù)寫入 relayfs 緩沖區(qū)
6. 用戶空間 stap 進(jìn)程從 relayfs 讀取并輸出
7. Ctrl+C 時(shí) rmmod 卸載模塊
與 eBPF 的對(duì)比:
┌──────────────┬──────────────────┬──────────────────┐
│ │ SystemTap │ eBPF │
├──────────────┼──────────────────┼──────────────────┤
│ 安全性 │ 需要加載 .ko │ 驗(yàn)證器保證安全 │
│ │ 可能崩潰內(nèi)核 │ 不會(huì)崩潰內(nèi)核 │
├──────────────┼──────────────────┼──────────────────┤
│ 權(quán)限 │ root │ root / CAP_BPF │
├──────────────┼──────────────────┼──────────────────┤
│ 編譯 │ GCC 編譯 .ko │ LLVM → BPF 字節(jié)碼│
│ │ 需要內(nèi)核頭文件 │ 不需要內(nèi)核頭文件 │
├──────────────┼──────────────────┼──────────────────┤
│ 性能 │ 中等(kprobe) │ 高(JIT 編譯) │
├──────────────┼──────────────────┼──────────────────┤
│ 功能范圍 │ 更靈活 │ 受驗(yàn)證器限制 │
├──────────────┼──────────────────┼──────────────────┤
│ 生產(chǎn)可用性 │ 風(fēng)險(xiǎn)較高 │ 適合生產(chǎn)環(huán)境 │
└──────────────┴──────────────────┴──────────────────┘
8.4 bcc 工具集
功能:基于 eBPF 的性能分析工具集,提供數(shù)十個(gè)現(xiàn)成工具。
常用工具與底層實(shí)現(xiàn):
┌──────────────────┬──────────────────────────────────────────────────┐
│ 工具 │ 功能 + 底層 eBPF 掛載點(diǎn) │
├──────────────────┼──────────────────────────────────────────────────┤
│ execsnoop │ 追蹤新進(jìn)程執(zhí)行 │
│ → 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 (記錄開始時(shí)間) │
│ → tracepoint:block:block_rq_complete (計(jì)算延遲) │
│ → BPF_MAP_TYPE_HASH 存儲(chǔ)進(jìn)行中的請(qǐng)求 │
│ → BPF_MAP_TYPE_PERCPU_ARRAY/LRU_HASH 存直方圖 │
├──────────────────┼──────────────────────────────────────────────────┤
│ biosnoop │ 追蹤每個(gè)塊 I/O 請(qǐng)求 │
│ → 同 biolatency 的 tracepoint │
│ → perf_event 輸出每條 I/O 詳情 │
├──────────────────┼──────────────────────────────────────────────────┤
│ cachestat │ 頁緩存統(tǒng)計(jì) │
│ → kprobe:mark_page_accessed (緩存命中) │
│ → kprobe:mark_buffer_dirty (臟頁) │
│ → kprobe:add_to_page_cache_lru (緩存添加) │
│ → kprobe:__remove_mapping (緩存移除) │
├──────────────────┼──────────────────────────────────────────────────┤
│ tcpconnect │ 追蹤 TCP 主動(dòng)連接 │
│ → kprobe:tcp_v4_connect / tcp_v6_connect │
├──────────────────┼──────────────────────────────────────────────────┤
│ tcpaccept │ 追蹤 TCP 被動(dòng)接受 │
│ → kprobe:inet_csk_accept │
├──────────────────┼──────────────────────────────────────────────────┤
│ offcputime │ 追蹤 CPU 外等待時(shí)間 │
│ → kprobe:schedule (記錄離開 CPU 時(shí)間) │
│ → kprobe:finish_task_switch (記錄回到 CPU 時(shí)間) │
│ → BPF_MAP_TYPE_STACK_TRACE 采集調(diào)用棧 │
├──────────────────┼──────────────────────────────────────────────────┤
│ memleak │ 內(nèi)存泄漏檢測(cè) │
│ → kprobe:kmalloc / kmem_cache_alloc (記錄分配) │
│ → kprobe:kfree / kmem_cache_free (記錄釋放) │
│ → BPF_MAP_TYPE_HASH 存儲(chǔ)未釋放的分配 │
│ → 定期掃描,長(zhǎng)時(shí)間未釋放的視為泄漏 │
├──────────────────┼──────────────────────────────────────────────────┤
│ profile │ CPU profiling(基于定時(shí)器采樣) │
│ → perf_event (CPU 周期采樣) │
│ → BPF_MAP_TYPE_STACK_TRACE 采集調(diào)用棧 │
│ → BPF_MAP_TYPE_PERCPU_HASH 統(tǒng)計(jì)棧出現(xiàn)次數(shù) │
└──────────────────┴──────────────────────────────────────────────────┘
8.5 kprobe / uprobe
功能:內(nèi)核/用戶空間動(dòng)態(tài)插樁機(jī)制,是 ftrace、eBPF、SystemTap 的底層基礎(chǔ)設(shè)施。
kprobe 底層實(shí)現(xiàn):
x86 上 kprobe 的實(shí)現(xiàn):
1. 注冊(cè) kprobe:
→ 用戶指定目標(biāo)函數(shù)地址
→ 保存目標(biāo)地址的原始指令
→ 將目標(biāo)地址第一個(gè)字節(jié)替換為 INT 3(0xCC,斷點(diǎn)指令)
2. 執(zhí)行到 INT 3:
→ CPU 觸發(fā) #DB 異常 → 進(jìn)入 kprobe_handler()
→ 單步執(zhí)行原始指令(設(shè)置 TF 位)
→ 執(zhí)行 pre_handler 回調(diào)
→ 執(zhí)行后恢復(fù)執(zhí)行
優(yōu)化:kprobe 跳轉(zhuǎn)優(yōu)化
→ 如果目標(biāo)函數(shù)開頭 ≥ 5 字節(jié),直接替換為 JMP 指令
→ 跳轉(zhuǎn)到 kprobe 的處理代碼
→ 避免 INT 3 的異常處理開銷
→ 需要啟用 CONFIG_OPTPROBES
uprobe 底層實(shí)現(xiàn):
用戶空間插樁:
1. 注冊(cè) uprobe:
→ 指定可執(zhí)行文件 + 偏移量
→ 內(nèi)核在目標(biāo)文件的 page cache 中找到對(duì)應(yīng)頁
→ 將目標(biāo)地址替換為 INT 3(寫時(shí)拷貝 COW)
2. 進(jìn)程執(zhí)行到 INT 3:
→ 觸發(fā) #DB 異常
→ 內(nèi)核檢查是否是 uprobe 斷點(diǎn)
→ 執(zhí)行 uprobe handler
→ 單步執(zhí)行原始指令
→ 恢復(fù)執(zhí)行
3. uprobe 與 kprobe 的區(qū)別:
→ kprobe 修改內(nèi)核代碼段(所有進(jìn)程共享)
→ uprobe 修改用戶空間頁(通過 COW,只影響目標(biāo)進(jìn)程)
→ uprobe 需要處理進(jìn)程 fork 時(shí)的斷點(diǎn)繼承
8.6 tracepoint
功能:內(nèi)核中預(yù)定義的靜態(tài)追蹤點(diǎn),比 kprobe 開銷更低、更穩(wěn)定。
底層實(shí)現(xiàn):
tracepoint 的定義(內(nèi)核源碼中):
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)
);
編譯后展開為:
// 函數(shù)入口的鉤子
static inline void trace_sched_switch(struct task_struct *prev,
struct task_struct *next) {
if (trace_sched_switch_enabled()) { ← 快速檢查(分支預(yù)測(cè)優(yōu)化)
__trace_sched_switch(prev, next); ← 慢路徑:寫入 ring buffer
}
}
// 未啟用時(shí):trace_sched_switch_enabled() 返回 false
// 開銷:一次條件判斷 + 分支預(yù)測(cè)正確 ≈ 0
tracepoint vs kprobe:
┌──────────────┬─────────────────────┬──────────────────────┐
│ │ tracepoint │ kprobe │
├──────────────┼─────────────────────┼──────────────────────┤
│ 插樁方式 │ 編譯時(shí)靜態(tài)插入 │ 運(yùn)行時(shí)動(dòng)態(tài)替換指令 │
│ 穩(wěn)定性 │ ABI 穩(wěn)定 │ 依賴內(nèi)核內(nèi)部實(shí)現(xiàn) │
│ 未啟用開銷 │ ≈ 0(條件跳轉(zhuǎn)) │ ≈ 0(ftrace nop) │
│ 啟用開銷 │ 低(直接調(diào)用) │ 中(INT 3 → 異常) │
│ 可追蹤位置 │ 僅預(yù)定義位置 │ 任意函數(shù)入口/返回 │
│ 參數(shù)訪問 │ 結(jié)構(gòu)化(有類型) │ 需要知道寄存器/棧布局 │
└──────────────┴─────────────────────┴──────────────────────┘
8.7 常用 tracepoint 列表
┌──────────────────────┬───────────────────────────────────────────┐
│ 子系統(tǒng) │ 關(guān)鍵 tracepoint │
├──────────────────────┼───────────────────────────────────────────┤
│ sched(調(diào)度) │ sched_switch → 進(jìn)程切換 │
│ │ sched_wakeup → 進(jìn)程喚醒 │
│ │ sched_process_fork → 進(jìn)程創(chuàng)建 │
│ │ sched_process_exit → 進(jìn)程退出 │
│ │ sched_migrate_task → 進(jìn)程遷移 │
├──────────────────────┼───────────────────────────────────────────┤
│ block(塊層) │ block_rq_issue → I/O 請(qǐng)求提交 │
│ │ block_rq_complete → I/O 請(qǐng)求完成 │
│ │ block_rq_requeue → I/O 請(qǐng)求重入隊(duì)列 │
│ │ block_bio_merge → bio 合并 │
├──────────────────────┼───────────────────────────────────────────┤
│ syscalls(系統(tǒng)調(diào)用) │ sys_enter_openat → open 系統(tǒng)調(diào)用入口 │
│ │ sys_exit_openat → open 系統(tǒng)調(diào)用返回 │
│ │ sys_enter_read / write / ... │
├──────────────────────┼───────────────────────────────────────────┤
│ net(網(wǎng)絡(luò)) │ net_dev_xmit → 網(wǎng)絡(luò)包發(fā)送 │
│ │ netif_receive_skb → 網(wǎng)絡(luò)包接收 │
│ │ napi_poll → NAPI 輪詢 │
├──────────────────────┼───────────────────────────────────────────┤
│ irq(中斷) │ irq_handler_entry → 中斷處理入口 │
│ │ irq_handler_exit → 中斷處理退出 │
│ │ softirq_entry/exit → 軟中斷處理 │
├──────────────────────┼───────────────────────────────────────────┤
│ rpm(電源管理) │ rpm_idle/suspend/resume → 運(yùn)行時(shí)電源管理 │
├──────────────────────┼───────────────────────────────────────────┤
│ writeback(回寫) │ writeback_start → 回寫開始 │
│ │ writeback_written → 回寫完成 │
│ │ writeback_wait → 回寫等待 │
└──────────────────────┴───────────────────────────────────────────┘
查看所有可用 tracepoint:
ls /sys/kernel/debug/tracing/available_events
或 perf list tracepoint