1. 程式人生 > >轉載:exit()與_exit()的區別

轉載: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函式庫中的原型是:

#i nclude<unistd.h>
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!

 更詳細的介紹:

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);
}

程式並沒有輸出"c", 說明_exit()沒有進行io flush