linux中對errno是EINTR的處理

慢系統調用(slow system call):此術語適用於那些可能永遠阻塞的系統調用。永遠阻塞的系統調用是指調用有可能永遠無法返回,多數網絡支持函數都屬於這一類。如:若沒有客戶連接到服務器上,那麼服務器的accept調用就沒有返回的保證。


EINTR錯誤的產生:當阻塞於某個慢系統調用的一個進程捕獲某個信號且相應信號處理函數返回時,該系統調用可能返回一個EINTR錯誤。例如:在socket服務器端,設置了信號捕獲機制,有子進程,當在父進程阻塞於慢系統調用時由父進程捕獲到了一個有效信號時,內核會致使accept返回一個EINTR錯誤(被中斷的系統調用)。


當碰到EINTR錯誤的時候,可以採取有一些可以重啓的系統調用要進行重啓,而對於有一些系統調用是不能夠重啓的。例如:accept、read、write、select、和open之類的函數來說,是可以進行重啓的。不過對於套接字編程中的connect函數我們是不能重啓的,若connect函數返回一個EINTR錯誤的時候,我們不能再次調用它,否則將立即返回一個錯誤。針對connect不能重啓的處理方法是,必須調用select來等待連接完成。
 
在 linux 或者 unix 環境中, errno 是一個十分重要的部分。在調用的函 數出現問題的時候,我們可以通過 errno 的值來確定出錯的原因,這就會 涉及到一個問題,那就是如何保證 errno 在多線程或者進程中安全?我們希望在多線程或者進程中,每個線程或者進程都擁有自己獨立和唯一的一個 errno ,這樣就能夠保證不會有競爭條 件的出現。一般而言,編譯器會自動保證 errno 的安全性,但是爲了妥善期間,我們希望在寫 makefile 的時 候把 _LIBC_REENTRANT 宏定義,比 如我們在檢查 <bits/errno.h> 文件中發現如下的定義:

C代碼

    # ifndef __ASSEMBLER__
    /* Function to get address of global `errno' variable. */
    extern int *__errno_location (void) __THROW __attribute__ ((__const__));
     
     
    # if !defined _LIBC || defined _LIBC_REENTRANT
    /* When using threads, errno is a per-thread value. */
    # define errno (*__errno_location ())
    # endif
    # endif /* !__ASSEMBLER__ */
    #endif /* _ERRNO_H */


也就是說,在沒有定義 __LIBC 或者定義 _LIBC_REENTRANT 的時候, errno 是多線程 / 進程安全的。
一般而言, __ASSEMBLER__, _LIBC 和 _LIBC_REENTRANT 都不會被編譯器定義,但是如果我們定義 _LIBC_REENTRANT 一次又何妨那?
爲了檢測一下你編譯器是否定義上述變量,不妨使用下面一個簡單程序。
C代碼

    #include <stdio.h>
    #include <errno.h>
     
    int main( void )
    {
    #ifndef __ASSEMBLER__
    printf( "Undefine __ASSEMBLER__\n" );
    #else
    printf( "define __ASSEMBLER__\n" );
    #endif
     
    #ifndef __LIBC
    printf( "Undefine __LIBC\n" );
    #else
    printf( "define __LIBC\n" );
    #endif
     
    #ifndef _LIBC_REENTRANT
    printf( "Undefine _LIBC_REENTRANT\n" );
    #else
    printf( "define _LIBC_REENTRANT\n" );
    #endif
     
    return 0;
    }

希望讀者在進行移植的時候,讀一下相關的 unix 版本的 <bits/errno.h> 文 件,來確定應該定義什麼宏。不同的 unix 版本可能存在着一些小的差別!
 
有時候,在調用系統調用時,可能會接收到某個信號而導致調用退出。譬如使用system調用某個命令之後該進程會接收到SIGCHILD信號,然後如果這個進程的線程中有慢系統調用,那麼接收到該信號的時候可能就會退出,返回EINTR錯誤碼。
EINTR
  linux中函數的返回狀態,在不同的函數中意義不同:
1)write
  表示:由於信號中斷,沒寫成功任何數據。
  The call was interrupted by a signal before any data was written.
2)read
  表示:由於信號中斷,沒讀到任何數據。
  The call was interrupted by a signal before any data was read.
3)sem_wait
  函數調用被信號處理函數中斷
  The call was interrupted by a signal handler.
4)recv
  由於信號中斷返回,沒有任何數據可用。
  function was interrupted by a signal that was caught, before any data was available.
 
調用系統調用的時候,有時系統調用會被中斷.此時,系統調用會返回-1,並且錯誤碼被置爲EINTR.但是,有時並不將這樣的情況作爲錯誤.有兩種處理方法:


1.如果錯誤碼爲EINTR則重新調用系統調用,例如Postgresql中有一段代碼:

    retry1:
    if (send(port->sock, &SSLok, 1, 0) != 1)
    {
       if (errno == EINTR)
       goto retry1; /* if interrupted, just retry */
    }


2.重新定義系統調用,忽略錯誤碼爲EINTR的情況.例如,Cherokee中的一段代碼:

    int cherokee_stat (const char *restrict path, struct stat *buf)
    {
      int re;
      do {
         re = stat (path, buf);
      } while ((re == -1) && (errno == EINTR));
      return re;
    }



今天使用select調用的時候總是出錯,返回EINTR錯誤->Interrupted system call,主要是由於代碼中調用了signal捕獲子進程退出信號SIGCHLD的處理,故我採用忽略EINTR的策略,代碼改爲如下解決

        signal(SIGCHLD,sig_wait);
        while(1){
            rdset=ctlset;        
    /* 如果可用處理子進程等於0個,那麼select就暫時不再監聽連接描述符,由listen函數的backlog控制連接數目隊列
            if(iavailable_child <= 0)
                FD_CLR(ifdlisten, &rdset);    // turn off if no available children
    */            
            iselectret=select(ifdmax+1, &rdset,NULL,NULL,NULL);    
            if(iselectret<0){            
                if(errno==EINTR){
    //                printf("Receives the interrupt signal\n");
                    continue;
                }
                else{
                    printf("select error,will be exit,error msg:%s \n",strerror(errno));    
                    exit(-1);
                }
            }
---------------------  
作者:奔跑吧,行者  
來源:CSDN  
原文:https://blog.csdn.net/hnlyyk/article/details/51444617  
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章