在exit,_exit的區別 ? -?exit()與_exit()函數的區別(Linux系統中)2012-03-20?15:19:53 分類:?LINUX 注:exit()就是退出,傳入的參數是程序退出時的狀態碼,0表示正常退出,其他表示非正常退出,一般都用-1或者1,標準C里有EXIT_SUCCESS和EXIT_FAILURE兩個宏,用exit(EXIT_SUCCESS);可讀性比較好一點。 作為系統調用而言,_exit和exit是一對孿生兄弟,它們究竟相似到什么程度,我們可以從Linux的源碼中找到答案:
"__NR_"是在Linux的源碼中為每個系統調用加上的前綴,請注意第一個exit前有2條下劃線,第二個exit前只有1條下劃線。 這時隨便一個懂得C語言并且頭腦清醒的人都會說,_exit和exit沒有任何區別,但我們還要講一下這兩者之間的區別,這種區別主要體現在它們在函數庫中的定義。_exit在Linux函數庫中的原型是:
和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! 更詳細的介紹: Calling?exit() The?exit()?function?causes?normal?program?termination. The?exit()?function?performs?the?following?functions: 1.?All?functions?registered?by?the?Standard?C?atexit()?function?are?called?in?the?reverse order?of?registration.?If?any?of?these?functions?calls?exit(),?the?results?are?not?portable. 2.?All?open?output?streams?are?flushed?(data?written?out)?and?the?streams?are?closed. 3.?All?files?created?by?tmpfile()?are?deleted. 4.?The?_exit()?function?is?called. Calling?_exit() The?_exit()?function?performs?operating?system-specific?program?termination?functions. These?include: 1.?All?open?file?descriptors?and?directory?streams?are?closed. 2.?If?the?parent?process?is?executing?a?wait()?or?waitpid(),?the?parent?wakes?up?and status?is?made?available. 3.?If?the?parent?is?not?executing?a?wait()?or?waitpid(),?the?status?is?saved?for?return?to the?parent?on?a?subsequent?wait()?or?waitpid(). 4.?Children?of?the?terminated?process?are?assigned?a?new?parent?process?ID.?Note:?the termination?of?a?parent?does?not?directly?terminate?its?children. 5.?If?the?implementation?supports?the?SIGCHLD?signal,?a?SIGCHLD?is?sent?to?the?parent. 6.?Several?job?control?signals?are?sent. 為何在一個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()’的使用將更加危險,因為它將影響父進程的狀態。 #include?<sys/types.h>; #include?<stdio.h> int?glob?=?6;?/*?external?variable?in?initialized?data?*/ int main(void) { int?var;?/*?automatic?variable?on?the?stack?*/ pid_t?pid; var?=?88; printf("before?vfork\n";?/*?we?don't?flush?stdio?*/ if?(?(pid?=?vfork())?<?0) printf("vfork?error\n"; else?if?(pid?==?0)?{?/*?child?*/ glob++;?/*?modify?parent's?variables?*/ var++; exit(0);?/*?child?terminates?*/?//子進程中最好還是用_exit(0)比較安全。 } /*?parent?*/ printf("pid?=?%d,?glob?=?%d,?var?=?%d\n",?getpid(),?glob,?var); exit(0); } 在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?<stdlib.h> #include?<stdio.h> 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?<unistd.h> #include?<stdio.h> int?main(void) { printf("Using?exit...\n");?//如果此處不加“\n”的話,這條信息有可能也不會顯示在終端上。 printf("This?is?the?content?in?buffer"); _exit(0); } 則只輸出: Using?exit... 說明:在一個進程調用了exit之后,該進程并不會馬上完全消失,而是留下一個稱為僵尸進程(Zombie)的數據結構。僵尸進程是一種非常特殊的進程,它幾乎已經放棄了所有的內存空間,沒有任何可執行代碼,也不能被調度,僅僅在進程列表中保留一個位置,記載該進程的退出狀態等信息供其它進程收集,除此之外,僵尸進程不再占有任何內存空間。 #include?<stdio.h>; int?main() { printf("%c",?'c'); _exit(0); } ? ? |
轉載于:https://www.cnblogs.com/coffewei/p/4053475.html