CPU 上下文切換

Linux 是一個多任務(wù)操作系統(tǒng)蠕趁,它支持遠大于 CPU 數(shù)量的任務(wù)同時運行娜搂。當然迁霎,這些任務(wù)實際上并不是真的在同時運行,而是因為系統(tǒng)在很短的時間內(nèi)百宇,將 CPU 輪流分配給它們考廉,造成多任務(wù)同時運行的錯覺。

而在每個任務(wù)運行前携御,CPU 都需要知道任務(wù)從哪里加載昌粤、又從哪里開始運行,也就是說啄刹,需要系統(tǒng)事先幫它設(shè)置好 CPU 寄存器和程序計數(shù)器(Program Counter婚苹,PC)。

CPU 寄存器鸵膏,是 CPU 內(nèi)置的容量小膊升、但速度極快的內(nèi)存。而程序計數(shù)器谭企,則是用來存儲 CPU 正在執(zhí)行的指令位置廓译、或者即將執(zhí)行的下一條指令位置。它們都是 CPU 在運行任何任務(wù)前债查,必須的依賴環(huán)境非区,因此也被叫做 CPU 上下文。

知道了什么是 CPU 上下文盹廷,我想你也很容易理解 CPU 上下文切換征绸。CPU 上下文切換,就是先把前一個任務(wù)的 CPU 上下文(也就是 CPU 寄存器和程序計數(shù)器)保存起來俄占,然后加載新任務(wù)的上下文到這些寄存器和程序計數(shù)器管怠,最后再跳轉(zhuǎn)到程序計數(shù)器所指的新位置,運行新任務(wù)缸榄。

而這些保存下來的上下文渤弛,會存儲在系統(tǒng)內(nèi)核中,并在任務(wù)重新調(diào)度執(zhí)行時再次加載進來甚带。這樣就能保證任務(wù)原來的狀態(tài)不受影響她肯,讓任務(wù)看起來還是連續(xù)運行佳头。

我猜肯定會有人說,CPU 上下文切換無非就是更新了 CPU 寄存器的值嘛晴氨,但這些寄存器康嘉,本身就是為了快速運行任務(wù)而設(shè)計的,為什么會影響系統(tǒng)的 CPU 性能呢亭珍?

在回答這個問題前块蚌,不知道你有沒有想過膘格,操作系統(tǒng)管理的這些“任務(wù)”到底是什么呢财松?

也許你會說辆毡,任務(wù)就是進程舶掖,或者說任務(wù)就是線程。是的主慰,進程和線程正是最常見的任務(wù)共螺。但是除此之外情竹,還有沒有其他的任務(wù)呢?

不要忘了雏蛮,硬件通過觸發(fā)信號底扳,會導(dǎo)致中斷處理程序的調(diào)用衷模,也是一種常見的任務(wù)。

所以阱冶,根據(jù)任務(wù)的不同木蹬,CPU 的上下文切換就可以分為幾個不同的場景镊叁,也就是進程上下文切換線程上下文切換以及中斷上下文切換疤苹。

這節(jié)課我就帶你來看看卧土,怎么理解這幾個不同的上下文切換尤莺,以及它們?yōu)槭裁磿l(fā) CPU 性能相關(guān)問題生棍。

進程上下文切換

Linux 按照特權(quán)等級涂滴,把進程的運行空間分為內(nèi)核空間和用戶空間氢妈,分別對應(yīng)著下圖中首量, CPU 特權(quán)等級的 Ring 0 和 Ring 3。

  • 內(nèi)核空間(Ring 0)具有最高權(quán)限鸭叙,可以直接訪問所有資源沈贝;
  • 用戶空間(Ring 3)只能訪問受限資源勋乾,不能直接訪問內(nèi)存等硬件設(shè)備,必須通過系統(tǒng)調(diào)用陷入到內(nèi)核中学歧,才能訪問這些特權(quán)資源。

換個角度看袁铐,也就是說剔桨,進程既可以在用戶空間運行洒缀,又可以在內(nèi)核空間中運行帝洪。進程在用戶空間運行時脚猾,被稱為進程的用戶態(tài)龙助,而陷入內(nèi)核空間的時候提鸟,被稱為進程的內(nèi)核態(tài)称勋。

從用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)變涯竟,需要通過系統(tǒng)調(diào)用來完成庐船。比如筐钟,當我們查看文件內(nèi)容時,就需要多次系統(tǒng)調(diào)用來完成:首先調(diào)用 open() 打開文件李破,然后調(diào)用 read() 讀取文件內(nèi)容嗤攻,并調(diào)用 write() 將內(nèi)容寫到標準輸出狱庇,最后再調(diào)用 close() 關(guān)閉文件恶耽。

那么偷俭,系統(tǒng)調(diào)用的過程有沒有發(fā)生 CPU 上下文的切換呢?答案自然是肯定的淹遵。

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 寄存器需要恢復(fù)原來保存的用戶態(tài),然后再切換到用戶空間寂玲,繼續(xù)運行進程梗摇。所以断序,一次系統(tǒng)調(diào)用的過程,其實是發(fā)生了兩次 CPU 上下文切換糜烹。

不過违诗,需要注意的是,系統(tǒng)調(diào)用過程中疮蹦,并不會涉及到虛擬內(nèi)存等進程用戶態(tài)的資源诸迟,也不會切換進程。這跟我們通常所說的進程上下文切換是不一樣的:

  • 進程上下文切換愕乎,是指從一個進程切換到另一個進程運行阵苇。
  • 而系統(tǒng)調(diào)用過程中一直是同一個進程在運行。

所以感论,系統(tǒng)調(diào)用過程通常稱為特權(quán)模式切換绅项,而不是上下文切換。但實際上笛粘,系統(tǒng)調(diào)用過程中,CPU 的上下文切換還是無法避免的

那么,進程上下文切換跟系統(tǒng)調(diào)用又有什么區(qū)別呢?

首先凿可,你需要知道敛助,進程是由內(nèi)核來管理和調(diào)度的,進程的切換只能發(fā)生在內(nèi)核態(tài)。所以,進程的上下文不僅包括了虛擬內(nèi)存、棧纵东、全局變量等用戶空間的資源衰絮,還包括了內(nèi)核堆棧淌友、寄存器等內(nèi)核空間的狀態(tài)器联。

因此,進程的上下文切換就比系統(tǒng)調(diào)用時多了一步:在保存當前進程的內(nèi)核狀態(tài)和 CPU 寄存器之前,需要先把該進程的虛擬內(nèi)存祟身、棧等保存下來婉陷;担神;而加載了下一進程的內(nèi)核態(tài)后亥贸,還需要刷新進程的虛擬內(nèi)存和用戶棧。

如下圖所示厕倍,保存上下文和恢復(fù)上下文的過程并不是“免費”的,需要內(nèi)核在 CPU 上運行才能完成。

根據(jù) Tsuna 的測試報告乱陡,每次上下文切換都需要幾十納秒到數(shù)微秒的 CPU 時間膜廊。這個時間還是相當可觀的匙瘪,特別是在進程上下文切換次數(shù)較多的情況下薄货,很容易導(dǎo)致 CPU 將大量時間耗費在寄存器、內(nèi)核棧以及虛擬內(nèi)存等資源的保存和恢復(fù)上碍论,進而大大縮短了真正運行進程的時間税娜。這也正是上一節(jié)中我們所講的弧岳,導(dǎo)致平均負載升高的一個重要因素柳恐。

另外却嗡,我們知道坪它, Linux 通過 TLB(Translation Lookaside Buffer)來管理虛擬內(nèi)存到物理內(nèi)存的映射關(guān)系。當虛擬內(nèi)存更新后帝牡,TLB 也需要刷新往毡,內(nèi)存的訪問也會隨之變慢。特別是在多處理器系統(tǒng)上靶溜,緩存是被多個處理器共享的开瞭,刷新緩存不僅會影響當前處理器的進程,還會影響共享緩存的其他處理器的進程罩息。

知道了進程上下文切換潛在的性能問題后嗤详,我們再來看,究竟什么時候會切換進程上下文扣汪。顯然断楷,進程切換時才需要切換上下文,換句話說崭别,只有在進程調(diào)度的時候冬筒,才需要切換上下文。Linux 為每個 CPU 都維護了一個就緒隊列茅主,將活躍進程(即正在運行和正在等待 CPU 的進程)按照優(yōu)先級和等待 CPU 的時間排序舞痰,然后選擇最需要 CPU 的進程,也就是優(yōu)先級最高和等待 CPU 時間最長的進程來運行诀姚。

那么响牛,進程在什么時候才會被調(diào)度到 CPU 上運行呢?

最容易想到的一個時機赫段,就是進程執(zhí)行完終止了呀打,它之前使用的 CPU 會釋放出來,這個時候再從就緒隊列里糯笙,拿一個新的進程過來運行贬丛。其實還有很多其他場景,也會觸發(fā)進程調(diào)度给涕,在這里我給你逐個梳理下豺憔。

  • 其一,為了保證所有進程可以得到公平調(diào)度够庙,CPU 時間被劃分為一段段的時間片恭应,這些時間片再被輪流分配給各個進程。這樣耘眨,當某個進程的時間片耗盡了昼榛,就會被系統(tǒng)掛起,切換到其它正在等待 CPU 的進程運行剔难。

  • 其二胆屿,進程在系統(tǒng)資源不足(比如內(nèi)存不足)時,要等到資源滿足后才可以運行钥飞,這個時候進程也會被掛起莺掠,并由系統(tǒng)調(diào)度其他進程運行。

  • 其三读宙,當進程通過睡眠函數(shù) sleep 這樣的方法將自己主動掛起時彻秆,自然也會重新調(diào)度。
  • 其四结闸,當有優(yōu)先級更高的進程運行時唇兑,為了保證高優(yōu)先級進程的運行,當前進程會被掛起桦锄,由高優(yōu)先級進程來運行扎附。

  • 最后一個,發(fā)生硬件中斷時结耀,CPU 上的進程會被中斷掛起留夜,轉(zhuǎn)而執(zhí)行內(nèi)核中的中斷服務(wù)程序匙铡。

了解這幾個場景是非常有必要的,因為一旦出現(xiàn)上下文切換的性能問題碍粥,它們就是幕后兇手鳖眼。

線程上下文切換

說完了進程的上下文切換,我們再來看看線程相關(guān)的問題嚼摩。

線程與進程最大的區(qū)別在于钦讳,線程是調(diào)度的基本單位,而進程則是資源擁有的基本單位枕面。說白了愿卒,所謂內(nèi)核中的任務(wù)調(diào)度,實際上的調(diào)度對象是線程潮秘;而進程只是給線程提供了虛擬內(nèi)存琼开、全局變量等資源。所以唇跨,對于線程和進程稠通,我們可以這么理解:

  • 當進程只有一個線程時,可以認為進程就等于線程买猖。

  • 當進程擁有多個線程時改橘,這些線程會共享相同的虛擬內(nèi)存和全局變量等資源。這些資源在上下文切換時是不需要修改的玉控。

  • 另外飞主,線程也有自己的私有數(shù)據(jù),比如棧和寄存器等高诺,這些在上下文切換時也是需要保存的碌识。

這么一來,線程的上下文切換其實就可以分為兩種情況:

  • 第一種虱而, 前后兩個線程屬于不同進程筏餐。此時,因為資源不共享牡拇,所以切換過程就跟進程上下文切換是一樣魁瞪。
  • 第二種,前后兩個線程屬于同一個進程惠呼。此時导俘,因為虛擬內(nèi)存是共享的,所以在切換時剔蹋,虛擬內(nèi)存這些資源就保持不動旅薄,只需要切換線程的私有數(shù)據(jù)、寄存器等不共享的數(shù)據(jù)泣崩。

到這里你應(yīng)該也發(fā)現(xiàn)了少梁,雖然同為上下文切換洛口,但同進程內(nèi)的線程切換,要比多進程間的切換消耗更少的資源猎莲,而這绍弟,也正是多線程代替多進程的一個優(yōu)勢技即。

中斷上下文切換

除了前面兩種上下文切換著洼,還有一個場景也會切換 CPU 上下文,那就是中斷而叼。

為了快速響應(yīng)硬件的事件身笤,中斷處理會打斷進程的正常調(diào)度和執(zhí)行,轉(zhuǎn)而調(diào)用中斷處理程序葵陵,響應(yīng)設(shè)備事件液荸。而在打斷其他進程時,就需要將進程當前的狀態(tài)保存下來脱篙,這樣在中斷結(jié)束后娇钱,進程仍然可以從原來的狀態(tài)恢復(fù)運行。

跟進程上下文不同绊困,中斷上下文切換并不涉及到進程的用戶態(tài)文搂。所以,即便中斷過程打斷了一個正處在用戶態(tài)的進程秤朗,也不需要保存和恢復(fù)這個進程的虛擬內(nèi)存煤蹭、全局變量等用戶態(tài)資源。中斷上下文取视,其實只包括內(nèi)核態(tài)中斷服務(wù)程序執(zhí)行所必需的狀態(tài)硝皂,包括 CPU 寄存器、內(nèi)核堆棧作谭、硬件中斷參數(shù)等稽物。

對同一個 CPU 來說,中斷處理比進程擁有更高的優(yōu)先級折欠,贝或,所以中斷上下文切換并不會與進程上下文切換同時發(fā)生。怨酝。同樣道理傀缩,由于中斷會打斷正常進程的調(diào)度和執(zhí)行,所以大部分中斷處理程序都短小精悍农猬,以便盡可能快的執(zhí)行結(jié)束赡艰。

另外,跟進程上下文切換一樣斤葱,中斷上下文切換也需要消耗 CPU慷垮,切換次數(shù)過多也會耗費大量的 CPU揖闸,甚至嚴重降低系統(tǒng)的整體性能。所以料身,當你發(fā)現(xiàn)中斷次數(shù)過多時汤纸,就需要注意去排查它是否會給你的系統(tǒng)帶來嚴重的性能問題。

小結(jié)

總結(jié)一下芹血,不管是哪種場景導(dǎo)致的上下文切換贮泞,你都應(yīng)該知道:

  1. CPU 上下文切換,是保證 Linux 系統(tǒng)正常工作的核心功能之一幔烛,一般情況下不需要我們特別關(guān)注啃擦。

2.但過多的上下文切換,會把 CPU 時間消耗在寄存器饿悬、內(nèi)核棧以及虛擬內(nèi)存等數(shù)據(jù)的保存和恢復(fù)上令蛉,從而縮短進程真正運行的時間,導(dǎo)致系統(tǒng)的整體性能大幅下降狡恬。

原文

https://time.geekbang.org/column/article/69859

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末珠叔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子弟劲,更是在濱河造成了極大的恐慌祷安,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件函卒,死亡現(xiàn)場離奇詭異辆憔,居然都是意外死亡,警方通過查閱死者的電腦和手機报嵌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門虱咧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锚国,你說我怎么就攤上這事腕巡。” “怎么了血筑?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵绘沉,是天一觀的道長。 經(jīng)常有香客問我豺总,道長车伞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任喻喳,我火速辦了婚禮另玖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己谦去,他們只是感情好慷丽,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鳄哭,像睡著了一般要糊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妆丘,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天锄俄,我揣著相機與錄音,去河邊找鬼飘痛。 笑死珊膜,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的宣脉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼剔氏,長吁一口氣:“原來是場噩夢啊……” “哼塑猖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谈跛,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤羊苟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后感憾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜡励,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年阻桅,在試婚紗的時候發(fā)現(xiàn)自己被綠了凉倚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡嫂沉,死狀恐怖稽寒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情趟章,我是刑警寧澤杏糙,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站蚓土,受9級特大地震影響宏侍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜀漆,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一谅河、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦旧蛾、人聲如沸莽龟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽毯盈。三九已至,卻和暖如春病袄,著一層夾襖步出監(jiān)牢的瞬間搂赋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工益缠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脑奠,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓幅慌,卻偏偏與公主長得像宋欺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子胰伍,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容