歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

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

一、內核如何實現信號的捕捉

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

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

/*************************************************************************
> File Name: process_.c
> Author: Simba
> Mail: [email protected]
> 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 函數,如下:

/*************************************************************************
> File Name: process_.c
> Author: Simba
> Mail: [email protected]
> 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 的作用:

/*************************************************************************
> File Name: process_.c
> Author: Simba
> Mail: [email protected]
> 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 參數的示例看這裡:http://blog.csdn.net/simba888888/article/details/8947652

Copyright © Linux教程網 All Rights Reserved