信号中断的系统调用仍然需要完成

时间:2012-11-13 06:38:23

标签: c++ c unix interrupt eintr

许多系统调用如close( fd )可以被信号中断。在这种情况下,通常会返回-1并设置errno EINTR

问题是做什么是正确的?说,我仍然希望关闭此fd

我能想出的是:

while( close( fd ) == -1 )
  if( errno != EINTR ) {
    ReportError();
    break;
  }

有人可以提出更好/更优雅/标准的方法来处理这种情况吗?

更新: 正如mux所注意到的,在安装信号处理程序时可以使用SA _ RESTART标志。 有人可以告诉我哪些功能可以保证在所有POSIX系统上重启(不只是Linux)?

3 个答案:

答案 0 :(得分:9)

某些系统调用是可重启的,这意味着内核将在中断时重新启动调用,如果在安装信号处理程序时使用SA_RESTART标志,signal(7)手册页说:

  

如果对以下某个接口的阻止呼叫被中断   通过信号处理程序,然后调用将自动重启   信号之后          如果使用了SA_RESTART标志,则处理程序返回;否则呼叫将失败并显示错误EINTR:

没有提及close()是否可重启,但这些是:

  

read(2),readv(2),write(2),writev(2),ioctl(2),open(2),wait(2),   wait3(2),wait4(2),waitid(2)和waitpid,accept(2),connect(2),   recv(2),recvfrom(2),recvmsg(2),send(2),sendto(2)和sendmsg(2)   flock(2)和fcntl(2)mq_receive(3),mq_timedreceive(3),mq_send(3),   和mq_timedsend(3)sem_wait(3)和sem_timedwait(3)futex(2)

请注意,这些详细信息,特别是不可重新启动的调用列表,是特定于Linux的

我发布了一个相关的问题,关于哪些系统调用可以重启,如果它是由某个POSIX指定的,它是由POSIX指定的,但是它是可选的,所以你应该查看你的操作系统的不可重启调用列表,如果它不在那里它应该是可重启的。这是我的问题: How to know if a Linux system call is restartable or not?

更新:关闭是一种特殊情况,它不能重新启动,不应在Linux中重试,请参阅此答案以获取更多详细信息: https://stackoverflow.com/a/14431867/1157444

答案 1 :(得分:3)

对于记录:基本上在每个UNIX上,如果返回EINTR,则不能重试close() 不要将EINTR重试循环放置到适当的位置,就像使用waitpid()read()一样。有关详细信息,请参阅此页面:http://austingroupbugs.net/view.php?id=529在Linux,Solaris,BSD和其他人上,重试close()不正确。 HP-UX是我能找到的唯一需要它的常见(!)系统。

对于read()select()以及waitpid()等,EINTR意味着与close()相比有所不同。对于大多数电话,你在EINTR上重试,因为你要求完成哪些阻止,如果你被打断,这意味着它没有发生,所以你再试一次。对于close(),您请求的操作是从fd表中删除一个条目,该条目是即时的,没有错误,并且无论close()返回什么都会发生。[*]唯一的原因close()块有时候,对于特殊语义(如TCP逗留),它可以等到I / O完成后再返回。如果关闭返回EINTR,这意味着你要求它等待,但它不能。但是, fd仍然关闭;你只是失去了等待它的机会。

结论:除非您知道无法接收信号,否则使用close()进行等待是一件非常愚蠢的事情。使用应用程序级ACK(TCP)或fsync(文件I / O)以确保在关闭fd之前完成任何写入。

[*]有一个警告:如果进程的另一个线程在同一个fd上的阻塞系统调用内,那么......它取决于。

答案 2 :(得分:2)

假设您正在使用更短的代码,您可以尝试以下方法:

while (((rc = close (fd)) == -1) && (errno == EINTR));
if (rc == -1)
    complainBitterly (errno);

假设您除了更短的时间之外还有更多可读代码,只需创建一个函数:

int closeWithRetry (int fd);

并将可读代码放在那里。然后它并不重要,它仍然是你所谓的单行,但你可以使函数体本身非常易读:

int closeWithRetry (int fd) {
    // Initial close attempt.

    int rc = close (fd);

    // As long as you failed with EINTR, keep trying.
    // Possibly with a limit (count or time-based).

    while ((rc == -1) && (errno == EINTR))
        rc = close (fd);

    // Once either success or non-retry failure, return error code.

    return rc;
}