首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么Linux不接受()返回EINTR?

为什么Linux不接受()返回EINTR?
EN

Stack Overflow用户
提问于 2014-05-15 18:17:33
回答 2查看 2.7K关注 0票数 3

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

我完全期望,如果在accept()正在进行时传递了一个信号,则accept应该会失败,从而留下errno==EINTR。然而,我的不是这样做的,我想知道为什么。下面是示例程序和strace输出。

代码语言:javascript
复制
#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输出:

代码语言:javascript
复制
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 ...>
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-05-15 18:17:33

就在我即将发布这篇文章时,strace输出中的SA_RESTART标志引起了我的注意。信号(2)手册页说signal() "...calls sigaction(2)使用提供BSD语义的标志.“从glibc 2.x开始。

SA_RESTART标志"...makes特定系统调用可跨信号重新启动.“,这隐藏了用户重新启动调用的过程。因此,这并不是accept()特有的,许多其他系统调用也会受到影响,而不是有明确的列表。

因此,如果需要对可能在系统调用中被阻塞的线程的信号作出反应,则应该使用sigaction()来设置信号处理程序,而不是signal()。下面是修改过的示例程序,它就是这样做的,以供参考。

代码语言:javascript
复制
#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);

}

和绞刑:

代码语言:javascript
复制
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, 
票数 1
EN

Stack Overflow用户

发布于 2016-02-24 20:58:17

Unix网络编程书中,有一节说:

我们用“慢系统调用”这个术语来描述accept,我们用这个术语来描述任何可能永远阻塞的系统调用。也就是说,系统调用永远不需要返回。大多数网络功能都属于这一类。例如,如果没有连接到服务器的客户机,则无法保证服务器对accept的调用将返回。类似地,如果客户机从未为服务器回显发送一行,则图5.3中的服务器对read的调用将永远不会返回。慢系统调用的其他例子是管道和终端设备的读写。一个值得注意的例外是磁盘I/O,它通常返回给调用者(假设没有灾难性的硬件故障)。 这里适用的基本规则是,当一个进程在一个缓慢的系统调用中被阻塞,并且该进程捕获一个信号并且信号处理程序返回时,系统调用可以返回一个EINTR错误。一些内核会自动重启一些中断的系统调用。为了便于移植,当我们编写一个捕获信号的程序(大多数并发服务器都捕获SIGCHLD)时,我们必须为缓慢的系统调用返回EINTR做好准备。可移植性问题是由前面使用的限定符"can“和"some”引起的,而且支持POSIX SA_RESTART标志是可选的。即使实现支持SA_RESTART标志,也不会自动重新启动所有中断的系统调用。例如,大多数伯克利派生的实现从不自动重新启动select,其中一些实现从未重新启动acceptrecvfrom

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23685816

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档