linux内核的进程管理,Linux内核设计与实现——进程管理
主要內容
進程
進程描述符及任務結構
進程創建
線程在linux中的實現
進程終結
1. 進程
進程不僅僅是一段可執行程序代碼,還包含其他資源,如打開的文件,掛起的信號,內核內部數據,處理器狀態,一個或多個具有內存映射的內存地址空間及一個或多個執行線程,存放全局變量的數據段等等。具體可見進程的地址空間。
線程是進程中活動的對象,擁有獨立的PC程序計數器,進程棧和一組進程寄存器,是內核調度的基本對象。
進程的兩種虛擬機制:虛擬處理器和虛擬內存
相關函數:
fork(),系統調用從內核返回兩次,一次回到父進程,一次回到新產生的子進程
exec(),創建新的地址空間并載入程序
exit() 退出執行
wait() , waitpid(),父進程等待子進程終結
2. 進程描述符及任務結構
雙向鏈表的任務隊列
分配進程描述符
linux通過slab分配器分配task_struct結構,通過預先分配和重復使用,可以避免動態分配和釋放帶來的資源消耗。
在進程的內核棧中,每個任務的thread_info結構在它內核棧的尾端分配,結構中task域存放的是指向該任務實際task_struct的指針。
進程描述符的存放
pid,最大值默認為short int的最大值32768,可以修改pid_max文件來提高上限
進程狀態
進程狀態轉化.png
TASK_RUNNING,運行——進程或者正在執行,或者在運行隊列中等待執行
TASK_INTERRUPTIBLE,可中斷——進程正在睡眠(被阻塞),等待某些條件的達成
TASK_UNINTERRUPTIBLE,不可中斷
_TASK_TRACED,被其它進程跟蹤的進程
_TASK_STOPPED,進程停止執行,如接收到SIGSTOP,SIGTINT等信號
設置當前進程狀態
set_task_state(task, state)
進程上下文
當一個程序執行了系統調用或者觸發了某個異常,它就陷入了內核空間,此時,我們呈內核“代表進程執行”并處于進程上下文中
進程家族樹
Unix系統的進程之間存在一個明顯的集成關系,所有進程都是PID為1的init進程的后代
進程關系:父子,兄弟,在進程描述符中存放,每個task_struct都有指向父進程、子進程的指針
3. 進程創建
linux中創建進程分兩步,fork和exec
linux的fork()使用寫時拷貝(copy-on-write)實現,因為有可能fork后是執行一個新的映像,父進程和子進程共享同一份拷貝,只有在需要寫入的時候,才會被復制。因此,fork()的實際開銷就是復制父進程的頁表以及給子進程創建唯一的進程描述符
linux通過clone()系統調用do_fork(),do_fork()調用copy_process()函數,然后讓進程開始運行,copy_process()完成的工作如下:
調用dup_task_struct()為新進程分配內核棧,task_struct等,其中的值與當前進程相同,此時,子進程與父進程的描述符也相同
檢查創建子進程后,當前用戶所擁有的進程數目沒有超出給它分配的資源限制
子進程使自己與父進程區別開來。進程描述符內許多統計信息成員都要被清0或設為初始值
子進程狀態設置為TASK_UNINTERRUPTIBLE,以保證不會投入運行
更新task_struct的flags成員,表明進程權限等
調用alloc_pid()為新進程分配一個有效的PID
根據傳遞給clone()的參數,copy_process()拷貝或共享打開的文件,文件系統信息,信號處理函數,進程地址空間和命名空間等
掃尾工作,返回指向子進程的指針
回到do_fork()函數后,內核會優先選擇子進程首先執行,因為子進程通常會馬上調用exec()函數,可以避免寫時拷貝的額外開銷。
創建進程的fork()函數實際上最終是調用clone()函數。
vfork()調用,不拷貝父進程的頁表項,子進程作為父進程的一個單獨的線程在它的地址空間里運行,父進程被阻塞直到子進程退出或執行exec(),子進程不能向地址空間寫入。
現在for()引入了寫時拷貝并且明確了子進程先執行,vfork()的好處就僅限于不執行父進程的頁表項了。
而且如果exec()調用失敗會怎樣?
4. 線程在Linux中的實現
從內核的角度來說,并沒有線程這個概念,Linux把所有的線程都當做進程來實現,線程僅僅被視為一個與其他進程共享某些資源的進程。
Windows在內核中提供了專門支持線程的機制。而對Linux來說,只是一種進程間共享資源的手段。
線程的創建和普通進程的創建類似,只不過調用clone()的時候需要傳遞額外參數標識
一個普通的fork():clone(SIGCHLD, 0)
創建線程:clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0) 使子進程和父進程共享地址空間,文件資源,文件描述符,信號處理程序等。
內核線程:
內核線程沒有獨立的地址空間,只在內核空間運行,可以被調度或搶占
5. 進程終結
當一個進程終結時,內核必須釋放它所占有的資源并告知父進程
終結任務依靠do_exit()來完成:
設置task_struct中的標識成員設置為PF_EXITING
調用del_timer_sync()刪除內核定時器, 確保沒有定時器在排隊和運行
調用exit_mm()釋放進程占用的mm_struct
調用sem__exit(),使進程離開等待IPC信號的隊列
調用exit_files()和exit_fs(),釋放進程占用的文件描述符和文件系統資源
把存放在task_struct的exit_code成員中的任務退出代碼置為exit()提供的退出代碼,或者去完成任何其他由內核機制規定的退出動作,退出代碼存放在此供父進程隨時檢索
調用exit_notify()向父進程發送信號,給子進程重新找養父(init進程),并把進程狀態設為EXIT_ZOMBIE
do_exit()調用schedule()切換到新的進程,不會再被調度,do_exit()永不返回
至此,與進程相關聯的所有資源都被釋放掉了,進程處于(EXIT_ZONBIE退出狀態)且不可運行,占用的內存僅剩內核棧、thread_info結構和tast_struct結構,此時進程存在的唯一目的就是向它的父進程提供信息。父進程檢索到信息后,由進程所持有的剩余內存被釋放,歸還給系統。
wait()通過系統調用wait4()來實現,返回子進程的PID,調用該函數時提供的指針會包含子函數退出時的退出代碼
刪除進程描述符,父進程調用release_task()
如果沒有父進程,子進程exit_notify()的時候,調用forget_original_parent() -> find_new_reaper()來執行尋父過程
總結
以上是生活随笔為你收集整理的linux内核的进程管理,Linux内核设计与实现——进程管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux dma拷贝数据到用户态,图解
- 下一篇: 王牌竞速荒漠沙丘在哪?