天天看點

linux系統程式設計之信号(四):信号的捕捉與sigaction函數

一、核心如何實作信号的捕捉

如果信号的處理動作是使用者自定義函數,在信号遞達時就調用這個函數,這稱為捕捉信号。由于信号處理函數的代碼是在使用者空間的,處理過程比較複雜,舉例如下:

1. 使用者程式注冊了SIGQUIT信号的處理函數sighandler。

2. 目前正在執行main函數,這時發生中斷或異常切換到核心态。

3. 在中斷處理完畢後要傳回使用者态的main函數之前檢查到有信号SIGQUIT遞達。

4. 核心決定傳回使用者态後不是恢複main函數的上下文繼續執行,而是執行sighandler函數,sighandler和main函數使用不同的堆棧空間,它們之間不存在調用和被調用的關系,是兩個獨立的控制流程。

1

2

(By default,  the  signal  handler  is invoked on the normal process stack.  It is possible to arrange that the signal handler

 uses an alternate stack; see sigaltstack(2) for a discussion of how to do this and when it might be useful.)

5. sighandler函數傳回後自動執行特殊的系統調用sigreturn再次進入核心态。

6. 如果沒有新的信号要遞達,這次再傳回使用者态就是恢複main函數的上下文繼續執行了。

linux系統程式設計之信号(四):信号的捕捉與sigaction函數

上圖出自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函數舉個小例子:

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./sigaction 

^Crev sig=2

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

即按下ctrl+c 會一直産生信号而被處理列印recv語句。

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

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

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

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

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./sa_mask 

^\

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

sa_flags 和 sa_sigaction 參數的示例看這裡。

在多線程環境下,編寫信号處理函數需要安全地處理,可以參考這篇文章:

tgkill()發給指定程序中的指定線程;

pthread_kill()由一個線程發給同程序中的另一個線程,實際上是通過封裝tgkill()實作的;

《Linux 多線程應用中如何編寫安全的信号處理函數》

http://www.ibm.com/developerworks/cn/linux/l-cn-signalsec/

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