msgrcv出錯errno=4[Interrupted system call]系統調用被信號中斷

原創: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響應才能完成。

3pausewait函數。pause函數使調用進程睡眠,直到捕獲到一個信號。wait等待子進程終止。

4)某些ioctl操作。

5)某些IPC操作。

2.EINTR介紹

早期的Unix系統,如果進程在一個慢系統調用(slowsystem call)中阻塞時,當捕獲到某個信號且相應信號處理函數返回時,這個系統調用被中斷,調用返回錯誤,設置errnoEINTR(相應的錯誤描述爲“Interruptedsystem call”)。

如下表所示的系統調用就會產生EINTR錯誤,當然不同的函數意義也不同。

 

系統調用函數

errnoEINTR表徵的意義

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錯誤的時候,有一些可以重啓的系統調用要進行重啓,而對於有一些系統調用是不能夠重啓的。例如:acceptreadwriteselect、和open之類的函數來說,是可以進行重啓的。不過對於套接字編程中的connect函數我們是不能重啓的,若connect函數返回一個EINTR錯誤的時候,我們不能再次調用它,否則將立即返回一個錯誤。針對connect不能重啓的處理方法是,必須調用select來等待連接完成。

這裏的重啓怎麼理解?

       一些IO系統調用執行時,如 read等待輸入期間,如果收到一個信號,系統將中斷read轉而執行信號處理函數.當信號處理返回後,系統遇到了一個問題:是重新開始這個系統調用,還是讓系統調用失敗?早期UNIX系統的做法是,中斷系統調用,並讓系統調用失敗,比如read返回 -1同時設置 errnoEINTR中斷了的系統調用是沒有完成的調用,它的失敗是臨時性的,如果再次調用則可能成功,這並不是真正的失敗,所以要對這種情況進行處理典型的方式爲:

while((r= read(fd, buf,len))<0&&errno == EINTR);/*do nothing*/

connect的問題,當connect遇到EINTR錯誤時,不能向上面那樣重新進入循環處理,原因是,connect的請求已經發送向對方,正在等待對方迴應,這是如果重新調用connect,而對方已經接受了上次的connect請求,這一次的connect就會被拒絕,因此,需要使用selectpoll調用來檢查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);  

但注意,並不是所有的系統調用都可以自動恢復msgsndmsgrcv就是典型的例子,我遇到的也是這種情況。msgsnd/msgrcvblock方式發送/接收消息時,會因爲進程收到了信號而中斷。此時msgsnd/msgrcv將返回-1errno被設置爲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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章