轉自:http://blog.sina.com.cn/s/blog_636a55070101vs2d.html
轉自:http://blog.csdn.net/tiany524/article/details/17048069
首先感謝上述兩位博主的詳細講解。
雖然內容有點長,但是分析的很全面,各種實例應用基本都考慮到了。
本文將從以下幾個方面來闡述信號:
(1)信號的基本知識
(2)信號生命周期與處理過程分析
(3) 基本的信號處理函數
(4) 保護臨界區不被中斷
(5) 信號的繼承與執行
(6)實時信號中鎖的研究
第一部分: 信號的基本知識
1.信號本質:
信號的本質是軟件層次上對中斷的一種模擬(軟中斷)。它是一種異步通信的處理機制,事實上,進程並不知道信號何時到來。
2.信號來源
(1)程序錯誤,如非法訪問內存
(2)外部信號,如按下了CTRL+C
(3)通過kill或sigqueue向另外一個進程發送信號
3.信號種類
信號分為可靠信號與不可靠信號,可靠信號又稱為實時信號,非可靠信號又稱為非實時信號。
信號代碼從1到32是不可靠信號,不可靠信號主要有以下問題:
(1)每次信號處理完之後,就會恢復成默認處理,這可能是調用者不希望看到的(早期的signal函數,linux2.6.35.6內核經驗證已經不再恢復默認動作)。
(2)存在信號丟失的問題(進程收到的信號不作排隊處理,相同的信號多次到來會合並為一個)。
現在的Linux對信號機制進行了改進,因此,不可靠信號主要是指信號丟失。
信號代碼從SIGRTMIN到SIGRTMAX之間的信號是可靠信號。可靠信號不存在丟失,由sigqueue發送,可靠信號支持排隊。
可靠信號注冊機制:
內核每收到一個可靠信號都會去注冊這個信號,在信號的未決信號鏈中分配sigqueue結構,因此,不會存在信號丟失的問題。
不可靠信號的注冊機制:
而對於不可靠的信號,如果內核已經注冊了這個信號,那麼便不會再去注冊,對於進程來說,便不會知道本次信號的發生。
可靠信號與不可靠信號與發送函數沒有關系,取決於信號代碼,前面的32種信號就是不可靠信號,而後面的32種信號就是可靠信號。
4.信號響應的方式
(1)采用系統默認處理SIG_DFL,執行缺省操作
(2)捕捉信號處理,即用戶自定義的信號處理函數來處理
(3)忽略信號SIG_IGN ,但有兩種信號不能被忽略SIGKILL,SIGSTOP
第二部分: 信號的生命周期與處理過程分析
1. 信號的生命周期
信號產生->信號注冊->信號在進程中注銷->信號處理函數執行完畢
(1)信號的產生是指觸發信號的事件的發生
(2)信號注冊
指的是在目標進程中注冊,該目標進程中有未決信號的信息:
struct sigpending pending:
struct sigpending{
struct sigqueue *head, **tail;
sigset_t signal;
};
struct sigqueue{
struct sigqueue *next;
siginfo_t info;
}
其中 sigqueue結構組成的鏈稱之為未決信號鏈,sigset_t稱之為未決信號集。
*head,**tail分別指向未決信號鏈的頭部與尾部。
siginfo_t info是信號所攜帶的信息。
信號注冊的過程就是將信號值加入到未決信號集siginfo_t中,將信號所攜帶的信息加入到未決信號鏈的某一個sigqueue中去。
因此,對於可靠的信號,可能存在多個未決信號的sigqueue結構,對於每次信號到來都會注冊。而不可靠信號只注冊一次,只有一個sigqueue結構。
只要信號在進程的未決信號集中,表明進程已經知道這些信號了,還沒來得及處理,或者是這些信號被阻塞。
(3)信號在目標進程中注銷
在進程的執行過程中,每次從系統調用或中斷返回用戶空間的時候,都會檢查是否有信號沒有被處理。如果這些信號沒有被阻塞,那麼就調用相應的信號處理函數來處理這些信號。則調用信號處理函數之前,進程會把信號在未決信號鏈中的sigqueue結構卸掉。是否從未決信號集中把信號刪除掉,對於實時信號與非實時信號是不相同的。
非實時信號:由於非實時信號在未決信號鏈中只有一個sigqueue結構,因此將它刪除的同時將信號從未決信號集中刪除。
實時信號:由於實時信號在未決信號鏈中可能有多個sigqueue結構,如果只有一個,也將信號從未決信號集中刪除掉。如果有多個那麼不從未決信號集中刪除信號,注銷完畢。
(4)信號處理函數執行完畢
執行處理函數,本次信號在進程中響應完畢。
在第4步,只簡單的描述了信號處理函數執行完畢,就完成了本次信號的響應,但這個信號處理函數空間是怎麼處理的呢? 內核棧與用戶棧是怎麼工作的呢? 這就涉及到了信號處理函數的過程。
信號處理函數的過程:
(1)注冊信號處理函數
信號的處理是由內核來代理的,首先程序通過sigal或sigaction函數為每個信號注冊處理函數,而內核中維護一張信號向量表,對應信號處理機制。這樣,在信號在進程中注銷完畢之後,會調用相應的處理函數進行處理。
(2)信號的檢測與響應時機
在系統調用或中斷返回用戶態的前夕,內核會檢查未決信號集,進行相應的信號處理。
(3)處理過程:
程序運行在用戶態時->進程由於系統調用或中斷進入內核->轉向用戶態執行信號處理函數->信號處理函數完畢後進入內核->返回用戶態繼續執行程序
首先程序執行在用戶態,在進程陷入內核並從內核返回的前夕,會去檢查有沒有信號沒有被處理,如果有且沒有被阻塞就會調用相應的信號處理程序去處理。首先,內核在用戶棧上創建一個層,該層中將返回地址設置成信號處理函數的地址,這樣,從內核返回用戶態時,就會執行這個信號處理函數。當信號處理函數執行完,會再次進入內核,主要是檢測有沒有信號沒有處理,以及恢復原先程序中斷執行點,恢復內核棧等工作,這樣,當從內核返回後便返回到原先程序執行的地方了。
信號處理函數的過程大概是這樣了。
具體的可參考http://www.spongeliu.com/165.html Linux內核信號處理機制介紹
http://blog.csdn.net/tiany524/article/details/17048069
參考圖:
第三部分: 基本的信號處理函數
首先看一個兩個概念: 信號未決與信號阻塞
信號未決: 指的是信號的產生到信號處理之前所處的一種狀態。確切的說,是信號的產生到信號注銷之間的狀態。
信號阻塞: 指的是阻塞信號被處理,是一種信號處理方式。
1. 信號操作
信號操作最常用的方法是信號的屏蔽,信號屏蔽主要用到以下幾個函數:
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
int sigismemeber(sigset_t* set,int signo);
int sigprocmask(int how,const sigset_t*set,sigset_t *oset);
信號集,信號掩碼,未決集
信號集: 所有的信號阻塞函數都使用一個稱之為信號集的結構表明其所受到的影響。
信號掩碼:當前正在被阻塞的信號集。
未決集: 進程在收到信號時到信號在未被處理之前信號所處的集合稱為未決集。
可以看出,這三個概念沒有必然的聯系,信號集指的是一個泛泛的概念,而未決集與信號掩碼指的是具體的信號狀態。
對於信號集的初始化有兩種方法: 一種是用sigemptyset使信號集中不包含任何信號,然後用sigaddset把信號加入到信號集中去。
另一種是用sigfillset讓信號集中包含所有信號,然後用sigdelset刪除信號來初始化。
sigemptyset()函數初始化信號集set並將set設置為空。
sigfillset()函數初始化信號集,但將信號集set設置為所有信號的集合。
sigaddset()將信號signo加入到信號集中去。
sigdelset()從信號集中刪除signo信號。
sigprocmask()將指定的信號集合加入到進程的信號阻塞集合中去。如果提供了oset,那麼當前的信號阻塞集合將會保存到oset集全中去。
參數how決定了操作的方式:
SIG_BLOCK 增加一個信號集合到當前進程的阻塞集合中去
SIG_UNBLOCK 從當前的阻塞集合中刪除一個信號集合
SIG_SETMASK 將當前的信號集合設置為信號阻塞集合
下面看一個例子:
#include
#include
#include
#include
#include
int main(){
sigset_t initset;
int i;
sigemptyset(&initset);//初始化信號集合為空集合
sigaddset(&initset,SIGINT);//將SIGINT信號加入到此集合中去
while(1){
sigprocmask(SIG_BLOCK,&initset,NULL);//將信號集合加入到進程的阻塞集合中去
fprintf(stdout,"SIGINT singal blocked/n");
for(i=0;i<10;i++){
sleep(1);//每1秒輸出
fprintf(stdout,"block %d/n",i);
}
//在這時按一下Ctrl+C不會終止
sigprocmask(SIG_UNBLOCK,&initset,NULL);//從進程的阻塞集合中去刪除信號集合
fprintf(stdout,"SIGINT SINGAL unblokced/n");
for(i=0;i<10;i++){
sleep(1);
fprintf(stdout,"unblock %d/n",i);
}
}
exit(0);
}
執行結果:
SIGINT singal blocked
block 0
block 1
block 2
block 3
block 4
block 5
block 6
block 7
block 8
block 9
在執行到block 3時按下了CTRL+C並不會終止,直到執行到block9後將集合從阻塞集合中移除。
[root@localhost C]# ./s1
SIGINT singal blocked
block 0
block 1
block 2
block 3
block 4
block 5
block 6
block 7
block 8
block 9
SIGINT SINGAL unblokced
unblock 0
unblock 1
由於此時已經解除了阻塞,在unblock1後按下CTRL+C則立即終止。
2. 信號處理函數
#include
int sigaction(
int signo,
const struct sigaction *act,
struct sigaction *oldact
);
這個函數主要是用於改變或檢測信號的行為。
第一個參數是變更signo指定的信號,它可以指向任何值,SIGKILL,SIGSTOP除外
第二個參數,第三個參數是對信號進行細粒度的控制。
如果*act不為空,*oldact不為空,那麼oldact將會存儲信號以前的行為。如果act為空,*oldact不為空,那麼oldact將會存儲信號現在的行為。
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_DFL,SIG_IGN.分別代表默認操作與內核將忽略進程的信號。這個函數只傳遞一個參數那就是信號代碼。
當SA_SIGINFO被設定在sa_flags中,那麼則會使用sa_sigaction來指示信號處理函數,而非sa_handler.
sa_mask設置了掩碼集,在程序執行期間會阻擋掩碼集中的信號。
sa_flags設置了一些標志, SA_RESETHAND當該函數處理完成之後,設定為為系統默認的處理模式。SA_NODEFER 在處理函數中,如果再次到達此信號時,將不會阻塞。默認情況下,同一信號兩次到達時,如果此時處於信號處理程序中,那麼此信號將會阻塞。
SA_SIGINFO表示用sa_sigaction指示的函數。
sa_restorer已經被廢棄。
sa_sigaction所指向的函數原型:
void my_handler(int signo,siginfo_t *si,void *ucontext);
第一個參數: 信號編號
第二個參數:指向一個siginfo_t結構。
第三個參數是一個ucontext_t結構。
其中siginfo_t結構體中包含了大量的信號攜帶信息,可以看出,這個函數比sa_handler要強大,因為前者只能傳遞一個信號代碼,而後者可以傳遞siginfo_t信息。
typedef struct siginfo_t{
int si_signo;//信號編號
int si_errno;//如果為非零值則錯誤代碼與之關聯
int si_code;//說明進程如何接收信號以及從何處收到
pid_t si_pid;//適用於SIGCHLD,代表被終止進程的PID
pid_t si_uid;//適用於SIGCHLD,代表被終止進程所擁有進程的UID
int si_status;//適用於SIGCHLD,代表被終止進程的狀態
clock_t si_utime;//適用於SIGCHLD,代表被終止進程所消耗的用戶時間
clock_t si_stime;//適用於SIGCHLD,代表被終止進程所消耗系統的時間
sigval_t si_value;
int si_int;
void * si_ptr;
void* si_addr;
int si_band;
int si_fd;
};
sigqueue(pid_t pid,int signo,const union sigval value)
union sigval{int sival_int, void*sival_ptr};
sigqueue函數類似於kill,也是一個進程向另外一個進程發送信號的。
但它比kill函數強大。
第一個參數指定目標進程的pid.
第二個參數是一個信號代碼。
第三個參數是一個共用體,每次只能使用一個,用來進程發送信號傳遞的數據。
或者傳遞整形數據,或者是傳遞指針。
發送的數據被sa_sigaction所指示的函數的siginfo_t結構體中的si_ptr或者是si_int所接收。
sigpending的用法
sigpending(sigset_t set);
這個函數的作用是返回未決的信號到信號集set中。即未決信號集,未決信號集不僅包括被阻塞的信號,也可能包括已經到達但沒有被處理的信號。
示例1: sigaction函數的用法
#include
#include
void signal_set1(int);//信號處理函數,只傳遞一個參數信號代碼
void signal_set(struct sigaction *act)
{
switch(act->sa_flags){
case (int)SIG_DFL:
printf("using default hander/n");
break;
case (int)SIG_IGN:
printf("ignore the signal/n");
break;
default:
printf("%0x/n",act->sa_handler);
}
}
void signal_set1(int x){//信號處理函數
printf("xxxxx/n");
while(1){
}
}
int main(int argc,char** argv)
{
int i;
struct sigaction act,oldact;
act.sa_handler = signal_set1;
act.sa_flags = SA_RESETHAND;
//SA_RESETHANDD 在處理完信號之後,將信號恢復成默認處理
//SA_NODEFER在信號處理程序執行期間仍然可以接收信號
sigaction (SIGINT,&act,&oldact) ;//改變信號的處理模式
for (i=1; i<12; i++)
{
printf("signal %d handler is : ",i);
sigaction (i,NULL,&oldact) ;
signal_set(&oldact);//如果act為NULL,oldact會存儲信號當前的行為
//act不為空,oldact不為空,則oldact會存儲信號以前的處理模式
}
while(1){
//等待信號的到來
}
return 0;
}
運行結果:
[root@localhost C]# ./s2
signal 1 handler is : using default hander
signal 2 handler is : 8048437
signal 3 handler is : using default hander
signal 4 handler is : using default hander
signal 5 handler is : using default hander
signal 6 handler is : using default hander
signal 7 handler is : using default hander
signal 8 handler is : using default hander
signal 9 handler is : using default hander
signal 10 handler is : using default hander
signal 11 handler is : using default hander
xxxxx
解釋:
sigaction(i,NULL,&oldact);
signal_set(&oldact);
由於act為NULL,那麼oldact保存的是當前信號的行為,當前的第二個信號的行為是執行自定義的處理程序。
當按下CTRL+C時會執行信號處理程序,輸出xxxxxx,再按一下CTRL+C會停止,是由於SA_RESETHAND恢復成默認的處理模式,即終止程序。
如果沒有設置SA_NODEFER,那麼在處理函數執行過程中按一下CTRL+C將會被阻塞,那麼程序會停在那裡。
示例2: sigqueue向本進程發送數據的信號
#include
#include
#include
#include
#include
void myhandler(int signo,siginfo_t *si,void *ucontext);
int main(){
union sigval val;//定義一個攜帶數據的共用體
struct sigaction oldact,act;
act.sa_sigaction=myhandler;
act.sa_flags=SA_SIGINFO;//表示使用sa_sigaction指示的函數,處理完恢復默認,不阻塞處理過程中到達下在被處理的信號
//注冊信號處理函數
sigaction(SIGUSR1,&act,&oldact);
char data[100];
int num=0;
while(num<10){
sleep(2);
printf("等待SIGUSR1信號的到來/n");
sprintf(data,"%d",num++);
val.sival_ptr=data;
sigqueue(getpid(),SIGUSR1,val);//向本進程發送一個信號
}
}
void myhandler(int signo,siginfo_t *si,void *ucontext){
printf("已經收到SIGUSR1信號/n");
printf("%s/n",(char*)(si->si_ptr));
}
程序執行的結果是:
等待SIGUSR1信號的到來
已經收到SIGUSR1信號
0
等待SIGUSR1信號的到來
已經收到SIGUSR1信號
1
等待SIGUSR1信號的到來
已經收到SIGUSR1信號
2
等待SIGUSR1信號的到來
已經收到SIGUSR1信號
3
等待SIGUSR1信號的到來
已經收到SIGUSR1信號
4
等待SIGUSR1信號的到來
已經收到SIGUSR1信號
5
等待SIGUSR1信號的到來
已經收到SIGUSR1信號
6
等待SIGUSR1信號的到來
已經收到SIGUSR1信號
7
等待SIGUSR1信號的到來
已經收到SIGUSR1信號
8
等待SIGUSR1信號的到來
已經收到SIGUSR1信號
9
解釋: 本程序用sigqueue不停的向自身發送信號,並且攜帶數據,數據被放到處理函數的第二個參數siginfo_t結構體中的si_ptr指針,當num<10時不再發。
一般而言,sigqueue與sigaction配合使用,而kill與signal配合使用。
示例3: 一個進程向另外一個進程發送信號,並攜帶信息
發送端:
#include
#include
#include
#include
#include
int main(){
union sigval value;
value.sival_int=10;
if(sigqueue(4403,SIGUSR1,value)==-1){//4403是目標進程pid
perror("信號發送失敗/n");
}
sleep(2);
}
接收端:
#include
#include
#include
#include
#include
void myhandler(int signo,siginfo_t*si,void *ucontext);
int main(){
struct sigaction oldact,act;
act.sa_sigaction=myhandler;
act.sa_flags=SA_SIGINFO|SA_NODEFER;
//表示執行後恢復,用sa_sigaction指示的處理函數,在執行期間仍然可以接收信號
sigaction(SIGUSR1,&act,&oldact);
while(1){
sleep(2);
printf("等待信號的到來/n");
}
}
void myhandler(int signo,siginfo_t *si,void *ucontext){
printf("the value is %d/n",si->si_int);
}
示例4: sigpending的用法
sigpending(sigset_t *set)將未決信號放到指定的set信號集中去,未決信號包括被阻塞的信號和信號到達時但還沒來得及處理的信號
#include
#include
#include
#include
#include
void myhandler(int signo,siginfo_t *si,void *ucontext);
int main(){
struct sigaction oldact,act;
sigset_t oldmask,newmask,pendingmask;
act.sa_sigaction=myhandler;
act.sa_flags=SA_SIGINFO;
sigemptyset(&act.sa_mask);//首先將阻塞集合設置為空,即不阻塞任何信號
//注冊信號處理函數
sigaction(SIGRTMIN+10,&act,&oldact);
//開始阻塞
sigemptyset(&newmask);
sigaddset(&newmask,SIGRTMIN+10);
printf("SIGRTMIN+10 blocked/n");
sigprocmask(SIG_BLOCK,&newmask,&oldmask);
sleep(20);//為了發出信號
printf("now begin to get pending mask/n");
if(sigpending(&pendingmask)<0){
perror("pendingmask error");
}
if(sigismember(&pendingmask,SIGRTMIN+10)){
printf("SIGRTMIN+10 is in the pending mask/n");
}
sigprocmask(SIG_UNBLOCK,&newmask,&oldmask);
printf("SIGRTMIN+10 unblocked/n");
}
//信號處理函數
void myhandler(int signo,siginfo_t *si,void *ucontext){
printf("receive signal %d/n",si->si_signo);
}
程序執行:
在另一個shell發送信號:
kill -44 4579
SIGRTMIN+10 blocked
now begin to get pending mask
SIGRTMIN+10 is in the pending mask
receive signal 44
SIGRTMIN+10 unblocked
可以看到SIGRTMIN由於被阻塞所以處於未決信號集中。
關於基本的信號處理函數就介紹到這了。
第四部分: 保護臨界區不被中斷
1. 函數的可重入性
函數的可重入性是指可以多於一個任務並發使用函數,而不必擔心數據錯誤。相反,不可重入性是指不能多於一個任務共享函數,除非能保持函數互斥(或者使用信號量,或者在代碼的關鍵部分禁用中斷)。可重入函數可以在任意時刻被中斷,稍後繼續執行,而不會丟失數據。
可重入函數:
* 不為連續的調用持有靜態數據。
* 不返回指向靜態數據的指針;所有數據都由函數的調用者提供。
* 使用本地數據,或者通過制作全局數據的本地拷貝來保護全局數據。
* 絕不調用任何不可重入函數。
不可重入函數可能導致混亂現象,如果當前進程的操作與信號處理程序同時對一個文件進行寫操作或者是調用malloc(),那麼就可能出現混亂,當從信號處理程序返回時,造成了狀態不一致。從而引發錯誤。
因此,信號的處理必須是可重入函數。
簡單的說,可重入函數是指在一個程序中調用了此函數,在信號處理程序中又調用了此函數,但仍然能夠得到正確的結果。
printf,malloc函數都是不可重入函數。printf函數如果打印緩沖區一半時,又有一個printf函數,那麼此時會造成混亂。而malloc函數使用了系統全局內存分配表。
2. 保護臨界區不被中斷 (參考 APUE sigsuspend章節)
由於臨界區的代碼是關鍵代碼,是非常重要的部分,因此,有必要對臨界區進行保護,不希望信號來中斷臨界區操作。這裡通過信號屏蔽字來阻塞信號的發生。
下面介紹兩個與保護臨界區不被信號中斷的相關函數。
int pause(void);
int sigsuspend(const sigset_t *sigmask);
pause函數掛起一個進程,直到一個信號發生。
sigsuspend函數的執行過程如下:
(1)設置新的mask去阻塞當前進程
(2)收到信號,調用信號的處理函數
(3)將mask設置為原先的掩碼
(4)sigsuspend函數返回
可以看出,sigsuspend函數是等待一個信號發生,當等待的信號發生時,執行完信號處理函數後就會返回。它是一個原子操作。
保護臨界區的中斷:
(1)首先用sigprocmask去阻塞信號
(2)執行後關鍵代碼後,用sigsuspend去捕獲信號
(3)然後sigprocmask去除阻塞
這樣信號就不會丟失了,而且不會中斷臨界區。
使用pause函數對臨界區的保護:
上面的程序是用pause去保護臨界區,首先用sigprocmask去阻塞SIGINT信號,執行臨界區代碼,然後解除阻塞。最後調用pause()函數等待信號的發生。但此時會產生一個問題,如果信號在解除阻塞與pause之間發生的話,信號就可能丟失。這將是一個不可靠的信號機制。
因此,采用sigsuspend可以避免上述情況發生。
使用sigsuspend對臨界區的保護:
使用sigsuspend對臨界區的保護就不會產生上述問題了。
3. sigsuspend函數的用法
sigsuspend函數是等待的信號發生時才會返回。
sigsuspend函數遇到結束時不會返回,這一點很重要。
示例:
下面的例子能夠處理信號SIGUSR1,SIGUSR2,SIGSEGV,其它的信號被屏蔽,該程序輸出對應的信號,然後繼續等待其它信號的出現。
#include
#include
#include
#include
void myhandler(int signo);
int main(){
struct sigaction action;
sigset_t sigmask;
sigemptyset(&sigmask);
sigaddset(&sigmask,SIGUSR1);
sigaddset(&sigmask,SIGUSR2);
sigaddset(&sigmask,SIGSEGV);
action.sa_handler=myhandler;
action.sa_mask=sigmask;
sigaction(SIGUSR1,&action,NULL);
sigaction(SIGUSR2,&action,NULL);
sigaction(SIGSEGV,&action,NULL);
sigfillset(&sigmask);
sigdelset(&sigmask,SIGUSR1);
sigdelset(&sigmask,SIGUSR2);
sigdelset(&sigmask,SIGSEGV);
while(1){
sigsuspend(&sigmask);//不斷的等待信號到來
}
return 0;
}
void myhandler(int signo){
switch(signo){
case SIGUSR1:
printf("received sigusr1 signal./n");
break ;
case SIGUSR2:
printf("received sigusr2 signal./n");
break;
case SIGSEGV:
printf("received sigsegv signal/n");
break;
}
}
程序運行結果:
received sigusr1 signal
received sigusr2 signal
received sigsegv signal
received sigusr1 signal
已終止
另一個終端用於發送信號:
先得到當前進程的pid, ps aux|grep 程序名
kill -SIGUSR1 4901
kill -SIGUSR2 4901
kill -SIGSEGV 4901
kill -SIGTERM 4901
kill -SIGUSR1 4901
解釋:
第一行發送SIGUSR1,則調用信號處理函數,打印出結果。
第二,第三行分別打印對應的結果。
第四行發送一個默認處理為終止進程的信號。
但此時,但不會終止程序,由於sigsuspend遇到終止進程信號並不會返回,此時並不會打印出"已終止",這個信號被阻塞了。當再次發送SIGURS1信號時,進程的信號阻塞恢復成默認的值,因此,此時將會解除阻塞SIGTERM信號,所以進程被終止。
第五部分: 信號的繼承與執行
當使用fork()函數時,子進程會繼承父進程完全相同的信號語義,這也是有道理的,因為父子進程共享一個地址空間,所以父進程的信號處理程序也存在於子進程中。
示例: 子進程繼承父進程的信號處理函數
#include
#include
#include
#include
#include
void myhandler(int signo,siginfo_t *si,void *vcontext);
int main(){
union sigval val;
struct sigaction oldact,newact;
newact.sa_sigaction=myhandler;
newact.sa_flags=SA_SIGINFO|SA_RESETHAND;//表示采用sa_sigaction指示的函數以及執行完處理函數後恢復默認操作
//注冊信號處理函數
sigaction(SIGUSR1,&newact,&oldact);
if(fork()==0){
val.sival_int=10;
printf("子進程/n");
sigqueue(getpid(),SIGUSR1,val);
}
else {
val.sival_int=20;
printf("父進程/n");
sigqueue(getpid(),SIGUSR1,val);
}
}
void myhandler(int signo,siginfo_t *si,void *vcontext){
printf("信號處理/n");
printf("%d/n",(si->si_int));
}
輸出的結果為:
子進程
信號處理
10
父進程
信號處理
20
可以看出來,子進程繼承了父進程的信號處理函數。
第六部分: 實時信號中鎖的研究
1. 信號處理函數與主函數之間的死鎖
當主函數訪問臨界資源時,通常需要加鎖,如果主函數在訪問臨界區時,給臨界資源上鎖,此時發生了一個信號,那麼轉入信號處理函數,如果此時信號處理函數也對臨界資源進行訪問,那麼信號處理函數也會加鎖,由於主程序持有鎖,信號處理程序等待主程序釋放鎖。又因為信號處理函數已經搶占了主函數,因此,主函數在信號處理函數結束之前不能運行。因此,必然造成死鎖。
示例1: 主函數與信號處理函數之間的死鎖
#include
#include
#include
#include
#include
#include
int value=0;
sem_t sem_lock;//定義信號量
void myhandler(int signo,siginfo_t *si,void *vcontext);//進程處理函數聲明
int main(){
union sigval val;
val.sival_int=1;
struct sigaction oldact,newact;
int res;
res=sem_init(&sem_lock,0,1);
if(res!=0){
perror("信號量初始化失敗");
}
newact.sa_sigaction=myhandler;
newact.sa_flags=SA_SIGINFO;
sigaction(SIGUSR1,&newact,&oldact);
sem_wait(&sem_lock);
printf("xxxx/n");
value=1;
sleep(10);
sigqueue(getpid(),SIGUSR1,val);//sigqueue發送帶參數的信號
sem_post(&sem_lock);
sleep(10);
exit(0);
}
void myhandler(int signo,siginfo_t *si,void *vcontext){
sem_wait(&sem_lock);
value=0;
sem_post(&sem_lock);
}
此程序將一直阻塞在信號處理函數的sem_wait函數處。
2. 利用測試鎖解決死鎖
sem_trywait(&sem_lock);是非阻塞的sem_wait,如果加鎖失敗或者是超時,則返回-1。
示例2: 用sem_trywait來解決死鎖
#include
#include
#include
#include
#include
#include
int value=0;
sem_t sem_lock;//定義信號量
void myhandler(int signo,siginfo_t *si,void *vcontext);//進程處理函數聲明
int main(){
union sigval val;
val.sival_int=1;
struct sigaction oldact,newact;
int res;
res=sem_init(&sem_lock,0,1);
if(res!=0){
perror("信號量初始化失敗");
}
newact.sa_sigaction=myhandler;
newact.sa_flags=SA_SIGINFO;
sigaction(SIGUSR1,&newact,&oldact);
sem_wait(&sem_lock);
printf("xxxx/n");
value=1;
sleep(10);
sigqueue(getpid(),SIGUSR1,val);//sigqueue發送帶參數的信號
sem_post(&sem_lock);
sleep(10);
sigqueue(getpid(),SIGUSR1,val);
exit(0);
}
void myhandler(int signo,siginfo_t *si,void *vcontext){
if(sem_trywait(&sem_lock)==0){
value=0;
sem_post(&sem_lock);
}
}
第一次發送sigqueue時,由於主函數持有鎖,因此,sem_trywait返回-1,當第二次發送sigqueue時,主函數已經釋放鎖,此時就可以在信號處理函數中對臨界資源加鎖了。
但這種方法明顯丟失了一個信號,不是很好的解決方法。
3. 利用雙線程來解決主函數與信號處理函數死鎖
我們知道,當進程收到一個信號時,會選擇其中的某個線程進行處理,前提是這個線程沒有屏蔽此信號。因此,可以在主線程中屏蔽信號,另選一個線程去處理這個信號。由於主線程與另外一個線程是平行執行的,因此,等待主線程執行完臨界區時,釋放鎖,這個線程去執行信號處理函數,直到執行完畢釋放臨界資源。
這裡用到一個線程的信號處理函數: pthread_sigmask
int pthread_sigmask(int how,const sigset_t *set,sigset_t *oldset);
這個函數與sigprocmask很相似。
how:
SIG_BLOCK 將信號集加入到線程的阻塞集中去
SIG_UNBLOCK 將信號集從阻塞集中刪除
SIG_SETMASK 將當前集合設置為線程的阻塞集
示例: 利用雙線程來解決主函數與信號處理函數之間的死鎖
#include
#include
#include
#include
#include
#include
#include
void*thread_function(void *arg);//線程處理函數
void myhandler(int signo,siginfo_t *si,void *vcontext);//信號處理函數
int value;
sem_t semlock;
int main(){
int res;
pthread_t mythread;
void *thread_result;
res=pthread_create(&mythread,NULL,thread_function,NULL);//創建一個子線程
if(res!=0){
perror("線程創建失敗");
}
//在主線程中將信號屏蔽
sigset_t empty;
sigemptyset(&empty);
sigaddset(&empty,SIGUSR1);
pthread_sigmask(SIG_BLOCK,&empty,NULL);
//主線程中對臨界資源的訪問
if(sem_init(&semlock,0,1)!=0){
perror("信號量創建失敗");
}
sem_wait(&semlock);
printf("主線程已經執行/n");
value=1;
sleep(10);
sem_post(&semlock);
res=pthread_join(mythread,&thread_result);//等待子線程退出
exit(EXIT_SUCCESS);
}
void *thread_function(void *arg){
struct sigaction oldact,newact;
newact.sa_sigaction=myhandler;
newact.sa_flags=SA_SIGINFO;
//注冊信號處理函數
sigaction(SIGUSR1,&newact,&oldact);
union sigval val;
val.sival_int=1;
printf("子線程睡眠3秒/n");
sleep(3);
sigqueue(getpid(),SIGUSR1,val);
pthread_exit(0);//線程結束
}
void myhandler(int signo,siginfo_t *si,void *vcontext){
sem_wait(&semlock);
value=0;
printf("信號處理完畢/n");
sem_post(&semlock);
}
運行結果如下:
主線程已經執行
子線程睡眠3秒
信號處理完畢
解釋一下:
在主線線程中阻塞了SIGUSR1信號,首先讓子線程睡眠3秒,目的讓主線程先運行,然後當主線程訪問臨界資源時,讓線程sleep(10),在這期間,子線程發送信號,此時子線程會去處理信號,而主線程依舊平行的運行,子線程被阻止信號處理函數的sem_wait處,等待主線程10後,信號處理函數得到鎖,然後進行臨界資源的訪問。這就解決了主函數與信號處理函數之間的死鎖問題。
擴展: 如果有多個信號到達時,還可以用多線程來處理多個信號,從而達到並行的目的,這個很好實現的,可以嘗試一下。
轉自:
http://blog.csdn.net/chenjin_zhong/article/details/6129628
http://xxxxxx/Linuxjc/1164540.html TechArticle