linux系統編程之訊號(四):訊號的捕捉與sigaction函數

來源:互聯網
上載者:User

一、核心如何?訊號的捕捉

如果訊號的處理動作是使用者自訂函數,在訊號遞達時就調用這個函數,這稱為捕捉訊號。由於訊號處理函數的代碼是在使用者空間的,處理過程比較複雜,舉例如下:
1. 使用者程式註冊了SIGQUIT訊號的處理函數sighandler。
2. 當前正在執行main函數,這時發生中斷或異常切換到核心態。
3. 在中斷處理完畢後要返回使用者態的main函數之前檢查到有訊號SIGQUIT遞達。
4. 核心決定返回使用者態後不是恢複main函數的上下文繼續執行,而是執行sighandler函數,sighandler和main函數使用不同的堆棧空間,它們之間不存在調用和被調用的關係,是兩個獨立的控制流程程。
5. sighandler函數返回後自動執行特殊的系統調用sigreturn再次進入核心態。
6. 如果沒有新的訊號要遞達,這次再返回使用者態就是恢複main函數的上下文繼續執行了。

出自ULK。


二、sigaction函數

#include <signal.h>

int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);


sigaction函數可以讀取和修改與指定訊號相關聯的處理動作。調用成功則返回0,出錯則返回-1。signo是指定訊號的編號。若act指標非空,則根據act修改該訊號的處理動作。若oact指標非空,則通過oact傳出該訊號原來的處理動作。act和oact指向sigaction結構體:  struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };


將sa_handler賦值為常數SIG_IGN傳給sigaction表示忽略訊號,賦值為常數SIG_DFL表示執行系統預設動作,賦值為一個函數指標表示用自訂函數捕捉訊號,或者說向核心註冊了一個訊號處理函數,該函數傳回值為void,可以帶一個int參數,通過參數可以得知當前訊號的編號,這樣就可以用同一個函數處理多種訊號。顯然,這也是一個回呼函數,不是被main函數調用,而是被系統所調用。
當某個訊號的處理函數被調用時,核心自動將當前訊號加入進程的訊號屏蔽字,當訊號處理函數返回時自動回復原來的訊號屏蔽字,這樣就保證了在處理某個訊號時,如果這種訊號再次產生,那麼它會被阻塞到當前處理結束為止。如果在調用訊號處理函數時,除了當前訊號被自動屏蔽之外,還希望自動屏蔽另外一些訊號,則用sa_mask欄位說明這些需要額外屏蔽的訊號,當訊號處理函數返回時自動回復原來的訊號屏蔽字。


需要注意的是sa_restorer 參數已經廢棄不用,sa_handler主要用於不可靠訊號(即時訊號當然也可以,只是不能帶資訊),sa_sigaction用於即時訊號可以帶資訊(siginfo_t),兩者不能同時出現。sa_flags有幾個選項,比較重要的有兩個:SA_NODEFER 和 SA_SIGINFO,當SA_NODEFER設定時在訊號處理函數執行期間不會屏蔽當前訊號;當SA_SIGINFO設定時與sa_sigaction
搭配出現,sa_sigaction函數的第一個參數與sa_handler一樣表示當前訊號的編號,第二個參數是一個siginfo_t 結構體,第三個參數一般不用。當使用sa_handler時sa_flags設定為0即可。


 siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
               sigval_t si_value;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               long     si_band;     /* Band event (was int in
                                        glibc 2.3.2 and earlier) */
               int      si_fd;       /* File descriptor */
               short    si_addr_lsb; /* Least significant bit of address
                                        (since kernel 2.6.32) */
           }


需要注意的是並不是所有成員都在所有訊號中存在定義,有些成員是共用體,讀取的時候需要讀取對某個訊號來說恰當的有定義的部分。


下面用sigaction函數舉個小例子:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);

int main(int argc, char *argv[])
{
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    if (sigaction(SIGINT, &act, NULL) < 0)
        ERR_EXIT("sigaction error");

    for (; ;)
        pause();

    return 0;

}

void handler(int sig)
{
    printf("rev sig=%d\n", sig);
}

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./sigaction 
^Crev sig=2
^Crev sig=2
^Crev sig=2

...........................

即按下ctrl+c 會一直產生訊號而被處理列印recv語句。


其實我們在前面文章說過的signal 函數是調用sigaction 實現的,而sigaction函數底層是調用 do_sigaction() 函數實現的。可以自己實現一個my_signal 函數,如下:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);
/* 系統調用signal()實際上調用了sigaction() */
__sighandler_t my_signal(int sig, __sighandler_t handler);

int main(int argc, char *argv[])
{
    my_signal(SIGINT, handler);

    for (; ;)
        pause();

    return 0;

}

__sighandler_t my_signal(int sig, __sighandler_t handler)
{
    struct sigaction act;
    struct sigaction oldact;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    if (sigaction(sig, &act, &oldact) < 0)
        return SIG_ERR;

    return oldact.sa_handler; // 返回先前的處理函數指標
}

void handler(int sig)
{
    printf("rev sig=%d\n", sig);
}

輸出測試的一樣的,需要注意的是 signal函數成功返回先前的handler,失敗返回SIG_ERR。而sigaction 是通過oact 參數返回先前的handler,成功返回0,失敗返回-1。


下面再舉個小例子說明sa_mask 的作用:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void handler(int sig);

int main(int argc, char *argv[])
{
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, SIGQUIT); // 在訊號處理函數執行期間屏蔽SIGQUIT訊號,完畢後會抵達
    /* 注意sigprocmask中屏蔽的訊號是一直不能抵達的,除非解除了阻塞*/
    act.sa_flags = 0;

    if (sigaction(SIGINT, &act, NULL) < 0)
        ERR_EXIT("sigaction error");

    for (; ;)
        pause();

    return 0;

}

void handler(int sig)
{
    printf("rev sig=%d\n", sig);
    sleep(5);
}

先按下ctrl+c ,然後馬上ctrl+\,程式是不會馬上終止的,即等到handler處理完畢SIGQUIT訊號才會抵達。

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./sa_mask 
^Crev sig=2
^\

5s過後接著才輸出Quit (core dumped),即在訊號處理函數執行期間sa_mask集合中的訊號被阻塞直到運行完畢。


sa_flags 和 sa_sigaction 參數的樣本看這裡。

參考:《APUE》、《linux c 編程一站式學習》

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.