linux內核設計與實現一書閱讀整理 之第三章

chapter 3 進程管理

3.1 進程

  1. 進程就是處於執行期的程序。
  2. 進程就是正在執行的程序代碼的實時結果。
  3. 內核調度的對象是線程而並不是進程。
  4. 在現代操做系統中,進程提供兩種虛擬機制:linux

    虛擬處理器
          虛擬內存
  5. 進程是處於執行期的程序以及相關的資源的總稱。
  6. 進程包括代碼段和其餘資源。
  7. 幾個函數:緩存

    fork():建立新進程
    exec():建立新的地址空間並把新的程序載入其中
    clone():fork實際由clone實現
    exit():退出執行
    wait4():父進程查詢子進程是否終結

    wait()、waitpid():程序退出執行後變爲僵死狀態,調用這兩個消滅掉。函數

3.2 進程描述符及任務結構

  • 內核把進程的列表存放在叫作任務隊列的雙向循環鏈表中。
  • 鏈表中的每一項都是類型爲task_struct、稱爲進程描述符的結構。
  • 進程描述符的類型爲task_struct,裏面包含的數據有:

3.2.1 分配進程描述符

  1. Linux經過slab分配器分配task_struct結構——能達到對象複用緩存着色的目的。
  2. 分配:每一個任務的堆棧尾端(好比,對於向上增加的堆棧來講,就是在堆棧的棧頂)有結構體threadinfo,它指向了taskstruct結構體
  3. 每一個任務的threadinfo結構在它的內核棧的尾端分配。 結構中task域中存放的是指向該任務實際taskstruct的指針。

3.2.2 進程描述符的存放

  1. 內核經過一個惟一的進程標識值PID來標識每一個進程。
  2. pid類型爲pidt,實際上就是一個int類型,最大值默認設置爲32768,如若須要,可有系統管理員經過修改/proc/sys/kernel/pidmax上限。
  3. pid存放在各自進程描述符中。
  4. 內核中的大部分處理處理進程的代碼都是經過task_struct進行的;所以,須要經過current宏查找到當前正在運行進程的進程描述符
  5. X86系統中,current把棧指針的後13個有效位屏蔽掉,用來計算出threadinfo的偏移(currentthread_info函數)學習

    movl $-8192, %eax
    andl %esp,%eax

3.2.3 進程狀態

進程描述符中的state域是用來描述進程當前狀態的。共有五種狀態,標誌以下:操作系統

  • TASK_RUNNING(運行):進程是可執行的,或者正在執行,或者在運行隊列中等待執行
  • TASK_INTERRUPTIBLE(可中斷):進程正在睡眠/被阻塞
  • TASK_UNINTERRUPTIBLE(不可中斷):睡眠/被阻塞進程不被信號喚醒
  • TASK_TRACED:被其餘進程跟蹤的進程
  • TASK_STOPPED(中止):進程中止執行;進程沒有投入運行也不能投入運行。 接收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信號時,或者調試時收到任何信號,均可以進入這種狀態。

狀態轉換圖以下:線程

3.2.4 設置當前進程狀態

  • 使用settaskstate(task,state)函數.設計

    • settaskstate(task,state); //將任務task的狀態設置爲state指針

    • setcurrentstate(state) 和下面等價調試

    • settaskstate(current,state)

3.2.5 進程上下文

  • 可執行代碼從一個可執行文件載入到進程的地址空間執行。當一個程序執行了系統調用,內核就會「表明進程執行」並處於進程上下文中
  • 對比:在中斷上下文中,系統不表明進程執行——不會有進程去幹擾這些中斷處理程序

3.2.6 進程家族樹

  • 全部的進程都是PID爲1的init進程的後代
  • 對於給定的進程,獲取鏈表中下一個進程:code

    - list_entry(task->tasks.prev,struct
    -  task_struct,tasks)

3.3 進程建立

  1. 通常操做系統產生進程的機制:

    1. 在新的地址空間建立進程
    2. 讀入可執行文件
    3. 執行
  2. Unix的機制:

    fork()和exec()。
    
     fork():
          經過拷貝當前進程建立一個子進程。
  3. 子進程與父進程的區別僅在於PID,PPID和某些資源和統計量

    exec():
       讀取可執行文件並將其載入地址空間開始運行。

3.3.1 寫時拷貝

  • Linux的fork()使用寫時拷貝推遲甚至免除拷貝。內核在建立新進程的時候並不複製整個地址空間,而是讓父進程和子進程共享同一個拷貝;直到子進程/父進程須要寫入的時候才進行拷貝
  • 於是,fork的實際開銷只是複製父進程的頁表以及給子進程建立惟一的進程描述符

3.3.2 fork()

-** Linux經過clone系統調用實現fork** - 建立進程的大概步驟以下:

fork()、vfork()、__clone()都根據各自須要的參數標誌調用clone()。
  由clone()去調用do_fork()。
  do_fork()調用copy_process()函數,而後讓進程開始運行。
  返回do_fork()函數,若是copy_process()函數成功返回,新建立的子進程被喚醒並讓其投入運行。

通常內核會選擇子進程首先執行

3.3.3 vfork()

  1. 除了不拷貝父進程的頁表項以外,vfork()系統調用和fork()的功能相同。理想狀況下不要調用vfork()。

    子進程做爲父進程的一個單獨的線程在它的地址空間裏運行 ,父進程被阻塞,直到子進程退出或執行exec()。子進程不能向地址空間寫入。
  2. vfork()系統調用的實現是經過向clone()傳遞一個特殊標誌來進行的。

  3. 調用copyprocess()是,taskstruct的vfor_done成員被設置爲NULL。

  4. 執行dofork()時,若是給定特定標誌,則vfordone會指向一個特定地址。
  5. 子進程先開始執行後,父進程不是立刻恢復執行,而是一直等待,知道子進程經過vfordone指針向它發送信號。 在調用mmrelease()時,該函數用於進程退出內存地址空間,而且檢查vfor_done是否爲空,若是不爲空,則會向父進程發送信號。
  6. 回到dofork(),父進程醒來並返回。 ## 3.4 線程在linux中實現 ## 在Linux系統中,線程僅僅被視爲一個與其餘進程共享某些資源的進程。每一個線程都有本身的taskstruct

3.4.1 建立線程

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

3.4.2 內核線程

與普通進程的區別只在於內核線程沒有獨立的地址空間。

- 它只能經過其餘內核線程建立;內核經過kthread內核進程衍生全部的內核線程
 - 新建立的線程處於不可運行狀態,直到wake_up_process()明確地喚醒它

## 3.5 進程終結 ## - 進程終結時,內核必須釋放它所佔有的資源並告知父進程。

  • 進程終結的緣由:通常是來自自身,發生在調用exit()系統調用時。

    顯式的調用
    隱式的從某個程序的主函數返回
  • 大部分依賴於do_exit()來完成。其中有幾個重點:

    ……
    給子進程從新找養父(線程組中的其餘線程或者init進程)
    調用schedule()切換到新的進程
    ……
  • 這以後,進程不可運行並處於EXITZONBIE退出狀態,佔用的全部內存就是內核棧、threadinfo結構和task_struct結構。此時進程存在的惟一目的就是向它的父進程提供信息。

3.5.1 刪除進程描述符

  • 釋放task_struct結構發生在父進程得到已終結的子進程信息而且通知內核不關注後,須要的系統調用是wait4():

    掛起調用它的進程,直到其中的一個子進程退出,此時函數返回該子進程的PID。
  • 釋放進程描述符時,須要調用release_task()。

3.5.2 孤兒進程

  • 概述:父進程在進程以前退出,就會遺留下子進程,也就是孤兒進程
  • 其解決方法:
    • 在當前的線程組內給孤兒進程尋找新的父進程;不然直接以init做爲其父進程
    • 調用順序:
    • -  do_exit()-- 
        -    >forget_original_parent()-
        -    >find_new_parent()-
        -    >ptrace_exit_finish()
        -    (這一函數是爲被跟蹤的進程尋找父進程,由於被跟蹤的進程會以調試程序做爲臨時父親)

總結

在本章中,我知道了操做系統的核心概念--進程,也跟着書本學習了進程的通常特性的重要性等等,頗有趣。

參考資料

《linux內核設計與實現》原書第三版

相關文章
相關標籤/搜索