天天看點

《linux下程序的建立,執行,監控和終止》

概述

        這篇文章主要講述linux下程序的相關操作,後續還會寫一篇關于linux線程操作的文章。這兩篇文章和我後續還要完成的一篇文章(linux下的IPC通信)組成一個完整的系列,可以說前兩篇是第三篇的鋪墊和基礎。

第一部分:fork建立程序

================================================

        首先來看一個fork() 建立子程序的例子。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

extern void error_exit(char*);
extern void success_exit(char*);

int main(int argc, char* argv[])
{
    pid_t child_pid;

    switch(child_pid = fork()) {
    case -1:
        error_exit("fork failed.");
        break;
    case 0:
        sleep(5);
        success_exit("child exit");
        break;
    default:
        if(wait(NULL) == -1) {
            error_exit("wait failed.");
        } else {
            success_exit("child has exit.");
        }
        break;
    }
    return 0;
}

void error_exit(char* msg)
{
    printf("NG: %s\n",msg);
    exit(-1);
}

void success_exit(char* msg)
{
    printf("OK: %s\n",msg);
    exit(0);
}
           

        上面的例子是最簡單的,建立子程序失敗則退出,否則子程序等待5秒和列印"child exit"并退出,父程序等待子程序退出後輸出“child has exit”退出。關于fork和父子程序之間的關系,這裡有有以下幾點需要說明:

1. fork有三類傳回值:失敗傳回-1;成功時,子程序傳回0;父程序傳回子程序的PID。

2. 理論上,fork建立的子程序會繼承(拷貝)一份父程序的資料段,堆棧,代碼段。實際上,為了避免浪費,fork不會立即給子程序做任何拷貝。對于代碼段,fork會将子程序的程序級頁表項指向與父程序相同的實體記憶體頁幀;對于資料段,堆棧段,則采用寫時複制技術來處理。這是非常有意義的,因為很多時候建立子程序不是為了讓子程序和父程序同時運作相同代碼,而是通過exec運作其他代碼。

3. 還有一點需要注意的是,父子程序完全共享fork之前打開的檔案的檔案偏移量和檔案狀态标志。說直白一點,如果子程序不執行exec操作,父子程序對fork前打開的檔案的寫入不會互相覆寫,而是被混雜在一起。是以如果不想這一幕發生,就需要對父子程序進行同步操作。

4. 子程序在退出時,系統會自動收回子程序的記憶體資源。利用這個特性,我們可以将一些複雜的操作提取出來,并建立一個子程序來專門執行這個操作,最後将結果通過檔案,管道或者其他程序間通信技術傳給父程序。這個應有技巧是非常有價值的,适合記憶體操作頻繁複雜的操作場合。利用子程序來做這些操作你就不需要擔心記憶體洩露以及堆記憶體過度碎片化的問題。

5. 不要對父子程序在fork之後的執行順序做任何假設(雖然曾經有過linux的核心版本對此做出過規範),最好能明确限定他們的執行順序,這個可以通過同步技術來實作(信号量,檔案鎖,管道消息,信号和wait()函數都可以)。在上述例子中,采用了兩種方式來改變父子程序的執行順序,一個是sleep(),一個是wait()。需要說明的是,sleep()并不是規範的方法,相形之下wait()顯得妥當很多,即使子程序睡了5秒鐘,父程序依然能有效地等待子程序先列印消息和退出。

        這裡再舉一個采用信号同步的例子:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>

#define SYNC_SIG SIGUSR1

extern void error_exit(char*);
extern void success_exit(char*);
static void handler(int sig)
{
    return;
}

int main(int argc, char* argv[])
{
    pid_t child_pid;
    sigset_t block_mask,old_mask,empty_mask;
    struct sigaction sa;

    // set process's signal mask as SYNC_SIG
    sigemptyset(&block_mask);
    sigaddset(&block_mask,SYNC_SIG);
    if(sigprocmask(SIG_BLOCK,&block_mask,&old_mask) == -1)
        error_exit("process mask failed.");

    // set handler for SYNC_SIG
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sa.sa_handler = handler;
    if(sigaction(SYNC_SIG,&sa,NULL) == -1)
        error_exit("set sigaction failed.");

    switch(child_pid = fork()) {
    case -1:
        error_exit("fork failed.");
        break;
    case 0:
        sleep(5);

        // child send SYNC_SIG to parent.
        printf("child: send signal.\n");
        kill(getppid(),SYNC_SIG);
        success_exit("child exit");
        break;
    default:
        // suspend parent to wait signale from child.
        printf("parent: suspend process.\n");
        sigemptyset(&empty_mask);
        if(sigsuspend(&empty_mask) == -1 && errno != EINTR)
            error_exit("parent: suspend failed");
        printf("parent: SYNC_SIG received,restart parent.\n");

        // wait child exit first.
        if(wait(NULL) == -1)
            error_exit("wait child failed.");
        success_exit("child has exit.");
        break;
    }

    return 0;
}

void error_exit(char* msg)
{
    printf("NG: %s\n",msg);
    exit(-1);
}

void success_exit(char* msg)
{
    printf("OK: %s\n",msg);
    exit(0);
}
           

        說明,這個例子在前一個例子的基礎上增加了信号同步。實際上在這個例子裡有兩處同步,第一處是信号同步實作的,這一同步的目的在于確定子程序中部分操作(如這裡的sleep(5),當然你可以将其換成任何你想執行的操作。)先于父程序的某些操作執行。第二個同步時wait實作的,他的作用是確定子程序先于父程序退出,這樣可以避免子程序成為孤兒程序。

第二部分:程序的終止

================================================

        關于程序的終止,主要涉及的函數有 _exit() , exit() , atexit() , on_exit()。下面針對程序終止說明以下幾點:

1. 程序的終止分為正常和異常兩種。異常終止可能是由于某些信号引起的,其中的一些信号還會導緻程序産生一個核心轉儲檔案。

2. 正常終止可以通過系統調用 _exit() 或者GNU C标準庫函數exit() 實作。二者都有一個status參數,用以表示函數退出值。常用的return 函數的效果類似與 exit() 函數(這裡是指在帶參數的情況下,return不帶參數的時候傳回值則取決于C語言的版本标準以及所使用的編譯器)。

3. 不管程序正常還是異常終止,核心都會執行多個清理步驟。與系統調用 _exit() 不同的是,調用庫函數 exit() 正常終止一個程序時,将會引發執行通過GNU C庫函數atexit() 或 on_exit() 注冊的退出處理程式(這些退出處理程式在調用函數 _exit() 或者因信号終止的情況下是不會執行的。),然後重新整理stdio緩沖區。

4. 關于重新整理緩沖的問題,也比較有意思。先看下面的例子:

直接運作下面的代碼的話,輸出的結果如下:

[[email protected] process]# ./process_test_stdio
Hello World!
YSY      

将輸出重定向到檔案時,輸出結果如下:

[[email protected] process]# ./process_test_stdio > log
[[email protected] process]# cat log
YSY
Hello World!
Hello World!      

代碼如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

extern void error_exit(char*);
extern void success_exit(char*);

int main(int argc, char* argv[])
{
    printf("Hello World!\n");
    write(STDOUT_FILENO, "YSY\n",4);

    if(fork() == -1)
        error_exit("fork failed.\n");

    return 0;
}

void error_exit(char* msg)
{
    printf("NG: %s\n",msg);
    exit(-1);
}

void success_exit(char* msg)
{
    printf("OK: %s\n",msg);
    exit(0);
}
           

        為什麼結果不一樣呢?這是因為,第一種情況标準輸出定向到終端,預設為行緩沖,是以fork之前就直接輸出了緩沖區的内容"Hello World!"。第二種情況,輸出是定義到檔案的,預設是塊緩沖,此時printf的内容會暫時存放在标準輸出的緩沖區内,随着fork的執行,子程序拷貝了标準輸出緩沖區,進而導緻輸出兩次"Hello World!"。至于為什麼"YSY"跑到前面去了,而且隻有一次呢?這是因為write() 系統調用會把資料直接傳遞給核心高速緩沖區,fork不會複制這一緩沖區。而且寫到檔案的時候,核心高速緩沖區會先寫進檔案,而标準輸出的内容則要等到程序結束時重新整理标準輸出緩沖區才會寫入檔案。

        為了避免刷緩沖區的時機問題帶來的困惑,使用者可以自己調用函數 fflush() 來重新整理緩沖區,或者使用 setvbuf() 和 setbuf() 來關閉标準輸出的緩沖功能。還可以盡量在子程序中使用_exit() 退出,因為_exit() 不會重新整理标準輸出的緩沖區,這樣至少對上面的問題不會導緻輸出兩次"Hello World!"。

第三部分:程序的監控

================================================

        這一部分講的是父程序對子程序的監控操作。主要涉及到三個知識點:監控子程序的必要性(即其目的);系統調用wait()及其相關調用;SIGCHLD信号的處理。後兩個知識點是父程序對子程序的監控手段。

一、監控子程序的必要性

        很多時候父程序都需要監控子程序的狀态,有以下幾點值得聲明:

1. 父子程序之間的同步,以及檢查子程序是否正常結束。比如父程序的某些操作需要等子程序結束才能執行,有時候父程序還需要擷取子程序的退出狀态等資訊,這就需要對子程序進行監控。

2. 避免僵屍程序大量産生。子程序結束後,核心會在父程序調用wait()或者waitpid()之類的函數之前,将結束的子程序轉為僵屍程序(關于僵屍程序及其危害可自行查閱資料)。父程序調用wait()或者waitpid()之類的函數之後,核心會完全清除已經結束的子程序,否則在父程序結束之後就會殘留下大量的僵屍程序。

3. 避免孤兒程序出現,雖然不像僵屍程序那樣對系統會帶來較大影響,依然不建議父程序不管子程序狀态擅自先結束執行。

二、wait()及其相關調用

        這一部分隻談 wait() 和 waitpid() 這兩個系統調用,他們都可以用于監控子程序的狀态。他們的定義如下:

       #include <sys/types.h>
       #include <sys/wait.h>
       pid_t wait(int *status);
       pid_t waitpid(pid_t pid, int *status, int options);      

        對于這兩個調用的關系做以下幾點說明:

1. 二者都有一個status參數用于傳回子程序終止狀态。

2. 二者都有一個pid_t類型的傳回值,表示監控到的子程序的PID。

3. 出錯時都傳回-1,如果errno被設定為ECHILD則表示沒有子程序可以等待,也就是說父程序的所有子程序都已結束并被父程序擷取過結束狀态。

4. wait() 隻能按順序等待結束的子程序(例如,調用wait之前已有多個子程序結束,則wait一次隻會傳回一個子程序的終止狀态,順序和子程序的結束順序一緻。),而waitpid則可以通過參數pid選擇等待方式(pid>0 等待指定的子程序;pid=0 等待與調用程序同一程序組的所有子程序;pid=-1等待所有子程序;pid<-1等待程序組标示符與pid絕對值相等的所有子程序)。

5. wait() 屬于阻塞式等待,直到有子程序結束才傳回,而waitpid() 可以通過參數options來指定等待方式(WUNTRACED:傳回已經終止的子程序和因信号而停止的子程序資訊;WCONTINUED:傳回因SIGCONT信号恢複執行的已停止的子程序的狀态資訊;WNOHANG:如果指定等待的子程序的狀态未發生改變則立即傳回,不會阻塞)。

        不管是wait() 還是waitpid() ,他們傳回的status都可以通過頭檔案<sys/wait.h>中定義的一組标準宏來解析(這些宏的名字還是很好記的:WIFEXITED(status);正常結束;WIFSIGNALED(status):被信号殺死的;WIFSTOPPED(status):被信号停止的;WIFCONTINUED(status):被信号停止後有被信号SIGCONT恢複執行的)。每一個傳回的status解析後隻會有一個宏傳回真值。

        下面舉一個執行個體,該執行個體會産生一個子程序,并會時刻傳回子程序的狀态變遷過程。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

extern void error_exit(char*);
extern void success_exit(char*);
extern void print_wait_status(const char*, int);

int main(int argc,char* argv[])
{
    int status;
    pid_t child_pid;

    switch(fork()) {
    case -1:
        error_exit("fork failed!");
    case 0:
        printf("child:started with pid=%d.\n",getpid());
        if(argc > 1) /* exit with the arg supplied on command line. */
            exit(atoi(argv[1]));
        else         /* wait for signal, until get kill signal. */
            for(;;)
                pause();

        error_exit("nerver be here.");
    default:
        for(;;) { /* chek child's status until child exit. */
            child_pid = waitpid(-1,&status,WUNTRACED|WCONTINUED);
            if (child_pid == -1)
                error_exit("waitpid failed!");
            printf("father:waitpid returned,child_pid=%ld,status=0x%04x (%d,%d).\n",
                   (long)child_pid,(unsigned int)status,status >> 8,status & 0xff);
            print_wait_status(NULL,status);
            if(WIFEXITED(status) || WIFSIGNALED(status))
                success_exit("child has exited");
        }
    }

    return 0;
}

void error_exit(char* msg)
{
    printf("NG: %s\n",msg);
    exit(-1);
}

void success_exit(char* msg)
{
    printf("OK: %s\n",msg);
    exit(0);
}

void print_wait_status(const char* msg, int status)
{
    if (msg != NULL) printf("%s\n",msg);

    if (WIFEXITED(status)) {
        printf("child exited: status=%d.\n",WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        printf("child killed by signal: %d\n",WTERMSIG(status));
    } else if (WIFSTOPPED(status)) {
        printf("child stopped by signal: %d\n",WSTOPSIG(status));
    } else if (WIFCONTINUED(status)) {
        printf("child continued.\n");
    } else {
        printf("unknow status.\n");
    }
}
           

        這個例子中,如果指令行已經制定一個參數,子程序會立即以這個參數退出。

[[email protected] process]# ./process_test_waitpid 9
        child:started with pid=21109.
        father:waitpid returned,child_pid=21109,status=0x0900 (9,0).
        child exited: status=9.
        OK: child has exited      

        如果沒有指定指令行參數,子程序會調用pause等待信号。父程序随時監控子程序狀态變化,一旦有變化立即輸出子程序的狀态變化資訊,直到子程序結束才退出。

        [[email protected]Brandy process]# ./process_test_waitpid &
        [1] 21111
        [[email protected] process]# child:started with pid=21112.
        [[email protected] process]# kill -STOP 21112
        [[email protected] process]# father:waitpid returned,child_pid=21112,status=0x137f (19,127).
        child stopped by signal: 19
        [[email protected] process]# kill -18 21112
        [[email protected] process]# father:waitpid returned,child_pid=21112,status=0xffff (255,255).
        child continued.
        [[email protected] process]# kill -15 21112
        [[email protected] process]# father:waitpid returned,child_pid=21112,status=0x000f (0,15).
        child killed by signal: 15
        OK: child has exited
        [1]+  Done                    ./process_test_waitpid
        [[email protected] process]#      

三、SIGCHLD信号

        前面介紹的wait和waitpid調用雖然也可以實作對子程序的監控,但是存在一些缺陷。例如大部分情況下都是阻塞模式,雖然waitpid可以設定options的為WNOHANG來輪詢,還是會造成CPU資源的浪費。怎麼辦呢,SIGCHLD可以幫助我們解決這個問題。

        子程序在結束時,系統會向其父程序發送SIGCHLD信号。是以我們也可以通過檢測這個信号來決定是否調用wait或者waitpid來傳回子程序退出狀态以及清理僵屍程序,即在信号SIGCHLD的處理程式中調用wait或者waitpid調用。不過設定信号SIGCHLD的處理程式有一些注意事項要強調,這很重要:

1. 當調用信号處理程式時,系統會暫時将引發調用的信号阻塞起來,并不會對SIGCHLD等标準信号進行排隊處理。那麼問題來了,要是相繼有兩個子程序結束,而我們的信号處理程式中又隻調用了一次 wait() 或者 waitpid() 調用,這時可能就會有僵屍程序成為漏網之魚不能被清理。是以在SIGCHLD信号的處理程式中最好使用下面的循環,它能夠確定所有結束的程序都被清理。

        while (waitpid(-1, NULL,WNOHANG) > 0)
            continue;      

2. 還有一個問題,如果建立的子程序在SIGCHLD信号處理函數設定之前就已經結束怎麼辦,還能順利檢測到SIGCHLD嗎?這個根據不同的系統實作是不一樣的,至少SUSv3對此并未作出規定。标準做法是在子程序被建立之前就設定好SIGCHLD信号處理函數。

3. 子程序stop的時候,系統也會向父程序發送SIGCHLD,如果希望父程序忽略這中情況,可以在 sigaction() 設定SIGCHLD信号處理函數時傳入SA_NOCLDSTOP标志,這樣系統就不會因為子程序的停止而發出SIGCHLD信号。

4. 如果父程序對子程序的退出狀态并不感興趣,而隻是單純的希望清理僵屍程序的話,那麼很簡單,我們隻需要顯式設定SIGCHLD的處理為SIG_IGN(雖然預設系統也是忽略SIGCHLD信号,但是顯式指定為SIG_IGN時的行為是不一樣的)。這時系統會将終止的子程序直接删除,不會轉為僵屍程序。

        我又寫了一個例子,前面介紹 waitpid() 的時候也舉了一個例子,當時是在父程序裡面循環調用 waitpid() 直到子程序退出才結束。下面的例子是在信号SIGCHLD的處理函數裡調用 waitpid() 的,父程序調用 sigsuspend() 阻塞起來等待子程序發送SIGCHLD。

        在linux上測試結果如下:參數個數決定子程序個數和子程序延時的時間。

[[email protected] process]# ./process_test_sigchld 2 3 7
child-01: pid = 32722 start.
child-02: pid = 32723 start.
child-03: pid = 32724 start.
child-01: pid = 32722 exiting.
handler: signal SIGCHLD accept.
handler: child_pid=32721, status=0x0000(0,0)child exited: status=0.
child-02: pid = 32723 exiting.
child-03: pid = 32724 exiting.
handler: return.
handler: signal SIGCHLD accept.
handler: child_pid=32721, status=0x0000(0,0)child exited: status=0.
handler: child_pid=32721, status=0x0000(0,0)child exited: status=0.
handler: return.
END: forked 3 child; accept 2 time SIGCHLD      

        代碼如下:

<pre name="code" class="cpp"><pre name="code" class="plain">#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <stdlib.h>

void sigchld_handler(int);
void print_wait_status(const char*, int);

static int num_alive_child = 0;

int main(int argc, char* argv[])
{
    int j,num_child = 0,num_sig = 0;
    pid_t child_pid;
    sigset_t block_mask,empty_mask;
    struct sigaction sa;

    setbuf(stdout, NULL); /* disable buffering of stdout */

    /* 1:set handler for SIGCHLD */
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sa.sa_handler = sigchld_handler;
    if(sigaction(SIGCHLD,&sa,NULL) == -1) {
        printf("ERR: sigaction failed.\n");
        exit(-1);
    }

    /* 2:block signal SIGCHLD */
    sigemptyset(&block_mask);
    sigaddset(&block_mask,SIGCHLD);
    if(sigprocmask(SIG_SETMASK,&block_mask,NULL) == -1) {
        printf("ERR: sigprocmask failed.\n");
        exit(-1);
    }

    /* fork child process */
    for (j=1;j<argc;j++) {
        num_alive_child++;
        switch(child_pid = fork()){
        case -1:
            num_alive_child--;
            /* kill exist child and then exit will be better. */
            _exit(-1);
        case 0:
            printf("child-%02d: pid = %ld start.\n",j,(long)getpid());
            sleep((int)atoi(argv[j]));
            printf("child-%02d: pid = %ld exiting.\n",j,(long)getpid());
            _exit(0);
        default:
            num_child++;
            break;
        }
    }

    /* wait for signal SIGCHLD from child */
    sigemptyset(&empty_mask);
    while(num_alive_child > 0) {
        if(sigsuspend(&empty_mask) == -1 && errno != EINTR) {
            printf("ERR: sigsuspend failed.\n");
            exit(-1);
        }
        num_sig++;
    }

    printf("END: forked %d child; accept %d time SIGCHLD\n",num_child,num_sig);
    exit(0);
}

void sigchld_handler(int sig)
{
    int status, old_errno;
    pid_t child_pid;

    old_errno=errno;

    printf("handler: signal SIGCHLD accept.\n");

    /* 3:wait all exited child process */
    while((child_pid = waitpid(-1,&status,WNOHANG)) > 0) {
        printf("handler: child_pid=%ld, status=0x%04x(%d,%d)",
               (long)getpid(), (unsigned int)status, status >> 8, status & 0xff);
        print_wait_status(NULL,status);
        num_alive_child--;
    }

    if(child_pid == -1 && errno != ECHILD) {
        printf("handler: waitpid failed,errno=%d.\n",errno);
        exit(-1);
    }

    sleep(5);
    printf("handler: return.\n");

    errno = old_errno;
}
           

        在這個例子中,完全展現了前面強調的關于SIGCHLD信号處理函數設計的注意點。參照代碼中的注釋号,這裡再次說明如下:

1. 函數一開始就設定好信号處理函數,避免設定信号處理函數之前有子程序結束。

2. 建立子程序之前阻塞信号SIGCHLD,以防止子程序在檢查 num_aliave_child 和 執行 sigsuspend() 之間結束。如果子程序在此期間向父程序發送信号SIGCHLD,将會導緻父程序永遠阻塞在 sigsuspend() 裡,因為此時可能已經沒有子job會發送信号SIGCHLD了。

3. 在SIGCHLD信号處理函數中,使用輪訓方式調用 waitpid() 來處理所有結束的子程序。因為在執行信号處理函數是,系統會阻塞SIGCHLD并且不會對其排隊,是以這樣做可以是的在執行信号處理函數時結束的子程序也能很好地被回收處理。

4. 還有一點上面的例子裡沒有展現,子程序被暫停時,父程序也能接收到SIGCHLD信号。如果希望避免這一情況,調用 sigaction() 時可以指定SA_NOCLDSTOP标志。

第四部分:新程式的執行 execve()

================================================

        execve() 調用可以将新程式加載到某一個程序的記憶體空間,基于這個調用,C語言提供了exec系列庫函數。這裡針對新程式加載的調用做以下幾點說明:

1. exec() 系列函數有execve(),execle(),execlp(),execvp(),execv(),execl(),一共6個函數。他們的用法可查閱man手冊,不過其命名是有規律的,這一點有助于記憶其用法。

對程式檔案的描述(-,p):

        帶p的表示程式檔案指定時不需要補全路徑,系統會按照PATH路徑自動搜尋。例如execlp() 和 execvp()。

對參數的描述(v,l)         :

        v表示數組,l表示清單,例如 execve() 和 execle() 的差別就在于指定參數的方式上,execve是傳遞的數組,execle傳遞的是字元串清單。

對環境變量的描述(e,-):

        帶字幕e的表示可以通過一個參數向新的程式傳遞環境變量,否則就隻能繼承調用者的環境變量。

2. 解釋器腳本實際上就是利用了exec() 系列函數來實作對文本格式指令的程式的執行(機器實際上隻能執行二進制可執行檔案,對于文本格式指令程式實際上是通過解釋器調用exec() 來執行的,例如shell,以及awk,sed,perl,python,ruby。)。需要注意的還有,對于這種程式,在文本的第一行需要指出使用的解釋器的路徑名(以“#!”開頭)。

3. exec的調用程式所打開的檔案描述符在exec的執行過程中會保持打開狀态,且在新程式中依然有效,這是一把雙刃劍。先來說說好處,例如shell下執行一個指令,子shell在執行新的指令時直接将輸出資訊輸出到标準輸出而不需要重新打開标準輸出(當然,這裡的指令是非内部指令,對于shell的内部指令他是不需要fork子程序的)。再來說說壞處,那就是安全問題,最好在執行新程式之前關閉那些不必要的檔案描述符。但是直接關閉的話,又不能滿足exec執行失敗時的恢複,因為再次打開同一檔案是無法保證檔案描述符一緻的。這個時候就要用到“執行時關閉标志”了,可使用 fcntl() 系統調用來設定FD_CLOEXEX标志位。

4. 由于執行exec系列函數後會替換掉程序的文本段,那麼調用exec之前設定的信号處理函數當然也被覆寫了。對于之前設定了信号處理函數的信号,執行exec之後核心會将他們的信号處理函數重置為SIG_DFL。信号SIGCHLD是一個特例,調用exec之前如果設定SIGCHLD信号的處理函數為SIG_IGN的話,調用之後的處理在SUSv3中是沒有被規定的。是以,對于信号的處理,最通用的辦法是在調用exec之前顯式設定為SIG_DFL。

5. 調用exec系列函數期間,程序的信号掩碼和挂起信号的設定會儲存下來。這一點也是很重要的,使用的時候最好記住這個特性,以免帶來疑惑。