一篇學會CPU上下文
本文轉(zhuǎn)載自微信公眾號「運維開發(fā)故事」,作者沒有文案的夏老師 。轉(zhuǎn)載本文請聯(lián)系運維開發(fā)故事公眾號。
1.進程概念
進程定義
進程是并發(fā)環(huán)境下,一個具有獨立功能的程序在某個數(shù)據(jù)集上的一次執(zhí)行活動,它是操作系統(tǒng)進行資源分配和保護的基本單位,也是執(zhí)行的單位。
PCB
進程控制塊(Process Control Block,PCB)是為了描述和控制進程的運行而定義的一種數(shù)表結(jié)構(gòu),它是進程存在的唯一標志,也是進程實體的一部分。操作系統(tǒng)對進程的管理和控制主要以PCB為依據(jù)。PCB中包括了操作系統(tǒng)所需要的進程運行的所有信息。
進程的上下文
用戶級上下文: 正文、數(shù)據(jù)、用戶堆棧以及共享存儲區(qū);寄存器上下文: 通用寄存器、程序寄存器(IP)、處理器狀態(tài)寄存器(EFLAGS)、棧指針(ESP);系統(tǒng)級上下文: 進程控制塊task_struct、內(nèi)存管理信息(mm_struct、vm_area_struct、pgd、pte)、內(nèi)核棧。
2.線程概念
線程上下文
多線程環(huán)境下的進程定義,很顯然,進程是系統(tǒng)進行資源分配和保護的基本單位。進程包括容納進程映像的一個虛擬地址空間,以及對CPU、I/O資源、文件以及其他資源的有保護有控制的訪問。
可見,一個進程可以劃分為兩個部分:一部分是資源部分,一部分是線程部分。
TCB
線程由線程控制塊(Thread Control Block,TCB)、用戶堆棧、系統(tǒng)堆棧以及一組處理器狀態(tài)寄存器和一個私用內(nèi)存存儲區(qū)組成。
3.上下文
cpu上下文
CPU 寄存器,是 CPU 內(nèi)置的容量小、但速度極快的內(nèi)存。而程序計數(shù)器,則是用來存儲 CPU 正在執(zhí)行的指令位置、或者即將執(zhí)行的下一條指令位置。它們都是 CPU 在運行任何任務(wù)前,必須的依賴環(huán)境,因此也被叫做 CPU 上下文。
進程上下文切換
每當內(nèi)核壓入一個新的系統(tǒng)上下文層時,它就要保存一個進程的上下文。特別是當系統(tǒng)收到一個中斷,或一個進程執(zhí)行系統(tǒng)調(diào)用,或當內(nèi)核做上下文切換時,就要對進程的上下文進行保存。上下文切換情況:
- 一個進程結(jié)束,需要從隊列中重新選擇一個進程運行。
- 如果進程在系統(tǒng)資源不足(內(nèi)存不足等),則返回到運行隊列,并由系統(tǒng)調(diào)度其他進程運行。
- 如果不訪問磁盤I/O等資源就不能繼續(xù),它會自己主動掛起時,自然也會重新調(diào)度。
- 當有優(yōu)先級更高的進程運行時,為了保證高優(yōu)先級進程的運行,當前進程會被掛起,由高優(yōu)先級進程來運行。
- 發(fā)生硬中斷,CPU 上的進程會被中斷掛起,轉(zhuǎn)而執(zhí)行內(nèi)核中的中斷服務(wù)程序。
- 了解這幾個場景是非常有必要的,因為一旦出現(xiàn)上下文切換的性能問題,它們就是幕后兇手。
線程上下文切換
- 第一種, 前后兩個線程屬于不同進程。此時,因為資源不共享,所以切換過程就跟進程上下文切換是一樣。
- 第二種,前后兩個線程屬于同一個進程。此時,因為虛擬內(nèi)存是共享的,所以在切換時,虛擬內(nèi)存這些資源就保持不動,只需要切換線程的私有數(shù)據(jù)、寄存器等不共享的數(shù)據(jù)。到這里你應(yīng)該也發(fā)現(xiàn)了,雖然同為上下文切換,但同進程內(nèi)的線程切換,要比多進程間的切換消耗更少的資源,而這,也正是多線程代替多進程的一個優(yōu)勢。
雖然同為上下文切換,但同進程內(nèi)的線程切換,要比多進程間的切換消耗更少的資源,而這,也正是多線程代替多進程的一個優(yōu)勢。
系統(tǒng)調(diào)用
系統(tǒng)調(diào)用(system call),指運行在用戶空間的程序向操作系統(tǒng)內(nèi)核請求需要更高權(quán)限運行的服務(wù)。系統(tǒng)調(diào)用提供用戶程序與操作系統(tǒng)之間的接口。大多數(shù)系統(tǒng)交互式操作需求在內(nèi)核態(tài)運行。
典型實現(xiàn)(Linux)
Linux 在x86上的系統(tǒng)調(diào)用通過 int 80h 實現(xiàn),用系統(tǒng)調(diào)用號來區(qū)分入口函數(shù)。操作系統(tǒng)實現(xiàn)系統(tǒng)調(diào)用的基本過程是:
- 應(yīng)用程序調(diào)用庫函數(shù)(API);
- API 將系統(tǒng)調(diào)用號存入 EAX,然后通過中斷調(diào)用使系統(tǒng)進入內(nèi)核態(tài);
- 內(nèi)核中的中斷處理函數(shù)根據(jù)系統(tǒng)調(diào)用號,調(diào)用對應(yīng)的內(nèi)核函數(shù)(系統(tǒng)調(diào)用);
- 系統(tǒng)調(diào)用完成相應(yīng)功能,將返回值存入 EAX,返回到中斷處理函數(shù);
- 中斷處理函數(shù)返回到 API 中;
- API 將 EAX 返回給應(yīng)用程序。
CPU 寄存器里原來用戶態(tài)的指令位置,需要先保存起來。接著,為了執(zhí)行內(nèi)核態(tài)代碼,CPU 寄存器需要更新為內(nèi)核態(tài)指令的新位置。最后才是跳轉(zhuǎn)到內(nèi)核態(tài)運行內(nèi)核任務(wù)。而系統(tǒng)調(diào)用結(jié)束后,CPU 寄存器需要恢復原來保存的用戶態(tài),然后再切換到用戶空間,繼續(xù)運行進程。
總結(jié):一次系統(tǒng)調(diào)用的過程,其實是發(fā)生了兩次 CPU 上下文切換。但是主要是CPU寄存器,不會涉及到虛擬內(nèi)存等資源。
中斷上下文切換
無論是硬件中斷(如來自時鐘和外設(shè))、可編程中斷(programmed interrupt)(執(zhí)行引起“軟件中斷”(software interrupt)的指令),還是例外中斷(如頁面錯),都由系統(tǒng)負責處理。
- 硬件通過觸發(fā)信號,向CPU發(fā)送中斷信號,導致內(nèi)核調(diào)用中斷處理程序,進入內(nèi)核空間。這個過程中,硬件的一些變量和參數(shù)也要傳遞給內(nèi)核, 內(nèi)核通過這些參數(shù)進行中斷處理。
當發(fā)生一個中斷時,如果CPU正在比該中斷級低的處理機運行級上運行,它就在解碼下條指令之前,接收該中斷,并提高處理機執(zhí)行級。這樣,在它處理當前的中斷時,就不會響應(yīng)該級別或更低級別的中斷,從而維護了內(nèi)核數(shù)據(jù)結(jié)構(gòu)的完整性。內(nèi)核處理中斷的操作順序如下。
- 切換到到內(nèi)核線程并對正在執(zhí)行的進程,保存其當前寄存器上下文并創(chuàng)建(壓入)一個新上下文。
- 確定中斷源,識別中斷類型(如時鐘或磁盤)。若可以的話,還識別中斷的單元號(如哪個磁盤機引起中斷)。當系統(tǒng)接收一個中斷時,它從機器中得到一個數(shù),系統(tǒng)把這個數(shù)用作查表的偏移量。這個表通常被稱為中斷向量(interrupt vector)。中斷向量的內(nèi)容因機器而異。但一般都含有每種中斷源的中斷處理程序的地址以及中斷處理程序取得參數(shù)的方式。內(nèi)核調(diào)用中斷處理程序。從邏輯上講,新上下文層的核心棧不同于前一上下文層的核心棧。在實現(xiàn)方法上,有些是用正在運行的進程的核心棧存放中斷處理程序的棧結(jié)構(gòu),另一些則是使用全局中斷棧存放中斷處理程序的棧結(jié)構(gòu),后者能保證中斷處理程序不用進行上下文切換就能返回。
- 中斷處理程序工作完畢前返回。內(nèi)核執(zhí)行一系列特殊機器指令。這些指令恢復前一上下文層的寄存器上下文和核心棧,使它們和中斷發(fā)生時的情況一樣,并恢復該上下文層的運行。相應(yīng)的進程的工作情況可能會受到中斷處理程序的影響,因為中斷處理程序可能已修改過內(nèi)核的全局數(shù)據(jù)結(jié)構(gòu),并喚醒過睡眠的進程。但在一般情況下,進程會像從來未發(fā)生過中斷一樣繼續(xù)運行。
4.分析linux系統(tǒng)的cpu上下文切換
工具
vmstat
vmstat 是一個常用的系統(tǒng)性能分析工具,主要用來分析系統(tǒng)的內(nèi)存使用情況,也常用來分析 CPU 上下文切換和中斷的次數(shù)。
- $ vmstat 5
- rocs -----------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 27458868 145996 4781912 0 0 0 1 0 0 0 0 99 0 0
- 0 0 0 27459388 145996 4781928 0 0 0 3 8937 15791 1 1 99 0 0
- 0 0 0 27457272 145996 4781948 0 0 0 10 9022 15774 1 1 99 0 0
你可以通過man手冊解讀每列的含義?,F(xiàn)在我們重點強調(diào):
- cs(context switch)是每秒上下文切換的次數(shù)。
- in(interrupt)是每秒中斷的次數(shù)。
- r(Running or Runnable)是就緒隊列的長度,也就是正在運行和等待 CPU 的進程數(shù)。
- b(Blocked)是處于不可中斷睡眠狀態(tài)的進程數(shù)。
vmstat 只給出了系統(tǒng)總體的上下文切換情況,要想查看每個進程的詳細情況,就需要使用我們前面提到過的 pidstat 了。給它加上 -w 選項,你就可以查看每個進程上下文切換的情況了。
- # 每隔5秒輸出1組數(shù)據(jù)
- $ pidstat -wu -t 5
- Linux 4.4.0-142-generic (i-0nxoa13q) 07/22/2021 _x86_64_ (16 CPU)
- 03:54:53 PM UID TGID TID %usr %system %guest %CPU CPU Command
- 03:54:58 PM 0 3 - 0.00 0.20 0.00 0.20 0 ksoftirqd/0
- 03:54:58 PM 0 - 3 0.00 0.20 0.00 0.20 0 |__ksoftirqd/0
- 03:54:58 PM 0 7 - 0.00 0.20 0.00 0.20 12 rcu_sched
- 03:54:58 PM 0 6849 - 0.80 0.00 0.00 0.80 10 dockerd
- ...
- 03:54:53 PM UID TGID TID cswch/s nvcswch/s Command
- 03:54:58 PM 0 3 - 64.21 0.00 ksoftirqd/0
- 03:54:58 PM 0 - 3 64.21 0.00 |__ksoftirqd/0
- 03:54:58 PM 0 7 - 98.01 0.00 rcu_sched
- 03:54:58 PM 0 - 7 98.01 0.00 |__rcu_sched
- ...
這個結(jié)果中有兩列內(nèi)容是我們的重點關(guān)注對象。一個是 cswch ,表示每秒自愿上下文切換(voluntary context switches)的次數(shù),另一個則是 nvcswch ,表示每秒非自愿上下文切換(non voluntary context switches)的次數(shù)。這兩個概念你一定要牢牢記住,因為它們意味著不同的性能問題:
- 自愿上下文切換,是指進程無法獲取所需資源,導致的上下文切換。比如說, I/O、內(nèi)存等系統(tǒng)資源不足時,就會發(fā)生自愿上下文切換。
- 非自愿上下文切換,則是指進程由于時間片已到等原因,被系統(tǒng)強制調(diào)度,進而發(fā)生的上下文切換。比如說,大量進程都在爭搶 CPU 時,就容易發(fā)生非自愿上下文切換。
proc文件系統(tǒng)
Linux內(nèi)核提供了一種通過?/proc?文件系統(tǒng),在運行時訪問內(nèi)核內(nèi)部數(shù)據(jù)結(jié)構(gòu)、改變內(nèi)核設(shè)置的機制。proc文件系統(tǒng)是一個偽文件系統(tǒng),它只存在內(nèi)存當中,而不占用外存空間。它以文件系統(tǒng)的方式為訪問系統(tǒng)內(nèi)核數(shù)據(jù)的操作提供接口。深入了解可以去看這篇文章:https://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/proc.html中斷次數(shù)變多了,說明 CPU 被中斷處理程序占用,還需要通過查看 /proc/interrupts 文件來分析具體的中斷類型。
sysbench壓力測試
sysbench是一個開源的、模塊化的、跨平臺的多線程性能測試工具,可以用來進行CPU、內(nèi)存、磁盤I/O、線程、數(shù)據(jù)庫的性能測試。Sysbench的測試主要包括以下幾個方面:
- 1、磁盤io性能
- 2、cpu性能
- 3、內(nèi)存分配及傳輸速度
- 4、POSIX線程性能
- 5、調(diào)度程序性能
- 6、數(shù)據(jù)庫性能(OLTP基準測試)
下面的案例基于 Ubuntu 18.04,當然,其他的 Linux 系統(tǒng)同樣適用。我使用的案例環(huán)境如下所示:
- 機器配置:4 CPU,8GB
- 預(yù)先安裝 sysbench 和 sysstat 包,如 apt install sysbench sysstat。
- # 以10個線程運行5分鐘的基準測試,模擬多線程切換的問題
- sysbench --threads=10 --max-time=300 threads run
top命令:
- top - 11:10:05 up 10 days, 35 min, 3 users, load average: 2.72, 1.27, 0.64
- Tasks: 120 total, 1 running, 119 sleeping, 0 stopped, 0 zombie
- %Cpu0 : 22.3 us, 69.4 sy, 0.0 ni, 8.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
- %Cpu1 : 23.2 us, 66.3 sy, 0.0 ni, 10.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
- %Cpu2 : 22.3 us, 67.1 sy, 0.0 ni, 10.6 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
- %Cpu3 : 22.8 us, 68.8 sy, 0.0 ni, 8.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
- KiB Mem : 8008272 total, 2646040 free, 3650548 used, 1711684 buff/cache
- KiB Swap: 0 total, 0 free, 0 used. 4048240 avail Mem
- PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
- 18770 root 20 0 94368 3672 2696 S 378.4 0.0 2:43.48 sysbench
看到內(nèi)核cpu使用率很高??梢蛇M程為sysbench。此時可以使用pidstat命令查看:
- pidstat -wu -t 5 -p 18770
- Linux 3.10.0-1160.15.2.el7.x86_64 (pulsar-1-348d-0002-f741-0002) 2021年07月23日 _x86_64_ (4 CPU)
- 11時11分26秒 UID TGID TID %usr %system %guest %CPU CPU Command
- 11時11分31秒 0 18770 - 98.60 100.00 0.00 100.00 3 sysbench
- 11時11分31秒 0 - 18770 0.00 0.00 0.00 0.00 3 |__sysbench
- 11時11分31秒 0 - 18771 9.00 28.60 0.00 37.60 2 |__sysbench
- 11時11分31秒 0 - 18772 9.60 27.40 0.00 37.00 3 |__sysbench
- 11時11分31秒 0 - 18773 9.80 29.00 0.00 38.80 3 |__sysbench
- 11時11分31秒 0 - 18774 9.60 27.80 0.00 37.40 2 |__sysbench
- 11時11分31秒 0 - 18775 9.80 29.00 0.00 38.80 1 |__sysbench
- 11時11分31秒 0 - 18776 10.40 27.80 0.00 38.20 2 |__sysbench
- 11時11分31秒 0 - 18777 11.20 27.00 0.00 38.20 1 |__sysbench
- 11時11分31秒 0 - 18778 9.80 27.40 0.00 37.20 2 |__sysbench
- 11時11分31秒 0 - 18779 9.80 28.20 0.00 38.00 2 |__sysbench
- 11時11分31秒 0 - 18780 9.80 28.40 0.00 38.20 0 |__sysbench
- 11時11分26秒 UID TGID TID cswch/s nvcswch/s Command
- 11時11分31秒 0 18770 - 0.00 0.00 sysbench
- 11時11分31秒 0 - 18770 0.00 0.00 |__sysbench
- 11時11分31秒 0 - 18771 47647.40 132455.60 |__sysbench
- 11時11分31秒 0 - 18772 47523.60 148473.80 |__sysbench
- 11時11分31秒 0 - 18773 44959.80 162137.20 |__sysbench
- 11時11分31秒 0 - 18774 46765.00 138126.60 |__sysbench
- 11時11分31秒 0 - 18775 47758.40 157361.60 |__sysbench
- 11時11分31秒 0 - 18776 47324.00 142616.40 |__sysbench
- 11時11分31秒 0 - 18777 48346.40 145743.60 |__sysbench
- 11時11分31秒 0 - 18778 49163.60 140320.40 |__sysbench
- 11時11分31秒 0 - 18779 47157.00 144586.60 |__sysbench
- 11時11分31秒 0 - 18780 46882.20 144493.00 |__sysbench
非自愿上下文切換為15萬左右,說明系統(tǒng)存在強制調(diào)度,都在爭搶cpu時間,說明 CPU 為系統(tǒng)瓶頸。
上下文切換多少次才算正常?
這個數(shù)值其實取決于系統(tǒng)本身的 CPU 性能。如果系統(tǒng)的上下文切換次數(shù)比較穩(wěn)定,且內(nèi)核cpu使用率很低,都應(yīng)該算是正常的。當上下文切換在一萬左右波動,且內(nèi)核cpu使用率偏高,就很可能出現(xiàn)了性能問題。
總結(jié)
上下文切換發(fā)生在操作系統(tǒng)內(nèi)核中。當看到內(nèi)核cpu使用率過大,考慮在發(fā)生上下文切換。
自愿上下文切換變多了,說明進程都在等待資源,有可能發(fā)生了 I/O 等其他問題;
非自愿上下文切換變多了,說明進程都在被強制調(diào)度,也就是都在爭搶 CPU,說明 CPU 為系統(tǒng)瓶頸;
中斷次數(shù)變多了,說明 CPU 被中斷處理程序占用,還需要通過查看 /proc/interrupts 文件來分析具體的中斷類型。