作業系統除錯 (Operating-System Debugging)
本系列文章內容參考自經典教材 Operating System Concepts, 10th Edition (Silberschatz, Galvin, Gagne)。本文對應章節:Section 2.10 Operating-System Debugging。
廣義而言,除錯(Debugging) 是在系統中尋找並修復錯誤的活動,涵蓋硬體與軟體兩個層面。值得注意的是,效能問題也被視為一種 bug,因此除錯也包含效能調校(Performance Tuning),即透過消除處理瓶頸來提升整體效能。本章將探討 OS 如何支援三種核心除錯活動:失敗分析、效能監控,以及動態核心追蹤。
2.10.1 失敗分析 (Failure Analysis)
Process 層級的失敗:Core Dump
當一個 Process 發生錯誤而失敗時,OS 通常會做兩件事:
- 寫入 log 檔案:將錯誤資訊記錄到 log file,用於通知系統管理員或使用者問題的發生
- 產生 core dump:拍下 Process 在失敗當下的完整記憶體快照(a capture of the memory of the process),儲存到檔案中供後續分析
「core」這個詞來自早期電腦使用磁蕊記憶體(Magnetic Core Memory) 的時代,當時記憶體就被稱為「core」。雖然現代早已改用 DRAM,這個名稱卻沿用至 今。
core dump 檔案可以用偵錯工具(Debugger) 開啟。Debugger 允許程式設計師探索 Process 在失敗當下的程式碼與記憶體狀態,例如查看哪個變數持有非法值、哪個指標已超出有效範圍,是定位 bug 根本原因的重要工具。
Kernel 層級的失敗:Crash Dump
除錯 user-level Process 的程式碼本身已是一大挑戰,而除錯 OS kernel 則更為複雜,原因有三:
- 規模與複雜度:kernel 程式碼量龐大、子系統高度交織
- 直接控制硬體:kernel 中的錯誤可能直接導致整台機器失去回應
- 缺乏 user-level 工具:在 user-level 可用的偵錯工具在 kernel 層級幾乎派不上用場
kernel 中的失敗稱為 crash(崩潰)。當 crash 發生時,OS 同樣會將錯誤資訊記錄到 log 檔案,並將記憶體狀態儲存到 crash dump。
然而,有一個關鍵問題:如果 crash 發生在 file-system 的程式碼裡,kernel 還能把 crash dump 寫到檔案系統嗎?
答案是不行。一旦 file-system 的程式碼本身已發生嚴重錯誤,再嘗試透過它寫入檔案,不僅可能寫入失敗,還可能造成資料損毀。
因此,kernel crash 的標準做法是:
- 寫入專用磁碟區域:crash 發生時,OS 將整個記憶體內容(或至少是 kernel 擁有的部分)直接寫入磁碟上一個不屬於任何檔案系統的保留區域(reserved disk section),完全繞過 file system。
- 系統重新啟動後再整理:系統重啟後,一個獨立的 process 讀取該保留區的原始資料,將其整理成標準的 crash dump 檔案,寫入正常的 file system 供後續分析。
2.10.2 效能監控與調校 (Performance Monitoring and Tuning)
效能調校(Performance Tuning)的目標是消除處理瓶頸(Bottleneck),而要找出瓶頸在哪裡,必須先能夠量測系統行為。OS 因此需要提供一套機制來收集並展示系統的各種量測數據。
效能監控工具主要分成兩種技術路線:計數器(Counters) 與追蹤(Tracing),兩者收集資訊的方式根本不同。
計數器工具 (Counter-Based Tools)
OS 透過一系列計數器持續追蹤系統活動,例如:已執行的 system call 次數、網路裝置的傳輸次數、磁碟的讀寫次數等。計數器工具的特性是隨時可查、開銷固定低廉,適合快速掌握系統整體概況。
Linux 上常用的計數器工具分成兩類:
Process 層級(Per-Process):
| 工具 | 說明 |
|---|---|
ps | 回報單一 Process 或一組 Process 的資訊(狀態、CPU 使用、記憶體等) |
top | 即時顯示目前所有 Process 的動態統計數據,按 CPU 使用率排序 |
系統層級(System-Wide):
| 工具 | 說明 |
|---|---|
vmstat | 回報記憶體使用統計,包含虛擬記憶體的 page in/out 狀況 |
netstat | 回報網路介面的統計數據,例如封包的傳送與接收次數 |
iostat | 回報磁碟的 I/O 使用量,包含讀寫速率與等待時間 |
/proc 虛擬檔案系統
Linux 上大多數計數器工具,其資料來源都是 /proc 檔案系統。/proc 是一個 「虛擬」(Pseudo)檔案系統,它只存在於 kernel 記憶體中,並不對應到任何實際的磁碟儲存區,其主要用途是讓 user-space 程式查詢各 Process 以及 kernel 本身的統計數據。
要理解 /proc 存在的意義,必須先思考一個問題:在沒有 /proc 的情況下,user-space 程式要如何查詢 kernel 的內部狀態?
答案是:只能靠 system call。
- 要查詢 CPU 資訊,就得有一個
get_cpu_info()system call; - 要查詢記憶體使用量,就得有
get_mem_stats(); - 要列出所有 Process,就得有
get_process_list()。每新增一種監控需求,就要在 kernel 增加一個 system call
而 system call 一旦加入就很難修改(它是 kernel 與 user-space 之間的公開合約,不能隨意變動)。這條路很快就會讓 kernel ABI 膨脹成難以維護的怪物。
/proc 用了一個更聰明的解法:把 kernel 狀態偽裝成一個可瀏覽的目錄樹。這個設計的核心依據是 UNIX 的哲學:「一切皆檔案(Everything is a file)」。UNIX 上的所有工具(cat、grep、awk、shell 腳本)都已經知道如何讀取檔案,所有程式語言都有標準的檔案 I/O 介面(open/read/close)。只要 kernel 狀態能以檔案的形式呈現,整個 user-space 生態系就能不需要任何修改地直接使用:
# 不需要任何特殊 API,cat 就能查 CPU 資訊
cat /proc/cpuinfo
# grep 直接從 /proc/meminfo 抓空閒記憶體
grep MemFree /proc/meminfo
# shell 腳本直接讀取某個 process 的狀態
cat /proc/1234/status
為什麼是「目錄層級」,而不是單一的大檔案?
目錄結構帶來的是可探索性(Discoverability)。每個 PID 都有自己的子目錄,目錄裡的每個檔案是該 Process 的一種屬性:
/proc/1234/
status ← 基本狀態(名稱、PID、狀態、記憶體)
maps ← 記憶體映射區域
fd/ ← 目前開啟的所有 file descriptor
exe ← 指向執行檔的 symlink
cmdline ← 完整的命令列參數
...