關於Linux系統命令中exit與exit的區別

注:exit()就是退出,傳入的參數是程序退出時的狀態碼,0表示正常退出,其他表示非正常退出,一般都用-1或者1,標準C裏有EXIT_SUCCESS和EXIT_FAILURE兩個宏,用exit(EXIT_SUCCESS);可讀性比較好一點。

關於Linux系統命令中exit與exit的區別

作爲系統調用而言,_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函數庫中的原型是:

#includevoid_exit(intstatus);

和exit比較一下,exit()函數定義在stdlib.h中,而_exit()定義在unistd.h中,從名字上看,stdlib.h似乎比unistd.h高級一點,那麼,它們之間到底有什麼區別呢? _exit()函數的作用最爲簡單:直接使進程停止運行,清除其使用的內存空間,並銷燬其在內核中的各種數據結構;exit()函數則在這些基礎上作了一些包裝,在執行退出之前加了若干道工序,也是因爲這個原因,有些人認爲exit已經不能算是純粹的系統調用。 exit()函數與_exit()函數最大的區別就在於exit()函數在調用exit系統調用之前要檢查文件的打開情況,把文件緩衝區中的內容寫回文件,就是"清理I/O緩衝"。

exit()在結束調用它的進程之前,要進行如下步驟:

1.調用atexit()註冊的函數(出口函數);按ATEXIT註冊時相反的順序調用所有由它註冊的函數,這使得我們可以指定在程序終止時執行自己的清理動作.例如,保存程序狀態信息於某個文件,解開對共享數據庫上的鎖等.

nup();關閉所有打開的流,這將導致寫所有被緩衝的輸出,刪除用TMPFILE函數建立的所有臨時文件.

3.最後調用_exit()函數終止進程。

_exit做3件事(man): 1,Anyopenfiledescriptorsbelongingtotheprocessareclosed 2,anychildrenoftheprocessareinheritedbyprocess1,init 3,theprocess‘sparentissentaSIGCHLDsignal

exit執行完清理工作後就調用_exit來終止進程。

此外,另外一種解釋:

簡單的說,exit函數將終止調用進程。在退出程序之前,所有文件關閉,緩衝輸出內容將刷新定義,並調用所有已刷新的“出口函數”(由atexit定義)。

_exit:該函數是由Posix定義的,不會運行exithandler和signalhandler,在UNIX系統中不會flush標準I/O流。

簡單的說,_exit終止調用進程,但不關閉文件,不清除輸出緩存,也不調用出口函數。

共同:

不管進程是如何終止的,內核都會關閉進程打開的所有filedescriptors,釋放進程使用的memory!

更詳細的介紹:

Callingexit() Theexit()functioncausesnormalprogramtermination.

Theexit()functionperformsthefollowingfunctions:

unctionsregisteredbytheStandardCatexit()functionarecalledinthereverse yofthesefunctionscallsexit(),theresultsarenotportable. penoutputstreamsareflushed(datawrittenout)andthestreamsareclosed.

ilescreatedbytmpfile()ared.

_exit()functioniscalled. Calling_exit() The_exit()functionperformsoperatingsystem-specificprogramterminationfunctions. Theseinclude: penfiledescriptorsanddirectorystreamsareclosed.

eparentprocessisexecutingawait()orwaitpid(),theparentwakesupand statusismadeavailable.

eparentisnotexecutingawait()orwaitpid(),thestatusissavedforreturnto theparentonasubsequentwait()orwaitpid(). :the terminationofaparentdoesnotdirectlyterminateitschildren. eimplementationsupportstheSIGCHLDsignal,aSIGCHLDissenttotheparent. raljobcontrolsignalsaresent.

爲何在一個fork的子進程分支中使用_exit函數而不使用exit函數? ‘exit()’與‘_exit()’有不少區別在使用‘fork()’,特別是‘vfork()’時變得很 突出。

‘exit()’與‘_exit()’的基本區別在於前一個調用實施與調用庫裏用戶狀態結構(user-modeconstructs)有關的清除工作(clean-up),而且調用用戶自定義的清除程序(自定義清除程序由atexit函數定義,可定義多次,並以倒序執行),相對應,_exit函數只爲進程實施內核清除工作。 在由‘fork()’創建的`子進程分支裏,正常情況下使用‘exit()’是不正確的,這是因爲使用它會導致標準輸入輸出(stdio:StandardInputOutput)的緩衝區被清空兩次,而且臨時文件被出乎意料的刪除(臨時文件由tmpfile函數創建在系統臨時目錄下,文件名由系統隨機生成)。在C++程序中情況會更糟,因爲靜態目標(staticobjects)的析構函數(destructors)可以被錯誤地執行。(還有一些特殊情況,比如守護程序,它們的父進程需要調用‘_exit()’而不是子進程;適用於絕大多數情況的基本規則是,‘exit()’在每一次進入‘main’函數後只調用一次。) 在由‘vfork()’創建的子進程分支裏,‘exit()’的使用將更加危險,因爲它將影響父進程的狀態。

#include; #includeintglob=6;/*externalvariableininitializeddata*/ int main(void) { intvar;/*automaticvariableonthestack*/ pid_tpid; var=88; printf("beforevforkn";/*wedon‘tflushstdio*/ if((pid=vfork())<0) printf("vforkerrorn"; elseif(pid==0){/*child*/ glob++;/*modifyparent‘svariables*/ var++; exit(0);/*childterminates*///子進程中最好還是用_exit(0)比較安全。 } /*parent*/ printf("pid=%d,glob=%d,var=%dn",getpid(),glob,var); exit(0); } 在Linux系統上運行,父進程printf的內容輸出:pid=29650,glob=7,var=89

子進程關閉的是自己的,雖然他們共享標準輸入、標準輸出、標準出錯等“打開的文件”,子進程exit時,也不過是遞減一個引用計數,不可能關閉父進程的,所以父進程還是有輸出的。

但在其它UNIX系統上,父進程可能沒有輸出,原因是子進程調用了exit,它刷新關閉了所有標準I/O流,這包括標準輸出。雖然這是由子進程執行的,但卻是在父進程的地址空間中進行的,所以所有受到影響的標準I/OFILE對象都是在父進程中的。當父進程調用printf時,標準輸出已被關閉了,於是printf返回-1。

在Linux的標準函數庫中,有一套稱作"高級I/O"的函數,我們熟知的printf()、fopen()、fread()、fwrite()都在此列,它們也被稱作"緩衝I/O(bufferedI/O)",其特徵是對應每一個打開的文件,在內存中都有一片緩衝區,每次讀文件時,會多讀出若干條記錄,這樣下次讀文件時就可以直接從內存的緩衝區中讀取,每次寫文件的時候,也僅僅是寫入內存中的緩衝區,等滿足了一定的條件(達到一定數量,或遇到特定字符,如換行符和文件結束符EOF),再將緩衝區中的內容一次性寫入文件,這樣就大大增加了文件讀寫的速度,但也爲我們編程帶來了一點點麻煩。如果有一些數據,我們認爲已經寫入了文件,實際上因爲沒有滿足特定的條件,它們還只是保存在緩衝區內,這時我們用_exit()函數直接將進程關閉,緩衝區中的數據就會丟失,反之,如果想保證數據的完整性,就一定要使用exit()函數。

Exit的函數聲明在stdlib.h頭文件中。

_exit的函數聲明在unistd.h頭文件當中。

下面的實例比較了這兩個函數的區別。printf函數就是使用緩衝I/O的方式,該函數在遇到“n”換行符時自動的從緩衝區中將記錄讀出。實例就是利用這個性質進行比較的。

exit.c源碼

#include#includeintmain(void) { printf("Usingexit...n"); printf("Thisisthecontentinbuffer"); exit(0); }

輸出信息:

Usingexit...

Thisisthecontentinbuffer

#include#includeintmain(void) { printf("Usingexit...n");//如果此處不加“n”的話,這條信息有可能也不會顯示在終端上。 printf("Thisisthecontentinbuffer"); _exit(0); }

則只輸出:

Usingexit...

說明:在一個進程調用了exit之後,該進程並不會馬上完全消失,而是留下一個稱爲殭屍進程(Zombie)的數據結構。殭屍進程是一種非常特殊的進程,它幾乎已經放棄了所有的內存空間,沒有任何可執行代碼,也不能被調度,僅僅在進程列表中保留一個位置,記載該進程的退出狀態等信息供其它進程收集,除此之外,殭屍進程不再佔有任何內存空間。

#include;

intmain() { printf("%c",‘c‘); _exit(0); }