linux - 为什么 Linux accept() 不返回 EINTR?

标签 linux signals

环境:类似 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);

}

跟踪输出:

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 ...>

最佳答案

Unix Network Programming内书,有一节说:

We used the term "slow system call" to describe accept, and we use this term for any system call that can block forever. That is, the system call need never return. Most networking functions fall into this category. For example, there is no guarantee that a server's call to accept will ever return, if there are no clients that will connect to the server. Similarly, our server's call to read in Figure 5.3 will never return if the client never sends a line for the server to echo. Other examples of slow system calls are reads and writes of pipes and terminal devices. A notable exception is disk I/O, which usually returns to the caller (assuming no catastrophic hardware failure).

The basic rule that applies here is that when a process is blocked in a slow system call and the process catches a signal and the signal handler returns, the system call can return an error of EINTR. Some kernels automatically restart some interrupted system calls. For portability, when we write a program that catches signals (most concurrent servers catch SIGCHLD), we must be prepared for slow system calls to return EINTR. Portability problems are caused by the qualifiers "can" and "some," which were used earlier, and the fact that support for the POSIX SA_RESTART flag is optional. Even if an implementation supports the SA_RESTART flag, not all interrupted system calls may automatically be restarted. Most Berkeley-derived implementations, for example, never automatically restart select, and some of these implementations never restart accept or recvfrom.

关于linux - 为什么 Linux accept() 不返回 EINTR?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23685816/

相关文章:

Linux shell 脚本

linux脚本编写生成可执行文件

linux - 如何将完全散列的 menezes-qu-vanstone (fhmqv) 补丁应用到已从 Ubuntu 存储库安装的 crypto++

c - 在 Linux 上以相反顺序接收的实时信号

c - child 和 parent 之间使用信号的双向交流

c++ - Qt:将信号连接到具有更多参数的插槽

linux - 列出 Bash 中每个文本文件的文件名上限

c - 如何使用信号函数调用其他函数?

c - 何时应该在 C 中设置线程程序的信号掩码?

c - for_each_process - 它是否也遍历线程和进程?