APUE——進程終止

abort 與return exit的關係
進程終止
linux進程控制函數–fork,exec,exit,wait,sleep

1. 進程終止方式

進程有下面5種正常終止方式:

  1. 在main函數內執行return語句。這等效於調用exit。

  2. 調用exit函數。此函數有ISO C定義,其操作包括調用各終止處理程序(終止處理程序在調用atexit函數時登記),然後關閉所有標準I/O流等。

  3. 調用_exit或_Exit函數。ISO C定義_Exit,其目的是爲進程提供一種無需運行終止處理程序或信號處理程序而終止的方法。對標準I/O流是否進行沖洗,這取決於實現。在UNIX系統中,_Exit和_exit是同義的,並不清洗標準I/O流。_exit函數由exit調用,它處理UNIX特定的細節。

在大多數UNIX系統實現中,exit(3)是標準C庫中的一個函數,而_exit(2)則是一個系統調用。

  1. 進程的最後一個線程在其啓動例程中執行返回語句。但是,該線程的返回值不會用作進程的返回值。當最後一個線程從其啓動例程返回時,該進程以終止狀態0返回。

  2. 進程的最後一個線程調用pthread_exit函數。在這種情況下,進程終止狀態總是0,這與傳送給pthread_exit的參數無關。

三種異常終止方式如下:

  1. 調用abort。它產生SIGABRT信號,這是下一種異常終止的特例。

  2. 當進程接收到某些信號時。信號可由進程自身(例如調用abort函數)、其他進程或內核產生。

  3. 最後一個線程對“取消”(cancellation)請求作出響應。按系統默認,“取消”以延遲方式發生:一個線程要求取消另一個線程,一段時間之後,目標線程終止。

2. exit函數

有三個函數用於正常終止一個程序:_exit和_Exit立即進入內核,exit則先執行一些清理處理(包括調用執行各終止處理程序,關閉所有標準I/O流等),然後進入內核。

#include <stdlib.h>
void exit( int status ); //會先調用atexit函數,執行清理函數,然後清理IO,將buffer裏的打印出去
void _Exit( int status );//
#include <unistd.h>
void _exit( int status );

注意這裏status,爲退出時的狀態,與errno不同!!!,後者爲系統調用出錯的值!strerror(errno)

exit函數總是執行一個標準I/O庫的清理關閉操作:爲所有打開流調用fclose函數。這會造成所有緩衝的輸出數據都被沖洗(寫到文件上)。
exit(0)== return(0);0表示正常結束,通過exit的返回值,檢查是否正確返回
在這裏插入圖片描述

/*exit.c*/ 
#include <stdio.h> 
#include <stdlib.h> 

int main() 
{ 
      printf("Using exit...\n"); 
      printf("This is the content in buffer"); 
      exit(0); 
}
輸出Using exit...
This is the content in buffer

/*exit.c*/ 
#include <stdio.h> 
#include <stdlib.h> 

int main() 
{ 
      printf("Using exit...\n"); 
      printf("This is the content in buffer"); 
      _exit(0); 
}
輸出Using exit...

3. atexit函數

按照ISO C規定,一個進程可以登記多達32個函數,這些函數將由exit自動調用。我們稱這些函數爲終止處理程序(exit handler),並調用atexit函數來登記這些函數。

#include <stdlib.h>
int atexit( void (*func)(void) ); // void pthread_cleanup_push(void (*rtn)(void *), void *arg);
返回值:若成功則返回0,若出錯則返回非0

注意
線程退出清理函數和進行退出清理函數:

  1. 一個有參數,一個無參數
  2. 都是在棧上,都是起到註冊作用,pthread_cleanup_push是在pop或者exit,cancel時候調用,而atexit是在exit
void pthread_cleanup_push(void (*rtn)(void *), void *arg);//有入參,爲arg
int atexit( void (*func)(void) );//無入參

根據ISO C和POSIX.1,exit首先調用各終止處理程序,然後按需要多次調用fclose,關閉所有打開流。POSIX.1擴展了ISO C標準,它指定如若程序調用exec函數族中的任一函數,則將清除所有已安裝的終止處理程序。圖7-1顯示了一個C程序是如何啓動的,以及它可以終止的各種方式

在這裏插入圖片描述

[root@localhost apue]# cat prog7-2.c
#include "apue.h"

static void my_exit1(void);
static void my_exit2(void);

int
main(void)
{
        if(atexit(my_exit2) != 0)
                err_sys("can't register my_exit2");

        if(atexit(my_exit1) != 0)
                err_sys("can't register my_exit1");

        if(atexit(my_exit1) != 0)
                err_sys("can't register my_exit1");

        printf("main is done\n");
        return(0);
}

static void
my_exit1(void)
{
        printf("first exit handler\n");
}

static void
my_exit2(void)
{
        printf("second exit handler\n");
}

[root@localhost apue]# ./prog7-2
main is done
first exit handler
first exit handler
second exit handler
  1. 在說明fork函數時,顯而易見,子進程是在父進程調用fork後生成的。上面又說了子進程將其終止狀態返回給父進程。但是如果父進程在子進程之前終止,則將如何呢?其回答是:對於父進程已經終止的所有進程,它們的父進程都改變爲init進程。我們稱這些進程由init進程領養。其操作過程大致如下:在一個進程終止時,內核逐個檢查所有活動進程,以判斷它是否是正要終止進程的子進程,如果是,則將該進程的父進程ID更改爲1(init進程的ID)。這種處理方法保證了每個進程都有一個父進程。

  2. 另一個我們關心的情況是如果子進程在父進程之前終止,那麼父進程又如何能在做相應檢查時得到子進程的終止狀態呢?對此問題的回答是:內核爲每個終止子進程保存了一定量的信息,所以當終止進程的父進程調用wait或waitpid時,可以得到這些信息。這些信息至少包括進程ID、該進程的終止狀態、以及該進程使用的CPU時間總量。內核可以釋放終止進程所使用的所有存儲區,關閉其所有打開文件。在UNIX術語中,一個已經終止,但是其父進程尚未對其進行善後處理(使用wait獲取終止子進程的有關信息,釋放它仍佔用的資源)的進程稱爲僵死進程(zombie)(更多關於殭屍進程可參考:http://www.cnblogs.com/bettercoder/p/3501086.html)。ps(1)命令將僵死進程的狀態打印爲Z。如果編寫一個長期運行的程序,它調用fork產生了很多子進程,那麼除非父進程等待取得子進程的終止狀態,否則這些子進程終止後就會變成僵死進程。

  3. 最後一個要考慮的問題是:一個由init進程領養的進程終止時會發生什麼?它會不會變成一個僵死進程?對此問題的回答是:“否”,因爲init被編寫成無論何時只要有一個子進程終止,init就會調用wait函數取得其終止狀態。這樣也就防止了在系統中有很多僵死進程。當提及“一個init的子進程”時,這指的可能是init直接產生的進程,也可能是其父進程已終止,由init領養的進程。

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