《Linux內核分析與實現》 第五週 讀書筆記

第3章 進程管理

20135307張嘉琪linux


3.1 進程

  • 進程就是處於執行期的程序(目標碼存放在某種存儲介質上),但進程並不只僅侷限於一段可執行程序代碼。一般進程還要包含其餘資源,像打開的文件,掛起的信號,內核內部數據,處理器狀態,一個或多個具備內存映射的內存地址空間及一個或多個執行線程。固然還包括用來存放全局變量的數據段等,實際上,進程就是正在執行的程序代碼的實時結果,內核須要有效而又透明地管理全部細節。算法

  • 執行線程,簡稱線程,是在進程中活動的對象,每一個線程都擁有一個獨立的程序計數器、進程棧和一組進程寄存器,內核調度的對象是線程,而不是進程,在傳統的Linux系統中,一個進程只包含一個線程,但如今的系統中,包含多個線程的多線程程序司空見慣。Linux系統的線程實現很是特別:它對線程和進程並不特別區分,編程

  • 對Linux而言,線程只不過是一種特殊的進程罷了緩存

  • 在現代操做系統中,進程提供兩種虛擬機制:虛擬處理器和虛擬內存數據結構

  • 在現代Linux內核中,fork()其實是由clone()系統調用實現的。多線程

3.2 進程描述符及任務結構

  • 內核把進程的列表存放在叫作任務隊列的雙向循環鏈表中。併發

  • 鏈表中的每項都是類型爲task_struct、稱爲進程描述符的結構,該結構定義在<linux/sched.h>文件中。函數

3.2.1 分配進程描述符

  • Linux以經過slab分配器分配task_struct結構,這樣能達到對到對象複用和緩存着色的目的。這樣作是爲了讓那些像x86那樣寄存器較少的硬件體系結結構只要經過棧指針就能夠估算出他的位置。

  • 每一個任務的thread_info結構在他的內核棧的尾端分配。

3.2.2 進程描述符的存放

  • 內核經過一個惟一的進程標識值或PID來標識每一個進程,內核把每一個進程的PID存放在它們各自的進程描述符中。優化

  • 在內核中,訪問任務一般須要得到指向其taskstruct的指針,實際上,內核中大部分處理進程程描述符的速度就顯得尤其重要。硬件體系結構不一樣,該宏的實現也不一樣,它必須針對專門的硬件體系結構作處理,有的硬件體系結構能夠拿出―個專門寄存器來存放指向當前進程taskstruct的指針,用於加快訪問速度。操作系統

3.2.3 進程狀態

  • 五種進程狀態:運行、可中斷、不可中斷、被其餘進程跟蹤的進程、中止。

3.2.4 設置當前進程狀態

  • 內核須要常常調整某個進程的狀態:settaskstate(task,state)函數

3.2.5 進程上下文

  • 可執行程序代碼是進程的重要組成部分。這些代碼從一個可執行文件載入到進程的地址空間執行。通常程序在用戶空間執行。當一個程序調執行了系統調用(參見第5章)或者觸發了某個異常,它就陷入了內核空間。此時,咱們稱內核「表明進程執行」並處於進程上下文中。在此上下文中current宏是有效的。除非在此間隙有更高優先級的進程須要執行並由調度器作出了相應調整,不然在內核退出的時候,程序恢復在用戶空間會繼續執行。 系統調用和異常處理程序是對內核明肯定義的接口。進程只有經過這些接口才能陷入內核執行——對內核的全部訪問都必須經過這些接口。

3.2.6 進程家族樹

Unⅸ系統的進程之間存在—個明顯的繼承關係,在Linux系統中也是如此。全部的進程都是PID爲1的init進程的後代。內核在系統啓動的最後階段啓動init進程。該進程讀取系統的初始化腳本並執行其餘的相關程序,最終完成系統啓動的整個過程。

3.3 進程建立

3.3.1 寫時拷貝

  • 傳統的fork()系統調用直接把全部的資源複製給新建立的進程,這種實現過於簡單而且效率低下,由於它拷貝的數據也許並不共享,更糟的狀況是,若是新進程打算當即執行一個新的映像,那麼全部的拷貝都將前功盡棄。Linux的fork()使用寫時拷貝頁實現,寫時拷貝是一種能夠推遲甚至免除拷貝數據的技術。內核此時並不複製整個進程地址空間,而是讓父進程和子進程共享同一個拷貝。

  • 只有在須要寫入的時候,數據纔會被複制,從而使各個進程擁有各自的拷貝,也就是說資源的複製只有在須要寫入的時候才進行,在此以前,只是以只讀方式共享,這種技術使地址空間上的頁的拷貝被推遲到實際發生寫入的時候才進行在頁根本不會被寫入的狀況下它們就無須複製了。

  • fork()的實際開銷就是複製父進程的頁表以及給子進程建立惟一的進程描述符。在通常狀況下,進程建立後都會立刻運行一個可執行的文件,這種優化能夠避免拷貝大量根本就不會被使用的數據(地址空間裏經常包含數十兆的數據)因爲Unix強調進程快速執行的能力,因此這個優化是很重要的。

3.3.2 fork()27

  • copy_process()完成的工做:

    1. 調用duptaskstruct()爲新進程建立一個內核棧、threadinfo結構和taskstruct,這些值與當前進程的值相同,此時,子進程和父進程的描述符是徹底相同的。
    2. 檢查並確保新建立這個子進程後,當前用戶所擁有的進程數目沒有超出給它分配的資源的限制
    3. 子進程着手使本身與父進程區別開來,進程描述符內的許多成員都要被清0或設爲初始值,那些不是繼承而來的進程描述符成員,主要是統計信息,task_struct中的大多數數據都依然未被修改
    4. 子進程的狀態被設置爲TASK_UNINTERRUPTIBLE,以保證它不會投入運行
    5. copyprocess()調用copyflags以更新task_struct的flags成員
    6. 調用alloc_pid()爲新進程分配一個有效的PID
    7. 根據傳遞給clone()的參數標誌,copy_process()拷貝或共享打開的文件、文件系統信息、信號處理函數、進程地址空間和命名空間等,在通常狀況下,這些資源會被給定進程的全部線程共享;不然,這些資源對每一個進程是不一樣的,所以被拷貝到這裏。

3.3.3 vfork()

3.4 線程在Linux中的實現

線程機制是現代編程技術中經常使用的一種抽象概念,該機制提供了在同―程序內共享內存地址空間運行的―組線程,這些線程還能夠共享打開的文件和其餘資源,線程機制支持併發程序設計技術,在多處理器系統上,它也能保證真正的並行處理。 Linux實現線程的機制很是獨特,從內核的角度來講,它並無線程這個概念,Linux把全部的線程都當作進程來實現,內核並無準備特別的調度算法或是定義特別的數據結構來表徵線程,相反,線程僅僅被視爲―個與其餘進程共享某些資源的進程,每一個線程都擁有惟一隸屬於本身task_struct,因此在內核中,它看起來就像是一個普通的進程(只是線程和其餘一些進程共享某些資源,如地址空間)。

3.4.1 建立線程

進程的建立與普通進程的建立相似,只不過在調用clone()時須要傳遞一些參數標誌來指明所須要共享的資源。

3.4.2 內核線程

內核常常須要在後臺執行一些操做,這種任務能夠經過內核線程完成——獨立運行在內核空間的標準進程。內核線程和普通的進程間的區別在於內核線程沒有獨立的地址空間。它們只在內核空間運行,歷來不切換用戶空間去,內核進程和普通進程同樣,能夠被調度,也能夠被搶佔。 Linux確實會把一些任務交給內核線程去作,像flush和ksofirqd這些任務就是明顯的例子,在裝有Linux系統的機子上運行ps -ef命令,你能夠看到內核線程,有不少!這些線程在系統啓動時由另一些內核線程建立,實際上,內核線程也只能由其餘內核線程建立,內核是經過從kthreadd內核進程中衍生出全部新的內核線程來自動處理這一點的,在<linux/kthreadd>中申明有接口。

3.5 進程終結

當一個進程終結時,內核必須釋放它所佔有的資源並把這一不幸告知其父進程。

3.5.1 刪除進程描述符

3.5.2 孤兒進程形成的進退維谷

若是父進程在子進程以前退出,必須有機制來保證子進程能找到一個新的父親不然這些成爲孤兒的進程就會在退出時永遠處於僵死狀態,白白地耗費內存。前面的部分已經有所暗示於這個問題,解決方法是給子進程在當前線程組內找—個線程做爲父親,若是不行就讓init作它們的父進程。

3.6 小結

  • 在本章中,咱們考察了操做系統中的核心概念——進程,咱們它爲什麼如此重要,以及進程與線程之間的關係,然也討論了進程的通常特性,而後,討論了Linux如何存放和表示進程,如何建立進程,如何把新的執行映像裝入到地址空間,如何表示進程的層次關係,父進程又是如何收集其後代的信息以及進程最終如何消亡。

  • 進程是一個很是基礎、很是關鍵的抽象概念,位於每一種現代操做系統的核心位置,也是咱們擁有操做系統(用來運行程序)的最終緣由。

相關文章
相關標籤/搜索