系统调用被信号中断仍然必须完成。

12

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

问题是该怎么做?比如说,我仍然希望关闭这个fd

我能想到的方法是:

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

有没有人能够建议一种更好/更优雅/更标准的方法来处理这种情况?

更新: 正如mux所指出的那样,在安装信号处理程序时可以使用SA_RESTART标志。 有人能告诉我在所有POSIX系统上(不仅仅是Linux),哪些函数保证是可重入的?


6
虽然总体来说这可能是正确的方法,但是对于close()特别是在Linux上,不建议这样做。请在此处阅读:https://lkml.org/lkml/2002/7/17/165 - alk
我明白了。但我不认为在所有UNIX系统上都能保证发生同样的情况。我需要一个可移植的解决方案。 - Shamdor
也许你可以对更新的问题发表评论吗?那就太棒了。 - Shamdor
3个回答

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中有规定,但是它是可选的,因此您应该检查您的操作系统中的不可重启调用列表,如果列表中没有,则应该是可重启的。这是我的问题: 如何知道Linux系统调用是否可重启?

更新:Close是一个特殊情况,它不可重新启动且在Linux中不应重试,请参阅此答案以获取更多详细信息:https://dev59.com/z2Yr5IYBdhLWcg3w29hI#14431867


2
你在Linux系统上从man signal(7)获得了这个列表吗?如果是的话:此列表仅适用于Linux。在同一手册页面上,还指出:“详细信息因Unix系统而异”。 - alk
好的,你能给我指出一个由POSIX保证可以重新启动的函数列表吗? - Shamdor
close()在每个正常的UNIX系统上都不应该重试(HP-UX是唯一的常见例外)。我们能为记录修复这个答案吗? - Nicholas Wilson

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;
}

2
记录一下:在几乎所有UNIX系统上,如果close()返回EINTR,则不应该重试。请不要像为waitpid()read()那样为close放置一个EINTR重试循环。有关详细信息,请参见此页面:http://austingroupbugs.net/view.php?id=529 在Linux、Solaris、BSD和其他系统上,重试close()是错误的。HP-UX是我能找到唯一需要这样做的常见(!)系统。

EINTR对于read()select()waitpid()等调用的含义与对于close()的含义非常不同。对于大多数调用,您会在EINTR上重试,因为您请求执行的操作是阻塞的,如果您被中断了,那就意味着它没有发生,所以您会再尝试一次。对于close(),您请求的操作是从fd表中删除一个条目,这是瞬时的,没有错误,并且无论close()返回什么都将始终发生。[*] close()阻塞的唯一原因是,有时候出于特殊语义(如TCP延迟),它可以在返回之前等待I/O完成。如果close返回EINTR,那就意味着您要求它等待但它无法等待。然而,fd仍然被关闭;您只是失去了等待它的机会。

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接