2012年8月21日 星期二

linux後端服務程式之信號處理


信號就是通知某個進程發了某個事件,也稱為軟體插斷。信號提供了一種處理非同步事件的方法。信號通常是非同步發生的,進程預先不知道信號準確發生的時刻。後端程式(daemon)往往需要提供7*24不間斷的服務,因此,程式設計daemon程式時對信號的正確處理尤為重要。


下面和大家分享編寫daemon程式時信號處理的注意事項,內容都來自Internet,只是進行了整理和總結。關於信號的基礎只是請參考APUE

常見的信號

SIGHUP 1 和終端的連接斷開,發送該信號給控制進程。通常用此信號來通知daemon重新讀取設定檔(因為daemon不會有控制終端,通常不會收到該信號)。

SIGINT 2
用戶中斷(Ctrl + C)。
SIGABRT 6 調用abort函數產生(通常是自殺)。
SIGKILL 9 可以殺死任意進程,不能被捕獲或忽略(俗稱酒殺)。
SIGSEGV 11 無效的記憶體引用(segmentation fault)。
SIGPIPE 13
對於socket fd,當一個進程向某個已經收到RSTfd執行寫操作時,內核會向該進程發送該信號。
SIGTERM 15 kill
命令發送的預設終止信號。
SIGCHLD 17
進程終止時向其父進程發送的信號。
SIGPROF 27
使用gprof工具測試時會收到該信號。

別用signal,用sigaction

可以通過signal或者sigaction函數來設置信號處理函數,但signal函數太過古老,因此推薦使用sigaction。理由如下:
1. sigaction可以提供更多接收到信號的資訊。
2. 調用完信號處理函數後重新設置處理函數不會對sigaction有影響,因為sigaction默認是不會去重置處理函數的,同時在執行處理函數會遮罩掉該信號,也不會有競爭。
3. signal
函數在某些系統中會預設重啟被中斷的系統調用,而sigaction預設不會這樣做。
4. signal
函數在多執行緒環境中的行為是未定義的,必須使用sigaction函數。

資料傳輸和信號

使用了系統調用的函數都有可能被信號中斷,立刻返回的函數(不需要等待I/O操作的完成或sleep)不會被中斷,而需要等待的函數(等待網路傳輸,管道的讀或者sleep)將會被中斷,比如select, read, connect
daemon程式中,恰當地處理被中斷的系統調用是非常重要的。如果read, write等傳輸資料的函數被中斷,必須處理這種情況並恢復資料的傳輸。有兩種被中斷的場景:
1.
當沒有資料傳輸時就被中斷,函數返回-1,這時可以通過判斷errno的值來識別這種錯誤,如果errno == EINTR,則表示函數在沒有任何資料傳輸的情況下就被中斷,這時可以通過同樣的參數來再次調用該資料。
2.
另一種情況是資料傳輸已經在進行,但在沒有完成之前被中斷;這種情況下函數不會返回錯誤,而是返回一個小於期望大小的值,同時errno也不會有錯誤設置,想識別這種情況只能捕獲導致中斷的信號。在中斷之後恢復資料傳輸時一定記得部分資料已經被傳輸,必須從正確的偏移再次發起傳輸。
不要通過sigaction函數設置SA_RESTART來處理被中斷的系統調用。

多執行緒和信號

多執行緒程式的信號處理和單執行緒程式有很大的區別。根據POSIX規範,一個多執行緒的程式只有一個進程和一個pid,哪個執行緒會被中斷並處理到達的信號呢?有兩種情況:
·         發送給進程的信號 比如用kill向一個pid發送信號,每個執行緒都有單獨的信號遮罩,可以通過pthread_sigmask來設置。信號不會分發給已經遮罩了該信號的執行緒,而是在沒有遮罩該信號的執行緒中任選一個來接收,但沒有指定哪個執行緒一定可以接收到。如果所有的執行緒都遮罩了該信號,該信號將在預處理佇列中排隊。
·         發送給執行緒的信號 pthread_kill可以用來向指定的執行緒發送信號,分發和排隊都是執行緒級別的。如果沒有指定信號處理函數,而預設是結束進程,發送給執行緒的信號也會導致整個進程退出。
不要在信號處理函數中使用鎖。

同步化信號處理 signalfd

signalfd是一個在linux kernel 2.6.22提供的系統調用,功能是使用一個fd來接收信號。這樣就可以同步地處理信號,也不需要設置處理函數。可以man signalfd 查看示例程式。首先必須使用sigprocmask來遮罩要使用signalfd來處理的信號,然後調用signalfd創建一個fd用來讀取到達的信號。當被遮罩的信號到達時,程式將不會被中斷,也不會有處理函數被調用。信號會在fd中排隊。signalfd創建的fd可以和其他fd一樣:可以放在select, poll, epoll中;可以設置為非阻塞;可以為不同的信號創建不同的fd;在fork之後該fd也不會關閉掉,子進程同樣可以讀懂發送給父進程的信號。signalfd非常適合在主迴圈中執行epoll處理大量連接的單進程網路服務程式中使用,信號的處理可以和其他fd一樣加到epoll中。由於程式不會被中斷,可以選擇合適的時機才去處理信號。

沒有留言: