撤消進程

很多進程終止了它們本該執行的代碼,從這種意義上說,這些進程“死”了。當這種情況發生是,必須通知內核以便內核釋放進程所擁有的資源,包括內存,打開文件及其它我們在本書中講到的零碎東西,如信號量。


進程終止的一般方式是調用exit()庫函數,該函數釋放C函數庫所分配的資源,執行編程者所註冊的每個函數,並結束從系統回收進程的那個系統調用。exit()函數可能由編程者顯式的插入。另外,C編譯程序總是把exit()函數插入到main()函數的最後一條語句之後。


內核可以有選擇地強迫整個線程組死掉。這發生在以下兩種典型情況下:當進程接收到一個不能處理或忽略的信號時,或者當內核正在代表進程運行時在內核態產生一個不可恢復的CPU異常時。


進程終止


linux2.6中有兩個終止用戶態應用的系統調用:

  1. exit_group()系統調用,它終止整個線程組,即整個基於多線程的應用。do_group_exit()是實現這個系統調用的主要內核函數。這是C庫函數exit()應該調用的系統調用。

  2. exit()系統調用,它終止某一個線程,而不管線程所屬線程組中的所有其它進程,do_exit()是實現這個系統調用的主要內核函數。這是被諸如pthread_exit()Linux線程庫的函數所調用的系統調用。


do_group_exit()函數


do_group_exit()函數殺死屬於current線程組的所有進程。它接受進程終止代碼作爲參數,進程終止代號可能是系統調用exit_group()指定的一個值,也可能是內核提供的一個錯誤代號。該函數執行下述操作:

  1. 檢查退出進程的SIGNAL_GROUP_EXIT標誌是否不爲0,如果不爲0,說明內核已經開始爲線性組執行退出的過程。在這種情況下,就把存放在current->signal->group_exit_code的值當作退出碼,然後跳轉到第4步。

  2. 否則,設置進程的SIGNAL_GROUP_EXIT標誌並把終止代號放到current->signal->group_exit_code字段。

  3. 調用zap_other_threads()函數殺死current線程組中的其它進程。爲了完成這個步驟,函數掃描與current->tgid對應的PIDTYPE_TGID類型的散列表中的每PID鏈表,向表中所有不同於current的進程發送SIGKILL信號,結果,所有這樣的進程都將執行do_exit()函數,從而被殺死。

  4. 調用do_exit()函數,把進程的終止代碼傳遞給它。正如我們將在下面看到的,do_exit()殺死進程而且不再返回。


do_exit()函數


所有進程的終止都是由do_exit()函數來處理的,這個函數從內核數據結構中刪除對終止進程的大部分引用。do_exit()函數接受進程的終止代號作爲參數並執行下列操作:

  1. 把進程描述符的flag字段設置爲PF_EXITING標誌,以表示進程正在被刪除。

  2. 如果需要,通過函數del_timer_sync()從動態定時器隊列中刪除進程描述符。

  3. 分別調用exit_mm()exit_sem()__exit_files()__exit_fs()exit_namespace()exit_thread()函數從進程描述符中分離出與分頁、信號量、文件系統、打開文件描述符、命名空間以及I/O權限位圖相關的數據結構。如果沒有其它進程共享這些數據結構,那麼這些函數還刪除所有這些數據結構中。

  4. 如果實現了被殺死進程的執行域和可執行格式的內核函數包含在內核模塊中,則函數遞減它們的使用計數器。

  5. 把進程描述符的exit_code字段設置成進程的終止代號,這個值要麼是_exit()exit_group()系統調用參數,要麼是由內核提供的一個錯誤代碼。

  6. 調用exit_notify()函數執行下面的操作:

a更新父進程和子進程的親屬關係。如果同一線程組中有正在運行的進程,就讓終止進程所創建的所有子進程都變成同一線程組中另外一個進程的子進程,否則讓它們成爲init的子進程

b檢查被終止進程其進程描述符的exit_signal字段是否不等於-1,並檢查進程是否是其所屬進程組的最後一個成員。在這種情況下,函數通過給正被終止進程的父進程發送一個信號,以通知父進程子進程死亡。

c.否則,也就是exit_signal字段等於-1,或者線程組中還有其它進程,那麼只要進程正在被跟蹤,就向父進程發送一個SIGCHLD信號。

d.如果進程描述符的exit_signal字段等於-1,而且進程沒有被跟蹤,就把進程描述符的exit_state字段置爲EXIT_DEAD,然後調用release_task()回收進程的其它數據結構佔用的內存,並遞減進程描述符的使用計數器,以使進程描述符本身正好不會被釋放。

e.否則,如果進程描述符的exit_signal字段不等於-1,或進程正在被跟蹤,就把exit_state字段置爲EXIT_ZOMBIE

f.把進程描述符的flags字段設置爲PF_DEAD標誌。

  1. 調用schedule()函數選擇一個新進程運行。調度程序忽略處於EXIT_ZOMBIE狀態的進程,所以這種進程正好在schedule()中的宏switch_to被調用之後停止執行。


進程刪除


Unix允許進程查詢內核以獲得其父進程的PID,或者其任何於進程的執行狀態。例如,進程可以創建一個子進程來執行特定的任務,然後調用諸如wait()這樣的一些庫函數檢查子進程是否終止。如果子進程已經終止,那麼,它的終止代號將告訴父進程這個任務是否已成功地完成。


爲了遵循這些設計選擇,不允許Unix內核在進程一終止後就丟棄包含在進程描述符字段中的數據。只有父進程發出了與被終止的進程相關的wait()類系統調用之後,才允許這樣做。這就是引入僵死狀態的原因:儘管從技術上來說進程已死,但必須保存它的描述符,直到父進程得到通知。


如果父進程在子進程結束之前結束會發生什麼情況呢?在這種情況下,系統中會到處是僵死的進程,而且它們的進程描述符永久佔據着RAM。如前所述,必須強迫所有的孤兒進程成爲init進程的子進程來解決這個問題。這樣,init進程在用wait()類系統調用檢查其合法的子進程終止時,就會撤消僵死的進程。


release_task()函數從僵死進程的描述符中分離出最後的數據結構;對僵死進程的處理有兩種可能方式;如果父進程不需要接收來自子進程的信號,就調用do_exit();如果已經給父進程發送了一個信號,就調用wait4()waitpid()系統調用。在後一種情況下,函數還將回收進程描述符所佔用的內存空間,而在前一種情況下,內存的回收將由進程調度程序來完成。該函數執行下述步驟:

  1. 遞減終止進程擁有者的進程個數。這個值存放在本章前面提到的user_struct結構中。

  2. 如果進程正在被跟蹤,函數將它從調試程序的ptrace_children鏈表中刪除,並讓該進程重新屬於初始的父進程。

  3. 調用__exit_signal()刪除所有的掛起信號並釋放進程的signal_struct描述符。如果該描述符不再被其它的輕量級進程使用,函數進一步刪除這個數據結構。此外,函數調用exit_itimers()從進程中剝離掉所有的POSIX時間間隔定時器。

  4. 調用__exit_sighand()刪除信號處理函數。

  5. 調用__unhash_process(),該函數依次執行下面的操作:

a.變量nr_threads減。

b.兩次調用detach_pid(),分別從PIDTYPE_PIDPIDTYPE_TGID類型的PID散列表中刪除進程描述符。

c.如果進程是線程組的領頭進程,那麼再調用兩次detach_pid(),PIDTYPE_PGIDPIDTYPE_SID類型的散列表中刪除進程描述符。

d.用宏REMOVE_LINKS從進程鏈表中解除進程描述符的鏈接。

  1. 如果進程不是線程的領頭進程,領頭進程處於僵死狀態,而且進程是線程組的最後一個成員,則該函數向領頭進程的父進程發送一個信號,通知它進程已終止。

  2. 調用sched_exit()函數來調整父進程的時間片。

  3. 調用put_task_struct()遞減進程描述符的使用計數器,如果計數器變爲0,則函數終止所有殘留的對進程的引用。

a.遞減進程所有都的user_struct數據結構的使用計數器,如果使用計數器變成0,就釋放該數據結構。

b.釋放進程描述符以及thread_info描述符和內核堆棧所佔用的內存區域。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章