說明:本文的上下文環境是 unix/linux作業系統,g++編譯環境, gdb調試環境。
signal是 阻塞模式的良好搭擋。一些需要低速調用的程式,例如 socket、oracle串連,若工作在非阻塞模式下,輪詢會有大量的CPU開銷,而阻塞模式則使得程式不能及時處理那些需要即時響應的操作。signal可以將程式從阻塞狀態喚醒,以處理緊急事件,然後再做別的事情。
unix_program_advance 一書中第十章,詳細地介紹了 unix 系統提供的訊號捕獲函數。
在開發中應注意:
1、確定要使用阻塞還是非阻塞。oci和 socket中,預設都是阻塞的。
在socket 中,可以使用 fcntl()來設定O_NONBLOCK, O_NDELAY.
if (fcntl(fd, F_SETFL, orig_flags | O_NONBLOCK) < 0)
{
}
在oci中,可以使用 OCIAttrSet()函數的 OCI_ATTR_NONBLOCKING_MODE 屬性值來設定oci的操作為非阻塞模式。詳細的用法見 Oracle Call Interface Programmer's Guide B10779-01. chapter 2 中 Nonblocking Mode in OCI 一節。
當socket為阻塞模式時,可以使用 setsockopt來設定 recv的逾時時間。
1、任何一個可能無限阻塞的調用,調用之間加一個alarm()
在oracle中,如果在呼叫 oracle時網路掉線。則函數會永遠阻塞。所以,有必要在可能阻塞的函數前,加一個alarm()以在逾時後將其喚醒。
下面樣本了訊號的使用。當函數調用超過3秒後,跳出函數並重新串連資料庫以執行此操作。直到操作三次仍不能成功,則放棄操作。
do
{
alarm(3);
if( table.query() == false && errno == EINTR )
{
conndb();
}
else
{
break;
}
}while (retryTimes < 3)
alarm(0);
2、用 sigaction() 而不是 signal() 來設定訊號鉤子。signal()跨平台效能不好。在 linux下跑得很健壯的程式,到了 hp-unix或 ibm-aix上表現得非常糟糕,而 solaris上 signal 又有不同的表現。較於 signal, sigaction 定義了被中斷的低速調用是否重新喚起 ( 預設不喚起);sigactionl() 還有屏蔽功能,這使得訊號處理函數可以專註地處理當前的訊號。
3、當前訊號處理函數結束前要喚起先前定義的訊號處理函數。
sigaction( signo, NULL, oldHandle );
4、可以設定捕獲所有的訊號,以避免程式收到訊號時意外退出。訊號編號下界限為1,最高界限是SIGMAX,可以使用for()迴圈設定鉤子。
5、子進程會繼承父進程的訊號鉤子,但不能繼承父進程中已經設定的訊號(例如,鬧鐘訊號)。
6、如果想在收到訊號後跳出當前的上下文環境,用 throw()。
相關資料 :
1、 <the design of the unix operationg system>第 7.2 訊號 從作業系統實現的角度,詳細地介紹了訊號的分類、訊號如何被喚醒、訊號在什麼情況下會丟失或引發意外。(注 : 書中討論的內容是 原始版本的 signal(),實際上, sigaction 已經解決了訊號丟失、訊號異常的問題。)
2、<Advanced Programming in the UNIX Environment> 第 10章詳細介紹了不可靠訊號signal、可靠訊號sigaction在 posix.1、SVR4、4.3+BSD中,訊號的實現。並給出了對訊號進行處理的相關源碼。
使用訊號的完整樣本:
訊號一個極有用的使用是:將進程從阻塞狀態喚醒。
訊號有一定的局限性。訊號不能像 message 那樣傳遞訊息結構,而只能傳遞一個簡單的訊號。
訊號會破壞程式的流程。因為我們不知道訊號究竟什麼時候會發生。
一個比較合理的處理辦法是:
當進程收到關注的訊號時,執拋出一個異常。這樣,程式可以根據自己的意願想停就停,想開就開。
樣本如下: