目錄
01-4. Context Switch 詳解
⏱️ 閱讀時間: 12 分鐘 🎯 難度: ⭐⭐⭐ (中等)
🎯 本篇重點
理解 Context Switch(上下文切換)的原理、成本以及為什麼它很昂貴。
🤔 什麼是 Context Switch?
一句話解釋:
Context Switch 是作業系統將 CPU 從一個 Process 切換到另一個 Process 的過程,需要保存當前 Process 的狀態並載入新 Process 的狀態。
🎭 用演員換場來比喻
舞台 = CPU
舞台上正在演出:
演員 A(Process A)正在表演
↓
導演(作業系統): "換人!"
↓
1. 記錄演員 A 的進度(保存 Context)
2. 演員 A 下台(Process A 離開 CPU)
3. 演員 B 上台(Process B 獲得 CPU)
4. 恢復演員 B 的進度(載入 Context)
↓
演員 B 繼續表演(Process B 執行)這整個過程就是 Context Switch!
📦 什麼是 Context(上下文)?
Process 的 Context 包含:
Process Context(進程上下文)
├─ CPU 暫存器狀態
│ ├─ Program Counter (PC):下一條要執行的指令位址
│ ├─ Stack Pointer (SP):堆疊頂端位址
│ ├─ General Registers:通用暫存器的值
│ └─ Status Register:狀態旗標
│
├─ 記憶體管理資訊
│ ├─ Page Table:虛擬記憶體映射表
│ └─ Memory Limits:記憶體邊界
│
├─ I/O 狀態
│ ├─ 開啟的檔案清單
│ ├─ 網路連接
│ └─ 裝置狀態
│
└─ Process 狀態
├─ Process ID (PID)
├─ Priority(優先級)
└─ Scheduling Information(調度資訊)這些資訊存放在:Process Control Block (PCB)
🔄 Context Switch 完整流程
步驟分解
時間 t0: Process A 執行中
│
├─ 步驟 1:觸發 Context Switch
│ • Time Slice 用完
│ • Process A 發起 I/O 請求
│ • 高優先級 Process B 就緒
│
├─ 步驟 2:保存 Process A 的 Context
│ • 保存所有 CPU 暫存器
│ • 保存 PC、SP
│ • 保存狀態旗標
│ • 更新 PCB_A
│
├─ 步驟 3:選擇下一個 Process
│ • 調度器選擇 Process B
│ • Process B 狀態:Ready → Running
│
├─ 步驟 4:載入 Process B 的 Context
│ • 從 PCB_B 讀取狀態
│ • 恢復所有暫存器
│ • 切換記憶體映射(Page Table)
│ • 恢復 PC、SP
│
└─ 步驟 5:Process B 開始執行
• CPU 跳轉到 Process B 的程式碼
• Process B 繼續執行
時間 t1: Process B 執行中🔍 實際案例
案例 1:Time Slice 用完
import os
import time
from multiprocessing import Process
def task_a():
pid = os.getpid()
for i in range(5):
print(f"[Process A - {pid}] 執行中...{i}")
time.sleep(0.1) # 模擬工作
def task_b():
pid = os.getpid()
for i in range(5):
print(f"[Process B - {pid}] 執行中...{i}")
time.sleep(0.1)
if __name__ == '__main__':
p1 = Process(target=task_a)
p2 = Process(target=task_b)
p1.start()
p2.start()
p1.join()
p2.join()輸出(交錯執行):
[Process A - 1234] 執行中...0
[Process B - 1235] 執行中...0
[Process A - 1234] 執行中...1 ← Context Switch
[Process B - 1235] 執行中...1 ← Context Switch
[Process A - 1234] 執行中...2
[Process B - 1235] 執行中...2
...每次切換都發生了 Context Switch!
案例 2:I/O 阻塞
import os
import time
def cpu_bound_task():
"""CPU 密集任務"""
pid = os.getpid()
print(f"[CPU Task - {pid}] 開始計算...")
for i in range(10000000):
_ = i * i
print(f"[CPU Task - {pid}] 計算完成")
def io_bound_task():
"""I/O 密集任務"""
pid = os.getpid()
print(f"[I/O Task - {pid}] 開始讀取檔案...")
time.sleep(2) # 模擬檔案 I/O
print(f"[I/O Task - {pid}] 讀取完成")
# I/O Task 在等待時,CPU Task 可以執行
# 這需要 Context Switch時間軸:
0ms - 10ms : I/O Task Running → 發起 I/O 請求
10ms - 12ms : Context Switch (I/O Task → CPU Task)
12ms - 2000ms: CPU Task Running (I/O Task 在 Waiting)
2000ms - 2002ms: I/O 完成,Context Switch (CPU Task → I/O Task)
2002ms - 2010ms: I/O Task Running → 繼續執行💰 Context Switch 的成本
為什麼 Context Switch 很昂貴?
1. 直接成本:保存/載入 Context
操作 時間成本
─────────────────────────────────────
保存 CPU 暫存器 ~10 個時鐘週期
保存 PC、SP、Flags ~5 個時鐘週期
更新 PCB ~20 個時鐘週期
載入新 PCB ~20 個時鐘週期
切換 Page Table ~100 個時鐘週期
載入新 Context ~15 個時鐘週期
─────────────────────────────────────
總計 ~170 個時鐘週期在 2.5 GHz CPU 上:
170 個時鐘週期 = 170 / 2,500,000,000 = 68 奈秒 (ns)看起來很快?但是…
2. 間接成本:Cache 失效
這才是最大的成本!
Process A 執行時:
CPU Cache 載入了 Process A 的資料
↓
Context Switch 到 Process B
↓
CPU Cache 全部失效(Cache Miss)
↓
需要從記憶體重新載入 Process B 的資料
↓
記憶體存取:~100 奈秒 × 數千次 = 數十微秒 (μs)Cache Miss 的影響:
| 操作 | 時間 |
|---|---|
| CPU 暫存器存取 | ~1 ns |
| L1 Cache | ~1 ns |
| L2 Cache | ~3 ns |
| L3 Cache | ~10 ns |
| 主記憶體 (RAM) | ~100 ns |
| SSD | ~50,000 ns |
Cache Miss 造成效能下降 100 倍!
3. TLB 失效
TLB (Translation Lookaside Buffer): 虛擬記憶體位址轉換的快取
Process A:
虛擬位址 0x1000 → 實體位址 0xABCD (存在 TLB)
Context Switch 到 Process B
↓
TLB 失效
↓
Process B:
虛擬位址 0x1000 → 需要查 Page Table → 很慢!完整成本估算
直接成本(保存/載入) ~68 ns
Cache 失效重新載入 ~50,000 ns
TLB 失效 ~10,000 ns
調度器開銷 ~1,000 ns
─────────────────────────────────────
總計 ~61,068 ns ≈ 61 微秒 (μs)這意味著:
- 每次 Context Switch ≈ 61 微秒
- 每秒 1000 次 Context Switch = 61 毫秒的純開銷
- 佔用約 6% CPU 時間(在 1 秒鐘內)
⚠️ Context Switch 頻繁的影響
場景:高並發系統
# 啟動 1000 個 Process
from multiprocessing import Process
def task():
for i in range(100):
_ = i * i
processes = [Process(target=task) for _ in range(1000)]
for p in processes:
p.start()
for p in processes:
p.join()問題:
- 1000 個 Process 爭搶 CPU(假設 4 核)
- 每個 Process 只能短暫執行(Time Slice ≈ 10ms)
- 頻繁的 Context Switch
結果:
Context Switch 次數:~100,000 次/秒
純 Context Switch 開銷:100,000 × 61μs = 6.1 秒
實際工作時間:1 秒
總時間:7.1 秒
效率:1 / 7.1 = 14% ← 大部分時間在切換!🎯 如何減少 Context Switch?
方法 1:減少 Process/Thread 數量
# ❌ 過多的 Process
processes = [Process(target=task) for _ in range(1000)]
# ✅ 使用 Process Pool
from multiprocessing import Pool
with Pool(processes=4) as pool: # 只創建 4 個 Process
pool.map(task, range(1000))方法 2:使用協程(Coroutine)
# ❌ 多個 Process(有 Context Switch)
from multiprocessing import Process
def io_task():
response = requests.get('https://api.example.com')
processes = [Process(target=io_task) for _ in range(100)]
# ✅ 使用 asyncio(沒有 Context Switch)
import asyncio
import aiohttp
async def io_task():
async with aiohttp.ClientSession() as session:
async with session.get('https://api.example.com') as response:
return await response.text()
async def main():
tasks = [io_task() for _ in range(100)]
await asyncio.gather(*tasks)
# 單個 Process,無 Context Switch!方法 3:批次處理
# ❌ 每個請求一個 Process
for item in items:
p = Process(target=process_item, args=(item,))
p.start()
# ✅ 批次處理
def process_batch(batch):
for item in batch:
process_item(item)
# 每 100 個 item 一個 Process
batch_size = 100
for i in range(0, len(items), batch_size):
batch = items[i:i+batch_size]
p = Process(target=process_batch, args=(batch,))
p.start()📊 Context Switch 監控
Linux 查看 Context Switch 次數
# 查看系統整體 Context Switch
vmstat 1
# 輸出:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 1234567 123 4567 0 0 12 34 123 5678 10 5 85 0 0
↑
cs = Context Switch (每秒)Python 監控
import psutil
import time
def monitor_context_switches():
# 獲取初始值
ctx_start = psutil.cpu_stats().ctx_switches
time.sleep(1)
# 獲取 1 秒後的值
ctx_end = psutil.cpu_stats().ctx_switches
cs_per_second = ctx_end - ctx_start
print(f"Context Switch: {cs_per_second} 次/秒")
monitor_context_switches()✅ 重點回顧
Context Switch 的本質:
- 保存當前 Process 的 Context
- 載入新 Process 的 Context
- 切換 CPU 執行
Context Switch 的成本:
- 直接成本:保存/載入暫存器 (~68 ns)
- 間接成本:Cache/TLB 失效 (~61 μs) ← 主要成本
- 調度開銷:選擇下一個 Process (~1 μs)
為什麼昂貴:
- ✅ Cache 失效是最大成本
- ✅ TLB 失效導致記憶體存取變慢
- ✅ 頻繁切換浪費大量 CPU 時間
如何優化:
- ✅ 減少 Process/Thread 數量
- ✅ 使用協程(asyncio)替代多進程
- ✅ 批次處理減少切換次數
- ✅ 調整調度策略
上一篇: 01-3. Process 的生命週期 下一篇: 01-5. Process vs Thread 完整對比
最後更新:2025-01-04