章節概覽#

本章深入探討 process、kernel 與系統資源之間的關係。系統有三種基本硬體資源:CPUmemoryI/O。Process 會競爭這些資源,而 kernel 的工作就是公平地分配它們。此外,kernel 本身也是一種軟體資源,process 透過它來建立新 process 和進行 inter-process communication。

本章介紹的許多工具屬於 performance-monitoring tools,在系統變慢時特別有用。但作者建議不要過度執著於效能調校,而是專注理解工具實際測量的內容,藉此深入了解 kernel 如何運作。


8.1 追蹤 Process(Tracking Processes)#

top 指令#

ps 指令只能列出某一時間點的 process 快照,無法顯示 process 隨時間的變化。top 提供互動式介面,每秒更新一次,預設將最活躍的 process(佔用最多 CPU 時間)排在最上方。

常用按鍵指令:

  • Spacebar — 立即更新顯示
  • M — 依 resident memory 使用量排序
  • T — 依累計 CPU 使用時間排序
  • P — 依目前 CPU 使用率排序(預設)
  • u — 只顯示某個使用者的 process
  • f — 選擇要顯示的統計欄位
  • ? — 顯示所有 top 指令的摘要

top 的按鍵指令是區分大小寫的。類似工具 atophtop 提供更多增強功能。


8.2 用 lsof 查找開啟的檔案(Finding Open Files with lsof)#

lsof 指令可列出系統中所有開啟的檔案及使用它們的 process。由於 Unix 非常重視檔案,lsof 是排查問題最實用的工具之一。它不僅能列出一般檔案,還能顯示 network resources、dynamic libraries、pipes 等。

8.2.1 讀取 lsof 輸出#

lsof 輸出的主要欄位:

  • COMMAND — 持有檔案的 process 名稱
  • PID — Process ID
  • USER — 執行 process 的使用者
  • FD — 檔案用途或 file descriptor 編號
  • TYPE — 檔案類型(regular file、directory、socket 等)
  • DEVICE — 裝置的 major 和 minor number
  • SIZE/OFF — 檔案大小
  • NODE — 檔案的 inode number
  • NAME — 檔案名稱

以 root 身份執行 lsof 可以看到更多資訊。

8.2.2 使用 lsof#

兩種基本使用方式:

  • 列出所有並 pipe 到 less 搜尋
  • 使用 command-line options 縮小範圍
# 顯示 /usr 及其子目錄下開啟的檔案
$ lsof +D /usr

# 列出特定 PID 的開啟檔案
$ lsof -p pid

lsof 高度依賴 kernel 資訊。如果進行了 distribution update(更新 kernel 與 lsof),更新後的 lsof 可能要到重啟後才能正常運作。


8.3 追蹤程式執行與 System Call#

當程式一啟動就立刻崩潰時,toplsof 都無法幫上忙。這時需要使用 strace(system call trace)和 ltrace(library trace)。

8.3.1 strace#

System call 是 user-space process 要求 kernel 執行特權操作(如開啟、讀取檔案)的機制。strace 會印出 process 發出的所有 system call。

# 追蹤 cat 指令的 system call
$ strace cat /dev/null

# 追蹤 daemon 及其子 process
$ strace -o crummyd_strace -ff crummyd

strace 輸出的關鍵部分:

  • 開頭會看到 execve()brk()(記憶體初始化)
  • 中間大量的 shared library 載入(通常可忽略)
  • 尾端是實際的檔案操作,如 openat()read()close()

當指令遇到錯誤時,strace 能顯示精確的錯誤資訊。例如開啟不存在的檔案會回傳 -1 ENOENT (No such file or directory)

找不到缺失檔案是 Unix 程式最常見的問題。當 system log 無法提供有用資訊時,strace 特別適合用來追蹤缺失的檔案。

8.3.2 ltrace#

ltrace 追蹤 shared library call,輸出格式類似 strace,但它不追蹤 kernel 層級的操作。由於 library call 數量遠多於 system call,務必過濾輸出。

ltrace 無法用於 statically linked binaries。


8.4 Thread#

在 Linux 中,部分 process 會被分割為多個 thread。Thread 與 process 很相似,有自己的 thread ID (TID),kernel 也會排程和執行它們。但不同於各自獨立的 process,同一 process 內的所有 thread 會共享系統資源和部分記憶體

8.4.1 Single-Threaded 與 Multithreaded Process#

  • Single-threaded — 只有一個 thread 的 process
  • Multithreaded — 有多個 thread 的 process

Multithreaded 的主要優勢:

  • Thread 可同時在多個 processor 上執行,加速運算
  • Thread 啟動比 process 更快
  • Thread 間透過 shared memory 通訊比 process 間透過 pipe/socket 更有效率

8.4.2 檢視 Thread#

# 顯示 process 及其 thread
$ ps m

# 顯示 PID、TID 和指令
$ ps m -o pid,tid,command

ps m 的輸出中,PID 欄有數字的行代表 process,PID 欄為 dash 的行代表該 process 的 thread。Single-threaded process 的 TID 與 PID 相同。

top 預設不顯示 thread,需按 H 鍵開啟。大多數資源監控工具也需要額外設定才能顯示 thread。


8.5 資源監控入門(Introduction to Resource Monitoring)#

本節討論 processor (CPU) time、memory 和 disk I/O 的監控,涵蓋系統層級和 per-process 層級。

大多數 Linux 系統在預設設定下已有良好效能。與其追求效能調校,不如專注觀察 kernel 如何在 process 之間分配資源。

8.5.1 測量 CPU Time#

使用 top -p pid 可監控特定 process 的 CPU 使用情況。使用 time 指令可了解指令的 CPU 消耗:

$ time ls
real    0m0.442s
user    0m0.052s
sys     0m0.091s

三種時間的意義:

  • User time (user) — CPU 執行程式自身程式碼的時間
  • System time (sys) — kernel 為 process 工作的時間(如讀寫檔案、目錄操作)
  • Real time (real) — 又稱 elapsed time,程式從開始到結束的總時間

Real time 減去 user time 和 system time,可大致估算 process 等待系統和外部資源的時間。

8.5.2 調整 Process 優先權(Adjusting Process Priorities)#

Kernel 依據 scheduling priority(範圍 -20 到 20,-20 為最高優先權)來排程每個 process。

  • PR 欄 — kernel 目前的排程優先權
  • NI 欄 — nice value,使用者可控制的部分

Nice value 預設為 0。數值越高,process 對其他 process 越「友善」(讓出更多 CPU 時間)。

# 將 process 的 nice value 設為 20(最低優先權)
$ renice 20 pid

在現代單一使用者的桌面系統上,通常不太需要調整 nice value。它在多使用者共用機器的年代更為重要。

8.5.3 用 Load Average 衡量 CPU 效能#

Load average 是目前準備執行的 process 平均數量,包含正在執行和等待 CPU 的 process。

$ uptime
... up 91 days, ... load average: 0.08, 0.03, 0.01

三個數字分別代表過去 1 分鐘5 分鐘15 分鐘的 load average。

解讀方式:

  • Load average 接近 0 — CPU 很閒
  • Load average 接近 1 — 大約有一個 process 持續使用 CPU
  • 對於多核心系統,load average 等於核心數代表所有核心都剛好滿載

處理高 load average:

  • 高 load average 不一定代表系統有問題。若系統有足夠的 memory 和 I/O 資源,仍可能運作良好
  • 若 load average 很高且系統明顯變慢,可能是遇到 memory performance 問題,kernel 開始 thrash(頻繁地在 disk 和 memory 之間交換資料)

8.5.4 監控記憶體狀態(Monitoring Memory Status)#

使用 free 指令或查看 /proc/meminfo 可檢查系統記憶體狀態。

記憶體運作原理:

  • CPU 透過 MMU(Memory Management Unit)管理記憶體存取
  • Kernel 將 process 使用的記憶體切割成 page(通常為 4KB)
  • Kernel 維護 page table,將虛擬位址對應到實體位址
  • 採用 on-demand paging(demand paging):不需一次載入所有 page,而是在需要時才載入
# 查看系統的 page size
$ getconf PAGE_SIZE
4096

Page Fault 的兩種類型:

  • Minor page fault — 所需 page 在記憶體中但 MMU 不知道位置,kernel 告知 MMU 即可,影響很小
  • Major page fault — 所需 page 不在記憶體中,kernel 必須從 disk 載入,大量 major page fault 會嚴重拖慢系統
# 查看指令的 page fault 數量
$ /usr/bin/time cal > /dev/null

# 查看特定 PID 的 page fault
$ ps -o pid,min_flt,maj_flt 20365

當記憶體不足時,kernel 會開始將 working memory 的 page swap 到 disk,導致 thrashing,系統效能會急遽下降。

8.5.5 用 vmstat 監控 CPU 與記憶體效能#

vmstat 是最古老的系統監控工具之一,開銷極小,適合快速了解系統的 swap、I/O 和 CPU 使用情況。

# 每 2 秒報告一次統計
$ vmstat 2

輸出分為幾個類別:

  • procs — process 資訊
  • memory — 記憶體使用(swpd、free、buff、cache)
  • swap — page swap in/out
  • io — disk I/O(blocks in/out)
  • system — kernel context switch 次數
  • cpu — CPU 時間分佈(us=user、sy=system、id=idle、wa=waiting for I/O)

當記憶體耗盡時,so(swap out)欄會出現活動,buffer 和 cache 大小也會下降。

8.5.6 I/O 監控#

iostat#

iostat 專門用於 per-device I/O 監控,比 vmstat -d 更易使用。

$ iostat
$ iostat -p ALL    # 顯示所有分割區
$ iostat -d 2      # 每 2 秒更新 device 報告

每個 device 的輸出欄位:

  • tps — 每秒平均資料傳輸次數
  • kB_read/s — 每秒平均讀取 KB
  • kB_wrtn/s — 每秒平均寫入 KB
  • kB_read — 總讀取 KB
  • kB_wrtn — 總寫入 KB

iotop#

iotop 可查看個別 process 的 I/O 使用情況,類似 top 的介面。它顯示的是 TID(而非 PID),是少數以 thread 為單位顯示的工具之一。

I/O scheduling class(排程類別):

  • be (Best effort) — 大多數 process 使用此類別
  • rt (Real time) — 優先於其他所有 I/O
  • idle — 只在沒有其他 I/O 時才執行

8.5.7 用 pidstat 做 Per-Process 監控#

pidstatvmstat 風格顯示單一 process 的資源消耗,不會像 top 那樣每次更新就覆蓋前一次的輸出。

# 監控 PID 1329,每秒更新
$ pidstat -p 1329 1

pidstat 還支援 -r(memory)和 -d(disk)選項進行更多監控。


8.6 Control Groups (cgroups)#

cgroup(control group)是 Linux kernel 的功能,可將多個 process 放入一個群組,以群組為單位管理和限制資源消耗。例如限制一組 process 可累計使用的記憶體上限。

建立 cgroup 後,可加入 process 並使用 controller 控制行為:

  • cpu controller — 限制 processor time
  • memory controller — 限制記憶體使用
  • pids controller — 限制 process 數量

雖然 systemd 大量使用 cgroup 功能,但 cgroup 是 kernel space 的功能,不依賴 systemd。

8.6.1 區分 cgroup 版本#

目前有 cgroups v1cgroups v2 兩個版本,可能同時存在於系統中:

  • v1 — 每種 controller(cpu、memory 等)有自己獨立的 cgroup 集合。一個 process 可同時屬於多個 cgroup(每個 controller 一個)

Figure 8-1: cgroups v1

  • v2 — 一個 process 只能屬於一個 cgroup,但一個 cgroup 可以有多個 controller

Figure 8-2: cgroups v2

由於 v1 正在被淘汰,本書主要討論 cgroups v2

# 查看目前 shell 的 cgroup
$ cat /proc/self/cgroup

8.6.2 檢視 cgroups#

cgroup 完全透過 filesystem 存取,通常掛載在 /sys/fs/cgroup(v2)或 /sys/fs/cgroup/unified

# 查看 cgroup 目錄
$ cat /proc/self/cgroup
0::/user.slice/user-1000.slice/session-2.scope
$ cd /sys/fs/cgroup/user.slice/user-1000.slice/session-2.scope/

# 查看 cgroup 內的 controller
$ cat cgroup.controllers
memory pids

# 查看 thread 數量
$ cat pids.current

# 查看記憶體上限
$ cat memory.max

8.6.3 操作與建立 cgroups#

# 將 process 加入 cgroup
# echo pid > cgroup.procs

# 限制 cgroup 的最大 PID 數
# echo 3000 > pids.max

建立 cgroup 的規則:

  • Process 只能放在 leaf(最外層)cgroup 中
  • 子 cgroup 的 controller 必須存在於父 cgroup 中
  • 需透過 cgroup.subtree_control 檔案明確指定子 cgroup 可用的 controller

8.6.4 查看資源使用#

即使沒有啟用 controller,也可以透過 cgroup 查看資源使用:

# 查看 CPU 使用統計
$ cat cpu.stat
usage_usec 4617481
user_usec 2170266
system_usec 2447215

啟用 controller 後,memory.currentmemory.stat 等檔案可提供更詳細的資訊。


8.7 延伸主題(Further Topics)#

資源監控工具種類繁多,因為不同類型的資源以不同方式被消耗。本章涵蓋了 CPU、memory 和 I/O 作為系統資源,被 process、thread 和 kernel 所使用。

這些工具存在的原因是資源是有限的,系統的各元件必須努力減少資源消耗才能維持良好效能。

其他值得探索的主題:

  • sar (System Activity Reporter) — 類似 vmstat 但可記錄歷史資料,適合回溯分析過去的系統事件
  • acct (process accounting) — 可記錄 process 及其資源使用
  • Quotas — 可限制使用者的 disk space 使用量

如果對系統調校和效能分析有更深入的興趣,推薦 Brendan Gregg 的 Systems Performance: Enterprise and the Cloud(第二版,Addison-Wesley, 2020)。