1、進程的表示方式
linux將一個個進程抽象為一個個任務,並定義了一個結構體task_struct用於表示一個任務,對於每一個進程,在其生命週期裡都會有一個相應task_struct類型的進程描述符存在於記憶體中,儲存了核心用於管理進程所需要的重要訊息,一個task_struct包含了以下這些域:
通過為每一個進程儲存以上這些資訊,系統核心才得以合理地進行進程的管理。比如當進行進程調度時,核心需要得到每個進程的優先順序,來決定要分多少時間片;在進程接收到一個訊號時,核心需要查看進程指定了什麼方式進行處理,這些所有的資訊都需要通過在進程描述符中尋找。
由於系統中同時運行了多個進程,記憶體中也就儲存了多個進程描述符,為了方便地管理和支援快速尋找,核心維護了一個雜湊表,使用PID作索引值,採用開散列的方式解決衝突,同一個槽的元素使用雙向鏈表串連,如所示:(只是)
通過上面這種儲存方式,當核心需要尋找一個進程描述符時,只需要將進程的ID映射到雜湊表的一個槽中,並在該槽的鏈表上進行順序尋找即可。接下來我們再看看一個進程在linux系統中是如何建立的,有了以上資訊,這個過程就很容易理解了。
當一個fork系統調用執行時,調用fork的進程將切換至核心模式並建立一個task_struct類型的進程描述符(還有其他一些結構,這裡就不提了),新建立的進程描述符中的大多數內容將根據父進程的進程描述符進行設定,然後系統分配一個新的PID,並根據這個PID,映射到雜湊表裡對應的槽,若該槽已被占,則建立一個元素添加到鏈表裡,該元素儲存了新的進程描述符的記憶體位址。接下來,系統再為子進程分配記憶體空間,並將父進程的記憶體空間中的內容複寫過來,這個過程完成之後,子進程便可以開始運行了。
2、進程調度
在瞭解了進程的表示方式之後,我們再看看linux如何?進程調度。
(1)關於線程:linux系統的調度是基於線程的,一般將進程視為資源容器,而將線程視為一個執行單元(也就是一段連續,獨立的執行過程),事實上前面對應的task_struct是對應了一個線程,一個單線程的進程表示為一個task結構,而一個多線程的進程有多個task結構,每一個線程有一個。(這裡可能有點亂,因為linux裡進程和線程的概念有些模糊,不像其他系統還區分了進程,輕量級進程和線程,不過我們編程的時候不需要考慮這麼多,所以沒什麼影響)
(2)優先順序:linux將線程分成三類:即時FIFO線程、即時輪轉線程、普通分時線程
其中即時跟普通分時的區別僅在於優先順序不同,即時線程為0-99,普通分時線程為100-139(優先順序總共140個,0-139),優先順序數值越低表示優先順序越高,而FIFO跟輪轉的區別在於FIFO是非搶佔的,即到達的線程任務必須完全完成之後,下一個線程任務才能進行,輪轉則是每個線程分配了一個時間片,在該時間片內線程可以運行,當時間片耗完則切換下一個線程運行不管當前任務是否完成。
除了優先順序之外,與進程調度有關的還有另外一個值叫NICE值,它的範圍是-20~+19,nice值的意思是表示其他進程的友好程度,友好是指把cpu時間讓出來,所有一個進程的NICE值越大,它本身使用的cpu時間會越少,其預設的值為0,可以使用nice系統調用進行修改。優先順序和NICE值共同決定了一個進程在cpu的已耗用時間。
(3)進程調度的資料結構:linux的調度器為每一個cpu維護了一個資料結構成為runqueue,如下:
如所示,一個runqueue中有兩個域active和expired,它們是一個指標分別指向了一個長度為140的數組,而這每個數組裡有儲存了140個鏈表的頭指標,每個鏈表實際上對應了一個優先順序,鏈表裡存了屬於該優先順序的進程。調度器進行調度的過程基本上是:先從active裡優先順序最高的鏈表中取出一個任務執行,當該進程的時間片耗光後,則把它移動expired裡對應的優先順序的鏈表中,然後再取出下一個進程執行,這樣一級級往下,就保證了優先順序高的進程先被執行,同時進程優先順序越高,所分配的時間片也越長。而當active裡的所有進程都已經執行過後,只要將active和expired的這兩個指標的指向交換,則原先expired裡的現在進程現在又變成了active,之後再重複上面的步驟即可,這種方式就保證了低優先順序的進程能夠得到cpu時間。
OK,關於linux系統的進程管理的部分就到這裡了,其實實現這一部分還是很複雜的,我的水平有限也沒有辦法講得很清楚,而且因為對實際應用的影響也不是很大,所以也沒什麼興趣進行再深入的研究了,我們只要知道它實現的機制和思路,證明它真的只是一個程式,而且設計的方式多種多樣,沒有絕對標準就達到目的了,下一篇將進入到linux的記憶體管理模組,敬請期待。