理解linux的CPU上下文切換
前言
linux是一個多工作業系統,它支援遠大於CPU數量的任務同時執行。當然,這個同時執行不是真的同時執行,而是系統在很短的時間內輪流分配CPU資源,由於CPU的速度很快,所以給人一種同時執行的錯覺。
每個任務執行前,CPU需要知道任務從哪載入、從哪開始執行,也就是需要系統設定好任務的CPU暫存器和程式計數器。這倆是CPU執行任何任務前所必須的依賴環境,因此也被叫做CPU上下文。而CPU上下文切換便是,先把前一個任務的CPU上下文儲存起來,然後載入新任務的上下文到CPU暫存器和程式計數器中,最後再跳轉到程式計數器所指向的新位置來執行新任務。
根據任務的不同,CPU的上下文切換可以分為幾個不同的場景:程序上下文切換、執行緒上下文切換,以及中斷上下文切換。
程序上下文切換
linux程序的執行空間分為核心空間和使用者空間。核心空間具有最高許可權,可以訪問所有資源;使用者空間只能訪問受限資源,不能直接訪問記憶體等硬體裝置,必須通過系統呼叫陷入核心,才能訪問特權資源。當程序在使用者空間執行時,被稱為程序的使用者態;當程序陷入到核心空間,被稱為程序的核心態。
使用者態通過系統呼叫陷入核心態的時候,會發生CPU上下文切換。系統需要先儲存使用者態的上下文,然後載入核心態程式碼的上下文,執行完核心態任務後,再進行一次CPU上下文切換,切換回使用者態的上下文。因此一次系統呼叫會發生兩次CPU上下文切換。這裡需要注意,系統呼叫過程一直是同一個程序在執行,不是一個程序切換到另一個程序。
程序是由核心管理和排程的,程序的切換隻能在核心空間。因此,程序的上下文不僅包括虛擬記憶體、棧、全域性變數等使用者空間的資源,還包括了核心堆疊、暫存器等核心空間的狀態。由於程序上下文較多,切換時候不僅包括使用者態資訊,還包括核心態資訊,所以上下文切換時耗費的時間更多。雖然一般只需要納秒級的時間,但程序上下文切換次數較多的時候,總體耗費的時間就比較可觀了。在大量程序上下文切換的情況下,CPU將大量時間耗費在暫存器、核心棧、虛擬記憶體等資源的儲存和恢復上,進而縮短了真正執行程序的時間。
Linux通過TLB(Translation Lookaside Buffer)來管理虛擬記憶體到實體記憶體的對映關係。當虛擬記憶體更新後,TLB也需要重新整理,記憶體的訪問也會隨之變慢。
只有在程序排程的時候,才需要切換程序上下文。linux為每個CPU維護了一個就序佇列,將活躍程序(在執行和等待中的程序)按照優先順序和等待CPU的時間排序,然後選擇最需要CPU的程序,也就是優先順序最高和等待時間最長的程序來執行。
觸發程序排程的常見場景:
- 程序的CPU時間片用完,就會被系統掛起,切換到其他程序。
- 程序所需的系統資源不足時,需要等到資源滿足後才可以執行,這時候也會被掛起。
- 程序通過sleep()函式主動掛起。
- 有優先順序更高的程序需要執行時,當前程序會被掛起。
- 硬體中斷時,程序會被掛起。
執行緒上下文切換
程序是資源分配的基本單位,執行緒是排程的基本單位。程序內有多個執行緒時,這些執行緒會共享相同的虛擬記憶體和全域性變數等資源,這些資源在上下文切換時是不需要修改的。執行緒也有自己的私有資源,比如棧和暫存器等,這些在上下文切換時也需要儲存。
- 執行緒上下文切換前後的兩個執行緒如果不屬於同一個程序,此時資源不共享,切換過程同進程上下文切換。
- 切換前後的兩個執行緒若屬於同一個程序,共享的虛擬記憶體等資源保持不動,切換執行緒的私有資源即可。在這種情況下,切換消耗的資源更少。
中斷上下文切換
為了快速響應硬體的時間,中斷處理會打斷程序的正常排程和執行,轉而呼叫中斷處理程式。
中斷上下文切換並不涉及程序的使用者態,當中斷髮生在一個正處於使用者態的程序時,不需要儲存和恢復這個程序的使用者態資源。中斷上下文值包括核心態中斷服務程式執行所必需的狀態,包括CPU暫存器、核心堆疊、硬體中斷引數等。
檢視系統的上下文切換情況
使用vmstat
(apt或yum安裝sysstat
)可檢視系統的上下文切換情況
# 每2秒輸出一組報告,共1份報告
vmstat 2 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b 交換 空閒 緩衝 快取 si so bi bo in cs us sy id wa st
1 0 0 2118592 100844 1011392 0 0 211 259 188 249 3 1 96 0 0
- cs(context switch):每秒上下文切換的次數。此處為249
- in(interrupt): 每秒中斷的次數。此處為188
- r(running or runnable): 就緒佇列的長度,即正在執行和等待CPU的程序數
- b(blocked): 處於不可中斷睡眠狀態的程序數
檢視每個程序的上下文切換情況
使用pidstat
(同樣依賴於sysstat)可檢視程序的上下文切換情況。
# 每3秒輸出一組報告,-w表示輸出程序的上下文切換指標,-wt表示輸出執行緒的上下文切換指標
pidstat -w 3
13時53分56秒 UID PID cswch/s nvcswch/s Command
13時53分59秒 0 7 4.95 0.00 kworker/0:1-ata_sff
13時53分59秒 0 13 2.64 0.00 rcu_sched
13時53分59秒 0 14 0.33 0.00 migration/0
13時53分59秒 0 19 0.33 0.00 migration/1
13時53分59秒 0 31 1.98 0.00 kcompactd0
13時53分59秒 0 122 1.32 0.00 kworker/1:1H-kblockd
13時53分59秒 0 247 0.99 0.33 jbd2/dm-0-8
13時53分59秒 116 501 0.66 0.00 avahi-daemon
13時53分59秒 0 505 0.99 0.00 NetworkManager
13時53分59秒 104 541 0.33 0.00 rsyslogd
13時53分59秒 0 549 0.33 0.00 wpa_supplicant
13時53分59秒 0 5102 2.31 0.33 kworker/1:1-events
13時53分59秒 0 5576 1.98 0.00 kworker/u4:0-events_freezable_power_
13時53分59秒 1000 5707 0.33 0.00 pidstat
- cswch: 每秒自願上下文切換的次數(資源不足時,程序自願上下文切換)
- nvcswch: 每秒非自願上下文切換的次數(時間片到期等原因,被系統強制排程而發生的上下文切換)
檢視系統中斷情況
# 持續檢視/proc/interrupts, 並高亮變化的地方
watch -d cat /proc/interrupts
CPU0 CPU1
0: 29 0 IO-APIC 2-edge timer
1: 602 0 IO-APIC 1-edge i8042
8: 0 0 IO-APIC 8-edge rtc0
9: 0 0 IO-APIC 9-fasteoi acpi
12: 0 366 IO-APIC 12-edge i8042
14: 0 0 IO-APIC 14-edge ata_piix
15: 0 2563 IO-APIC 15-edge ata_piix
18: 0 2 IO-APIC 18-fasteoi vboxvideo
19: 13747 23341 IO-APIC 19-fasteoi enp0s3
20: 8310 0 IO-APIC 20-fasteoi vboxguest
21: 17084 18354 IO-APIC 21-fasteoi ahci[0000:00:0d.0], snd_intel8x0
22: 25 0 IO-APIC 22-fasteoi ohci_hcd:usb1
NMI: 0 0 Non-maskable interrupts
LOC: 309756 154510 Local timer interrupts
SPU: 0 0 Spurious interrupts
PMI: 0 0 Performance monitoring interrupts
IWI: 0 0 IRQ work interrupts
RTR: 0 0 APIC ICR read retries
RES: 95556 93683 Rescheduling interrupts
CAL: 7548 8726 Function call interrupts
TLB: 7126 6564 TLB shootdowns
TRM: 0 0 Thermal event interrupts
THR: 0 0 Threshold APIC interrupts
DFR: 0 0 Deferred Error APIC interrupts
MCE: 0 0 Machine check exceptions
MCP: 9 9 Machine check polls
ERR: 0
MIS: 0
PIN: 0 0 Posted-interrupt notification event
NPI: 0 0 Nested posted-interrupt event
PIW: 0 0 Posted-interrupt wakeup event
- RES:重排程中斷。表示喚醒空閒狀態的CPU來排程新的任務執行。
參考
- 極客時間 - linux效能優化實戰