APR分析-信號(hào)篇
U know信號(hào)是Unix的重要系統(tǒng)機(jī)制。信號(hào)機(jī)制使用起來(lái)很簡(jiǎn)單,但是理解起來(lái)有并不是那么Easy。APR
Signal的封裝也并不繁瑣,代碼量很少,所以分析APR Signal的過(guò)程其實(shí)就是學(xué)習(xí)Signal機(jī)制的過(guò)程。
一、信號(hào)介紹
1、Signal“歷史久遠(yuǎn)”,在最初的Unix系統(tǒng)上就能看到它“偉岸”的身影。它的引入用來(lái)進(jìn)行User
Mode進(jìn)程間的交互,系統(tǒng)內(nèi)核也可以利用它通知User Mode進(jìn)程發(fā)生了哪些系統(tǒng)事件。從最開始引入到現(xiàn)在,信號(hào)只是做了很小的一些改動(dòng)(不可靠信號(hào)模型到可靠信號(hào)模型)。
2、信號(hào)服務(wù)于兩個(gè)目的:
1)通知某進(jìn)程某特定事件發(fā)生了;
2)強(qiáng)制其通知進(jìn)程執(zhí)行相應(yīng)的信號(hào)處理程序。
二、基礎(chǔ)概念
1、信號(hào)的一個(gè)特性就是可以在任何時(shí)候發(fā)給某一進(jìn)程,而無(wú)需知道該進(jìn)程的狀態(tài)。如果該進(jìn)程當(dāng)前并未處于執(zhí)行態(tài),則該信號(hào)被內(nèi)核Save起來(lái),直到該進(jìn)程恢復(fù)執(zhí)行才傳遞給它;如果一個(gè)信號(hào)被進(jìn)程設(shè)置為阻塞,則該信號(hào)的傳遞被延遲,直到其阻塞被取消它才被傳遞給進(jìn)程。
2、系統(tǒng)內(nèi)核嚴(yán)格區(qū)分信號(hào)傳送的兩個(gè)階段:
1) Signal Generation :系統(tǒng)內(nèi)核更新目標(biāo)進(jìn)程描述結(jié)構(gòu)來(lái)表示一個(gè)信號(hào)已經(jīng)被發(fā)送出去。
2) Signal Delivery :內(nèi)核強(qiáng)制目標(biāo)進(jìn)程對(duì)信號(hào)做出反應(yīng),或執(zhí)行相關(guān)信號(hào)處理函數(shù),或改變進(jìn)程執(zhí)行狀態(tài)。
信號(hào)的誕生和傳輸我們可以這樣理解:把信號(hào)作為“消費(fèi)品”,其Generation狀態(tài)就是“消費(fèi)品誕生”,其Delivery狀態(tài)就是理解為“被消費(fèi)了”。這樣勢(shì)必存在這樣的一個(gè)情況:“消費(fèi)品誕生了,但是還沒(méi)有被消費(fèi)掉”,在信號(hào)模型中,這樣的狀態(tài)被稱為“pending”(懸而未決)。
任何時(shí)候一個(gè)進(jìn)程只能有一個(gè)這樣的某類型的pending信號(hào),同一進(jìn)程的其他同類型的pending信號(hào)將不排隊(duì),將被簡(jiǎn)單的discard(丟棄)掉。
3、如何消費(fèi)一個(gè)signal
1)忽略該信號(hào);[注1]
2)響應(yīng)該信號(hào),執(zhí)行一特定的信號(hào)處理函數(shù);
3)響應(yīng)該信號(hào),執(zhí)行系統(tǒng)默認(rèn)的處理函數(shù)。包括:Terminate、Dump、Ignore、Stop、Continue等。
這里有特殊:SIGKILL和SIGSTOP兩個(gè)信號(hào)不能忽略、不能捕捉、不能阻塞,而只是執(zhí)行系統(tǒng)默認(rèn)處理函數(shù)。
三、APR Signal封裝
APR Signal源代碼的位置在$(APR_HOME)//threadproc目錄下,本篇blog著重分析unix子目錄下的signals.c文件內(nèi)容,其相應(yīng)頭文件為$(APR_HOME)/include/apr_signal.h。
1、apr_signal函數(shù)
Unix信號(hào)機(jī)制提供的最簡(jiǎn)單最常見的接口是signal函數(shù),用來(lái)設(shè)置某特定信號(hào)的處理函數(shù)。但是由于早期版本和后期版本處理信號(hào)方式的不同,導(dǎo)致現(xiàn)在直接使用signal函數(shù)在不同的平臺(tái)上可能得到不同的結(jié)果。
早期版本處理方式:進(jìn)程每次處理信號(hào)后,隨即將信號(hào)的處理動(dòng)作重置為默認(rèn)值。
后期版本處理方式:進(jìn)程每次處理信號(hào)后,信號(hào)的處理動(dòng)作不被重置為默認(rèn)值。
我們舉例測(cè)試一下:分別在Solaris9、Cygwin和RedHat Linux 9上。
例子:
E.G 1:
void siguser1_handler(int sig);
int main(void)
{
if (signal(SIGUSR1,siguser1_handler) == SIG_ERR) {
perror("siguser1_handler error");
exit(1);
}
while (1) {
pause();
}
}
void siguser1_handler(int sig)
{
printf("insiguser1_handler, %d/n", sig);
}
input:
kill -USR1 9122
kill -USR1 9122
output:(Solaris 9)
in siguser1_handler, 16
用戶信號(hào)1 (程序終止)
output:(Cygwin and RH9)
in siguser1_handler, 30
in siguser1_handler, 30
...
..
E.G 1結(jié)果表示在Solaris 9上,信號(hào)的處理仍然按照早期版本的方式,而Cygwin和RH9則都按照后期版本的方式。
那么有什么替代signal函數(shù)的辦法么?在最新的X/Open和UNIXspecifications中都推薦使用一個(gè)新的信號(hào)接口sigaction,該接口采用后期版本的信號(hào)處理方式。在《Unix高級(jí)環(huán)境編程》中就有使用sigaction實(shí)現(xiàn)signal的方法,而APR恰恰也是使用了該方法實(shí)現(xiàn)了apr_signal。其代碼如下:
APR_DECLARE(apr_sigfunc_t *) apr_signal(int signo, apr_sigfunc_t *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);?------------------(1)
act.sa_flags = 0;
#ifdefSA_INTERRUPT????????????/* SunOS */
act.sa_flags |= SA_INTERRUPT;
#endif
... ...
if (sigaction(signo, &act, &oact) <0)
return SIG_ERR;
return oact.sa_handler;
}
(1)這里有一個(gè)Signal Set(信號(hào)集)的概念,通過(guò)相關(guān)函數(shù)操作信號(hào)集以改變內(nèi)核傳遞信號(hào)給進(jìn)程時(shí)的行為。Unix用sigset_t結(jié)構(gòu)來(lái)表示信號(hào)集。信號(hào)集總是和sigprocmask或sigaction一起使用。關(guān)于信號(hào)集和sigprocmask函數(shù)將在下面詳述。
2、apr_signal_block和apr_signal_unblock
這兩個(gè)函數(shù)分別負(fù)責(zé)阻塞和取消阻塞內(nèi)核傳遞某信號(hào)給目標(biāo)進(jìn)程。其主要利用的就是sigprocmask函數(shù)來(lái)實(shí)現(xiàn)的。每個(gè)進(jìn)程都有其對(duì)應(yīng)的信號(hào)屏蔽字,它讓目標(biāo)進(jìn)程能夠通知內(nèi)核“哪些傳給我的信號(hào)該阻塞,哪些暢通無(wú)阻”。在《Unix高級(jí)環(huán)境編程》中作者有這么一段說(shuō)明“如果在調(diào)用sigprocmask后有任何未決的、不再阻塞的信號(hào),則在sigprocmask返回前,至少將其中之一遞送給該進(jìn)程。”能理解這句我想信號(hào)屏蔽字這塊兒也就沒(méi)什么問(wèn)題了。在Unix高級(jí)環(huán)境編程》中作者舉了一個(gè)很不錯(cuò)的例子,講解的也很詳細(xì)。這里想舉例說(shuō)明的是:如果多次調(diào)用SET_BLOCK的sigprocmask設(shè)置屏蔽字,結(jié)果是什么呢?
E.G 3
int main(void)
{
sigset_t newmask,oldmask, pendmask;
/*設(shè)置進(jìn)程信號(hào)屏蔽字,阻塞SIGQUIT */
sigemptyset(&newmask);
sigaddset(&newmask,SIGQUIT);
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
perror("SIG_BLOCK error");
}
printf("1st towait 30 seconds/n");
sleep(30);
/*第一次察看當(dāng)前的處于pend狀態(tài)的信號(hào)*/
if(sigpending(&pendmask) < 0) {
perror("sigpending error");
}
if(sigismember(&pendmask, SIGQUIT)) {
printf("SIGQUIT pending/n");
} else {
printf("SIGQUIT unpending/n");
}
if(sigismember(&pendmask, SIGUSR1)) {
if(sigismember(&pendmask, SIGUSR1)) {
printf("SIGUSR1 pending/n");
} else {
printf("SIGUSR1 unpending/n");
}
/*重新設(shè)置屏蔽字,阻塞SIGUSR1 */
sigemptyset(&newmask);
sigaddset(&newmask,SIGUSR1);
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
perror("SIG_BLOCK error");
}
printf("2nd towait 30 seconds/n");
sleep(30);
/*再次察看當(dāng)前的處于pend狀態(tài)的信號(hào)*/
if(sigpending(&pendmask) < 0) {
perror("sigpending error");
}
if(sigismember(&pendmask, SIGQUIT)) {
printf("SIGQUIT pending/n");
} else {
printf("SIGQUIT unpending/n");
}
if(sigismember(&pendmask, SIGUSR1)) {
printf("SIGUSR1 pending/n");
} else {
printf("SIGUSR1 unpending/n");
}
exit(0);
}
//output:
1st to wait 30 seconds
^/
SIGQUIT pending
SIGUSR1 unpending
2nd to wait 30 seconds --這之后發(fā)送kill -USR128821
SIGQUIT pending
SIGUSR1 pending
第一次輸出SIGUSR1unpending是因?yàn)椴⑽窗l(fā)送USR1信號(hào),所以自然為unpending狀態(tài);我想說(shuō)的是第二次重新sigprocmask時(shí)我們僅加入了SIGUSR1,并未顯示加入SIGQUIT,之后察看pending信號(hào)中SIGQUIT仍然為pending狀態(tài),這說(shuō)明兩次SET_BLOCK的sigprocmask調(diào)用是"或"的關(guān)系,第二次SET_BLOCK的sigprocmask調(diào)用不會(huì)將第一次SET_BLOCK的sigprocmask調(diào)用設(shè)置的阻塞信號(hào)變?yōu)榉亲枞摹?/p>
四、總結(jié)
信號(hào)簡(jiǎn)單而強(qiáng)大,如果想深入了解signal的實(shí)現(xiàn),參考資料中的第二本書會(huì)給你滿意的答案。
五、參考資料:
1、《Unix高級(jí)環(huán)境編程》
2、《深入理解Linux內(nèi)核》
[注1]
忽略信號(hào)和阻塞信號(hào)
前者相當(dāng)于一個(gè)消費(fèi)行為,該信號(hào)的狀態(tài)為“已消費(fèi)”,而后者只是將信號(hào)做緩存,等待阻塞打開,再交給進(jìn)程消費(fèi),其狀態(tài)為“未消費(fèi)”,也相當(dāng)于處于pending狀態(tài)。