2014-05-15 90 views
2

環境:RedHat-like發行版,2.6.39內核,glibc 2.12。Linux爲什麼不接受()返回EINTR?

我完全認爲,如果在accept()進行過程中發送了一個信號,接受應該失敗,並留下errno == EINTR。但是,我不這樣做,我想知道爲什麼。以下是示例程序和strace輸出。

#include <stdio.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <signal.h> 
#include <errno.h> 
#include <arpa/inet.h> 
#include <string.h> 

static void sigh(int); 

int main(int argc, char ** argv) { 

    int s; 
    struct sockaddr_in sin; 

    if ((s = socket(AF_INET, SOCK_STREAM, 0))<0) { 
     perror("socket"); 
     return 1; 
    } 
    memset(&sin, 0, sizeof(struct sockaddr_in)); 
    sin.sin_family = AF_INET; 
    if (bind(s, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) { 
     perror("bind"); 
     return 1; 
    } 
    if (listen(s, 5)) { 
     perror("listen"); 
    } 

    signal(SIGQUIT, sigh); 

    while (1) { 
     socklen_t sl = sizeof(struct sockaddr_in); 
     int rc = accept(s, (struct sockaddr*)&sin, &sl); 
     if (rc<0) { 
      if (errno == EINTR) { 
       printf("accept restarted\n"); 
       continue; 
      } 
      perror("accept"); 
      return 1; 
     } 
     printf("accepted fd %d\n", rc); 
     close(rc); 
    } 

} 

void sigh(int s) { 

    signal(s, sigh); 

    unsigned char p[100]; 
    int i = 0; 
    while (s) { 
     p[i++] = '0'+(s%10); 
     s/=10; 
    } 
    write(1, "sig ", 4); 
    for (i--; i>=0; i--) { 
     write(1, &p[i], 1); 
    } 
    write(1, "\n", 1); 

} 

strace的輸出:

execve("./accept", ["./accept"], [/* 57 vars */]) = 0 
<skipped> 
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3 
bind(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 
listen(3, 5)       = 0 
rt_sigaction(SIGQUIT, {0x4008c4, [QUIT], SA_RESTORER|SA_RESTART, 0x30b7e329a0}, {SIG_DFL, [], 0}, 8) = 0 
accept(3, 0x7fffe3e3c500, [16])   = ? ERESTARTSYS (To be restarted) 
--- SIGQUIT (Quit) @ 0 (0) --- 
rt_sigaction(SIGQUIT, {0x4008c4, [QUIT], SA_RESTORER|SA_RESTART, 0x30b7e329a0}, {0x4008c4, [QUIT], SA_RESTORER|SA_RESTART, 0x30b7e329a0}, 8) = 0 
write(1, "sig ", 4sig)      = 4 
write(1, "3", 13)      = 1 
write(1, "\n", 1 
)      = 1 
rt_sigreturn(0x1)      = 43 
accept(3, ^C <unfinished ...> 

回答

1

就在我正要張貼此,在strace的輸出 「SA_RESTART」 標誌引起了我的注意。 signal(2)手冊頁說,signal()「...使用提供BSD語義的標誌調用sigaction(2)...」從glibc2開始的

SA_RESTART標誌「...使某些系統調用可以跨信號重新啓動......」,它隱藏了重新啓動用戶呼叫的過程。所以,這不是特定的accept(),其他一些系統調用也會受到影響,而不是明確列出哪些系統調用。因此,如果您需要對來自可能在系統調用中阻塞的線程的信號作出反應,則應使用sigaction()來設置您的信號處理程序,而不是signal()。下面是修改後的示例程序,完全可以作爲參考。

#include <stdio.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <signal.h> 
#include <errno.h> 
#include <arpa/inet.h> 
#include <string.h> 

static void sigh(int); 

static struct sigaction sa; 

int main(int argc, char ** argv) { 

    int s; 
    struct sockaddr_in sin; 

    if ((s = socket(AF_INET, SOCK_STREAM, 0))<0) { 
     perror("socket"); 
     return 1; 
    } 
    memset(&sin, 0, sizeof(struct sockaddr_in)); 
    sin.sin_family = AF_INET; 
    if (bind(s, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) { 
     perror("bind"); 
     return 1; 
    } 
    if (listen(s, 5)) { 
     perror("listen"); 
    } 

    memset(&sa, 0, sizeof(struct sigaction)); 
    sa.sa_handler = sigh; 
    sigemptyset(&sa.sa_mask); 
    sigaction(SIGQUIT, &sa, 0); 

    while (1) { 
     socklen_t sl = sizeof(struct sockaddr_in); 
     int rc = accept(s, (struct sockaddr*)&sin, &sl); 
     if (rc<0) { 
      if (errno == EINTR) { 
       printf("accept restarted\n"); 
       continue; 
      } 
      perror("accept"); 
      return 1; 
     } 
     printf("accepted fd %d\n", rc); 
     close(rc); 
    } 

} 

void sigh(int s) { 

    sigaction(SIGQUIT, &sa, 0); 

    unsigned char p[100]; 
    int i = 0; 
    while (s) { 
     p[i++] = '0'+(s%10); 
     s/=10; 
    } 
    write(1, "sig ", 4); 
    for (i--; i>=0; i--) { 
     write(1, &p[i], 1); 
    } 
    write(1, "\n", 1); 

} 

而且strace的:

execve("./accept", ["./accept"], [/* 57 vars */]) = 0 
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3 
bind(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 
listen(3, 5)       = 0 
rt_sigaction(SIGQUIT, {0x400994, [], SA_RESTORER, 0x30b7e329a0}, NULL, 8) = 0 
accept(3, 0x7fffb626be90, [16])   = ? ERESTARTSYS (To be restarted) 
--- SIGQUIT (Quit) @ 0 (0) --- 
rt_sigaction(SIGQUIT, {0x400994, [], SA_RESTORER, 0x30b7e329a0}, NULL, 8) = 0 
write(1, "sig ", 4)     = 4 
write(1, "3", 13)      = 1 
write(1, "\n", 1)      = 1 
rt_sigreturn(0x1)      = -1 EINTR (Interrupted system call) 
write(1, "accept restarted\n", 17)  = 17 
accept(3, 
+0

謝謝帕維爾。它有助於。 –

1

Unix Network Programming書,有一節它說:

我們使用的術語 「慢行系統調用」 來形容accept,我們使用 這個術語適用於任何可以永久阻止的系統調用。也就是說,系統調用不需要返回。大多數網絡功能都屬於 這個類別。例如,如果沒有客戶端將 連接到服務器,則不能保證服務器的呼叫 到accept將永遠返回。同樣,如果客戶端永遠不會爲服務器發送一條線來回顯,我們服務器對圖5.3中的read的調用將永遠不會返回 。 緩慢系統調用的其他示例是讀寫管道和終端設備。一個值得注意的例外是磁盤I/O,它通常返回給調用者 (假設沒有災難性的硬件故障)。

此處適用的是,當被阻止的過程中 一個緩慢的系統調用和進程捕獲的信號,並將信號 處理程序返回時,系統調用可以返回的EINTR錯誤的基本規則。一些 內核會自動重啓一些中斷的系統調用。對於 的可移植性,當我們編寫捕獲信號的程序(大多數 併發服務器捕獲SIGCHLD)時,我們必須爲慢速系統準備 調用返回EINTR。可移植性問題是由前面使用的 限定符「can」和「some」引起的,支持POSIX SA_RESTART標誌的事實 是可選的。即使 實現支持SA_RESTART標誌,但並非所有中斷的 系統調用都可能會自動重新啓動。例如,大多數伯克利派生的 實現從不自動重新啓動select,並且這些實現中的一些從不會重新啓動acceptrecvfrom