TL;DR數據結構
筆者最美好的記憶來自於早年在6502 cpu的cc800上寫彙編的年代, 那個時代的計算機甚至沒有操做系統,也沒有實模式等保護機制。在6502上寫彙編應用其實很是簡單,系統會把bin文件加載到一個固定的內存地址中,cpu會固定地從一個特定的位置開始執行。而後cpu就按照你提供的機器指令開始一條一條的執行。在高級語言中的「函數調用」的概念,在彙編裏主要體現爲兩個寄存器。寄存器是cpu內部臨時保存數據的區域,至關於高級語言裏的變量。可是有一個寄存器是特殊的,它存放了cpu當前正在執行的指令的內存地址(Instruction Register)。一個高級語言中的函數通常會被編譯成指令存放在一段連續的內存空間中(data segment)。那麼所謂函數執行到了第幾行這樣的信息其實就是保存在這個Instruction Register中的。另一個很特殊的寄存器是Stack Register,它其中存放的內存地址指向的內存區域用於函數之間傳遞參數和返回值,以及存放一個函數內的局部變量。若是不考慮現代計算機cpu中各類各樣其餘存放中間結果的寄存器,理論上保存了Instruction Register(執行到哪兒了)和Stack Register(堆棧上的變量)就保存了一個函數的當前執行狀態,分別是函數當前執行到了哪,以及這個函數局部變量所表明的當前state。函數
事實上,操做系統的幾個關鍵切換也是這麼來完成的。操做系統提供了兩個執行態,一個是用戶態,通常咱們的代碼都是執行在用戶態的。另一個是內核態,像驅動程序之類的代碼會用各類方式被加載到操做系統內部執行在內核之中。內核態裏的代碼能夠徹底控制CPU的I/O中斷,從而能夠和外部設備交互。用戶態的代碼屬於受限代碼,必須把I/O請求經過syscall交由運行在內核態的操做系統來完成。當一個cpu的核在執行用戶態代碼時,其寄存器裏存放的狀態是你的應用的代碼的狀態,可是應用要進行I/O操做的時候,cpu要被切換到內核的代碼裏去執行內核態的代碼。這裏就須要進行一次context switch,所謂context switch其實原理不會比把寄存器的值存到內存的一個地方,等回來的時候再把內存中臨時保存的值加載回寄存器複雜多少。操作系統
操做系統還有一個須要進行context switch的地方,那就是在協程與協程之間。操做系統在執行一個ELF或者PE的可執行文件的時候,對於這個可執行文件內的彙編代碼來講,整個內存尋址空間是獨立的。也就是1.exe的執行狀態徹底沒法感知到2.exe的執行狀態的內存。也就是現代操做系統的虛擬內存空間。有cpu在兩個進程之間切換狀態的時候,須要把內存的映射關係調整過來,不然虛擬內存的地址是沒法對應到正確的物理地址的。一個進程內的兩個線成切換的時候,要稍微簡單一些,只須要把當前線成正在執行的位置和棧作切換就能夠了。線程
不管是操做系統作user/kernel的switch,仍是process/process,thread/thread的switch,其實現方式都是大同小異的。經過把「當前執行狀態」這樣的一個抽象概念落實爲一個具體的數據結構存儲起來,而後指揮cpu在不一樣的場合加載不一樣的數據恢復不一樣的「當前執行狀態」。設計
在高級語言中,一個函數正在執行的位置以及其狀態,內部均可以有一個抽象的表達方式。有的高級語言直接被編譯成原生的機器碼,那麼其執行狀態的表述就和操做系統的context switch的context很是相似。有的高級語言自身執行在一個虛擬機之上,那麼其context的表述多是虛擬機的instruction register和stack register,而不是80x86這樣原生的機器的物理寄存器。可是原理是很是相似的。協程
取決於語言設計者的覺悟,有的語言會把這種表達執行狀態的能力直接提供出來,讓一個函數在執行過程當中能夠把當前狀態保存,而後把執行權交給另一個函數執行,等那個函數放棄執行權回來的時候再把保存的狀態恢復。這也就是所謂的協程(co-routine)。協程與線程的區別在於,協程的context switch是在徹底在用戶態,由語言的runtime或者是庫來完成的。而線程的context switch則是操做系統來完成的。進程