系統(tǒng)工具

Linux 系統(tǒng)工具功能與底層實(shí)現(xiàn)

目錄


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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容