工具地圖#
核心觀測有四大類工具,目的不同:
| 類別 | 工具 | 解決什麼問題 |
|---|---|---|
| 日誌 | printk、pr_debug、dynamic_debug | 「現在發生什麼了」 |
| 追蹤 | ftrace、tracepoints、kprobes、eBPF | 「事件何時發生、誰呼叫誰」 |
| 取樣分析 | perf、perf 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對診斷某個行程「卡住了」極度有用 ── 看 stack 與 syscall 通常就知道原因。
/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/schedulerftrace ── 核心內建的追蹤神器#
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 -30trace-cmd ── 友善的包裝#
sudo apt install trace-cmd
trace-cmd record -p function -F -- ./my_program
trace-cmd reporttracepoints#
核心預埋的事件點,比函式追蹤更穩定(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/tracetracepoints 是 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 寫(
tcpconnect、execsnoop、opensnoop、biosnoop) - 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 scriptuprobes 是同樣機制但用在 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_oops、panic sysctl)。
kdump ── 崩潰時的記憶體 dump#
對生產環境,崩潰當下要保留現場。kdump 機制:
- 開機載入第二個 kernel(capture kernel)到保留區
- 主 kernel panic ➡️ kexec 跳到 capture kernel
- capture kernel 把主 kernel 的記憶體 dump 到磁碟(
/var/crash/) - 重啟回主 kernel
事後用 crash 工具分析:
sudo crash /usr/lib/debug/.../vmlinux /var/crash/.../vmcore
crash> bt # 崩潰時的 stack
crash> ps # 當時行程列表
crash> mod # 已載入模組
crash> log # dmesg記憶體錯誤:KASAN、KMEMLEAK、KFENCE#
| 工具 | 偵測什麼 | 開銷 |
|---|---|---|
| KASAN | use-after-free、out-of-bounds | ~3x 慢 |
| KMEMLEAK | 記憶體洩漏 | 中度 |
| KFENCE | 取樣式 KASAN(生產環境可開) | 微乎其微 |
| UBSAN | undefined 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。
| 資源 | 觀測工具 |
|---|---|
| CPU | mpstat -P ALL 1、uptime |
| 記憶體 | vmstat 1、free |
| 磁碟 | iostat -x 1 |
| 網路 | sar -n DEV 1、ss -s |
效能問題不要直接撲到工具,先想:
- 系統層哪個資源飽和了?(USE method 過一遍)
- 哪個行程貢獻最多?(top、pidstat)
- 那個行程在做什麼?(perf record + flamegraph)
- 為什麼?(具體 syscall、具體核心路徑)
從上而下,每層都用對的工具。
小結#
核心觀測的工具梯子:
- 看「現在怎麼了」➡️
dmesg、/proc、/sys - 看「事件何時發生」➡️ ftrace、tracepoint、bpftrace
- 看「時間花在哪」➡️ perf record + flamegraph
- 看「為什麼掛了」➡️ Oops 解析、kdump + crash
- 看「有沒有 bug」➡️ KASAN、lockdep(開發期)
實務上 90% 的問題用 dmesg + perf + bpftrace 三件套就夠了。