README ABOUT SIGNAL
信號是什么?
- 信號是一種很古老的IPC(進程間通信)方式端幼。在早期的類Unix操作系統(tǒng)中礼烈,信號只能傳遞很簡單的信息,即一個int(信號的編號)值静暂。
- 信號是一種異步的消息傳遞方式或者說通知機制济丘。
舉一個不恰當?shù)睦樱阏诤芘d奮地解決一個BUG洽蛀,突然手機響了摹迷,女朋友說要分手。你立馬放下手上的活郊供,去處理女朋友那件事峡碉。手機隨時都會響,隨時都會中斷你當下的事情驮审。所以稱之為異步事件鲫寄。這個例子中的信號就體現(xiàn)在手機鈴聲響了。你去處理女朋友分手就是對信號做出的響應(yīng)疯淫。 - 雖然信號是一種低級的IPC方式地来,但同時它保持了很簡單的特性。在一些大型服務(wù)端程序中熙掺,很多時候也要考慮信號造成的影響未斑。還是值得一學(xué)的。
從上面的例子可以看出币绩,當產(chǎn)生一個信號(如:手機鈴聲響)蜡秽,之后就要去響應(yīng)這個信號,如何響應(yīng)這個信號則需要編程人員來寫一個函數(shù)缆镣。當產(chǎn)生信號時芽突,程序去執(zhí)行之這個函數(shù)(如:處理女朋友分手)。
Linux下信號分兩種.
- 第一種: 用于內(nèi)核向進程通知事件.即傳統(tǒng)或者標準信號.(信號編號由1~31)
- 第二種: 實時信號(信號編號由34~64)
- 0號信號用來測試對應(yīng)進程是否存在或者是否由權(quán)限給其發(fā)送信號
一般程序收到信號時進程的現(xiàn)象:
- 忽略信號
- 終止(殺死)進程
- 產(chǎn)生核心轉(zhuǎn)儲文件,同時進程終止(核心轉(zhuǎn)儲文件即保存了程序跪掉的現(xiàn)場信息的文件董瞻,用來分析程序為何跪掉)寞蚌。
- 停止進程
- 于之前暫停后再度恢復(fù)執(zhí)行
編程可以做到的
- 設(shè)置信號到來時采取默認的行為.
- 忽略掉該信號.
- 執(zhí)行信號處理器程序.
常用信號類型即默認行為
信號名稱 | 產(chǎn)生的效果 | 對進程默認的效果 |
---|---|---|
SIGINT | Ctrl-C終端下產(chǎn)生 | 終止當前進程 |
SIGABRT | 產(chǎn)生SIGABRT信號 | 默認終止進程,并產(chǎn)生core(核心轉(zhuǎn)儲)文件 |
SIGALRM | 由定時器如alarm(),setitimer()等產(chǎn)生的定時器超時觸發(fā)的信號 | 終止當前進程 |
SIGCHLD | 子進程結(jié)束后向父進程發(fā)送 | 忽略 |
SIGBUS | 總線錯誤钠糊,即發(fā)生了某種內(nèi)存訪問錯誤 | 終止當前進程并產(chǎn)生核心轉(zhuǎn)儲文件 |
SIGKILL | 必殺信號挟秤,收到信號的進程一定結(jié)束,不能捕獲 | 終止進程 |
SIGPIPE | 管道斷裂眠蚂,向已關(guān)閉的管道寫操作 | 進程終止 |
SIGIO | 使用fcntl注冊I/O事件,當管道或者socket上由I/O時產(chǎn)生此信號 | 終止當前進程 |
SIGQUIT | 在終端下Ctrl-\產(chǎn)生 | 終止當前進程煞聪,并產(chǎn)生core文件 |
SIGSEGV | 對內(nèi)存無效的訪問導(dǎo)致即常見的“段錯誤” | 終止當前進程斗躏,并產(chǎn)生core文件 |
SIGSTOP | 必停信號逝慧,不能被阻塞昔脯,不能被捕捉 | 停止當前進程 |
SIGTERM | 終止進程的標準信號 | 終止當前進程 |
0號信號用來測試對應(yīng)進程是否存在或者是否有權(quán)限向?qū)?yīng)進程發(fā)送該信號 (后面就理解了)
如何設(shè)計信號處理函數(shù)?
- 一般而言笛臣,信號處理函數(shù)設(shè)計的越簡單越好云稚,因為當前代碼的執(zhí)行邏輯被打斷,最好盡快恢復(fù)到剛才被打斷之前的狀態(tài)沈堡。從而避免競爭條件的產(chǎn)生静陈。
- 在信號處理函數(shù)中,建議不要調(diào)用printf等與I/O相關(guān)的函數(shù)鲸拥。以及一些慢設(shè)備操作。這樣會使得信號處理函數(shù)的執(zhí)行時間變長,可能,操作系統(tǒng)就會切換其它程序去在CPU上執(zhí)行。但如果有特殊需要,則也可以使用村生。
- 在信號處理函數(shù)中,不要使用任何不可重入的函數(shù)后面會說到典徘。保證信號處理函數(shù)可以安全地執(zhí)行完帜平。并不會影響主邏輯執(zhí)行。
信號的發(fā)送與處理
來簡單看一個實例,看看信號最簡單的使用方式(當鍵入Ctrl-C時候即程序收到SIGINT信號時進行處理。):
// signal 的函數(shù)原型
// void (*signal(int sig , void (*func)(int)))(int);
// 至于這個怎么理解,這里就不再贅述了,請參考 《C Traps and Pitfalls》2.1節(jié)即理解函數(shù)聲明。
// filename : simple_signal.cpp
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#define MSG "Catch signal SIGINT processing \n"
#define MSG_END "Finished process SIGINT return \n"
void do_too_heavy_work () {
long long s = 0 ;
for (long long i = 0 ; i < 500000000L ; i++ ) {
s += i ;
}
}
void sig_handler (int signuum ) {
// 本程序只是為了來進行演示,
// 在信號處理程序中,盡量不要調(diào)用與標準IO相關(guān)的和不可重入的函數(shù)。
write ( STDOUT_FILENO , MSG , strlen (MSG) ) ;
do_too_heavy_work();
write ( STDOUT_FILENO , MSG_END , strlen (MSG_END) ) ;
}
int main() {
// 注冊信號處理函數(shù)
if ( SIG_ERR == signal ( SIGINT , sig_handler ) ) {
fprintf (stderr , "signal error ") , perror ("") ;
exit (1) ;
}
// 讓主程序不退出送讲,掛起,等待信號產(chǎn)生
while (1) {
pause () ;
}
return EXIT_SUCCESS ;
}
程序會一直停著等待用戶行為。當我們鍵入Ctrl-C時程序打印相關(guān)信息味榛,之后程序自己退出善茎。那么程序的執(zhí)行流程就類似這樣:
[tutu@localhost Linux-Book]$ gcc simple_signal.cpp
[tutu@localhost Linux-Book]$ ./a.out
^CCatch signal SIGINT processing
Finished process SIGINT return
^CCatch signal SIGINT processing
Finished process SIGINT return
^CCatch signal SIGINT processing
Finished process SIGINT return
這是一種古老的注冊信號并設(shè)置信號處理函數(shù)的方式「福現(xiàn)在我們使用新的信號注冊函數(shù)即sigaction函數(shù)。它提供了更多的控制字段(舊的signal已經(jīng)使用sigaction進行了實現(xiàn)岔激。祥見glibc源碼炫彩,我自己的Fedora 22 glibc 版本2.21可以在glibc官網(wǎng)Glibc下載Glibc源碼划址,對應(yīng)路徑下glibc/glibc-2.21/sysdeps/posix/signal.c看到)包括屏蔽信號集,設(shè)置高級信號處理函數(shù)等(稍后會詳細講述寥裂,別擔(dān)心).
為什么不是用signal來進行信號注冊了嵌洼?
- signal 無法設(shè)置在執(zhí)行信號處理程序時要屏蔽哪些信號的產(chǎn)生。
- signal 函數(shù)注冊的信號處理函數(shù)只能攜帶很少的信息(也不常用)封恰,在信號處理函數(shù)進行信號處理時麻养。
- signal 無法設(shè)置一些標志位來執(zhí)行一些動作(后面再講)。
- signal 只能設(shè)置所給信號的處理方式但sigaction還可以獲取之前這個信號的處理方式
廢話這么多诺舔,大家都嫌棄了鳖昌,來一個真實的例子吧,和上面的程序功能一樣低飒,但是使用sigaction進行處理许昨。
// filename : simple_sigaction.cpp
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#define MSG "Catch signal SIGINT processing \n"
#define MSG_END "Finished process SIGINT return \n"
void do_too_heavy_work () {
long long s = 0 ;
for (long long i = 0 ; i < 500000000L ; i++ ) {
s += i ;
}
}
void sig_handler (int signuum ) {
// 本程序只是為了來進行演示,
// 在信號處理程序中褥赊,盡量不要調(diào)用與標準IO相關(guān)的糕档,不可重入的函數(shù)。
write ( STDOUT_FILENO , MSG , strlen (MSG) ) ;
do_too_heavy_work();
write ( STDOUT_FILENO , MSG_END , strlen (MSG_END) ) ;
}
int main() {
// 注冊信號處理函數(shù)
struct sigaction newact ;
// 將信號處理函數(shù)執(zhí)行期間掩碼設(shè)置為空
sigemptyset (&newact.sa_mask ) ;
// 將標志設(shè)置為0即默認
newact.sa_flags = 0 ;
// 注冊信號處理函數(shù)
newact.sa_handler = sig_handler ;
if ( 0 > sigaction ( SIGINT , &newact , NULL ) ) {
fprintf (stderr , "sigaction error ") , perror ("") ;
exit (1) ;
}
// 讓主程序不退出拌喉,掛起速那,等待信號產(chǎn)生
while (1) {
pause () ;
}
return EXIT_SUCCESS ;
}
執(zhí)行效果和剛才的一樣,在這里就不再貼過來了尿背。
sigaction是怎么做到上述功能
首先sigaction比signal多了參數(shù):
int sigaction ( int sig , const struct sigaction * restrict act ,\
struct sigaction * restrict oact ) ;
而結(jié)構(gòu)體 struct sigaction act 里有什么呢琅坡? 我們來看看:
man 3 sigaction 可以看到對應(yīng)的sigaction結(jié)構(gòu)體中的內(nèi)容。
Member Tyep | Member Name | Description |
---|---|---|
void (*)(int) | sa_handler | 指向一個信號處理函數(shù)残家,或者使用SIG_IGN,SIG_DFL |
sigset_t | sa_mask | 在執(zhí)行信號處理函數(shù)時榆俺,屏蔽sa_mask中的信號的產(chǎn)生 |
int | sa_flags | 特殊的標志位,影響信號的處理行為 |
void () ( int , siginfo_t * ,void) | sa_sigaction | 指向一個信號處理函數(shù)(當設(shè)置了SA_SIGINFO標志位時) |
以下幾個函數(shù)也與上述內(nèi)容相關(guān):
// 設(shè)置信號集為全空
int sigemptyset (sigset_t * set ) ;
// 設(shè)置信號集為全滿
int sigfillset ( sigset_t * set ) ;
// 將信號signum添加到信號集中
int sigaddset ( sigset_t * set , int signum) ;
// 去除信號集中signum信號
int sigdelset ( sigset_t * set , int signum ) ;
// 判斷signum信號是否在信號集set中
int sigismember ( const sigset_t * set , int signum ) ;
flags相關(guān)的選項理解:
- SA_SIGINFO: 設(shè)置此標志位表示可以使用sa_sigaction字段進行注冊信號處理函數(shù)
- SA_RESTART: 被中斷的系統(tǒng)調(diào)用自動重啟
- SA_NODEFER: 在執(zhí)行信號處理函數(shù)期間不屏蔽引起信號處理函數(shù)執(zhí)行的信號
更多的標志位信息還請各位man 3 sigaction 這里只說常用的坞淮。
實際上判斷某個信號是否在對應(yīng)的信號集中茴晋,即判斷對應(yīng)信號在信號集中的那一個位是否被置1。去除信號集中的signum信號只需要對應(yīng)位置0即可回窘。
以上函數(shù)可以操作與sigset_t類型相關(guān)的變量诺擅。
當然除了在執(zhí)行信號處理函數(shù)時候可以用sigaction設(shè)置要屏蔽的信號時,在程序中也可以給進程設(shè)置要屏蔽的信號啡直。需要使用sigprocmask這個系統(tǒng)調(diào)用烁涌。
int sigprocmask ( int how , const sigset_t *set , sigset_t * oldset ) ;
how字段的含義,具體使用大家man手冊一下喵~
- SIG_SETMASK : 將set中的信號集設(shè)置為當前進程的阻塞信號集酒觅。
- SIG_BLOCK:將set中的信號集加到當前進程的阻塞信號集中撮执。
- SIG_UNBLOCK:將set中的信號集從當前阻塞信號集中去除。
那么問題來了舷丹,為什么要設(shè)置屏蔽信號集抒钱?簡單理解,就和你設(shè)置接電話的黑名單一樣,你不想讓這些電話中斷你正在干的事情(什么原因我就不知道了)谋币,同樣的你不想讓這些信號中斷你程序正常運行(SIGSTOP和SIGKILL不能設(shè)置忽略或者捕捉仗扬,由于內(nèi)核必須保證能夠殺死(終止)一個進程,所以這兩個信號就是這個作用)蕾额。通常情況下我們是要對SIGINT(終端下Ctrl-C),SIGTERM(kill 的默認信號),SIGQUIT(終端下Ctrl+),SIGHUP(終端掉線網(wǎng)絡(luò)斷開收到)做一些處理(通常做一些IO同步如將還沒有寫到數(shù)據(jù)庫中的要同步到數(shù)據(jù)庫早芭,將還在緩沖的東西同步到文件等或者是做一些清理銷毀對象等工作),假如你要寫大型程序的時候诅蝶。
說了這么多關(guān)于退个,該說說如何如何發(fā)送信號了,各位久等了
- 在終端下可以用kill/killall命令發(fā)送(缺省為SIGTERM信號)秤涩,如下:
[tutu@localhost Linux-Book]$ ps
PID TTY TIME CMD
2711 pts/0 00:00:00 bash
3990 pts/0 00:00:00 a.sh
3991 pts/0 00:00:00 sleep
3992 pts/0 00:00:00 ps
// kill 可以給一個進程組發(fā)送信號 詳細的大家 man kill
[tutu@localhost Linux-Book]$ kill -SIGKILL 3990
[tutu@localhost Linux-Book]$ ps
PID TTY TIME CMD
2711 pts/0 00:00:00 bash
3991 pts/0 00:00:00 sleep
3993 pts/0 00:00:00 ps
[1]+ 已殺死 ./a.sh
[tutu@localhost Linux-Book]$
- 可以在終端下使用Ctrl+C(SIGINT信號),Ctrl+(SIGQUIT信號).Ctrl+z(SIGSTOP信號)...
- 在程序中可以使用:
// 給指定pid的進程發(fā)送信號
int kill(pid_t pid , int sig) ; 詳細使用,大家man 2 kill
// 給當前進程發(fā)信號司抱,也就是給自己發(fā)信號
int raise (int signo) ;
// 給進程號為pid的進程發(fā)送sig信號筐眷,并攜帶參數(shù)value(需要設(shè)置sigaction的flags的SA_SIGINFO標志才能使用)
int sigqueue ( pid_t pid , int sig , const union sigval value ) ;
// 雖然功能很好,但自己平時使用的比較少习柠,或者基本不用
typedef union sigval {
int sival_int;
// sival_ptr 字段使用的較少匀谣,或者說幾乎不用
void __user *sival_ptr;
} sigval_t;
// 對應(yīng)的結(jié)構(gòu)定義如上。在sigaction函數(shù)中资溃,對應(yīng)struct sigaction中的信號處理函數(shù)的使用
4.與信號相關(guān)的函數(shù)
// 一下詳細內(nèi)容均可以 man 手冊N漪帷!H芏А1Χ瘛!趴捅!
// 古老的定時函數(shù)垫毙,用來測試程序,粗略的定時函數(shù)
unsigned int alarm (unsigned int seconds) ;
// 新的定時函數(shù)拱绑,用于定時综芥,當然也是簡單的定時實現(xiàn)
int setitimer ( int which , const struct itimerval *new_value , \
struct itimerval *old_value ) ;
// 產(chǎn)生SIGABRT信號,一般用于程序出現(xiàn)異常的情況下調(diào)用猎拨,如malloc分配內(nèi)存失敗膀藐。
void abort (void) ;
// 掛起進程,等待設(shè)置在set中的信號產(chǎn)生红省,通過sig返回發(fā)生的并在set中設(shè)置的信號额各。不屏蔽其它信號
int sigwait (const sigset_t * set , int *sig) ;
// 掛起進程等待設(shè)置在set中的信號產(chǎn)生,并接受攜帶的額外信息吧恃,返回產(chǎn)生的信號臊泰。不屏蔽其它信號
int sigwaitinfo (const sigset_t *set , siginfo_t *info ) ;
// 掛起進程等待信號產(chǎn)生,mask中的信號集在等待信號產(chǎn)生期間進行屏蔽
int sigsuspend (const sigset_t * mask ) ;
// 以上內(nèi)容不能逐一舉例,還望大家多加操練操練~喵~
說了這么多廢話缸逃,來看一個實例吧 (例子比較簡單)
[tutu@localhost Linux-Book]$ cat sigqueue_post_signal.cpp
// 信號發(fā)送端:
// filename : sigqueue_post_signal.cpp
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
int main (int argc , char * argv[]) {
if ( 2 != argc ) {
fprintf (stderr , "Bad argument!\nUsage ./post_signal pid\n") ;
exit (1) ;
}
pid_t pid = atoi ( argv[argc-1] ) ;
printf ("Sending signal to %d , by using sigqueue\n" , pid) ;
sigval_t sigval ;
sigval.sival_int = 8888 ;
int errcode = 0 ;
if ( 0 > ( errcode = sigqueue ( pid , SIGUSR1 , sigval ) )) {
if ( ESRCH == errcode ) {
fprintf (stderr , "No such process!\n") ;
exit (1) ;
} else {
fprintf (stderr , "sigqueue error "),perror ("") ;
exit (1) ;
}
}
printf ("Finished!\n") ;
return 0 ;
}
[tutu@localhost Linux-Book]$ gcc sigqueue_post_signal.cpp -o post_signal
[tutu@localhost Linux-Book]$ cat sigqueue_wait.cpp
// filename sigqueue_wait.cpp
// 信號接收端
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
// 注冊SIGUSR1的信號處理函數(shù)
// man sigaction 顯示针饥,當前第三個參數(shù)無卵用,第一個參數(shù)是信號編號需频,第二個是攜帶的信息
void sig_handler (int signo , siginfo_t * info , void * extra ) {
// print signo
printf ("Catch SIGUSR1\n") ;
printf ("signo is %d\n" , signo) ;
// print info -> si_value.sival_ptr
printf ("sigval is %d\n" , info->si_value.sival_int ) ;
}
int main () {
struct sigaction act ;
act.sa_sigaction = sig_handler ;
act.sa_flags = 0 ;
act.sa_flags |= SA_SIGINFO ;
sigemptyset (&act.sa_mask) ;
printf ("My pid is %d\n" , getpid() ) ;
if ( 0 > sigaction (SIGUSR1 , &act , NULL ) ) {
fprintf (stderr , "sigaction error ") , perror ("") ;
exit (1) ;
}
while (1) {
pause () ;
}
return 0 ;
}
[tutu@localhost Linux-Book]$ gcc sigqueue_wait.cpp -o wait_signal
[tutu@localhost Linux-Book]$
首先執(zhí)行wait_signal丁眼,wait_signal 會打印自己的pid,再執(zhí)行post_signal并傳參數(shù)即pid昭殉,之后就可以看到效果了苞七。
對于info->sig_value.sival_ptr字段,在這里暫時不討論其使用挪丢,有興趣的大家可以自行研究蹂风,因為用的十分少。
信號的可靠性概述
上面說了乾蓬,Linux下的信號分為:
- 不可靠信號: [1~31]均為不可靠信號
- 可靠信號:[32~63]為可靠信號或者叫實時信號
可靠信號是為了彌補Linux的不可靠信號以及用戶可使用的信號太少的缺陷惠啄。
怎樣理解可靠與不可靠?下面要引進幾個概念:
- 未決信號:已經(jīng)產(chǎn)生任内,但是沒有給進程遞送的信號(即還未處理)撵渡。
- 已被遞送信號:已經(jīng)遞送給進程并處理過。
當我們在執(zhí)行第一個示例程序的時候,如果在打印完
Catch signal SIGINT processing
之后,我們很快多次按下Ctrl-C瓜挽。會發(fā)現(xiàn)當打印完
Finished process SIGINT return
后阅畴,僅會再響應(yīng)一次信號。這是為什么?為什么我們按下那么多次Ctrl-C卻只響應(yīng)那么一次。原因其時就在于SIGINT是一個不可靠信號。
** 注意铜跑,不是說用sigaction注冊的信號就是可靠的信號了。**
根本原因在于Linux的SIGINT本身就不是可靠的骡澈,也就是說锅纺,在信號處理函數(shù)處理期間或者信號被屏蔽期間多次產(chǎn)生該信號,當信號處理函數(shù)結(jié)束或者進程解除對該信號的屏蔽時只會再報告一次該信號肋殴。因為對于不可靠信號系統(tǒng)并沒有給他們排隊囤锉。也就多次產(chǎn)生只記錄一次了。
說了這么多护锤,測試一下:
[tutu@localhost Linux-Book]$ gcc simple_signal.cpp
[tutu@localhost Linux-Book]$ ./a.out
^CCatch signal SIGINT processing
^C^C^C^C^C^CFinished process SIGINT return
Catch signal SIGINT processing
Finished process SIGINT return
證實了SIGINT不是可靠信號官地,因為多次發(fā)生后會丟失。如何驗證信號被屏蔽后多次發(fā)生烙懦,再解除屏蔽后只會遞送一次驱入?需要用到sigprocmask函數(shù)。再自己寫一個簡單的腳本多次發(fā)送信號,就可以了亏较。這里就不多進行演示了莺褒。
驗證可靠信號(其時就是將第一個程序的注冊信號改成SIGRTMIN,再改改信號處理函數(shù)中打印的信息):
// filenam : simple_realiable_signal.cpp
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#define MSG "Catch signal SIGRTMIN processing \n"
#define MSG_END "Finished process SIGRTMIN return \n"
void do_too_heavy_work () {
long long s = 0 ;
for (long long i = 0 ; i < 500000000L ; i++ ) {
s += i ;
}
}
void sig_handler (int signuum ) {
// 本程序只是為了來進行演示雪情,
// 在信號處理程序中遵岩,盡量不要調(diào)用與標準IO相關(guān)的,不可重入的函數(shù)巡通。
write ( STDOUT_FILENO , MSG , strlen (MSG) ) ;
do_too_heavy_work();
write ( STDOUT_FILENO , MSG_END , strlen (MSG_END) ) ;
}
int main() {
// 注冊信號處理函數(shù)
if ( SIG_ERR == signal ( SIGRTMIN , sig_handler ) ) {
fprintf (stderr , "signal error ") , perror ("") ;
exit (1) ;
}
// 讓主程序不退出尘执,掛起,等待信號產(chǎn)生
while (1) {
pause () ;
}
return EXIT_SUCCESS ;
}
還有一個腳本宴凉,用來發(fā)送信號:
#!/bin/bash
# 過濾出realiable_signal的pid
pid=$(ps aux|grep realiable_signal | grep -v grep | awk '{print $2}')
# 循環(huán)發(fā)送5次信號
for((i=0;i<5;i++))
do
kill -34 $pid
done
執(zhí)行一下看看誊锭,首先編譯realiable_signal
[tutu@localhost Linux-Book]$ gcc simple_realiable_signal.cpp -o realiable_signal
[tutu@localhost Linux-Book]$ ./realiable_signal
再起一個終端運行腳本:
[tutu@localhost Linux-Book]$ ./kill_SIGRTMIN.sh
[tutu@localhost Linux-Book]$
看看運行結(jié)果:
[tutu@localhost Linux-Book]$ gcc simple_realiable_signal.cpp -o realiable_signal
[tutu@localhost Linux-Book]$ ./realiable_signal
Catch signal SIGRTMIN processing
Finished process SIGRTMIN return
Catch signal SIGRTMIN processing
Finished process SIGRTMIN return
Catch signal SIGRTMIN processing
Finished process SIGRTMIN return
Catch signal SIGRTMIN processing
Finished process SIGRTMIN return
Catch signal SIGRTMIN processing
Finished process SIGRTMIN return
講講本質(zhì)上的原因,或許現(xiàn)在不能很快理解弥锄,想詳細了解本質(zhì)上的原因丧靡,大家可以去看內(nèi)核源碼的東西,這個是本質(zhì)上的東西叉讥。我們看到的都是表象窘行,內(nèi)核里寫的才是真實/原理的東西饥追。
每個進程都會有一個結(jié)構(gòu)體图仓,來記錄未決信號集,用戶可以通過
int sigpending (sigset_t *set ) ;
來獲取進程的未決信號集(就是收到信號但是還沒有處理的信號集)但绕,對于不可靠信號集救崔,當被屏蔽或者在信號處理函數(shù)正在執(zhí)行的時候多次發(fā)生,只會將對應(yīng)信號位置1捏顺。多次將一位置1的效果和只置一次1的效果相同六孵,更核心的原因是,不可靠信號內(nèi)核不負責(zé)給它排隊記錄幅骄。這就造成了不可靠劫窒。
對于可靠信號,存在一個雙向鏈表拆座,內(nèi)核判斷是否是可靠信號主巍,如果是,那么即使是屏蔽了該可靠信號或者正在處理可靠信號的信號處理函數(shù)挪凑,在這期間多次發(fā)生孕索,也會將其記錄下來,鏈接到一個雙向鏈表上躏碳,當進程解除了該信號的屏蔽或者處理完信號處理函數(shù)之后搞旭,將其再次遞送給進程,直到鏈表上的信號全部遞送完。這個在struct sigpending 結(jié)構(gòu)體中(現(xiàn)在別看哈肄渗,直到這個概念就好镇眷,過早陷入內(nèi)核,有點不知頭緒恳啥,先打好數(shù)據(jù)結(jié)構(gòu)偏灿,基礎(chǔ)算法,C語言钝的,一定哦o)翁垂。
被中斷的系統(tǒng)調(diào)用
當系統(tǒng)調(diào)用正在被執(zhí)行的時候,發(fā)生了信號腫么辦硝桩?一般情況下我們會這樣做:
ssize_t ret ;
while ( len != 0 && ( ret = read ( fd , buf , len ) != 0 ) {
if ( -1 == ret ) {
// 通過判斷errno即錯誤類型來片判斷read出錯是由于什么具體原因造成的
if ( errno == EINTR ) {
continue ;
}
perror ("read error") ;
break ;
}
len -= ret ;
buf += ret ;
}
ssize_t ret ;
while ( 0 != len && ( ret = write ( fd , buf , len)) != 0 ) {
if ( -1 == ret ) {
if ( errno == EINTR ) {
continue ;
}
perror ( "write error ") ;
break ;
}
len -= ret ;
buf += ret ;
}
但在這里想說的是:如read沿猜,write這樣的系統(tǒng)調(diào)用內(nèi)核已經(jīng)支持其自動重啟了,不需要放太多注意力在這里碗脊。當然有些系統(tǒng)調(diào)用或者說庫函數(shù)啼肩,還是會出現(xiàn)這樣的情況,相關(guān)牽扯到的還有sigaction 的flags的SA_RESTART標志位衙伶,詳細的man 7 signal 介紹的很詳細很詳細祈坠。這里就不抄過來了。
幾種常見的信號處理函數(shù)的設(shè)計方式:
- 信號處理函數(shù)設(shè)置全局性標志變量并退出矢劲。主程序?qū)Υ藰酥具M行周期性檢查赦拘,一旦標志被置位,則進行相應(yīng)的處理動作(如果主程序監(jiān)聽一個或者多個文件描述符的I/O狀態(tài)而無法進行自旋檢測標志是否被設(shè)置芬沉,則可以創(chuàng)建一個管道躺同,通過對管道的文件描述符進行監(jiān)聽,就可以在信號處理函數(shù)中向管道fd寫入一字節(jié)內(nèi)容丸逸,通過主程序中對管道fd事件的處理從而達到檢測全局標志改變的事件蹋艺。)。
簡單看看一個例子:
/*
這個文件是用來實現(xiàn)通過設(shè)置標志位來判斷程序是否在規(guī)定事件內(nèi)完成輸入黄刚,在超時事件內(nèi)如果完成輸入則打印輸入內(nèi)容捎谨,如果未完成則打印錯誤(超時)信息。
*/
//filename : sig_flag.cpp
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/time.h>
#include <string.h>
#define TIMED_OUT 5
#define BUFFER_SIZE 1024
// 這里是一個全局的標志
static volatile sig_atomic_t timedout_flag = 0;
// 原子的將 timedout_flag 置 1
void sig_handler(int signum) {
/* 產(chǎn)生超時事件*/
timedout_flag = 1;
}
int main() {
struct sigaction old_act, new_act;
new_act.sa_handler = sig_handler ;
new_act.sa_flags = 0 ;
sigemptyset (&new_act.sa_mask) ;
// 注冊信號處理函數(shù)憔维, 對應(yīng)定時器到時時調(diào)用sig_handler
if (0 > sigaction(SIGALRM, &new_act, &old_act)) {
fprintf(stderr, "sigaction error "), perror("");
exit(1);
}
struct itimerval new_timer ;
bzero (&new_timer , sizeof new_timer ) ;
new_timer.it_value.tv_sec = TIMED_OUT;
new_timer.it_value.tv_usec = 0;
// 在這里啟動定時器涛救,開始等待是否超時。
if ( 0 > setitimer (ITIMER_REAL , (const struct itimerval *)&new_timer , NULL ) ) {
fprintf(stderr, "setitimer error , line %d\n" , __LINE__) ;
exit(1);
}
// 在這里用read 來模擬任何阻塞的系統(tǒng)調(diào)用
char buffer[BUFFER_SIZE] = {0};
int bytes = read(STDIN_FILENO, buffer, BUFFER_SIZE);
if ( 0 < bytes ) {
printf("正常收到鍵盤輸入埋同。\n");
// 設(shè)置全局標志為 0
timedout_flag = 0 ;
}
// 判斷是否產(chǎn)生超時事件 州叠。
if (1 == timedout_flag) {
printf("等待超時!\n");
// 在這里做一些清理操作,比如 close (fd) 等凶赁, free() 空間等咧栗。
printf("Quit programm now .\n");
} else {
// 未發(fā)生超時事件逆甜,即正常輸入了數(shù)據(jù)。
// 取消鬧鐘
new_timer.it_value.tv_usec = 0 ;
if (0 > setitimer(ITIMER_REAL, &new_timer, NULL)) {
fprintf(stderr, "setitmer error "), perror("");
exit(1);
}
write(STDOUT_FILENO, buffer, strlen(buffer));
}
return 0;
}
2.信號處理函數(shù)值某種類型的清理操作致板。接著終止進程或者使用非本地跳轉(zhuǎn)交煞。將控制返回到主程序的預(yù)定位置。
// 這里涉及到非本地跳轉(zhuǎn)斟或,即:
int setjmp ( jmp_buf env) ;
int longjmp ( jmp_buf env , int val) ;
int sigsetjmp ( sigjmp_buf env , int savesigs) ;
int siglongjmp ( sigjmp_buf env , int val) ;
首先需要理解上述函數(shù)的含義素征,兩組函數(shù)的區(qū)別就是是否在跳轉(zhuǎn)后恢復(fù)信號屏蔽集。前兩個函數(shù)不保留萝挤,即在跳轉(zhuǎn)后不恢復(fù)信號屏蔽集御毅,后兩個函數(shù)則恢復(fù)(當savesigs為非0詳細的 man sigsetjmp)。
** 由于只是初學(xué)怜珍,就不研究這個例子了 **
設(shè)置信號處理函數(shù)需要注意到的
- 應(yīng)該了解到端蛆,每一個線程只有一個errno值,所以信號處理程序可能會修改errno的值酥泛。因此今豆,作為一個通用的規(guī)則,應(yīng)當在調(diào)用那些可能改變errno的函數(shù)之前保存errno值柔袁,之后再進行恢復(fù)呆躲。
- 在信號處理函數(shù)中調(diào)用一個非可重入函數(shù),其結(jié)果未知的捶索,所以盡量不要在信號處理
函數(shù)中使用非可重入函數(shù) 插掂, 判斷是否是可重入函數(shù)有一個簡單的原則:
- 函數(shù)內(nèi)部不使用操作靜態(tài)變量。
- 不使用與標準I/O相關(guān)函數(shù)情组。
- 不使用malloc 燥筷,free() 函數(shù)箩祥。
- 不調(diào)用其它非可重入函數(shù)院崇。
關(guān)于可重入的概念,大家自行查找一下袍祖,或參考APUE底瓣,TLPI相關(guān)章節(jié)。簡單理解就是可以同時被多個進程/線程執(zhí)行的一段代碼(函數(shù))而不會出現(xiàn)錯誤蕉陋。
本章完
參考文獻及博客:
《Unix環(huán)境高級編程 第三版》
《The Linux Programming Interface》
http://blog.chinaunix.net/uid-24774106-id-4061386.html
上述鏈接共4篇
http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html
上述鏈接共2篇
** 初學(xué)者以前兩本書為主捐凭,當有Linux操作系統(tǒng)知識后可以詳細參考后兩個鏈接中內(nèi)容。十分詳實凳鬓。喵~**
.