Linux內核設計與實現——進程管理(續)
線程在Linux中的實現
- 線程機制是現代編程技術中常用的一種抽象概念;該機制提供了在統一程序內共享內存地址空間運行的一組線程。這些線程還可以共享打開的文件和其他資源。線程機制支持併發程序設計技術,在多處理器系統上,能保證真正的並行處理
- Linux把所有的線程都當做進程來實現,被視爲一個與其他進程共享某些資源的進程
創建線程
-
線程的創建和普通進程的創建類似,只不過在調用clone()的時候需要傳遞一些參數標誌來指明需要共享的資源
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONES_SIGHAND,0);
該代碼產生的結果和fork()差不多,只是父子倆共享地址空間、文件系統資源、文件描述符和信號處理程序
-
傳遞給clone()的參數標誌決定了新創建進程的行爲方式和父子進程之間共享的資源種類
內核線程
-
內核經常需要在後臺執行一些操作。這種任務可以通過內核線程完成:獨立運行中內核空間的標準進程
-
內核線程和普通的進程間的區別在於內核線程沒有獨立的地址空間,他們只在內核空間運行,從來不切換到用戶空間去
-
內核線程和普通進程一樣,可以被調度,也可以被搶佔
-
內核線程只能由其他內核線程創建,創建方法:
struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[], ...)
進程終結
-
一般來說,進程的析構是自身引起的。發生在進程調用exit()系統調用時
-
當進程接受到它既不能處理也不能忽略的信號或異常時,它還可能被動地終結
-
不管進程怎麼終結的,該任務大部分都要靠do_exit()來完成:
- 將task_struct中的標誌成員設置爲PE_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進程,並把進程狀態設成EXIT_ZOMBIE
- do_exit()調用schedule()切換到新的進程。因爲處於EXIT_ZOMBIE狀態的進程不會再被調度,所以這是進程所執行的最後一段代碼。do_exit()永不返回
-
至此,與進程相關聯的所有資源都被釋放掉了。進程不可運行並處於EXIT_ZOMBIE退出狀態。它佔用的所有內存就是內核棧、thread_info結構和tast_struct結構。此時進程存在的唯一目的就是向它的父進程提供信息。父進程檢索到信息後,或者通知內核那是無關的信息後,由進程所持有的剩餘內存被釋放,歸還給系統使用
刪除進程描述符
-
調用do_exit()後,系統還保留着僵死進程的的進程描述符,在父進程獲得已終結的子進程的信息後,或者通知內核它並不關注那些信息後,子進程的task_struct結構才被釋放
-
wait()這一族函數都是通過唯一(但很複雜)的一個系統調用wait4()來實現的;它的標準動作是掛起調用它的進程,知道其中的一個子進程退出,此時函數會返回該子進程的PID
-
當最終需要釋放進程描述符時,release_task()會被調用,完成以下工作:
- 它調用__exit_signal(),該函數調用_unhash_process(),後者又調用detach_pid()從pidhash上刪除該進程,同時也要從任務列表中刪除該進程
- _exit_signal()釋放目前僵死進程所使用的所有剩餘資源,並進行最終統計和記錄
- 如果這個進程是線程組最後一個進程,並且領頭進程已經死掉,那麼release_task()就要通知僵死的零頭進程的父進程
- release_task()調用put_task_struct()釋放進程內核棧和thread_info結構所佔的頁,並釋放task_struct所站的slab告訴緩存
-
至此,進程描述符和所有進程獨享的資源就全部釋放掉了
孤兒進程造成的進退維谷
- 如果父進程在子進程之前退出,必須有機制來保證子進程能找到一個新的父親,否則這些成爲孤兒的進程就會在退出時永遠處於僵死狀態,白白耗費內存
- 解決方法是給子進程在當前進程組內找一個線程做父親,不行的話就讓init做他們的父進程
end
進程管理這一節就有點力不從心了,純理論還是不能留下深刻的印象,之後結合實踐再瞭解瞭解吧