在Linux中,程序的執行從開始到結束的過程如下圖所示:
首先由內核使程序執行。內核首先創建一個啓動例程,然後調用exec函數使得該啓動例程執行。然後啓動例程從內核中取得命令行參數和環境變量值,用來調用main函數。於是開始執行main函數。在main函數中可以調用用戶函數,然後可以從用戶函數中通過return語句返回其主調函數。當然,我們還可以隨時使得程序終止,即通過調用exit()或_exit()和_Exit()函數,這幾個函數是程序進程自願主動終止的方法,它是系統級別的,是通過直接調用exit系統調用完成。這裏有兩點需要強調:exit()系列函數和return的區別;幾種exit函數的區別;
1、exit()系列函數和return的區別
<1>exit()函數是用來隨時終止程序進程的,它是系統級別的,將直接返回到操作系統中。
<2>return語句是語言級的,它的作用是終止當前函數的運行,並將操作權返回給調用者。是在調用堆棧中返回。如果是在main函數中運行return語句,main函數的調用者是啓動例程,然後啓動例程通過調用exit函數來結束進程,這就是所謂的在main執行到最後一條語句的時候會隱式返回。
<3>exit()函數的參數是用來將應用程序的狀態返回給操作系統,一般0狀態爲正常退出,非0狀態爲非正常退出。main函數可能有一個整型返回值,這個返回值返回給啓動例程,然後啓動例程將這個返回值作爲exit函數的參數同樣返回給操作系統。
2、exit()與_exit()和_Exit()函數的區別
主要如下圖所示:
最後都是將調用exit系統調用來終止進程,但是在調用exit系統調用之前,exit()多做了兩件事情:它將調用終止處理程序,並且將關閉所有打開的流。首先,進程在處理終止處理程序時,具體是調用哪些程序呢?終止處理程序主要是用來對進程終止需要進行的資源釋放等操作的一些函數,是通過int atexit(void (*func) (void))函數來註冊的。注意到參數是一個返回類型和參數類型均爲空的函數指針,即exit()在調用終止處理程序的時候不需要對其傳遞參數,並且也不指望終止處理程序能夠返回任何值。另外,exit()函數調用終止處理程序的順序與atexit()函數註冊這些終止處理程序的順序是相反的。同一個終止處理函數可以被註冊多次,這意味這在終止進程時,這個終止處理程序也會被執行多次。ISO C規定的一個進程可以登記多至32個函數。
我們通過一個小程序來證明一下atexit函數和exit的關係:
由上面的代碼可以看出,函數的註冊順序是:my_exit2my_exit1 my_exit1,其中my_exit1還被註冊了兩次,所以在從main函數中return後將隱式調用exit()函數,將以與atexit函數註冊的相反順序調用這些被註冊的終止處理程序,並且重複多次註冊的函數也將被重複多次執行。結果如下:#include <apue.h> #include <error.c> static void my_exit1(void); static void my_exit2(void); int main() { 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 in done\n"); return(0); } static void my_exit1(void) { printf("first exit handler\n"); } static void my_exit2(void) { printf("second exit handler\n"); }
從運行結果可以看出,my_exit1函數被執行兩次,並且是逆序執行的。