socket中的函式遇見EINTR的處理
轉載 : https://blog.csdn.net/benkaoya/article/details/17262053
http://blog.chinaunix.net/uid-21501855-id-4490453.html
這幾天,寫伺服器程式碼過程當中,遇見EINRT訊號的問題,我是借鑑 《unp 》,採用continue或者goto again迴圈解決的。但是感覺這個還是很有必要記錄一下。網路上查詢到的資訊很多。下面是我查詢到的和EINTR有關的介紹:
1 http://blog.csdn.net/yanook/article/details/7226019 慢系統呼叫函式如何處理中斷訊號EINTR
2 http://blog.csdn.net/benkaoya/article/details/17262053 訊號中斷 與 慢系統呼叫
3 http://1.guotie.sinaapp.com/?p=235 socket,accept,connect出現EINTR錯誤的解決方法
個人認為2說的比較明確,建議大家多看看。
我的記錄基本上是對2的抄襲:
慢系統呼叫:可能永遠阻塞的系統呼叫,這很關鍵,不適用於非諸塞的情況。永遠阻塞的系統呼叫是指呼叫永遠無法返回,多數網路支援函式都屬於這一類。如:若沒有客戶連線到伺服器上,那麼伺服器的accept呼叫就會一直阻塞。
(以下為抄襲2原文)
EINTR說明:如果程序在一個慢系統呼叫(slow system call)中阻塞時,當捕獲到某個訊號且相應訊號處理函式返回時,這個系統呼叫被中斷,呼叫返回錯誤,設定errno為EINTR(相應的錯誤描述為“Interrupted system call”)。
怎麼看哪些系統條用會產生EINTR錯誤呢?man 7 signal,在ubuntu 10.04上可以檢視,哪些系統呼叫會產生 EINTR錯誤。
如何處理被中斷的系統呼叫
既然系統呼叫會被中斷,那麼別忘了要處理被中斷的系統呼叫。有三種處理方式:
◆ 人為重啟被中斷的系統呼叫
◆ 安裝訊號時設定 SA_RESTART屬性(該方法對有的系統呼叫無效)
◆ 忽略訊號(讓系統不產生訊號中斷)
人為重啟被中斷的系統呼叫
人為當碰到EINTR錯誤的時候,有一些可以重啟的系統呼叫要進行重啟,而對於有一些系統呼叫是不能夠重啟的。例如:accept、read、write、select、和open之類的函式來說,是可以進行重啟的。不過對於套接字程式設計中的connect函式我們是不能重啟的
這裡的“重啟”怎麼理解?
一些IO系統呼叫執行時,如 read 等待輸入期間,如果收到一個訊號,系統將中斷read, 轉而執行訊號處理函式. 當訊號處理返回後, 系統遇到了一個問題: 是重新開始這個系統呼叫, 還是讓系統呼叫失敗?早期UNIX系統的做法是, 中斷系統呼叫,並讓系統呼叫失敗, 比如read返回 -1, 同時設定 errno 為EINTR中斷了的系統呼叫是沒有完成的呼叫,它的失敗是臨時性的,如果再次呼叫則可能成功,這並不是真正的失敗,所以要對這種情況進行處理, 典型的方式為:
另外,原文建議上去github上看看別人怎麼處理EINTR錯誤的,下面2個處理方面均是截圖過來的:
connect處理方式,抄襲3原文,沒有測試過,處理方法是對的。
connect的問題,當connect遇到EINTR錯誤時,不能向上面那樣重新進入迴圈處理,原因是,connect的請求已經發送向對方,正在等待對方迴應,這是如果重新呼叫connect,而對方已經接受了上次的connect請求,這一次的connect就會被拒絕,因此,需要使用select或poll呼叫來檢查socket的狀態,如果socket的狀態就緒,則connect已經成功,否則,視錯誤原因,做對應的處理。
#include poll.h int check_conn_is_ok(socket_t sock) { struct pollfd fd; int ret = 0; socklen_t len = 0; fd.fd = sock; fd.events = POLLOUT; while ( poll (&fd, 1, -1) == -1 ) { if( errno != EINTR ){ perror("poll"); return -1; } } len = sizeof(ret); if ( getsockopt (sock, SOL_SOCKET, SO_ERROR, &ret, &len) == -1 ) { perror("getsockopt"); return -1; } if(ret != 0) { fprintf (stderr, "socket %d connect failed: %s\n", sock, strerror (ret)); return -1; } return 0; }
在呼叫connect時,這樣使用:
#include erron.h .... if(connnect()) { if(errno == EINTR) { if(check_conn_is_ok() < 0) { perror(); return -1; } else { printf("connect is success!\n"); } } else { perror("connect"); return -1; } }
我一般使用continue或者goto來處理。
安裝訊號時設定 SA_RESTART屬性
我們還可以從訊號的角度來解決這個問題, 安裝訊號的時候, 設定 SA_RESTART屬性,那麼當訊號處理函式返回後, 不會讓系統呼叫返回失敗,而是讓被該訊號中斷的系統呼叫將自動恢復。
[cpp] view plaincopy
- struct sigaction action;
- action.sa_handler = handler_func;
- sigemptyset(&action.sa_mask);
- action.sa_flags = 0;
- /* 設定SA_RESTART屬性 */
- action.sa_flags |= SA_RESTART;
- sigaction(SIGALRM, &action, NULL);
但注意,並不是所有的系統呼叫都可以自動恢復。如msgsnd喝msgrcv就是典型的例子,msgsnd/msgrcv以block方式傳送/接收訊息時,會因為程序收到了訊號而中斷。此時msgsnd/msgrcv將返回-1,errno被設定為EINTR。且即使在插入訊號時設定了SA_RESTART,也無效。在man msgrcv中就有提到這點:
msgsnd and msgrcv are never automatically restarted after being interrupted by a signal handler, regardless of the setting of the SA_RESTART flag when establishing a signal handler. |
忽略訊號
當然最簡單的方法是忽略訊號,在安裝訊號時,明確告訴系統不會產生該訊號的中斷。
[cpp] view plaincopy
- struct sigaction action;
- action.sa_handler = SIG_IGN;
- sigemptyset(&action.sa_mask);
- sigaction(SIGALRM, &action, NULL);
測試程式碼1
- #include
- #include
- #include
- #include
- #include
- #include
- void sig_handler(int signum)
- {
- printf("in handler\n");
- sleep(1);
- printf("handler return\n");
- }
- int main(int argc, char **argv)
- {
- char buf[100];
- int ret;
- struct sigaction action, old_action;
- action.sa_handler = sig_handler;
- sigemptyset(&action.sa_mask);
- action.sa_flags = 0;
- /* 版本1:不設定SA_RESTART屬性
- * 版本2:設定SA_RESTART屬性 */
- //action.sa_flags |= SA_RESTART;
- sigaction(SIGALRM, NULL, &old_action);
- if (old_action.sa_handler != SIG_IGN) {
- sigaction(SIGALRM, &action, NULL);
- }
- alarm(3);
- bzero(buf, 100);
- ret = read(0, buf, 100);
- if (ret == -1) {
- perror("read");
- }
- printf("read %d bytes:\n", ret);
- printf("%s\n", buf);
- return 0;
- }
在ubuntu 10.04 上測試結果:
不設定SA_RESTART,執行結果如下:
說明接受訊號處理完成以後,主函式收到EINTR訊號,read函式返回-1,退出
設定SA_RESTART,執行結果如下:
說明設定SA_RESTART引數以後,自動重新呼叫read函式,沒有體現在應用層程式碼中,在應用層看來,這個EINTR沒有造成任何影響。
個人認為下面的總結很重要:
慢系統呼叫(slow system call)會被訊號中斷,系統呼叫函式返回失敗,並且errno被置為EINTR(錯誤描述為“Interrupted system call”)。
處理方法有以下三種:①人為重啟被中斷的系統呼叫;②安裝訊號時設定 SA_RESTART屬性;③忽略訊號(讓系統不產生訊號中斷)。
有時我們需要捕獲訊號,但又考慮到第②種方法的侷限性(設定 SA_RESTART屬性對有的系統無效,如msgrcv),所以在編寫程式碼時,一定要“人為重啟被中斷的系統呼叫”。