linux內核--進程管理

      進程就是處理執行期的程序(目標代碼存放在某種存儲介質上)。查進程並不僅僅侷限於一段可執行程序代碼。通常進程包括:

  • 打開的文件
  • 掛起的信號
  • 內核內部數據
  • 處理器狀態
  • 地址空間
  • 一個或多個執行線程
  • 存放全局變量的數據段

      對linux而言,線程是特殊的進程,並不特別區分。在現代操作系統中,進程提供兩種虛擬機制:虛擬處理器和虛擬內存。雖然實際上可能是許多進程正在分享同一個處理器,但虛擬處理器給進程一種假象,讓這些進程覺得自己在獨享處理器。而虛擬內存讓進程在獲取和使用內存時覺得自己擁有整個系統的所有內存資源。同一個進程中的多個線程可以共享虛擬內存,但擁有各自的虛擬處理器。

進程描述符及任務結構

      內核把進程存放在叫做任務隊列(task list)的雙向循環鏈表中。鏈表中的每一項都是類型爲task_struct、稱爲進程描述符(process descriptor)的結構,進程描述符中包含一個具體進程的所有信息。進程描述符中包含的數據能完整的描述一個正在執行的程序:它打開的文件、進程的地址空間、掛起的信號、進程的狀態等。

      進程描述符結構圖:

      進程描述符

      內核通過一個惟一的進程標識值(process identification value)或PID來標識每一個進程。內核把每個進程的PID存放在它們各自的進程描述符中。linux系統默認的PID最大值爲32768,也可以通過修改內核參數kernel.pid_max = 65535來提高上限。

      進程描述符中的state域描述了進程的當前狀態,系統中的每個進程必然處理5種進程狀態中的一種:

  • task_running(運行):進程是可執行的,它或者正在執行,或者在運行隊列中等待執行。這是進程在用戶空間中執行惟一可能的狀態;也可以應用到內核空間中正在執行的進程
  • task_interruptible(可中斷):進程正在睡眠(也就是說它被阻塞),等待某些條件的達成。一旦這些條件達成,內核應該會把進程狀態設置爲運行。處於些狀態的進程也會因爲接收到信號而提前被響醒並投入運行。
  • task_uninterruptible(不可中斷):除了不會因爲接收到信號而被響醒從而投入運行外,這個狀態與可打斷狀態相同。這個狀態通常在進程必須等待時不受干擾或等待事件很快就會發生時出現。
  • task_zombie(僵死):該進程已經結束了,但是其父進程還沒有調用wait4()系統調用,爲了父進程能夠獲知它的消息,子進程的進程描述符仍然被保留着。一旦父進程調用了wait4(),進程描述符就會被釋放。
  • task_stopped(停止):進程停止執行;進程沒有投入運行也不能投入運行。通常這種狀態發生在接收到SIGSTOP、SIGTSTP、SIGTIN、SIGTTOU等信號的時候。此外,在調試期間接收到任何信號,都會使進程進入這種狀態。

       進程的狀態轉換圖:

進程的狀態轉換

      linux系統進程之間存在一個顯顯的繼承關係,所有進程都是PID爲1的init進程的後代,內核在系統啓動的最後階段啓動init進程,該 進程讀取系統的初始化腳本並執行其它的相關程序,最終完成系統啓動的整個過程。系統中的每個進程必有一個父進程,相應的,每個進程也可以擁有零個或多個子進程。進程間的關係存放在進程描述符中,每個task_struct都包含一個指向其父進程task_struct、叫做parent的指針,還包含 一個稱爲children的子進程鏈表。

 

進程創建

      linux進程創建使用兩個函數fork()和exec()來完成。首先,fork()通過拷貝當前進程創建一個子進程。子進程與父進程的區別僅僅在於PID(每個進程唯一)、PPID(父進程的進程號)和某些資源和統計量。exec()函數負責讀取可執行文件並將其載入地址空間開始運行。

      傳統的fork()系統調用直接把所有的資源複製給新創建的進程,實種實現過於簡單並且效率低下,因爲它拷貝的數據也許並不共享,而且,如果新進程打算立即執行一個新的映像,那麼所有的拷貝都將前功盡棄。linux fork()使用寫時拷貝(copy-on-write)而實現。寫時拷貝是一種可以推遲甚至免除拷貝數據的技術。內核此時並不複製整個地址空間,而是讓父進程和子進程同時共享同一個拷貝。只有在需要寫入的時候,數據纔會被複制,從而使各個進程擁有各自的拷貝。也就是說,資源的複製只有在需要寫入的時候才進行,在些這前,只是以只讀方式共享。這種技術使地址空間上的頁的拷貝被推遲到實際發生寫入的時候。在頁根本不會被寫入的情況下,舉例來說:fork()後立即調用exec(),fork()的實際開銷就是複製父進程的頁表以及給子進程創建惟一的進程描述符。在一般情況下,進程創建後都會馬上運行一個可執行的文件,這種優化可以避免拷貝大量根本就不會使用的數據。

線程在linux中的實現

      線程機制提供了同一程序內共享共享內存地址空間運行的一組線程。這些線程還可以共享打開的文件和其它資源。線程機制支持併發程序設計技術,在多處理器系統上,它也能保證真正的並行處理。linux把所有線程都當作進程來實現,線程僅僅被視爲一個與其它進程共享某些資源的進程。每個線程都擁有惟一隸屬於自己的task_struct,所以在內核中,它看起來就像一個普通的進程。

       對於linux來說,線程只是一種進程間共享資源的手段,假如有一個包含4個線程的進程,通常會有一個包含指向4個不同線程進程的指針的進程描述符。該描述符負責描述像地址空間、打開的文件這樣的共享資源。

進程終結

      當一個進程終結時,內核必須釋放它所佔有的資源,並把這一不幸告知其父進程。當父進程在子進程之前退出時,必須有機制來保證子進程找到一個新的父親,否則這些成爲孤兒的進程就會在退出時永遠處理僵死狀態,白白的消耗內存。對於這個問題,解決的方法是給子進程在當前線程組內找一個線程作爲父行,如果不行,就讓init進程來作爲它們的父親。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章