AIX 7.1,xl C/C++ 13.1.0
UNIX/LINUX信號方面涉及若干函數,如signal、sigcation、setjmp、longjmp函數,還有kill、pause、wait等函數。有關這些函數的介紹網上已經很多了,不再贅述。
下面我們先來看看signal和setjmp、longjmp的使用案例。考慮如下代碼:
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
jmp_buf g_sjbuf;
void onintr(int s);
int main(int argc, char * argv[])
{
if(SIG_IGN != signal(SIGINT, SIG_IGN))
{
printf("!= SIG_IGN\n"); <span style="color:#3333ff;">// <<<<<<<<<< 會不會打印出來?</span>
signal(SIGINT, onintr);
}
//setjmp(g_sjbuf); <span style="color:#3333ff;">// <<<<<<<<<< 甲處</span>
for(int i = 0; ; i++)
{
//setjmp(g_sjbuf); <span style="background-color: rgb(204, 204, 204);"> <span style="color:#3333ff;">// <<<<<<<<<< 乙處</span> </span>
printf("for = %d\n", i);
sleep(2);
//setjmp(g_sjbuf); <span style="color:#3333ff;">// <<<<<<<<<< 丙處</span>
}
return 0;
}
void onintr(int s)
{
signal(SIGINT, onintr);
printf("\nInterrupt s = %d\n", s);
longjmp(g_sjbuf, 0);
return;
}
編譯,運行。
首先,!= SIG_IGN會不會被打印出來?
結果:會。
程序一開始運行時,調用signal返回的是上一次處理該信號的回調函數(返回的是一個函數指針,這種類型:void(*)(int)),由於這是程序進來第一次調用signal,所以返回的應該是系統處理該信號的“方法”,就是默認的處理方式,當然不是SIG_IGN(忽略),所以printf就被執行了。那這返回值是個啥玩意兒呢?把相關代碼改成這樣(順便看看SIG_IGN是個什麼鬼):
void (* pVal)(int) = signal(SIGINT, SIG_IGN);
printf("pVal = 0x%X, SIG_IGN = 0x%X\n", (int)pVal, (int)SIG_IGN);
if(pVal != SIG_IGN)
瞅瞅。
結果:
# ./a.out
pVal = 0x0, SIG_IGN = 0x1
!= SIG_IGN
for = 0
...
那麼就很清楚了,SIG_IGN這個函數指針的值是1,系統默認處理SIGINT的函數指針是0(居然是0)。
第一個問題搞清楚了,我們再來研究中斷返回的問題。
調用signal把SIGINT信號的服務程序註冊為onintr函數,當該信號到來時,就會跳到onintr函數執行(要不要看看彙編代碼?嗷,算了吧),然後返回,返回點是onintr函數尾部的這麼一句:
longjmp(g_sjbuf, 0);
簡單來說,longjmp與setjmp配對使用,後者保存中斷前的狀態(就算是上下文吧),由longjmp跳轉回去。這裡畢竟跟操作系統的任務切換還是不同的,操作系統通過處理器的如壓棧、關中斷等等相關指令保存上下文、切換任務,都不需要程序關心,操作系統幫你做了,而在這裡的信號處理需要程序員手動指定在哪而保存,在哪返回。
假設上述代碼中的for循環是程序主要要做的事,按照常規思維通過signal函數設置好信號服務程序(這裡套用中斷服務程序的概念)後,就可以“保存現場”以供返回了,也就是代碼的甲處,實際上,書上也是這麼寫的。我們來看看這樣寫的運行結果:
# ./a.out
pVal = 0x0, SIG_IGN = 0x1
!= SIG_IGN
for = 0
for = 1
for = 2
for = 3
Interrupt s = 2 <<<<按下了CRTL + C鍵
for = 0 <<<<for從頭開始了
for = 1
for = 2
for = 3
for = 4
for = 5
Interrupt s = 2
for = 0 <<<<再次從頭開始
for = 1
Interrupt s = 2
for = 0
for = 1
for = 2
for = 3
Quit(coredump) <<<<按下CRTL + /鍵,結束程序
#
可見,每當按下CTRL + C鍵觸發SIGINT中斷,進入onintr函數打印Interrupts = 2語句,然後返回,返回到setjmp的位置,也就是甲處,所以for又得重頭來。剛才說過,for裡面是程序的主要任務,比如複雜的科學計算、氣象預報、飛彈執導、反擊外星人什麼的,這樣一搞就重來了,外星人都要被揍一把,顯然不合理啊。所以我們把setjmp移到乙處。
# ./a.out
pVal = 0x0, SIG_IGN = 0x1
!= SIG_IGN
for = 0
for = 1
for = 2
for = 3
Interrupt s = 2 <<<<按下了CRTL + C鍵
for = 3 <<<<什麼?這不是上次的結果嗎?
for = 4
Interrupt s = 2
for = 4 <<<<再次證明
for = 5
for = 6
Quit(coredump)
#
結果表明for在onintr返回後重複循環了一次,這也不太好。仔細研究發現這是由於乙處在循環的開頭,所以onintr執行完成後就有返回到當次循環的開頭了,所以重複執行那次循環。既然這樣,就把setjmp移到循環的尾部去,就像丙處那樣!
# ./a.out
pVal = 0x0, SIG_IGN = 0x1
!= SIG_IGN
for = 0
for = 1
for = 2
for = 3 <<<<注意,這裡是3
Interrupt s = 2 <<<<按下了CRTL + C鍵
for = 4 <<<<哈哈,接上了
for = 5
for = 6
Interrupt s = 2
for = 7 <<<<再試一把,也是正確的
for = 8
for = 9
for = 10
for = 11
Interrupt s = 2
for = 12
Quit(coredump)
#
# ./a.out
pVal = 0x0, SIG_IGN = 0x1
!= SIG_IGN
for = 0 <<<<剛一printf就按CTRL+ C
Interrupt s = 2 <<<<觸發了SIGINT信號
Segmentation fault(coredump) <<<<該返回了,出錯!
#
程序出錯了,還沒setjmp就longjmp,引發了個分段錯誤,屬於內存錯誤的一種,致命錯誤,cpu不知道該從哪執行了,所以進程被終止了。可以進一步研究,其實不是cpu不知道該從哪執行,g_sjbuf沒被初始化,裡面的值是未知的(不知道是不是像windows那樣的“燙燙燙”哈),cpu就按那裡面的內容解讀,跑到一個啥地方去執行了,這大概率是要出錯的(不是絕對哈,概率我沒算過。。),結果果真錯了,程序終止了。
這該如何是好?!甲乙丙三處setjmp都不好!
剛才我改代碼,無意少加了一處注釋,導致甲和丙的同時存在,結果。。。挺好使!
我們先看看代碼,一上來設置完“信號服務程序”就保存一把現場,這是能做到的最早的setjmp的地方了,有信號過來,就不怕了(有地兒返回),而且還沒進入循環,返回的地點也是正確的。然後循環開始做事,不能保存,一保存會造成重複循環;循環到尾部,保存一把,即丙處,這樣就算信號來了,也不會重複循環,因為本輪循環已經要結束了;下一輪循環也是一樣,不會有問題。測一把:
# ./a.out
pVal = 0x0, SIG_IGN = 0x1
!= SIG_IGN
for = 0 <<<<剛一printf就按CTRL+ C
Interrupt s = 2 <<<<沒完蛋
for = 0 <<<<出現了一次重複
for = 1
for = 2
for = 3
for = 4
Interrupt s = 2
for = 5 <<<<以後就都正常了
for = 6
Interrupt s = 2
for = 7
Quit(coredump)
#
沒出現段錯誤,但有一次重複,就是第一次,分析代碼發現第一次重複不可避免,後面的不會了,測試結果也表明如此。還有個問題,在某次循環當中發生SIGINT信號,會回到上次循環末執行,也就是中間可能有一部分代碼還是會被重複執行。能否避免呢?——除非你運行一行代碼就setjmp一把,步步保存,這肯定不可行。還需探索更好的方式。
http://blog.csdn.net/ncepubdtb/article/details/42458087
可以一讀。