exit()与_exit()函数的区别
注:exit()就是退出,傳入的參數是程序退出時的狀態碼,0表示正常退出,其他表示非正常退出,一般都用-1或者1,標準C里有EXIT_SUCCESS和EXIT_FAILURE兩個宏,用exit(EXIT_SUCCESS);可讀性比較好一點。
作為系統調用而言,_exit和exit是一對孿生兄弟,它們究竟相似到什么程度,我們可以從Linux的源碼中找到答案:#define __NR__exit __NR_exit /* 摘自文件include/asm-i386/unistd.h第334行 */
"__NR_"是在Linux的源碼中為每個系統調用加上的前綴,請注意第一個exit前有2條下劃線,第二個exit前只有1條下劃線。
這時隨便一個懂得C語言并且頭腦清醒的人都會說,_exit和exit沒有任何區別,但我們還要講一下這兩者之間的區別,這種區別主要體現在它們在函數庫中的定義。_exit在Linux函數庫中的原型是:
#include void _exit(int status)
和exit比較一下,exit()函數定義在stdlib.h中,而_exit()定義在unistd.h中,從名字上看,stdlib.h似乎比 unistd.h高級一點,那么,它們之間到底有什么區別呢?
_exit()函數的作用最為簡單:直接使進程停止運行,清除其使用的內存空間,并銷毀其在內核中的各種數據結構;exit() 函數則在這些基礎上作了一些包裝,在執行退出之前加了若干道工序,也是因為這個原因,有些人認為exit已經不能算是純粹的系統調用。
exit()函數與_exit()函數最大的區別就在于exit()函數在調用exit系統調用之前要檢查文件的打開情況,把文件緩沖區中的內容寫回文件,就是"清理I/O緩沖"。?
?
exit()在結束調用它的進程之前,要進行如下步驟:
1.調用atexit()注冊的函數(出口函數);按ATEXIT注冊時相反的順序調用所有由它注冊的函數,這使得我們可以指定在程序終止時執行自己的清理動作.例如,保存程序狀態信息于某個文件,解開對共享數據庫上的鎖等.
2.cleanup();關閉所有打開的流,這將導致寫所有被緩沖的輸出,刪除用TMPFILE函數建立的所有臨時文件.
3.最后調用_exit()函數終止進程。
?
_exit做3件事(man):
1,Any??open file descriptors belonging to the process are closed
2,any children of the process are inherited??by process 1, init
3,the process's parent is sent a SIGCHLD signal
exit執行完清理工作后就調用_exit來終止進程。
?
此外,另外一種解釋:
簡單的說,exit函數將終止調用進程。在退出程序之前,所有文件關閉,緩沖輸出內容將刷新定義,并調用所有已刷新的“出口函數”(由atexit定義)。 _exit:該函數是由Posix定義的,不會運行exit handler和signal handler,在UNIX系統中不會flush標準I/O流。 簡單的說,_exit終止調用進程,但不關閉文件,不清除輸出緩存,也不調用出口函數。 共同: 不管進程是如何終止的,內核都會關閉進程打開的所有file descriptors,釋放進程使用的memory!?
為何在一個fork的子進程分支中使用_exit函數而不使用exit函數?
‘exit()’與‘_exit()’有不少區別在使用‘fork()’,特別是‘vfork()’時變得很
突出。
‘exit()’與‘_exit()’的基本區別在于前一個調用實施與調用庫里用戶狀態結構(user-mode constructs)有關的清除工作(clean-up),而且調用用戶自定義的清除程序 (自定義清除程序由atexit函數定義,可定義多次,并以倒序執行),相對應,_exit函數只為進程實施內核清除工作。
在由‘fork()’創建的子進程分支里,正常情況下使用‘exit()’是不正確的,這是 因為使用它會導致標準輸入輸出(stdio: Standard Input Output)的緩沖區被清空兩次,而且臨時文件被出乎意料的刪除(臨時文件由tmpfile函數創建在系統臨時目錄下,文件名由系統隨機生成)。在C++程序中情況會更糟,因為靜態目標(static objects)的析構函數(destructors)可以被錯誤地執行。
(還有一些特殊情況,比如守護程序,它們的父進程需要調用‘_exit()’而不是子進程;
適用于絕大多數情況的基本規則是,‘exit()’在每一次進入‘main’函數后只調用一次。)
在由‘vfork()’創建的子進程分支里,‘exit()’的使用將更加危險,因為它將影響父進程的狀態。
在Linux系統上運行,父進程printf的內容輸出:pid = 29650, glob = 7, var = 89
子進程 關閉的是自己的, 雖然他們共享標準輸入、標準輸出、標準出錯等 “打開的文件”, 子進程exit時,也不過是遞減一個引用計數,不可能關閉父進程的,所以父進程還是有輸出的。
但在其它UNIX系統上,父進程可能沒有輸出,原因是子進程調用了e x i t,它刷新關閉了所有標準I / O流,這包括標準輸出。雖然這是由子進程執行的,但卻是在父進程的地址空間中進行的,所以所有受到影響的標準I/O FILE對象都是在父進程中的。當父進程調用p r i n t f時,標準輸出已被關閉了,于是p r i n t f返回- 1。
?
在Linux的標準函數庫中,有一套稱作"高級I/O"的函數,我們熟知的printf()、fopen()、fread()、fwrite()都在此 列,它們也被稱作"緩沖I/O(buffered I/O)",其特征是對應每一個打開的文件,在內存中都有一片緩沖區,每次讀文件時,會多讀出若干條記錄,這樣下次讀文件時就可以直接從內存的緩沖區中讀取,每次寫文件的時候,也僅僅是寫入內存中的緩沖區,等滿足了一定的條件(達到一定數量,或遇到特定字符,如換行符和文件結束符EOF),再將緩沖區中的 內容一次性寫入文件,這樣就大大增加了文件讀寫的速度,但也為我們編程帶來了一點點麻煩。如果有一些數據,我們認為已經寫入了文件,實際上因為沒有滿足特定的條件,它們還只是保存在緩沖區內,這時我們用_exit()函數直接將進程關閉,緩沖區中的數據就會丟失,反之,如果想保證數據的完整性,就一定要使用exit()函數。
Exit的函數聲明在stdlib.h頭文件中。
_exit的函數聲明在unistd.h頭文件當中。
下面的實例比較了這兩個函數的區別。printf函數就是使用緩沖I/O的方式,該函數在遇到“\n”換行符時自動的從緩沖區中將記錄讀出。實例就是利用這個性質進行比較的。
exit.c源碼
#include
#include
int main(void)
{
??? printf("Using exit...\n");
??? printf("This is the content in buffer");
??? exit(0);
}
輸出信息:
Using exit...
This is the content in buffer
?
#include
#include
int main(void)
{
??? printf("Using exit...\n");?? //如果此處不加“\n”的話,這條信息有可能也不會顯示在終端上。
??? printf("This is the content in buffer");
??? _exit(0);
}
則只輸出:
Using exit...
?
說明:在一個進程調用了exit之后,該進程并不會馬上完全消失,而是留下一個稱為僵尸進程(Zombie)的數據結構。僵尸進程是一種非常特殊的進程,它幾乎已經放棄了所有的內存空間,沒有任何可執行代碼,也不能被調度,僅僅在進程列表中保留一個位置,記載該進程的退出狀態等信息供其它進程收集,除此之外,僵尸進程不再占有任何內存空間。
#include ;
int main()
{
??? printf("%c", 'c');
??? _exit(0);
}
程序并沒有輸出"c", 說明_exit()沒有進行io flush
總結
以上是生活随笔為你收集整理的exit()与_exit()函数的区别的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux下的进程相关编程
- 下一篇: atexit注册进程终止处理函数