【问题标题】:Why doesn't Linux accept() return EINTR?为什么 Linux accept() 不返回 EINTR?
【发布时间】:2014-05-15 18:17:33
【问题描述】:

环境:类似 RedHat 的发行版,2.6.39 内核,glibc 2.12。

我完全期望如果在 accept() 进行时传递了一个信号,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 ...>

【问题讨论】:

    标签: linux signals


    【解决方案1】:

    Unix Network Programming 书中,有一段说:

    我们使用术语“慢系统调用”来描述accept,我们使用 这个术语用于任何可以永久阻塞的系统调用。那就是 系统调用永远不需要返回。大多数网络功能属于 这个类别。例如,不能保证服务器的调用 到 accept 将永远返回,如果没有客户端将连接 到服务器。同样,我们的服务器在图 5.3 中对read 的调用将 如果客户端从不发送一条线让服务器回显,则永远不会返回。 其他慢速系统调用的例子是管道的读写和 终端设备。一个值得注意的例外是磁盘 I/O,通常 返回给调用者(假设没有灾难性的硬件故障)。

    这里适用的基本规则是,当一个进程被阻塞时 一个缓慢的系统调用和进程捕获一个信号和信号 handler返回,系统调用可以返回EINTR的错误。一些 内核会自动重新启动一些中断的系统调用。为了 可移植性,当我们编写一个捕获信号的程序时(大多数 并发服务器捕获SIGCHLD),我们必须为慢速系统做好准备 调用返回EINTR。便携性问题是由 之前使用的限定词“can”和“some”,以及事实 对 POSIX SA_RESTART 标志的支持是可选的。即使一个 实现支持SA_RESTART标志,并非全部中断 系统调用可能会自动重新启动。大多数伯克利派生 例如,实现永远不会自动重新启动选择,并且 其中一些实现永远不会重新启动 acceptrecvfrom

    【讨论】:

      【解决方案2】:

      就在我即将发布此内容时,strace 输出中的SA_RESTART 标志引起了我的注意。 signal(2) 手册页说 signal() "...使用提供 BSD 语义的标志调用 sigaction(2)..." 从 glibc 2.x 开始。

      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, 
      

      【讨论】:

      • 谢谢帕维尔。它有帮助。
      猜你喜欢
      • 2012-08-04
      • 1970-01-01
      • 2017-12-20
      • 1970-01-01
      • 2019-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多