1. 程式人生 > >linux下 signal訊號機制的透徹分析與各種例項講解

linux下 signal訊號機制的透徹分析與各種例項講解

首先感謝上述兩位博主的詳細講解。

雖然內容有點長,但是分析的很全面,各種例項應用基本都考慮到了。

本文將從以下幾個方面來闡述訊號:

(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)處理過程:

程式執行在使用者態時->程序由於系統呼叫或中斷進入核心->轉向使用者態執行訊號處理函式->訊號處理函式完畢後進入核心->返回使用者態繼續執行程式

首先程式執行在使用者態,在程序陷入核心並從核心返回的前夕,會去檢查有沒有訊號沒有被處理,如果有且沒有被阻塞就會呼叫相應的訊號處理程式去處理。首先,核心在使用者棧上建立一個層,該層中將返回地址設定成訊號處理函式的地址,這樣,從核心返回使用者態時,就會執行這個訊號處理函式。當訊號處理函式執行完,會再次進入核心,主要是檢測有沒有訊號沒有處理,以及恢復原先程式中斷執行點,恢復核心棧等工作,這樣,當從核心返回後便返回到原先程式執行的地方了。

訊號處理函式的過程大概是這樣了。

參考圖:

 

第三部分: 基本的訊號處理函式

首先看一個兩個概念: 訊號未決與訊號阻塞

訊號未決: 指的是訊號的產生到訊號處理之前所處的一種狀態。確切的說,是訊號的產生到訊號登出之間的狀態。

訊號阻塞: 指的是阻塞訊號被處理,是一種訊號處理方式。

 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後將集合從阻塞集合中移除。
[[email protected] 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;

}

執行結果:


[[email protected] 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);

相關推薦

linux signal訊號機制透徹分析各種例項講解

首先感謝上述兩位博主的詳細講解。 雖然內容有點長,但是分析的很全面,各種例項應用基本都考慮到了。 本文將從以下幾個方面來闡述訊號: (1)訊號的基本知識 (2)訊號生命週期與處理過程分析 (3) 基本的訊號處理函式 (4) 保護臨界區不被中斷 (5)

linux訊號機制(signel)--持續更新中

1、訊號的基本概念        程序之間可以互相通過系統呼叫kill傳送軟中斷訊號。核心也可以因為內部事件而給程序傳送訊號,通知程序發生了某個事件。程序通過系統呼叫signal來指定程序對某個訊號的處理行為。2、訊號本質是int型數字編號(事先定義好的)        解釋

LinuxSignal訊號系統呼叫

前面兩節已經介紹了有關訊號的大部分知 識。這一節我們來了解一下這些系統呼叫。其中,系統呼叫signal是程序用來設定某個訊號的處理方法,系統呼叫kill是用來發送訊號給指定程序的。這 兩個呼叫可以形成訊號的基本操作。後兩個呼叫pause和alarm是通過訊號實現的程序暫停和

linux訊號LinuxSignal訊號太詳細了,終於找到了

訊號是Linux程式設計中非常重要的部分,本文將詳細介紹訊號機制的基本概念、Linux對訊號機制的大致實現方法、如何使用訊號,以及有關訊號的幾個系統呼叫。  訊號機制是程序之間相互傳遞訊息的一種方法,訊號全稱為軟中斷訊號,也有人稱作軟中斷。從它的命名可以看出,它的實質和使用很象中斷。所以,訊號可以說是程序控

Linux訊號透徹分析理解

本文將從以下幾個方面來闡述訊號: (1)訊號的基本知識 (2)訊號生命週期與處理過程分析 (3) 基本的訊號處理函式 (4) 保護臨界區不被中斷 (5) 訊號的繼承與執行 (6)實時訊號中鎖的研究 第一部分: 訊號的基本知識 1.訊號本質

linuxselect,poll,epoll的使用重點分析

end 復用 cps typedef lis callback 指向 hub 機制 好久沒用I/O復用了,感覺差點兒相同都快忘完了。記得當初剛學I/O復用的時候花了好多時間。可是因為那會不太愛寫博客,導致花非常多時間搞明確的東西,依舊非常easy忘記。俗

Linux訊號(三)----捕捉訊號sleep模擬

Linux下的訊號(一):訊號的基本概念與產生 Linux下的訊號(二):阻塞訊號 一,什麼是捕捉訊號? 1,捕捉訊號:訊號處理方式三種方式中的一種,意思是既不忽略該訊號,又不執行訊號預設的動作,而是讓訊號執行自定義動作。捕捉訊號要使用signal函式

Linux訊號(一)----訊號的基本概念產生

一,訊號的基本概念 1,什麼是訊號? 日常生活中,當我們走到馬路上時,看到的綠燈是一種訊號,它能提示我們怎樣安全的過馬路。又比如,新學期開始學校給每個班發的課表也是一種訊號,它能提示同學們在適當的時間地點去上相應的課程而不是虛度光陰……生活中其

Linux訊號signal

一、訊號的概念: 要理解訊號,我們先來進入一個場景。使用者在shell下開啟一個前臺程序,正在執行。在鍵盤上按下ctrl+C的組合鍵,當前前臺程序會中斷。是因為鍵盤上輸入的訊號通過硬體傳輸給驅動程式,將ctrl+C轉化為SIGNAL傳給該程序的PCB,修改了P

Linux程序的建立過程分析(_do_fork/do_fork詳解)--Linux程序的管理排程(八)

前言 Unix標準的複製程序的系統呼叫時fork(即分叉),但是Linux,BSD等作業系統並不止實現這一個,確切的說linux實現了三個,fork,vfork,clone(確切說vfork創造出來的是輕量級程序,也叫執行緒,是共享資源的程序

Linux input子系統編程、分析模板

linux輸入設備都有共性:中斷驅動+字符IO,基於分層的思想,Linux內核將這些設備的公有的部分提取出來,基於cdev提供接口,設計了輸入子系統,所有使用輸入子系統構建的設備都使用主設備號13,同時輸入子系統也支持自動創建設備文件,這些文件采用阻塞的IO讀寫方式,被創建在"/dev/input/"下。如下

LinuxNFS服務器的搭建配置

linux nfs 一.系統環境# cat /etc/redhat-release CentOS Linux release 7.3.1611 (Core)二.安裝NFS服務1、查看系統是否已安裝NFSrpm -qa | grep nfs rpm -qa | grep rpcbind2、安裝NFS

Linux內核哈希表分析應用

構造方法 init lis 個數 無需 表示 字節 div 擴展 目錄(?)[+] Linux內核哈希表分析與應用 Author:tiger-johnTime:2012-12-20mail:[email protected]/* */Blog

2017-2018-1 20155222 《信息安全系統設計基礎》第10周 Linux的IPC機制

mct 執行 除了 comm 同進程 href sem_flag 消息隊列 con 2017-2018-1 20155222 《信息安全系統設計基礎》第10周 Linux下的IPC機制 IPC機制 在linux下的多個進程間的通信機制叫做IPC(Inter-Process

梳理LinuxOSI七層網絡TCP/IP五層網絡架構

七層 端口 netstat 導致 七層模型 順序 二次 轉換 san 作為一個合格的運維人員,一定要熟悉掌握OSI七層網絡和TCP/IP四層網絡結構知識。一、OSI七層網絡協議OSI是Open System Interconnect的縮寫,意為開放式系統互聯。 OSI參考

linux的軟件包管理源代碼方式安裝軟件

軟件包 軟件包管理工具 dpkg與rpm 高級軟件包管理工具APT 源代碼編譯安裝軟件 1、什麽是軟件包?軟件包是指將應用程序、配置文件和管理數據打包的產物。2、Linux下常用的基本軟件包管理工具有兩種:dpkg工具和RPM工具。對應於兩種不同格式的軟件包,即.deb格式與.rpm格式。

c++ 網絡編程(六)TCP/IP LINUX socket編程 多播廣播 實現一次發送所有組客戶端都能接收到

send all users 代碼示例 proto 次數 不可 的人 ssa 原文作者:aircraft 原文鏈接:https://www.cnblogs.com/DOMLX/p/9614288.html 一.多播 鍥子:有這麽一種情況,網絡電臺可能需要同時向成

Linux訊號詳解及捕捉訊號

訊號的基本概念 每個訊號都有一個編號和一個巨集定義名稱 ,這些巨集定義可以在 signal.h 中找到。 使用kill -l命令檢視系統中定義的訊號列表: 1-31是普通訊號 regular signal(非可靠訊號); 34-64是實時訊號 real time sign

Linux應用程序消失原因分析

應用部署在Linux環境下,如果出現未知原因導致應用程序被殺(應用日誌中沒有任何異常現象,日誌出現中斷現象),如果對於程序消失原因沒有特別明確的方向,可以考慮從系統日誌方面查詢原因。 命令參考 dmesg | egrep -i -B100 'killed process' ##

linuxc程式 daemon、fork建立pthread的順序問題

近期發如今寫linux c服務程式的時候,daemon與執行緒建立之間出現故障。發現程式在daemon之後,起的執行緒就全掛了。 查過一些文件之後,最終知道了why. daemon函式的操作事實上非常easy, 1.fork一個程序,2.處理