信號就是通知某個進程發了某個事件,也稱為軟體插斷。信號提供了一種處理非同步事件的方法。信號通常是非同步發生的,進程預先不知道信號準確發生的時刻。後端程式(daemon)往往需要提供7*24不間斷的服務,因此,程式設計daemon程式時對信號的正確處理尤為重要。
下面和大家分享編寫daemon程式時信號處理的注意事項,內容都來自Internet,只是進行了整理和總結。關於信號的基礎只是請參考APUE。
常見的信號
SIGHUP 1 和終端的連接斷開,發送該信號給控制進程。通常用此信號來通知daemon重新讀取設定檔(因為daemon不會有控制終端,通常不會收到該信號)。
SIGINT 2 用戶中斷(Ctrl + C)。
SIGINT 2 用戶中斷(Ctrl + C)。
SIGABRT 6 調用abort函數產生(通常是自殺)。
SIGKILL 9 可以殺死任意進程,不能被捕獲或忽略(俗稱酒殺)。
SIGSEGV 11 無效的記憶體引用(segmentation fault)。
SIGPIPE 13 對於socket fd,當一個進程向某個已經收到RST的fd執行寫操作時,內核會向該進程發送該信號。
SIGTERM 15 kill命令發送的預設終止信號。
SIGCHLD 17 進程終止時向其父進程發送的信號。
SIGPROF 27 使用gprof工具測試時會收到該信號。
SIGPIPE 13 對於socket fd,當一個進程向某個已經收到RST的fd執行寫操作時,內核會向該進程發送該信號。
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函數。
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來處理被中斷的系統調用。
在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中。由於程式不會被中斷,可以選擇合適的時機才去處理信號。
沒有留言:
張貼留言