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函數我們是不能重啓的,若connect函數返回一個EINTR錯誤的時候,我們不能再次調用它,否則將立即返回一個錯誤。針對connect不能重啓的處理方法是,必須調用select來等待連接完成。
這裏的“重啓”怎麼理解?
一些IO系統調用執行時,如 read 等待輸入期間,如果收到一個信號,系統將中斷read, 轉而執行信號處理函數. 當信號處理返回後, 系統遇到了一個問題: 是重新開始這個系統調用, 還是讓系統調用失敗?早期UNIX系統的做法是, 中斷系統調用,並讓系統調用失敗, 比如read返回 -1, 同時設置 errno 爲EINTR中斷了的系統調用是沒有完成的調用,它的失敗是臨時性的,如果再次調用則可能成功,這並不是真正的失敗,所以要對這種情況進行處理, 典型的方式爲:
另外,原文建議上去github上看看別人怎麼處理EINTR錯誤的,下面2個處理方面均是截圖過來的:
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);
#include .h>
#include .h>
#include .h>
#include .h>
#include .h>
#include .h>
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;
}
不設置SA_RESTART,執行結果如下:
設置SA_RESTART,執行結果如下:
說明設置SA_RESTART參數以後,自動重新調用read函數,沒有體現在應用層代碼中,在應用層看來,這個EINTR沒有造成任何影響。
個人認爲下面的總結很重要:
慢系統調用(slow system call)會被信號中斷,系統調用函數返回失敗,並且errno被置爲EINTR(錯誤描述爲“Interrupted system call”)。
處理方法有以下三種:①人爲重啓被中斷的系統調用;②安裝信號時設置 SA_RESTART屬性;③忽略信號(讓系統不產生信號中斷)。
有時我們需要捕獲信號,但又考慮到第②種方法的侷限性(設置 SA_RESTART屬性對有的系統無效,如msgrcv),所以在編寫代碼時,一定要“人爲重啓被中斷的系統調用”。