工具地圖#

核心觀測有四大類工具,目的不同:

類別工具解決什麼問題
日誌printkpr_debug、dynamic_debug「現在發生什麼了」
追蹤ftrace、tracepoints、kprobes、eBPF「事件何時發生、誰呼叫誰」
取樣分析perfperf top「CPU 時間花在哪」
崩潰分析Oops 解析、kdump、crash「為什麼掛了」

選對工具 = 解決一半問題。

printk 與動態輸出#

pr_emerg("system unusable\n");
pr_alert("immediate action needed\n");
pr_crit("critical error\n");
pr_err("regular error: %d\n", err);
pr_warn("something fishy\n");
pr_notice("noteworthy event\n");
pr_info("hello world\n");
pr_debug("trace: x=%d\n", x);     // 預設不輸出

printk 不能用太多 ── 它會:

  • 寫到 ring buffer(環狀,舊的會被擠掉)
  • 同步輸出到 console(如序列埠、framebuffer)── 在嚴重熱路徑會拖很慢
  • 觸發 wake_up(喚醒 syslog daemon)

讀日誌:

dmesg                  # 列印 kernel ring buffer
dmesg -w               # tail -f 模式
dmesg -T               # 帶人類可讀時間
dmesg --level=err,warn # 只看特定等級
journalctl -k          # systemd 整合版

dynamic_debug#

pr_debug 預設關閉。可以動態開特定檔案 / 函式 / 行號的 debug:

mount -t debugfs none /sys/kernel/debug

# 開啟特定檔案的所有 pr_debug
echo 'file my_driver.c +p' > /sys/kernel/debug/dynamic_debug/control

# 特定函式
echo 'func my_function +p' > /sys/kernel/debug/dynamic_debug/control

# 關閉
echo 'file my_driver.c -p' > /sys/kernel/debug/dynamic_debug/control

+p 開、-p 關、+f 加上函式名、+l 加上行號、+t 加上 thread id。

procfs / sysfs / debugfs#

核心給 userspace 觀察與控制的三種檔案系統:

檔案系統路徑用途
procfs/proc行程資訊(/proc/<pid>/)+ 部分核心狀態
sysfs/sys設備、驅動、kobject 階層;正式介面
debugfs/sys/kernel/debug開發/除錯用,無穩定性保證;要 mount

/proc 重點#

cat /proc/cpuinfo            # CPU 資訊
cat /proc/meminfo            # 記憶體統計
cat /proc/loadavg            # 負載平均
cat /proc/version            # kernel 版本
cat /proc/cmdline            # boot 參數
cat /proc/modules            # 已載入模組
cat /proc/interrupts         # IRQ 統計
cat /proc/<pid>/status       # 行程狀態
cat /proc/<pid>/maps         # 虛擬記憶體佈局
cat /proc/<pid>/stack        # 核心 stack(被卡住時超有用)
cat /proc/<pid>/syscall      # 當前在哪個 syscall

對診斷某個行程「卡住了」極度有用 ── 看 stacksyscall 通常就知道原因。

/sys#

每個 device、driver、subsystem 都會在 /sys 下出現:

ls /sys/class/net/eth0/        # 一張網卡的所有屬性
cat /sys/class/net/eth0/operstate
cat /sys/block/sda/queue/scheduler
echo bfq > /sys/block/sda/queue/scheduler

ftrace ── 核心內建的追蹤神器#

ftrace 不需要任何外掛,內建在每個現代核心。功能涵蓋:

  • 函式追蹤(每個被呼叫的核心函式)
  • tracepoint(核心預埋的事件點)
  • 延遲追蹤(中斷關閉時間、preempt 關閉時間)
  • 函式計數圖(function graph)

直接用 tracefs#

mount -t tracefs none /sys/kernel/tracing
cd /sys/kernel/tracing

# 列出可用 tracer
cat available_tracers
# nop function function_graph wakeup wakeup_rt irqsoff preemptoff ...

# 啟用 function tracer
echo function > current_tracer
echo 1 > tracing_on
sleep 0.1
echo 0 > tracing_on
cat trace | head -30

trace-cmd ── 友善的包裝#

sudo apt install trace-cmd

trace-cmd record -p function -F -- ./my_program
trace-cmd report

tracepoints#

核心預埋的事件點,比函式追蹤更穩定(API)、更快(編譯期 nop):

ls /sys/kernel/tracing/events/
# block/  fs/  irq/  kvm/  net/  sched/  ...

# 追蹤 sched_switch
echo 1 > /sys/kernel/tracing/events/sched/sched_switch/enable
cat /sys/kernel/tracing/trace

tracepoints 是 eBPF / bpftrace 的常見鉤子

perf#

perf 是 Linux 的瑞士刀,做 sampling、counting、profiling:

# 系統層 CPU profile
sudo perf top                                      # 即時 top of stack
sudo perf record -a -g sleep 10                    # 全系統 10 秒,含 call graph
sudo perf report                                   # 互動式查看

# 特定行程
sudo perf record -p <pid> -g
sudo perf record -- ./my_program

# Hardware counter
sudo perf stat -- ./my_program
sudo perf stat -e cache-misses,instructions,cycles -- ./my_program

# 統計各 syscall
sudo perf trace -e 'syscalls:sys_enter_*' -p <pid>

# Off-CPU 分析(誰睡了多久)
sudo perf record -e sched:sched_switch -g sleep 10

火焰圖(flame graph):

sudo perf record -F 99 -ag -- sleep 30
sudo perf script | stackcollapse-perf.pl | flamegraph.pl > out.svg

把 perf 輸出餵給 FlameGraph ,產生視覺化 ── Brendan Gregg 推廣的這個方法是現代效能診斷必學工具。

eBPF / bpftrace#

eBPF 是把「安全的 sandbox 程式」掛到核心 hook 點的技術。bpftrace 是它的 awk-like 前端:

# 列舉 syscall
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s: %s\n", comm, str(args->filename)); }'

# 統計 page fault
sudo bpftrace -e 'software:page-faults:1 { @[comm] = count(); }'

# 看誰在做 ext4_sync
sudo bpftrace -e 'kprobe:ext4_sync_file { @[comm] = count(); }'

# I/O 大小分布
sudo bpftrace -e 'tracepoint:block:block_rq_issue { @bytes = hist(args->bytes); }'

eBPF 取代了 SystemTap、DTrace 的角色,成為 Linux 觀測的事實標準。

工具集:

  • bcc:完整 toolkit,多用 Python 寫(tcpconnectexecsnoopopensnoopbiosnoop
  • bpftrace:one-liner 友善
  • libbpf-tools:純 C,不依賴 Python,效能最好

kprobes / uprobes#

動態插入 trace point:

# 透過 ftrace 介面
echo 'p:my_probe vfs_read' > /sys/kernel/tracing/kprobe_events
echo 1 > /sys/kernel/tracing/events/kprobes/my_probe/enable

或透過 perf:

sudo perf probe -a 'vfs_read filename=$arg2:string'
sudo perf record -e probe:vfs_read -aR sleep 5
sudo perf script

uprobes 是同樣機制但用在 userspace 函式(/usr/lib/libssl.so:SSL_read)。

Oops 與 panic 解析#

當核心遇到非預期狀況(NULL deref、bad page fault),會印「Oops」。範例:

BUG: kernel NULL pointer dereference, address: 0000000000000028
#PF: supervisor read access in kernel mode
#PF: error_code(0x0000) - not-present page
PGD 0 P4D 0
Oops: 0000 [#1] PREEMPT SMP PTI
CPU: 0 PID: 1234 Comm: my_program
RIP: 0010:my_function+0x12/0x40 [my_module]
Code: 48 8b 47 28 ...
RSP: 0018:ffffb...
Call Trace:
 <TASK>
 do_something+0x45/0xa0 [my_module]
 vfs_read+0x9c/0x190
 ksys_read+0x68/0xe0
 do_syscall_64+0x5c/0x90
 entry_SYSCALL_64+0xae/0x130
 </TASK>

讀法:

  • RIP:出事的指令位址。my_function+0x12 表示 my_function 起點偏移 18 byte
  • Code:當下指令的 byte,可反組譯確認
  • Call Trace:呼叫鏈,從上到下是「最近 ➡️ 最遠」

把 RIP 翻回原始碼:

addr2line -e my_module.ko -f 0x12
# 或:
objdump -dS my_module.ko | grep -A20 'my_function>'

#1 表示「這是第 1 次 oops」── 之後核心會被標記為 tainted,但不會停。如果是 panic(Oops 致命到無法繼續),系統會凍結或重啟(取決於 panic_on_oopspanic sysctl)。

kdump ── 崩潰時的記憶體 dump#

對生產環境,崩潰當下要保留現場。kdump 機制:

  1. 開機載入第二個 kernel(capture kernel)到保留區
  2. 主 kernel panic ➡️ kexec 跳到 capture kernel
  3. capture kernel 把主 kernel 的記憶體 dump 到磁碟(/var/crash/
  4. 重啟回主 kernel

事後用 crash 工具分析:

sudo crash /usr/lib/debug/.../vmlinux /var/crash/.../vmcore
crash> bt              # 崩潰時的 stack
crash> ps              # 當時行程列表
crash> mod             # 已載入模組
crash> log             # dmesg

記憶體錯誤:KASAN、KMEMLEAK、KFENCE#

工具偵測什麼開銷
KASANuse-after-free、out-of-bounds~3x 慢
KMEMLEAK記憶體洩漏中度
KFENCE取樣式 KASAN(生產環境可開)微乎其微
UBSANundefined behavior中度

開發/CI 用 KASAN 抓記憶體錯誤;生產用 KFENCE 抓取樣樣本。CONFIG_KASAN 預設關閉,需要重編核心。

鎖偵錯:lockdep#

CONFIG_PROVE_LOCKING 開啟 lockdep,執行期追蹤所有鎖的取得順序,發現潛在死鎖立刻警告。即使沒實際死鎖、只要有「path A: lock1 ➡️ lock2」與「path B: lock2 ➡️ lock1」就會報。

WARNING: possible recursive locking detected
...
 (mylock){+.+.}, at: foo+0x10/0x50
but task is already holding lock:
 (mylock){+.+.}, at: bar+0x40/0x80

開發核心程式碼強烈建議開啟

效能調校的方法論#

Brendan Gregg 的 USE method:對每個資源檢查 Utilization、Saturation、Errors。

資源觀測工具
CPUmpstat -P ALL 1uptime
記憶體vmstat 1free
磁碟iostat -x 1
網路sar -n DEV 1ss -s

效能問題不要直接撲到工具,先想:

  1. 系統層哪個資源飽和了?(USE method 過一遍)
  2. 哪個行程貢獻最多?(top、pidstat)
  3. 那個行程在做什麼?(perf record + flamegraph)
  4. 為什麼?(具體 syscall、具體核心路徑)

從上而下,每層都用對的工具。

小結#

核心觀測的工具梯子:

  • 看「現在怎麼了」➡️ dmesg/proc/sys
  • 看「事件何時發生」➡️ ftrace、tracepoint、bpftrace
  • 看「時間花在哪」➡️ perf record + flamegraph
  • 看「為什麼掛了」➡️ Oops 解析、kdump + crash
  • 看「有沒有 bug」➡️ KASAN、lockdep(開發期)

實務上 90% 的問題用 dmesg + perf + bpftrace 三件套就夠了。