第三章 進程管理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)進程的五種狀態
(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()函數:
三、vfork()
(1)除了不拷貝父進程的頁表項外,vfork()系統調用和fork()的功能相同。子進程做爲父進程的一個單獨的線程在它的地址空間裏運行,父進程被阻塞,直到子進程推出或執行exec()。
(2)理想狀況下,系統最好不要調用vfork(),內核也不用實現它。
(3)vfork()系統調用的實現是經過向clone()系統調用傳遞一個特殊標誌來進行的。
4、線程在Linux中的實現
每一個線程都擁有惟一隸屬於本身的task_struct,因此在內核中,它看起來就像是一個普通的進程。
一、建立線程
線程的建立和普通進程的建立相似,只不過在調用clone()的時候須要傳遞一些參數標誌來指明須要共享的資源:
傳遞給clone()的參數標誌決定了新建立進程的行爲方式和父子進程之間共辜的資源種類。
二、內核線程
(1)內核線程和普通的進程闊的區別在於內核線程沒有獨立的地址空間(實際上指向地址空間的mm 指針被設置爲NULL),它們只在內核空間運行,歷來不切換到用戶空間去。
(2)內核進程和普通進程同樣,能夠被調度,也能夠被搶佔。
(3)內核錢程也只能囪其餘內核錢程建立
5、進程終結
通常來講,進程的析構是自身引發的。它發生在進程調用exit()系統調用時,既可能顯式地調用這個系統調用,也可能隱式地從某個程序的主函數返回(其實C 語言編譯器會在main()函數的返回點後面放置惆用exit()的代碼)。
進程的終結,大部分依靠do_exit():
至此,與進程相關聯的全部資源都被釋放掉了,線程不可運行(實際上也沒有地址空間讓它運行)並處於EXIT_ZOMBIE退出狀態。
一、刪除進程描述符
wait()這一族函數都是經過惟一(可是很複雜)的一個系統調用wait4()來實現的。它的標準動做是掛起調用它的進程,直到其中的一個子進程退出,此時函數會返回該子進程的PID。
當最終須要釋放進程描述符時,release_task()會被調用,用以完成如下工做:
二、孤兒進程形成的進退維谷
若是父進程在子進程以前退出,必須有機制來保證子進程能找到一個新的父親,不然這些成爲孤兒的進程就會在退出時永遠處於僵死狀態,白白地豔費內存。
*解決方法:
給子進程在當前線程組內找一個線程做爲父親,若是不行,就讓init作它們的父進程.在do_exit()中會調用exit_notify(),該函數會調用forget_original_parent(),然後者會調用find_new _reaper() 來執行尋父過程。
當一個進程被跟蹤時,它的臨時父親設定爲調試進程。尋找一個新的父進程的辦法:在一個單獨的被ptrace 跟蹤的子進程鏈表中搜索相關的兄弟進程一一用兩個相對較小的鏈襲減輕了遍歷帶來的消耗。
本章總結: