操做系統是一門重要的基礎知識,瞭解這門基礎知識不只能幫助咱們寫出更優秀的程序,還能提升咱們的學習能力。當我發現有時候常常看不懂大佬的文章,聽不懂大佬間談話,看不懂項目文檔的時候,我想我是時候補充一下基礎知識了。本系列篇章內容基於Operating Systems: Three Easy Pieces的讀後感,是一份操做系統知識的概括總結。linux
咱們的電腦使用一塊CPU「同時」運行着各式各樣的應用程序,操做系統經過分時共享的方式,讓每一個程序輪流使用CPU,就好像每一個程序都有本身的CPU同樣(就好像共享單車那樣)。爲了使CPU可以被各進程分時共享,操做系統要掌握分配CPU使用權的權利,同時也要履行服務好各項進程的義務。本篇文章就操做系統如何行使「權利」與履行「義務」作了一些概括總結——操做系統如何掌握系統的控制權?操做系統如何協調各項進程「共享」CPU?算法
操做系統是有「被害妄想症」的(事實上,它必需要有被害妄想症...),它不信任用戶進程,總想着用戶進程充滿惡意,會阻礙系統的正常運做。因而乎,只有操做系統纔有權限直接訪問諸如內存,硬盤,以及其餘系統資源。一但有用戶進程試圖越過操做系統執行這些「危險」的訪問操做,該進程就會被殺死。讓用戶進程直接訪問內存,硬盤等資源的確也是很危險的一件事,試想一下若是一個進程能夠任意讀取和修改其餘進程的內存數據,那麼基本上全部的進程都不能正常運行了。因此操做系統必需要限制用戶進程的行爲。編程
操做系統經過劃分用戶態和內核態來限制用戶進程的行爲:數據結構
爲了區分用戶態和內核態,操做系統須要硬件的幫助。CPU要提供某種權限機制,來區分用戶態和內核態的訪問權限。例如,x86 CPU提供4種特權級別(privilege level):0,1,2,3,等級越低權限越高(能夠執行的指令種類越多)。在任一時刻,CPU都是在一個特定的特權級下運行,若是進程執行了不符合當前特權級別的指令,CPU會拋出異常,該進程會崩潰退出,從而起到保護做用。函數
Unix/linux操做系統只用到了Ring0和Ring3:操做系統的特權級別是Ring0,而用戶進程的特權級別是Ring3。然而,咱們的應用程序老是會須要訪問內存以及磁盤的。但在用戶態下,用戶進程並無執行直接訪問這些硬件資源的指令權限。用戶進程須要經過操做系統提供的編程接口——系統調用(system call)與操做系統進行交互,由操做系統在內核態中來執行相應的指令。用戶進程經過觸發軟中斷(trap)讓CPU陷入內核態,由操做系統在內核態處理用戶請求。處理完畢後,操做系統經過執行return-from-trap指令似CPU返回用戶態,將結果返回給用戶進程,繼續用戶進程的執行。性能
當某些急切須要CPU處理的事件發生的時候,能夠由軟件或硬件觸發一箇中斷信號,讓CPU停下手頭上的事情,立馬處理當前觸發中斷的事件,中斷處理完畢後,CPU將繼續執行被暫停的程序。每一箇中斷都有一箇中斷編號,其對應的處理函數入口地址保存在中斷向量表(interrupt vector table)中。在操做系統的啓動階段,操做系統會設置中斷向量表,當中斷產生的時候,CPU根據中斷號查詢中斷向量表,從而得知執行哪個中斷處理函數。能夠對中斷進行一個簡單的分類:學習
int 0x80
彙編指令來觸發系統調用軟中斷(中斷編號爲0x80)。假設系統調用的中斷編號爲0x80,CPU可以經過0x80得知這是一個系統調用,執行系統調用中斷處理函數。可是咱們的系統調用中斷處理函數還不知道究竟是什麼系統調用,是讀寫文件?仍是分配內存?操做系統經過給系統調用編號來解決這個問題。當要執行系統調用的時候,用戶進程會將系統調用編號(system-call number),以及調用參數,傳給系統調用中斷處理函數(經過CPU寄存器傳遞),後者根據系統調用編號執行相應的代碼。編碼
圖片修改自 pages.cs.wisc.edu/~remzi/OSTE… Figure 6.2操作系統
在操做系統啓動階段初始化中斷向量表,並告知CPU硬件中斷向量表的訪問地址。當用戶進程執行系統調用的時候,CPU將部分寄存器信息保存到當前進程的內核棧中,切換至內核態,開始執行系統調用中斷處理函數。系統調用中斷處理函數根據系統調用編號執行相應系統調用,執行完畢後經過return-from-trap指令,恢復發生中斷前的寄存器內容,切換至用戶態,繼續執行用戶進程。指針
後文有對①②步驟的詳細說明。
當用戶進程正在CPU上運行時,意味着咱們的操做系統並無運行。既然操做系統沒有在運行,那實際上操做系統是沒有辦法作任何事的。爲了讓操做系統可以奪回CPU的控制權,咱們再一次須要硬件的幫助——藉助時鐘設備來按期觸發時鐘中斷(timer interrupt)。當時鍾中斷觸發後,當前進程會暫停執行,CPU切換至內核態,執行中斷處理函數(interrupt handler),此時,操做系統從新得到了CPU的使用權,根據進程調度算法的選擇,它能夠暫停當前進程運行,運行另外一個進程。
當進程從新得到CPU使用權的時候,CPU須要知道他上一次執行到哪裏才能正確繼續執行下去。當操做系統決定要執行進程切換的時候,會執行上下文切換(context switch)——保存當前進程的上下文信息,恢復即將執行的進程的上下文信息。
清閒的午後,你正在房間裏看書(閱讀進程)。忽然線上出bug了(中斷),你夾好書籤(保存上下文信息)合上書,火急火燎打開電腦開始排查(切換到修bug進程)。處理完成以後,回到房間繼續看書。多虧合書以前夾好了書籤,打開書你即可以繼續上次的閱讀了。
進程控制塊(PCB)是操做系統用於描述一個進程信息的數據結構。進程的上下文信息保存在PCB中。PCB保存下列信息:
更多信息可參考:en.wikipedia.org/wiki/Proces…
操做系統爲每一個進程獨立分配一段棧空間,當進程因某種緣由(中斷/上下文切換)暫停運行時,用於保存CPU或進程的上下文信息。當進程須要繼續執行的時候,從內核棧中恢復這些信息。
若是僅僅是中斷(例如系統調用),而未發生進程切換,其實並不須要保存進程的上下文信息。具體緣由在後續的圖解中詳細說明。
進程切換步驟如圖所示:
圖片修改自 pages.cs.wisc.edu/~remzi/OSTE… Figure 6.3
時鐘中斷的產生暫停了進程A的運行,由硬件將部分寄存器信息保存到內核棧,並切換至內核態。在時鐘中斷處理函數中,若是操做系統決定要從進程A切換到進程B(根據進程調度算法),開始調用switch()
方法執行上下文切換:
上下文切換完成後,操做系統執行return-from-trap指令,恢復進程B的寄存器,切換到用戶態,開始執行進程B。
圖中①②③④步驟都涉及到保存寄存器或恢復寄存器的操做。實際上,只有②跟③是屬於上下文切換的範疇——從進程A的上下文切換至進程B的上下文。而①和④步驟由CPU硬件完成,由中斷觸發,跟進程切換沒有直接關係。其目的是——保存CPU進入內核態前的狀態,確保調用return-from-trap指令後,能恢復正確的用戶態狀態(進入內核態前的狀態),其實也能夠把這個步驟歸稱之爲上下文切換,只不過這是CPU內核態與用戶態之間的上下文切換(mode switch)。
操做系統經過區分用戶態和內核態,配合CPU硬件提供的權限機制來限制用戶進程的執行權限。用戶進程只能經過系統調用來訪問硬件資源(內存,硬盤等)。操做系統經過時鐘中斷來保持對操做系統的控制,使用上下文切換的方式來保障進程在CPU上正常切換。執行一次系統調用或上下文切換的代價仍是挺高的(CPU作了不少數據搬運的工做),在編碼中,咱們能夠經過減小系統調用的次數來提高程序的性能(好比合並讀寫操做)。