Linux signal 函數的常用功能分析

先來看一批比較老的signal function:

#include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t sigset(int sig, sighandler_t disp);

       int sighold(int sig);

       int sigrelse(int sig);

       int sigignore(int sig);
他們的具體描述請參見linux manual: http://www.kernel.org/doc/man-pages/online/pages/man3/sigset.3.html

他們的作用很簡單的可以從他們的名字中看出來,這裏就不加敘述了。

現在的一些新的程序已經不在使用上面這些函數,取而代之的是我們接下來要看的這幾個函數:

#include <signal.h>

       int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
sigaction這個函數功能取代了上面的sigset(),用來設置在收到特定信號時所採取的動作。

參數介紹:

    第一個參數 int signum 用來說明這次設定是針對那一個信號。這裏要注意的是,不能用這個函數對 SIGKILLSIGSTOP進行設置。

    後兩個參數使用相同的類型,從名字上可以看出,第二個參數 const struct sigaction *act 是調用這個函數時要設置的行爲描述,而第三個參數 struct sigaction *oldact 則用來返回在調用這個函數之前原先對於這個信號的行爲描述。

當第二參數爲NULL時,sigaction可用來查詢舊的action設置。當第二個和第三個參數都爲NULL時,則此函數用來檢查當前設備是否支持signum這個信號。

下面就來分析一下這個結構體的成員:

struct sigaction {
//指定新的信號處理函數,可以是SIG_DEF(表示使用默認處理函數),或是SIG_IGN(用來表示忽略次信號)
void (*sa_handler)(int);
//當sa_flags被設置爲SA_SIGINFO時,信號處理函數將不再使用sa_handler,而是使用此項sa_sigaction。
//裏面儲存的是這次信號的詳細信息,包括髮送信號的pid,用戶id等。
    void     (*sa_sigaction)(int, siginfo_t *, void *);
//指定在執行信號處理函數時將被阻塞的信號,觸發此動作的信號默認被阻塞,除非在sa_flags中設置SA_NODEFER
sigset_t sa_mask;
//指定此次信號動作的flag,除了上面提到的SA_SIGINFO和SA_NODEFER外其他詳見linux manual
int sa_flags;
//已廢棄,不再使用
    void     (*sa_restorer)(void);
}; 值得注意的是通過fork()產生的子進程將會繼承父進程的信號處理行爲,而在調用execve後,信號處理行爲將被重置爲默認。對於忽略掉的信號處理則不做改變。如果SIGFPE(算術錯誤),SIGILL(錯誤指令),SIGSEGV(內存訪問錯誤)被忽視,該程序的執行將變的不可知。


說完sigaction,我們來看下一個取代最上面被廢棄了的後三個函數的函數:

 #include <signal.h>
       int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
此函數用來查看或者設置信號集傳遞被當前線程阻塞的情況。

參數how有三個可選值:SIG_BLOCK設置阻塞的信號集爲原先的和參數set中的並集,SIG_UNBLOCK從原先的阻塞信號集中移除參數set中的信號,SIG_SETMASK廢棄原先的阻塞集改爲參數set中的信號集。

對於後面兩個參數,起意義和sigaction的後面兩參數類似。而對他們的操作則有另一些函數來完成:

 #include <signal.h>
    int sigemptyset(sigset_t *set); //初始化信號集爲空
    int sigfillset(sigset_t *set);  //初始化信號集爲所有信號
    int sigaddset(sigset_t *set, int signum);  //向信號集中添加信號
    int sigdelset(sigset_t *set, int signum);  //從信號集中刪除信號
    int sigismember(const sigset_t *set, int signum);  //檢查信號是否在信號集中

需要注意的是此函數不適用於多線程程序,若要在多線程程序中使用則請使用:

       #include <signal.h>

       int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

       Compile and link with -pthread.
此函數和sigprocmask基本相同,而它則適用於多線程的程序。


除了設置signal handler之外,我們還看是一個線程掛起來等待一個信號的到來:

       #include <signal.h>

        int sigwait(const sigset_t *set, int *sig);
當調用這個函數時,當前線程會被掛起,知道set信號集中的一個信號到來時纔會再次喚醒這個線程,然後將喚醒線程的信號寫入sig中。


上面是信號接收端的一些設置函數,下面介紹的是信號發出端的常用的一些函數

首先這個是對這整個進程而言的一個信號發出函數:

       #include <sys/types.h>
      #include <signal.h>

       int kill(pid_t pid, int sig);
相對這個函數,可能在命令行下的kill命令大家會更熟悉一些,但命令行下的kill默認是殺死一個進程即發送一個TERM信號給pid所代表的線程。當然也可以在kill指定要發送的信號,那麼其功能也就等同於這個函數了。

有幾種特殊情況簡單介紹一下:

pid爲0,則是發送sig給所有和調用kill的進程在同一個進程組裏的進程。

pid爲-1,則發送sig給所有調用kill的進程有權限發送信號的出1號進程(init)之外的其他進程。

pid < -1,則發送sig給進程號爲絕對值pid的進程。

sig = 0,此時,這個函數用來檢查pid表示的進程或者進程組是否存在。

上面這個是發送信號給其他進程,而下面這個則是發送信號給自己:

       #include <signal.h>

       int raise(int sig);
具體用法同kill就不加敘述。

下面接觸線程間的信號傳送:

       #include <signal.h>

       int pthread_kill(pthread_t thread, int sig);

       Compile and link with -pthread.
這個函數的用法和kill相似,只是將進程號變爲了線程號。另外,把sig設成0同樣可以利用該函數的錯誤檢查機制來作爲線程是否存在的檢查工具。


特別注意:

1. 不要在signal handler中使用printf函數,因爲printf不屬於asyc-signal-safe function,在signal handler中使用的函數有限制,不然會出現無法預料的情況,詳細可使用的函數請見linux manual:http://www.kernel.org/doc/man-pages/online/pages/man7/signal.7.html

2. 如果對一個線程使用pthread_kill,而這個線程又設定了這個signal的handler,並且對於這個signal的處理是“stop”,“continue”,或者“terminate”,那麼,這個處理將會影響到整個進程。


上面是我對linux signal的理解,如果有錯誤的地方歡迎指正。

另外,我在學習過程中有幾個疑問卻一直無法想到很好的解釋,希望各位能提提自己的看法。先謝謝。我的主要問題如下:

1. 當一個線程mask了一些signal之後,在給這個signal設置handler,再發送signal給這個線程,它的handler是否還會被觸發?(後面的實驗證明會)

2. 當一個線程mask了一些signal之後,讓這個線程sigwait已經被mask了的signal,這個sigwait是否會捕捉到發給這個線程的signal?(同樣被證明可以被捕捉)

然後的疑問是這裏都會觸發響應動作是因爲設置handler或者sigwait的同時會修改該線程被block的信號集嗎??

3. 再有就是同一個信號即設置了handler又同時被該線程sigwait,誰會被觸發?(似乎和信號發送的快慢時機有關)

我的例程:()

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

#define THREAD_NUM 5
int hdler_cnt1 = 0;
int hdler_cnt2 = 0;

static void signal_handler1(int sig) {
//    printf("handler1: get sig(%d)\n", sig);
    hdler_cnt1++;
}

static void signal_handler2(int sig) {
//    printf("handler2: get sig(%d)\n", sig);
    hdler_cnt2++;
}


static void *thread_fn1(void *arg) {
    struct sigaction act;
    sigset_t set;
    int s, sig;

    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    sigaddset(&set, SIGUSR2);
    pthread_sigmask(SIG_BLOCK, &set, NULL);
    memset(&act, 0, sizeof(act));
    act.sa_handler = signal_handler1;
    s = sigaction(SIGUSR1, &act, NULL);
    if (0 != s)
        printf("thread1: sigaction failed!\n");

    s = sigwait(&set, &sig);
    if (0 != s)
        printf("thread1: sigwait failed!\n");

    printf("thread1: after sigwait, get sig=%d.\n", sig);
    while(1);
}

static void *thread_fn2 (void *arg) {
    struct sigaction act;
    sigset_t *set = (sigset_t *)arg;
    int s, sig;

    s = pthread_sigmask(SIG_UNBLOCK, set, NULL);
    if (0 != s)
        printf("thread2: sigmask failed!\n");

    memset(&act, 0, sizeof(act));
    act.sa_handler = signal_handler2;
    s = sigaction(SIGUSR2, &act, NULL);
    if (0 != s)
        printf("thread2: sigaction failed!\n");

    sig = 0;
    while (sig != SIGUSR2) {
        s = sigwait(set, &sig);
        if (0 != s)
            printf("thread2: sigwait failed!\n");
        printf("thread2: after sigwait, get sig=%d.\n", sig);
    }
    printf("thread2: after while sig.\n");
    while(1);
}

int main(int argc, char *argv[]) {
    pthread_t thread1, thread2;
    sigset_t set;
    int s;
    char input = 0;

    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    sigaddset(&set, SIGUSR2);
    s = pthread_sigmask(SIG_BLOCK, &set, NULL);
    if (0 != s) {
        printf("main: sigmask failed!\n");
    }

    s = pthread_create(&thread1, NULL, &thread_fn1, (void *)&set);
    if (0 != s)
        printf("main: pthread create failed!\n");

    s = pthread_create(&thread2, NULL, &thread_fn2, (void *)&set);
    if (0 != s)
        printf("main: pthread create failed!\n");

    input = 0;
    sleep(3);   //wait the thread to be ready
    while (4 > input) {
        s = input % 2;
        if (0 == s) {
            printf("main: SIGUSR1\n");
            pthread_kill(thread1, SIGUSR1);
            pthread_kill(thread2, SIGUSR1);
        }
        else if (1 == s) {
            printf("main: SIGUSR2\n");
            pthread_kill(thread1, SIGUSR2);
            pthread_kill(thread2, SIGUSR2);
        }
        input++;
        sleep(1);   //if here do not sleep, some signal will be lost, may because sigal when the thread in the handler.
    }
    printf("cnt1=%d, cnt2=%d\n", hdler_cnt1, hdler_cnt2);

    getchar();
    exit(0);
}
按上面的代碼編譯執行的結果:

$ ./a.out
main: SIGUSR1                                          //main線程向兩個子線程發送SIGUSR1信號
thread1: after sigwait, get sig=10.          //子線程1的sigwait捕捉到結束wait,進入while(1)
thread2: after sigwait, get sig=10.          //子線程2的sigwait捕捉到結束wait,但因爲等到的不是所要的sig所以進入下一次sigwait
main: SIGUSR2                                         //mainthread向兩個線程發送SIGUSR2信號,這時子線程1已經不在等待信號而且沒有設置SIGUSR2的handler。所以子線程1沒有動作
thread2: after sigwait, get sig=12.          //子線程2等到了SIGUSR2再次結束sigwait,並且不再sigwait進入while1
thread2: after while sig.
main: SIGUSR1                                          //之後main線程連續兩個signal分別被子線程1和2的handler不活並且計數。所以兩個cnt都是1.
main: SIGUSR2
cnt1=1, cnt2=1

從上面的結果來看sigwait會先於handler得到signal,而且根據後面的分析似乎沒有什麼問題。

當註釋掉上面程序紅色的sleep再次編譯執行的結果如下:

$ ./a.out
main: SIGUSR1
main: SIGUSR2
thread1: after sigwait, get sig=10.
main: SIGUSR1
main: SIGUSR2
cnt1=1, cnt2=1
thread2: after sigwait, get sig=10.

從結果上看子線程1的處理似乎沒有影響,但是子線程2似乎漏掉了一次SIGUSR2等捕獲,按照上一次運行結果的分析,如果sigwait真的優先於handler,那麼handler處理過程mask掉一個SIGUSR2這樣的解釋好像不太合理。而且多次執行的結果都是一樣,子線程而遺漏一個SIGUSR2。

這裏請教各位,對於這個運行結果有沒有什麼好的解釋。多謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章