標籤:
訊號是與一定的進程相聯絡的。也就是說,一個進程可以決定在進程中對哪些訊號進行什 麼樣的處理。例如,一個進程可以忽略某些訊號而只處理其他一些訊號;另外,一個進程還可以選擇如何處理訊號。總之,這些總與特定的進程相聯絡的。因此,首 先要建立其訊號和進程的對應關係,這就是訊號的安裝登記。
Linux 主要有兩個函數實現訊號的安裝登記:signal和sigaction。其中signal在系統調用的基礎上實現,是庫函數。它只有兩個參數,不支援訊號 傳遞資訊,主要是用於前32個非即時訊號的安裝;而sigaction是較新的函數(由兩個系統調用實現:sys_signal以及 sys_rt_sigaction),有三個參數,支援訊號傳遞資訊,主要用來與sigqueue系統調用配合使用。當然,sigaction同樣支援非 即時訊號的安裝,sigaction優於signal主要體現在支援訊號帶有參數。
對 於應用程式自行處理的訊號來說,訊號的生命週期要經過訊號的安裝登記、訊號集操作、訊號的發送和訊號的處理四個階段。訊號的安裝登記指的是在應用程式中, 安裝對此訊號的處理方法。訊號集操作的作用是用於對指定的一個或多個訊號進行訊號屏蔽,此階段對有些應用程式來說並不需要。訊號的發送指的是發送訊號,可 以通過硬體(如在終端上按下Ctrl-C)發送的訊號和軟體(如通過kill函數)發送的訊號。訊號的處理指的是作業系統對接收訊號進程的處理,處理方法 是先檢查訊號集操作函數是否對此訊號進行屏蔽,如果沒有屏蔽,作業系統將按訊號安裝函數中登記註冊的處理函數完成對此進程的處理。
1. signal函數
(1)函數說明
在signal函數中,有兩個形參,分別代表需要處理的訊號編號值和處理訊號函數的指標。它主要是用於前32種非即時訊號的處理,不支援訊號的傳遞資訊。但是由於使用簡單,易於理解,因此在許多場合被程式員使用。
對 於Unix系統來說,使用signal函數時,自訂處理訊號函數執行一次後失效,對該訊號的處理回到預設處理方式。下面以一個例子進行說明,例如一程式 中使用signal(SIGQUIT, my_func)函數調用,其中my_func是自訂函數。應用進程收到SIGQUIT訊號時,會跳轉到自訂處理訊號函數my_func處執行,執行 後訊號註冊函數my_func失效,對SIGQUIT訊號的處理回到作業系統的預設處理方式,當應用進程再次收到SIGQUIT訊號時,會按作業系統預設 的處理方式進行處理(即不再執行my_func處理函數)。而在Linux系統中,signal函數已被改寫,由sigaction函數封裝實現,則不存 在上述問題。
(2)signal函數原型
|
signal(設定訊號處理方式) |
|
所需標頭檔 |
#include |
|
函數說明 |
設定訊號處理方式。signal()會依參數signum指定的訊號編號來設定該訊號的處理函數。當指定的訊號到達時就會跳轉到參數handler指定的函數執行 |
|
函數原型 |
void (*signal(int signum,void(* handler)(int)))(int) |
|
函數傳入值
|
signum |
指定訊號編號 |
|
handle |
SIG_IGN:忽略參數signum指定的訊號 |
|
SIG_DFL:將參數signum指定的訊號重設為核心預設的訊號處理方式,即採用系統預設處理訊號 |
|
自訂訊號函數處理指標 |
|
函數傳回值 |
成功 |
返回先前的訊號處理函數指標 |
|
出錯 |
SIG_ERR(-1) |
|
附加說明 |
在Unix環境中,在訊號發生跳轉到自定的handler處理函數執行後,系統會自動將此處理函數換回原來系統預設的處理方式,如果要改變此情形請改用sigaction函數。在Linux環境中不存在此問題 |
signal函數原型比較複雜,如果使用下面的typedef,則可使其簡化。
typedef void sign(int);
sign *signal(int, handler *);
可見,該函數原型首先整體指向一個無傳回值帶一個整型參數的函數指標,也就是訊號的原始配置函數。接著該原型又帶有兩個參數,其中的第二個參數可以是使用者自訂的訊號處理函數的函數指標。對這個函數格式可以不理解,但需要學會模仿使用。
(3) signal函數使用執行個體
該樣本表明了如何使用signal函數進行安裝登記訊號處理函數。當該訊號發生時,登記的訊號處理函數會捕捉到相應的訊號,並做出給定的處理。這裡,my_func就是訊號處理的函數指標。讀者還可以將my_func改為SIG_IGN或SIG_DFL查看運行結果。
signal.c原始碼如下:
#include
#include
#include
/*自訂訊號處理函數*/
void my_func(int sign_no)
{
if(sign_no==SIGINT)
printf("I have get SIGINT\n");
else if(sign_no==SIGQUIT)
printf("I have get SIGQUIT\n");
}
int main()
{
printf("Waiting for signal SIGINT or SIGQUIT \n ");
/*發出相應的訊號,並跳轉到訊號處理函數處*/
signal(SIGINT, my_func);
signal(SIGQUIT, my_func);
pause();
pause();
exit(0);
}
編譯 gcc signal.c –o signal。
執行 ./signal,執行結果如下:
Waiting for signal SIGINT or SIGQUIT
I have get SIGINT /*按下Ctrl+C,作業系統就會向進程發送SIGINT訊號*/
I have get SIGQUIT /*按下Ctrl-\(退出),作業系統就會向進程發送SIGQUIT訊號*/
2. sigaction函數
(1)sigaction函數原型
sigaction函數用來查詢和設定訊號處理方式,它是用來替換早期的signal函數。sigaction函數原型及說明如下:
|
所需標頭檔 |
#include |
|
函數說明 |
sigaction()會依參數signum指定的訊號編號來設定該訊號的處理函數 |
|
函數原型 |
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact) |
|
函數傳入值
|
signum |
可以指定SIGKILL和SIGSTOP以外的所有訊號 |
|
act |
參數結構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:此參數和signal()的參數handler相同,此參數主要用來對訊號舊的安裝函數signal()處理形式的支援
② sa_sigaction:新的訊號安裝機制,處理函數被調用的時候,不但可以得到訊號編號,而且可以獲悉被調用的原因以及產生問題的內容相關的相關資訊。
③ sa_mask:用來設定在處理該訊號時暫時將sa_mask指定的訊號擱置
④ sa_restorer: 此參數沒有使用
⑤ sa_flags:用來設定訊號處理的其他相關操作,下列的數值可用。可用OR 運算(|)組合
? A_NOCLDSTOP:如果參數signum為SIGCHLD,則當子進程暫停時並不會通知父進程
? SA_ONESHOT/SA_RESETHAND:當調用新的訊號處理函數前,將此訊號處理方式改為系統預設的方式
? SA_RESTART:被訊號中斷的系統調用會自行重啟
? SA_NOMASK/SA_NODEFER:在處理此訊號未結束前不理會此訊號的再次到來
? SA_SIGINFO:訊號處理函數是帶有三個參數的sa_sigaction |
|
oldact |
如果參數oldact不是NULL指標,則原來的訊號處理方式會由此結構sigaction返回 |
|
函數傳回值 |
成功:0 |
|
出錯:-1,錯誤原因存於error中 |
|
附加說明 |
訊號處理安裝的新舊兩種機制:
① 使用舊的處理機制:struct sigaction act; act.sa_handler=handler_old;
② 使用新的處理機制:struct sigaction act; act.sa_sigaction=handler_new;
並設定sa_flags的SA_SIGINFO位 |
|
錯誤碼 |
EINVAL:參數signum不合法,或是企圖攔截SIGKILL/SIGSTOP訊號
EFAULT:參數act,oldact指標地址無法存取
EINTR:此調用被中斷 |
(2)sigaction函數使用執行個體
sigaction.c原始碼如下:
#include
#include
#include
#include
#include
void new_op(int, siginfo_t *, void *);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=new_op;
if(sigaction(sig,&act,NULL) < 0)
{
perror("install sigal error");
return -1 ;
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
return 0 ;
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d\n", signum);
sleep(5);
}
編譯 gcc sigaction.c -o sigaction。
執行 ./sigaction 2,執行結果如下:
wait for the signal
receive signal 2 /*按下Ctrl+C */
退出 /*按下Ctrl-\ */
3. 訊號集操作函數
由 於有時需要把多個訊號當作一個集合進行處理,這樣訊號集就產生了,訊號集用來描述一類訊號的集合,Linux所支援的訊號可以全部或部分的出現在訊號集 中。訊號集操作函數最常用的地方就是用於訊號屏蔽。比如有時候希望某個進程正確執行,而不想進程受到一些訊號的影響,此時就需要用到訊號集操作函數完成對 這些訊號的屏蔽。
訊號集操作函數按照功能和使用順序分為三類,分別為建立訊號集合函式,設定訊號屏蔽位函數和查詢被擱置(未決)的訊號函數。建立訊號集合函式只是建立一個訊號 的集合,設定訊號屏蔽位函數對指定訊號集中的訊號進行屏蔽,查詢被擱置的訊號函數是用來查詢當前“未決”的訊號集。訊號集合函式組並不能完成訊號的安裝登記 工作,訊號的安裝登記需要通過sigaction函數或signal函數來完成。
查 詢被擱置的訊號是訊號處理的後續步驟,但不是必需的。由於有時進程在某時間段內要求阻塞一些訊號,程式完成特定工作後解除對該訊號阻塞,這個時間段內被阻 塞的訊號稱為“未決”訊號。這些訊號已經產生,但沒有被處理,sigpending函數用來檢測進程的這些“未決”訊號,並進一步決定對它們做何種處理 (包括不處理)。
(1) 建立訊號集合函式
建立訊號集合函式有如下5個:
① sigemptyset:初始化訊號集合為空白。
② sigfillset:把所有訊號加入到集合中,訊號集中將包含Linux支援的64種訊號。
③ sigaddset:將指定訊號加入到訊號集合中去。
④ sigdelset:將指定訊號從訊號集中刪去。
⑤ sigismember:查詢指定訊號是否在訊號集合之中。
|
建立訊號集合函數原型 |
|
所需標頭檔 |
#include |
|
函數原型 |
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(sigset_t *set,int signum) |
|
函數傳入值 |
set:訊號集 |
|
signum:指定訊號值 |
|
函數傳回值 |
成功:0(sigismember函數例外,成功返回1,失敗返回 0) |
|
出錯:-1,錯誤原因存於error中 |
(2) 設定訊號屏蔽位函數
每個進程都有一個用來描述哪些訊號遞送到進程時將被阻塞的訊號集,該訊號集中的所有訊號在遞送到進程後都將被阻塞。調用函數sigprocmask可設定訊號集內的訊號阻塞或不阻塞。其函數原型及說明如下:
|
所需標頭檔 |
#include |
|
函數原型 |
int sigprocmask(int how,const sigset_t *set,sigset_t *oset) |
|
函數傳入值 |
how(決定函數的操作方式) |
SIG_BLOCK:增加一個訊號集合到當前進程的阻塞集合之中 |
|
SIG_UNBLOCK:從當前的阻塞集合之中刪除一個訊號集合 |
|
SIG_SETMASK:將當前的訊號集合設定為訊號阻塞集合 |
|
set:指定訊號集 |
|
oset:訊號屏蔽字 |
|
函數傳回值 |
成功:0 |
|
出錯:-1,錯誤原因存於error中 |
(3) 查詢被擱置(未決)訊號函數
sigpending函數用來查詢“未決”訊號。其函數原型及說明如下:
|
所需標頭檔 |
#include |
|
函數說明 |
將被擱置的訊號集合由參數set指標返回 |
|
函數原型 |
int sigpending(sigset_t *set) |
|
函數傳入值 |
set:要活動訊號集 |
|
函數傳回值 |
成功:0 |
|
出錯:-1,錯誤原因存於error中 |
|
錯誤碼 |
EFAULT:參數set指標地址無法存取
EINTR:此調用被中斷 |
(4) 對訊號集操作函數的使用方法
對訊號集操作函數的使用方法和順序如下:
① 使用signal或sigaction函數安裝和登記訊號的處理。
② 使用sigemptyset等定義訊號集合函式完成對訊號集的定義。
③ 使用sigprocmask函數設定訊號屏蔽位。
④ 使用sigpending函數檢測未決訊號,非必需步驟。
(5) 訊號集操作函數使用執行個體
該 執行個體首先使用sigaction函數對SIGINT訊號進行安裝登記,安裝登記使用了新舊兩種機制,其中#if 0進行注釋掉的部分為訊號安裝的新機制。接著程式把SIGQUIT、SIGINT兩個訊號加入訊號集,並把該訊號集設為阻塞狀態。程式開始睡眠30秒,此 時使用者按下Ctrl+C,程式將測試到此未決訊號(SIGINT);隨後程式再睡眠30秒後對SIGINT訊號解除阻塞,此時將處理SIGINT登記的信 號函數my_func。最後可以用SIGQUIT(Ctrl+\)訊號結束進程執行。
sigset.c原始碼如下:
#include
#include
#include
#include
#include
/*自訂的訊號處理函數*/
#if 0
void my_funcnew(int signum, siginfo_t *info,void *myact)
#endif
void my_func(int signum)
{
printf("If you want to quit,please try SIGQUIT\n");
}
int main()
{
sigset_t set, pendset;
struct sigaction action1,action2;
/*設定訊號處理方式*/
sigemptyset(&action1.sa_mask);
#if 0 /*訊號新的安裝機制*/
action1.sa_flags= SA_SIGINFO;
action1.sa_sigaction=my_funcnew;
#endif
/*訊號舊的安裝機制*/
action1.sa_flags= 0;
action1.sa_handler=my_func;
sigaction(SIGINT,&action1,NULL);
/*初始化訊號集為空白*/
if(sigemptyset(&set)<0)
{
perror("sigemptyset");
return -1 ;
}
/*將相應的訊號加入訊號集*/
if(sigaddset(&set,SIGQUIT)<0)
{
perror("sigaddset");
return -1 ;
}
if(sigaddset(&set,SIGINT)<0)
{
perror("sigaddset");
return -1 ;
}
/*設定訊號集屏蔽字*/
if(sigprocmask(SIG_BLOCK,&set,NULL)<0)
{
perror("sigprocmask");
return -1 ;
}
else
{
printf("blocked\n");
}
/*測試訊號是否加入該訊號集*/
if(sigismember(&set,SIGINT)){
printf("SIGINT in set\n") ;
}
sleep( 30 ) ;
/*測試未決訊號*/
if ( sigpending(&pendset) <0 )
{
perror("get pending mask error");
}
if(sigismember(&pendset, SIGINT) )
{
printf("signal SIGINT is pending\n");
}
sleep(30) ;
if(sigprocmask(SIG_UNBLOCK,&set,NULL)<0)
{
perror("sigprocmask");
return -1 ;
}
else
printf("unblock\n");
while(1)
{
sleep(1) ;
}
return 0 ;
}
編譯 gcc sigset.c -o sigset。
執行 ./sigset,執行結果如下:
blocked
SIGINT in set /*按下Ctrl+C */
signal SIGINT is pending
If you want to quit,please try SIGQUIT /*按下Ctrl+C */
退出
摘錄自《深入淺出Linux工具與編程》
signal函數、sigaction函數及訊號集(sigemptyset,sigaddset)操作函數