《Linux內核設計與實現》第三章學習筆記

第三章  進程管理linux

姓名:王瑋怡  學號:20135116緩存

1、進程函數

一、進程的含義優化

  進程是處於執行期的程序以及相關資源的總稱,程序自己並非進程,實際上就是正在執行的代碼的實時結果。Linux內核一般把進程也叫「任務」。spa

二、線程的含義線程

  執行線程簡稱線程,是在進程中互動的對象。內核調度的對象是線程而不是進程。Linux系統不區分線程和進程,線程只是一種特殊的進程。指針

三、進程的執行過程調試

(1)clone()調用fork(),經過複製一個現有進程來建立一個全新的進程,進程開始存活。其中調用fork()的進程爲父進程,新產生的進程爲子進程。在該系統調用結束時,在返回點這個相同位置上,父進程回覆執行,子進程開始執行。其中,fork()系統調用從內核返回兩次:一次回到父進程,另外一次返回到新產生的子進程。子進程和父進程的區別在於PID(每一個進程惟一)、PPID(父進程的進程號,子進程將其設置爲被拷貝進程的PID)和某些資源和統計量。code

(2)新的進程調用exec()這組函數,建立新的地址空間,並把新的程序載入其中。對象

(3)程序經過exit()系統調用推出執行,終結進程並將其佔用的資源釋放,進程退出執行後爲僵死狀態,直到父進程調用wait()或waitpid()爲止。其中父進程能夠經過wait4()系統調用查詢子進程是否終結。

2、進程描述符及任務結構

  內核把進程的列表存放在「任務隊列」的雙向循環鏈表中。鏈表中的每一項都是類型爲task_struct、稱爲進程描述符的結構,該結構定義在<linux/sched.h>文件中,進程描述符包含了一個具體進程的全部信息。

                           

 

一、分配進程描述符

  Linux經過slab分配器動態分配task_struct結構,這樣能達到對象複用和緩存着色(cache coloring)的目的。只需在棧底(向下增加的棧)或棧頂(向上增加的棧)建立一個新的結構struct thread_info。

  每一個任務的thread_info結構在它的內核棧的尾端分配。結構域中task域存放的是指向該任務實際task_struct的指針。

二、進程描述符的存放

  內核經過一個惟一的進程標識值或PID來標識每一個進程。PID是一個數,表示爲pid_t隱含類型,實際上就是一個int類型,最大默認值設置爲32768(short int短整型的最大值)。最大默認值表示系統中容許同時存在的進程的最大數目,這個值越小,轉一圈的速度越快。

  在內核中訪問任務一般須要得到指向其task_struct的指針。實際上,內核中大部分處理進程的代碼都是直接經過task_struct進行的。

三、進程狀態

  進程描述符中的state域描述了進程的當前狀態。

(1)進程的五種狀態

  • TASK RUNNING(運行):進程是可執行的,表示正在執行或者在運行隊列中等待執行。
  • TASK_INTERRUPTIBLE(可中斷):進程正在休眠(被阻塞),等待某種條件達成。
  • TASK_UNINTERRUPTIBLE(不可中斷):除了就算接收信號也不會被喚醒或準備投入運行外,這個狀態與可打斷狀態同樣,一般在進程必須等待時不受干擾或等待時間很快就會發生時出現。
  • __TASK_REACED:被其餘進程跟蹤的進程,例如,經過ptrace對調試程序進行跟蹤。
  • __TASK_STOPPED:進程停滯執行;進程沒有投入運行也不能投入運行

(2)進程狀態轉化

 

四、設置當前進程狀態

  使用set_task_state(task,state)函數將制定的進程設置爲制定的狀態。

*注:set_current_state(state)和set_task_state(task,state)含義是等同的。

五、進程上下文

  當一個程序調用執行了系統調用或觸發了某個異常,它就陷入了內核空間,此時,咱們稱內核「表明進程執行」並處於進程上下文中。在此上下文中current宏是有效的。

  進程只有經過某些明肯定義的接口才能陷入內核執行——對內核的全部訪問都必須是必須經過這些接口的。

六、進程家族樹

  全部的進程都是PID爲1的init進程的後代。進程間的關係存放在進程描述符中,每一個task_struct都包含一個指向其父進程task_struct、叫作parent的指針,還包含一個稱爲children的子進程鏈表。

3、進程建立

一、寫時拷貝

(1)Linux的fork()使用寫時拷貝(copy-on-write)頁實現,內核並不複製整個進程地址空間,而是讓父進程和子進程共享一個拷貝,而fork()的實際開銷就是複製父進程的頁表以及給子進程建立惟一地進程描述符。

(2)資源的複製只有在須要寫入時才進行,在此以前,只是以只讀方式共享。

(3)在通常狀況下,進程建立後都會立刻運行一個可執行的文件,這種優化能夠避免拷貝大量冗餘數據。

二、fork()

(1)fork()、vfork()、__clone()庫函數都會根據各自須要的參數標誌去調用clone(),而後由clone()去調用do_fork()。

(2)do_fork()函數調用copy_process()函數,若是copy_process()函數返回成功,新建立的子進程被喚醒並讓其投入運行,而內核有意選擇子進程先執行。

(3)關於copy_process()函數:

  • 調用dup_task_struct()爲新進程建立一個內核棧、thread_info結構和task_struct,這些值與當前值一致
  • 檢查並確保新建立這個子進程後,當前用戶所擁有的進程數目沒有超出給它分配的資源的限制。
  • 子進程着手使本身與父進程區分開來,而task_struct中的大多數數據都依然未被修改。
  • 子進程的狀態被設置爲TASK_UNINTERRUPTIBLE,以保證它不會投入運行。
  • copy_process()調用copy_flags()以更新task_struct的flags成員。
  • 調用alloc_pid()爲新進程分配一個有效的PID。
  • 根據傳遞給clone()的參數標誌,copy_process()拷貝或共享打開的文件、文件系統信息、信號處理函數、進程地址空間和命名空間等。
  • 最後,copy_process()作掃尾工做並返回一個指向子進程的指針。

三、vfork()

(1)除了不拷貝父進程的頁表項外,vfork()系統調用和fork()的功能相同。子進程做爲父進程的一個單獨的線程在它的地址空間裏運行,父進程被阻塞,直到子進程推出或執行exec()。

(2)理想狀況下,系統最好不要調用vfork(),內核也不用實現它。

(3)vfork()系統調用的實現是經過向clone()系統調用傳遞一個特殊標誌來進行的。

  • 在調用copy_process()時, task_struct 的vfor_done 成員被設置爲NULL
  • 在執行do_fork()時,若是給定特別標誌,則vfork_done 會指向一個特定地址
  • 子進程先開始執行後,父進程不是立刻恢復執行,而是一直等待,直到子進程經過vfork_done 指針向它發送信號。
  • 在調用mm_release()時,該函數用於進程退出內存地址空間,而且檢查vfork_done 是否爲空,若是不爲空,則會向父進程發送信號。
  • 回到do_fork(),父進程醒來並返回。

4、線程在Linux中的實現

  每一個線程都擁有惟一隸屬於本身的task_struct,因此在內核中,它看起來就像是一個普通的進程。

一、建立線程

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

                     

  傳遞給clone()的參數標誌決定了新建立進程的行爲方式和父子進程之間共辜的資源種類。

二、內核線程

(1)內核線程和普通的進程闊的區別在於內核線程沒有獨立的地址空間(實際上指向地址空間的mm 指針被設置爲NULL),它們只在內核空間運行,歷來不切換到用戶空間去。

(2)內核進程和普通進程同樣,能夠被調度,也能夠被搶佔。

(3)內核錢程也只能囪其餘內核錢程建立

  • 新的任務是由kthread內核進程經過clone()系統調用而建立的
  • 新建立的進程處於不可運行狀態,若是不經過調用wake_up _process()明確地喚醒它,它不會主動運行。
  • 建立一個進程並讓它運行起來,能夠經過調用ktbread_run()
  • 內核錢程啓動後就一直運行直到調用do_exit()退出,或者內核的其餘部分調用kthread_stop()退出。

5、進程終結

  通常來講,進程的析構是自身引發的。它發生在進程調用exit()系統調用時,既可能顯式地調用這個系統調用,也可能隱式地從某個程序的主函數返回(其實C 語言編譯器會在main()函數的返回點後面放置惆用exit()的代碼)。

  進程的終結,大部分依靠do_exit():

  • 將tast_struct 中的標誌成員設置爲PF_EXITING。
  • 調用del_timer_ sync()刪除任一內核定時器。根據返回的結果,它確保沒有定時器在排隊,也設有定時器處理程序在運行。
  • 若是BSD 的進程記帳功能是開啓的, do_exit()調用acct_update_ integrals()來輸出記帳信息。
  • 而後調用exit_mm()函數釋放進程佔用的mm_struct,若是沒有別的進程使用它們,就完全釋放它們。
  • 接下來調用sem_ exit()函數。若是進程排隊等候IPC信號,它則離開隊列。
  • 調用exit_files()和exit_fs(),以分別遞藏文件描述符、文件系統數據的引用計數。
  • 接着把存放在task_struct 的exit_code成員中的任務退出代碼置爲由exit()提供的退出代碼,或者去完成任何其餘由內核機制規定的退出動做。
  • 調用exit_notify()向父進程發送信號,給子進程從新找養父(線程組中的其餘線程或者爲init進程),並把進程狀態(存放在task_struct 結構的exit_state 中)設成EXIT_ZOMBLE。
  • do_exit()調用schedule()切換到新的進程。

  至此,與進程相關聯的全部資源都被釋放掉了,線程不可運行(實際上也沒有地址空間讓它運行)並處於EXIT_ZOMBIE退出狀態。

一、刪除進程描述符

  wait()這一族函數都是經過惟一(可是很複雜)的一個系統調用wait4()來實現的。它的標準動做是掛起調用它的進程,直到其中的一個子進程退出,此時函數會返回該子進程的PID。

  當最終須要釋放進程描述符時,release_task()會被調用,用以完成如下工做:

 

二、孤兒進程形成的進退維谷

  若是父進程在子進程以前退出,必須有機制來保證子進程能找到一個新的父親,不然這些成爲孤兒的進程就會在退出時永遠處於僵死狀態,白白地豔費內存。

*解決方法:

  給子進程在當前線程組內找一個線程做爲父親,若是不行,就讓init作它們的父進程.在do_exit()中會調用exit_notify(),該函數會調用forget_original_parent(),然後者會調用find_new _reaper() 來執行尋父過程。

  當一個進程被跟蹤時,它的臨時父親設定爲調試進程尋找一個新的父進程的辦法:在一個單獨的被ptrace 跟蹤的子進程鏈表中搜索相關的兄弟進程一一用兩個相對較小的鏈襲減輕了遍歷帶來的消耗。

 

本章總結:

  • Linux 如何存放和表示進程(用task_ struct 和thread_info )
  • 如何建立進程(經過fork(),實際上最終是clone())
  • 如何把新的執行映像裝入到地址空間(經過execO 系統調用族)
  • 如何表示進程的層次關係,父進程又是如何收集其後代的信息(經過wait()系統調用族)
  • 進程最終如何消亡(強制或自願地調用exit())
相關文章
相關標籤/搜索