原創:http://blog.csdn.net/guo8113/article/details/44355329
今天在嵌入式Linux中調試msgrcv時出現其返回爲-1,錯誤代碼4的錯誤:errno=4[Interruptedsystem call]。錯誤代碼4爲: 當進程睡眠等待接收消息時,被信號中斷。 這是由於在此線程中同時使用了信號,而慢系統調用(阻塞系統調用)在使線程休眠等待時被信號喚醒,當捕獲到某個信號且相應信號處理函數返回時,這個系統調用被中斷,調用返回錯誤,設置errno爲EINTR(相應的錯誤描述爲“Interruptedsystem call”)。
網上有人說在安裝信號時設置SA_RESTART屬性(該方法對有的系統調用無效,在http://blog.chinaunix.net/uid-21501855-id-4490453.html得到了驗證),並忽略EINTR錯誤,我試了無效,最終新開了一個線程,在新的線程裏處理msgrcv等msgq的操作,順利解決了問題。
1.術語
慢系統調用適用於那些可能永遠阻塞的系統調用。永遠阻塞的系統調用是指調用永遠無法返回,多數網絡支持函數都屬於這一類。如:若沒有客戶連接到服務器上,那麼服務器的accept調用就會一直阻塞。
慢系統調用可以被永久阻塞,包括以下幾個類別:
(1)讀寫‘慢’設備(包括pipe,終端設備,網絡連接等)。讀時,數據不存在,需要等待;寫時,緩衝區滿或其他原因,需要等待。讀寫磁盤文件一般不會阻塞。
(2)當打開某些特殊文件時,需要等待某些條件,才能打開。例如:打開中斷設備時,需要等到連接設備的modem響應才能完成。
(3)pause和wait函數。pause函數使調用進程睡眠,直到捕獲到一個信號。wait等待子進程終止。
(4)某些ioctl操作。
(5)某些IPC操作。
2.EINTR介紹
早期的Unix系統,如果進程在一個慢系統調用(slowsystem call)中阻塞時,當捕獲到某個信號且相應信號處理函數返回時,這個系統調用被中斷,調用返回錯誤,設置errno爲EINTR(相應的錯誤描述爲“Interruptedsystem call”)。
如下表所示的系統調用就會產生EINTR錯誤,當然不同的函數意義也不同。
系統調用函數 |
errno爲EINTR表徵的意義 |
write |
由於信號中斷,沒寫成功任何數據。 The call was interrupted by a signal before any data was written. |
open |
由於信號中斷,沒讀到任何數據。 The call was interrupted by a signal before any data was read. |
recv |
由於信號中斷返回,沒有任何數據可用。 The receive was interrupted by delivery of a signal before any data were available. |
sem_wait |
函數調用被信號處理函數中斷。 The call was interrupted by a signal handler. |
3.解決類似問題有3種方法
◆人爲重啓被中斷的系統調用
◆安裝信號時設置 SA_RESTART屬性(該方法對有的系統調用無效)
◆ 忽略信號(讓系統不產生信號中斷)
3.1.人爲重啓被中斷的系統調用
人爲當碰到EINTR錯誤的時候,有一些可以重啓的系統調用要進行重啓,而對於有一些系統調用是不能夠重啓的。例如:accept、read、write、select、和open之類的函數來說,是可以進行重啓的。不過對於套接字編程中的connect函數我們是不能重啓的,若connect函數返回一個EINTR錯誤的時候,我們不能再次調用它,否則將立即返回一個錯誤。針對connect不能重啓的處理方法是,必須調用select來等待連接完成。
這裏的“重啓”怎麼理解?
一些IO系統調用執行時,如 read等待輸入期間,如果收到一個信號,系統將中斷read,轉而執行信號處理函數.當信號處理返回後,系統遇到了一個問題:是重新開始這個系統調用,還是讓系統調用失敗?早期UNIX系統的做法是,中斷系統調用,並讓系統調用失敗,比如read返回 -1,同時設置 errno爲EINTR中斷了的系統調用是沒有完成的調用,它的失敗是臨時性的,如果再次調用則可能成功,這並不是真正的失敗,所以要對這種情況進行處理,典型的方式爲:
while((r= read(fd, buf,len))<0&&errno == EINTR);/*do nothing*/
connect的問題,當connect遇到EINTR錯誤時,不能向上面那樣重新進入循環處理,原因是,connect的請求已經發送向對方,正在等待對方迴應,這是如果重新調用connect,而對方已經接受了上次的connect請求,這一次的connect就會被拒絕,因此,需要使用select或poll調用來檢查socket的狀態,如果socket的狀態就緒,則connect已經成功,否則,視錯誤原因,做對應的處理。
#include poll.h
int check_conn_is_ok(socket_t sock) {
structpollfd fd;
intret = 0;
socklen_tlen = 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;
}
return0;
}
在調用connect時,這樣使用:
#include erron.h
....
if(connnect()) {
if(errno == EINTR) {
if(check_conn_is_ok() < 0) {
perror();
return -1;
}
else {
printf("connect issuccess!\n");
}
}
else {
perror("connect");
return -1;
}
}
3.2.設置SA_RESTART屬性
我們還可以從信號的角度來解決這個問題, 安裝信號的時候, 設置 SA_RESTART屬性,那麼當信號處理函數返回後, 不會讓系統調用返回失敗,而是讓被該信號中斷的系統調用將自動恢復。
1. struct sigaction action;
2. action.sa_handler = handler_func;
3. sigemptyset(&action.sa_mask);
4. action.sa_flags = 0;
5. /* 設置SA_RESTART屬性 */
6. action.sa_flags |= SA_RESTART;
7. 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. |
3.3.忽略信號
當然最簡單的方法是忽略信號,在安裝信號時,明確告訴系統不會產生該信號的中斷。
struct sigaction action;
action.sa_handler = SIG_IGN;
sigemptyset(&action.sa_mask);
sigaction(SIGALRM, &action, NULL);
總結
處理方法有以下三種:①人爲重啓被中斷的系統調用;②安裝信號時設置 SA_RESTART屬性;③忽略信號(讓系統不產生信號中斷)。
有時我們需要捕獲信號,但又考慮到第②種方法的侷限性(設置 SA_RESTART屬性對有的系統無效,如msgrcv),所以在編寫代碼時,一定要“人爲重啓被中斷的系統調用”。
參考:http://blog.chinaunix.net/uid-21501855-id-4490453.html
http://blog.csdn.net/benkaoya/article/details/17262053
轉載請註明:http://blog.csdn.net/guo8113/article/details/44355329