前幾天和同事討論到多核、多任務,感歎寫程式時多線程間同步的麻煩,擔心寫出來的程式會出問題,聽說多任務是比爾蓋茨發明的,然後,討論到如果這個世界沒有多任務會怎麼辦?
一個cpu在同一時間只能幹一件事情,所謂的多任務實際上是假的分時機制,我有一個同事寫了一個單片機上的類似於UCOS的作業系統,採用定時器
中斷來實現任務的切換以及以訊息驅動之。這種做法和現代作業系統中的多任務有什麼區別呢?
Linux對進程的定義有四個要素:有一段可以執行的代碼、有獨立的使用者空間、有系統堆棧、有task_struct。
如果沒有獨立的使用者空間,那就叫線程;如果完全沒有使用者空間,那就叫核心線程。
可以從Linux如何?多任務來理解這四個要素;每個進程都有自己的4G的虛擬位址空間。使用者進程間互不干擾。即使用者進程A不可以訪問使用者進程B的
空間;使用者進程B不可以訪問使用者進程A的空間。這是現代作業系統與簡單的作業系統之間的差異。在UCOS和我同事的那個系統中,實現進程切換的機
制和現代作業系統的類似,不同之處在於,簡單的作業系統上,進程之間沒有獨立性;也就是儲存管理做的那部分工作。這也是現代作業系統能夠分成
作業系統層、應用程式層,以及在應用程式層上面有那麼多應用的原因。從這個角度看,簡單作業系統無論多開放,它還是封閉的,這種特性決定了它無法發展
起來。
Linux是如何做到進程間相互獨立的呢?就是通過它的儲存管理機制和進程管理機制。一段可執行代碼、一個獨立的使用者空間、一塊系統堆棧空間和
一個task_struct結構;其中,系統堆棧空間和task_struct在系統空間,必須在系統模式下修改;而獨立的使用者空間和可執行代碼,它所隱含的意
思是:使用者進程能夠修改自己分配的空間的內容,不能修改不是自己分配的空間的內容,這樣,就保證了進程間的獨立。
我們可以想象進程的行為:
1. 進程的誕生,在系統模式下,為一個進程分配系統堆棧、task_struct;
2. 匯入一段可執行程式碼片段,根據程式碼片段中的內容,分配使用者空間中的全域變數和靜態變數,並分配好堆棧空間;
3. 執行程式碼片段,當需要的資料在棧中操作時,調整棧指標,MMU將虛擬位址映射到物理地址。
4. 執行程式碼片段,當需要的資料在堆中操作時,首先請求分配堆,進入核心模式,分配相應的虛擬位址空間,並加入task_struct中的虛存區間列表
中,同時分配物理頁面,在頁目錄表和頁表中加入相應的項,建立映射關係;然後訪問這些虛擬位址,由MMU進行地址映射。
核心是如何管理進程的?
有兩個部分:進程的建立和進程的調度與切換。
進程在系統空間中有兩部分,task_struct結構和系統堆棧空間,佔據了8K的物理空間,連續的兩個頁表。task_struct是進程的ID,核心通過
task_struct來管理進程,在task_struct中有四個隊列:
1. 線性隊列,用於遍曆整個task_struct列表;
2. 樹形隊列;在Linux中,進程必須由進程來建立,這樣就行了了父子關係,兄弟關係。通過樹形隊列,可以將所有的task_struct聯絡起來;
3. hash表;每個進程都有一個id,叫pid;核心在根據pid尋找進程時,用散列表方法速度更快;
4. 可執行隊列;進程有五種狀態:就緒、暫停、死亡、淺度休眠和深度休眠。其中正在執行的進程狀態也是就緒。
核心是如何調度進程的呢?這裡有兩個概念:核心線程和進程的系統空間;比如任務調度線程,就是核心線程,它的任務是根據當前處於就緒態的進
程的優先順序還有一些參數,計算出權值,決定調度那個進程。它如何擷取處於就緒態的進程的資訊呢?通過task_struct結構;
這裡有兩種方法:
1. 將當前進程的task_struct的地址儲存在某個位置,當需要時,讀取該地址。
2. 當需要擷取當前進程的task_struct地址時,計算它!
Linux採用的是第二種辦法,由於系統堆棧和task_struct處於連續的兩個4K的物理頁面中,而且task_struct位於低地址,有一個專用的寄存器叫
ESP,它指向當前進程的系統堆棧,通過計算可得到當前進程的task_struct地址。
進程的調度:在進程的調度時,要切換上下文;上下文包括:
1. 通用寄存器組;
2. 段寄存器組:CS、DS、ES、SS、FS;
3. 指令地址寄存器:EIP;
4. 特殊寄存器:EFLAGS;
5. 控制寄存器CR3;這個寄存器記錄了頁碼錶的地址,在任務切換時,要將進程的頁碼錶地址匯入該寄存器。
在系統堆棧還記錄一些資訊:
1. 通過系統調用進入系統模式時,要入系統棧;
2. 中斷時要入系統棧;