1. 程式人生 > >Linux定時器處理之實時訊號使用,訊息佇列阻塞模型,避免超時等待

Linux定時器處理之實時訊號使用,訊息佇列阻塞模型,避免超時等待

man msgrcv翻到msgrcv函式英文有段話說明了,意思是這樣,當msgrcv所在的程序捕獲到一個訊號的時候,該函式會呼叫失敗並且把errno設定為EINTR,也就是說這個時候msgrcv就不會繼續阻塞了,會直接返回,如果在這之前啟動了定時器,這個時候就可以進行超時判斷,判斷是否還需要阻塞等待。這個時候問題來了,定時器是通過訊號通知機制實現的,timer_t, sigevent

the calling process catches a signal. In this case the system call fails with the errno set to EINTR.(msgrcv is never automatically restarted after being interrupted by a signal handler, regardless of the setting the SA_RESTART flag when establshing a signal handler)

開始使用SIGALARM(14),作為定時器的到時通知標誌,一般情況下是不會有問題的,但是當tps壓力加大,併發請求增多的時候,msgrcv等待的內容始終沒有返回回來,導致執行緒一直阻塞,使用沒有退出來判斷超時,系統直接就停止對外提供響應了,必須重啟才能恢復。。。

原因在於訊號分為可靠訊號(實時訊號)和不可靠訊號(非實時訊號),前者會排隊等待,不會被丟棄,後者卻會被丟棄

另外實時訊號SIGRTMIN,有可能並不是一個常量的巨集定義,在SUSE環境下是一個函式定義,所以這個巨集此時在switch case語句裡面的時候就會編譯報錯。。。只能使用if判斷了。。

還有就是上面的英文括號裡面的話,如果訊號註冊函式設定了SA_RESTART標誌的話,那麼訊號的捕獲終端對原來的程式邏輯就不會產生影響,原有的阻塞型系統呼叫如read、msgrcv不會被呼叫失敗返回,會繼續阻塞等待。。

關於SA_RESTART訊號

http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28852942&id=3754478

#include
int sigaction(int sigon,const struct sigaction &restrict act,

            struct sigaction &restrict oact);
            成功返回0,出錯返回-1
此函式使用下列結構:
struct sigaction{
 void    ( *sa_handler)(int); //訊號處理函式地址

 sigset_t sa_mask;       //一個呼叫訊號捕捉函式之前要加到程序訊號遮蔽字中的訊號集
 int    sa_flags;       //訊號處理選項

 void   (*sa_sigaction)(int,siginfo_t *,void *); 
}

對於sigaction 函式本身我們不做多介紹。我們重點是在 sa_flags 的不同值的情況下,sigaction函式的處理方式


SA_INTERRUPT:由此訊號中斷的系統呼叫不會自動重啟動(針對sigaction的XSI預設處理方式不會自動重啟) 

SA_RESTART:  由此訊號中斷的系統呼叫會自動重啟。

   我們看下面這段程式:
 4 void sig_int(int signo){
  5         printf("\nint sig_int\n");
  6 }
  7 
  8 int main(void){
  9         struct sigaction new_action;
 10         new_action.sa_handler=sig_int;
 11         if(sigemptyset(&new_action.sa_mask)==-1){
 12                 printf("set new action mask error\n");
 13                 exit(1);
 14         }
 15         new_action.sa_flags=0;
 16 
 17         if(sigaction(SIGINT,&new_action,NULL)==-1){
 18                 printf("set SIGINT process error\n");
 19                 exit(1);
 20         }
 21         char c;
 22         read(0,&c,1);
 23         printf("read :%c\n",c);
 24         exit(0);
 25 }

第十五行中我們沒有使用任何標誌。設定捕捉 SIGINT 訊號,然後讀標準輸入

現在 我們將 第十五行改為  new_action.sa_flags=SA_RESTART; 再看看輸出情況
<a href="mailto:[email protected]:~/learn_linux_c_second/chapter_10$2><span style=" font-size:16px;"="" style="word-wrap: break-word; text-decoration: none; color: rgb(25, 89, 155);">[email protected]:~/learn_linux_c_second/chapter_10$ ./a.out
^C
int sig_int
^C
int sig_int
^C
int sig_int
^\Quit (core dumped)
 從輸出中我們看到  當多次按下 中斷鍵後 程序捕捉到訊號並列印訊息,但是程序並未結束,而是繼續等待輸入。按下退出鍵時程序才退出
 也就是說 使用了 SA_RESTART標誌後 read呼叫被中斷後 會自動重啟 繼續等待中斷輸入。


http://www.cnblogs.com/cobbliu/p/5592659.html

linux 多執行緒訊號總結(一)

1. 在多執行緒環境下,產生的訊號是傳遞給整個程序的,一般而言,所有執行緒都有機會收到這個訊號,程序在收到訊號的的執行緒上下文執行訊號處理函式,具體是哪個執行緒執行的難以獲知。也就是說,訊號會隨機發個該程序的一個執行緒。

2 signal函式BSD/Linux的實現並不在訊號處理函式呼叫時,恢復訊號的處理為預設,而是在訊號處理時阻塞此訊號,直到訊號處理函式返回。其他實現可能在呼叫訊號處理函式時,恢復訊號的處理為預設方式,因而需要在訊號處理函式中重建訊號處理函式為我們定義的處理函式,在這些系統中,較好的方法是使用sigaction來建立訊號處理函式。

3 傳送訊號給程序,哪個執行緒會收到?APUE說,在多執行緒的程式中,如果不做特殊的訊號阻塞處理,當傳送訊號給程序時,由系統選擇一個執行緒來處理這個訊號。

4 如果程序中,有的執行緒可以遮蔽了某個訊號,而某些執行緒可以處理這個訊號,則當我們傳送這個訊號給程序或者程序中不能處理這個訊號的執行緒時,系統會將這個訊號投遞到程序號最小的那個可以處理這個訊號的執行緒中去處理。

5 如果我們同時註冊了訊號處理函式,同時又用sigwait來等待這個訊號,誰會取到訊號?經過實驗,Linux上sigwait的優先順序高。 

6 在Linux中的posix執行緒模型中,執行緒擁有獨立的程序號,可以通過getpid()得到執行緒的程序號,而執行緒號儲存在pthread_t的值中。而主執行緒的程序號就是整個程序的程序號,因此向主程序傳送訊號只會將訊號傳送到主執行緒中去。如果主執行緒設定了訊號遮蔽,則訊號會投遞到一個可以處理的執行緒中去。

7 當呼叫SYSTEM函式去執行SHELL命令時,可以放心的阻塞SIGCHLD,因為SYSTEM會自己處理子程序終止的問題。 

8 使用sleep()時,要以放心的去阻塞SIGALRM訊號,目前sleep函式都不會依賴於ALRM函式的SIGALRM訊號來工作。

linux 多執行緒訊號總結(二)

1. 預設情況下,訊號將由主程序接收處理,就算訊號處理函式是由子執行緒註冊的

2. 每個執行緒均有自己的訊號遮蔽字,可以使用sigprocmask函式來遮蔽某個執行緒對該訊號的響應處理,僅留下需要處理該訊號的執行緒來處理指定的訊號。

3. 對某個訊號處理函式,以程式執行時最後一次註冊的處理函式為準,即在所有的執行緒裡,同一個訊號在任何執行緒裡對該訊號的處理一定相同

4. 可以使用pthread_kill對指定的執行緒傳送訊號

APUE的說法:每個執行緒都有自己的訊號遮蔽字,但是訊號的處理是程序中所有的執行緒共享的,這意味著儘管單個執行緒可以阻止某些訊號,但當執行緒修改了與某個訊號相關的處理行為後,所有的執行緒都共享這個處理行為的改變。這樣如果一個執行緒選擇忽略某個訊號,而其他執行緒可以恢復訊號的預設處理行為,或者為訊號設定一個新的處理程式,從而可以撤銷上述執行緒的訊號選擇。

程序中的訊號是送到單個執行緒的,如果訊號與硬體故障或者計時器超時有關,該型號就被髮送到引起該事件的執行緒中去,而其他的訊號則被髮送到任意一個執行緒。

sigprocmask的行為在多執行緒的程序中沒有定義,執行緒必須使用pthread_sigmask

總結:一個訊號可以被沒遮蔽它的任何一個執行緒處理,但是在一個程序內只有一個多個執行緒共用的處理函式。......

linux 多執行緒訊號總結(三)

1 Linux 多執行緒應用中,每個執行緒可以通過呼叫pthread_sigmask() 設定本執行緒的訊號掩碼。一般情況下,被阻塞的訊號將不能中斷此執行緒的執行,除非此訊號的產生是因為程式執行出錯如SIGSEGV;另外不能被忽略處理的訊號SIGKILL 和SIGSTOP 也無法被阻塞。

2 當一個執行緒呼叫pthread_create() 建立新的執行緒時,此執行緒的訊號掩碼會被新建立的執行緒繼承。

訊號安裝最好採用sigaction方式,sigaction,是為替代signal 來設計的較穩定的訊號處理,signal的使用比較簡單。signal(signalNO,signalproc);

不能完成的任務是:1.不知道訊號產生的原因;

2.處理訊號中不能阻塞其他的訊號

而signaction,則可以設定比較多的訊息。尤其是在訊號處理函式過程中接受訊號,進行何種處理。

sigaction函式用於改變程序接收到特定訊號後的行為。

4 sigprocmask函式只能用於單執行緒,在多執行緒中使用pthread_sigmask函式。

5 訊號是發給程序的特殊訊息,其典型特性是具有非同步性。

6 訊號集代表多個訊號的集合,其型別是sigset_t。

7 每個程序都有一個訊號掩碼(或稱為訊號遮蔽字),其中定義了當前程序要求阻塞的訊號集。

所謂阻塞,指Linux核心不向程序交付在掩碼中的所有訊號。於是程序可以通過修改訊號掩碼來暫時阻塞特定訊號的交付,被阻塞的訊號不會影響程序的行為直到該訊號被真正交付。 

忽略訊號不同於阻塞訊號,忽略訊號是指Linux核心已經嚮應用程式交付了產生的訊號,只是應用程式直接丟棄了該訊號而已。

10 sleep和nanosleep,如果沒有在它呼叫之前設定訊號遮蔽字的話,都可能會被訊號處理函式打斷。參見sleep和nanosleep的mannual。


http://www.cnblogs.com/blueyunchao0618/p/5953862.html

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

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

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

(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()函式時,子程序會繼承父程序完全相同的訊號語義,這也是有道理的,因為父子程序共享一個地址空間,所以父程序的訊號處理程式也存在於子程序中。

示例: 子程序繼承父程序的訊號處理函式

相關推薦

Linux定時處理實時訊號使用訊息佇列阻塞模型避免超時等待

man msgrcv翻到msgrcv函式英文有段話說明了,意思是這樣,當msgrcv所在的程序捕獲到一個訊號的時候,該函式會呼叫失敗並且把errno設定為EINTR,也就是說這個時候msgrcv就不會繼續阻塞了,會直接返回,如果在這之前啟動了定時器,這個時候就可以進行超時判

linux服務上使用crontab指令執行制定PHP文件生成定時任務。

php sage 對象 安裝 dbn 個人 mage message into 首先 寫個 PHP文件 ,我寫的是向數據庫表插入數據,如下圖 <?php $dbms=‘mysql‘; //數據庫類型 $host=‘localhost‘; //數據庫主機名 $dbN

Quartz cron 表示式(linux 定時java 定時任務spring task定時任務)

原文地址:https://blog.csdn.net/feng27156/article/details/39293403 Quartz cron 表示式的格式十分類似於 UNIX cron 格式,但還是有少許明顯的區別。區別之一就是 Quartz 的格式向下支援到秒級別的計劃,而 UNIX cron 計劃

Linux服務搭建DHCP服務

serve host x86_64 toc 開頭 redhat .com 範圍 rest Linux服務器搭建之DHCP服務器當局域網絡中有大量主機時,如果逐個為每一臺主機手動設置IP地址、默認網關、DNS服務器地址等網絡參數,顯然是一個費力也未必討好的辦法。而DHCP(D

Linux 服務搭建Samba服務

空行 環境 文件中 方便 兩個 cto mask 訪問服務器 找到 Linux 服務器搭建之Samba服務一、Samba服務基礎Samba是著名的開源軟件項目之一,它在Linux/UNIX系統中實現了微軟的SMB/CIFS網絡協議,從而使得跨平臺的文件共享變得更加容易。在部

linux定時【轉】

gprof class mil setitimer body 不知道 ssa pos 需要 轉自:http://www.cnblogs.com/processakai/archive/2012/04/11/2442294.html 今天看書看到了關於alarm的一些用

Linux 定時應用【轉】

激活 ble amp aps cond linux 進程 exit 利用 接收 Linux 定時器應用 實驗目的 閱讀 Linux 相關源代碼,學習 Linux 系統中的時鐘和定時器原理,即,ITIMER_REAL實時計數,ITIMER_VIRTUAL 統計進程在用戶模式執

Linux文本處理grep

腳本 進行 mail egrep 一次 裏的 轉義 span 數字 Linux 文本處理之grep 我們經常會遇到只需要一個文件裏的只言片語,比如從文件裏獲取一串字符或者樣式,可以用cat +文件一個一個找,這樣不僅效率低,而且海量的字符會讓你奔潰的。這個時候需要

Linux文本處理sed

left p s 而不是 備份文件 完成後 rep oca 十分 新增 Linux文本處理之sed 上一篇我介紹了文本處理grep,現在我來介紹一下文本處理三劍客的第二劍客——sed。 sed 是一個比較古老的,功能十分強大的用於文本處理的流編輯器,加上

linux定時的實現方法

this 就是 沒有 讀取數據 entry arm sigalrm read time Linux提供定時器機制,可以指定在未來的某個時刻發生某個事件,定時器的結構如下: struct timer_list { struct list_head list;

Linux:程序間通訊(匿名管道命名管道)(共享記憶體訊息佇列訊號量)

目錄 程序間通訊的介紹 管道 匿名管道 原理: 程式碼實現 匿名管道特性 實現管道符 |  命名管道 命名管道特性 程式碼實現 管道讀寫規則 作業系統中ipc的相關命令 共享記憶體(重點) 生命週期: 程式碼實現 程式碼實現獲

Linux的資料處理--------- cut

高階文字處理命令 cut cut:一個選取命令,將一段資料經過分析,取出我們想要的資料。一般來說,選取的資料通常是針對行來進行分析的,並不是整篇資料一起分析 語法 : cut -nb 檔名 cut -c 檔名 cut -df 檔名 cut

linux定時總結

1   參考資料 Ø  《linux系統程式設計》第“10.9 定時器”章節 2   概要 要在linux中使用定時器,有如下三種方法: 定時器方式 一個程序允許 使用的數量 通知方式

伺服器定時處理要注意的問題

gdb) bt#00x00ff9410in __kernel_vsyscall ()#10x004d593ein __lll_mutex_lock_wait () from /lib/libc.so.6#20x00465b38in _L_lock_14080 () from /lib/libc.so.6#30

Linux定時詳解

今天在看linux的定時任務,瞭解了一下crontab命令,下面我們來一起學習一下。 首先要知道 crontab 檔案的格式: {minute} {hour} {day-of-month} {month} {day-of-week} {full-path-to-shell-script} o m

Java定時(二)Spring定時任務、Quartz實現

使用基於註解配置的spring定時器 基於註解會相對簡單的多,直接編寫任務類Mytask @EnableScheduling @Component public class Mytask { @Scheduled(cron = "*/5 * * * * ?") p

5---linux定時消除按鍵抖動

概要:上一篇中我們用到了中斷來讀取按鍵電平,似乎很成功,然而當我們快速按下鬆開時,會有資料異常,是什麼導致呢? 按鍵抖動,在我們按下的時候按鍵可能會發生抖動,如圖 我們的中斷是雙邊沿觸發的,假如我們按鍵發生了抖動,那麼就觸發了五次中斷,這對我們來說實際應用上來看。是非常不穩定的,我

Linux定時在驅動程式中的應用

        核心提供了一組與定時器相關的介面用來簡化管理定時器的操作。所有這些介面都宣告在<linux/Timer.h>中,大多數介面在<kernel/timer.c>中的到實現。   建立定時器首先要先定義它,然後通過一個輔助函式

Linux程序間通訊--訊號管道訊息佇列訊號共享記憶體,socket

Linux 傳統的程序間通訊有很多,如各類管道、訊息佇列、記憶體共享、訊號量等等。但它們都無法介於核心態與使用者態使用,原因如表 通訊方法 無法介於核心態與使用者態的原因 管道(不包括命名管道) 侷限於父子程序間的通訊。 訊息佇列 在硬、軟中斷中無法無阻塞地接收資料。 訊號量 無法介於核

linux定時

簡介 這篇文章主要記錄我在試圖解決如何儘可能精確地在某個特定的時間間隔執行某項具體任務時的思路歷程,並在後期對相關的API進行的歸納和總結,以備參考。 問題引出 很多時候,我們會有類似“每隔多長時間執行某項任務”的需求,乍看這個問題並不難解決,實則並不容易,有很