12

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

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

我能想到的是:

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

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

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

4

3 回答 3

9

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

如果对以下接口之一的阻塞调用被信号处理程序中断,则如果使用了 SA_RESTART 标志,则在信号处理程序返回后将自动重新启动调用;否则调用将失败并出现错误 EINTR:

它没有提到是否close()可以重新启动,但它们是:

读取(2),读取(2),写入(2),写入v(2),ioctl(2),打开(2),等待(2),等待3(2),等待4(2),等待ID(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 系统调用是否可重新启动?

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

于 2012-11-13T07:06:00.950 回答
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;
}
于 2012-11-13T06:43:57.367 回答
2

记录一下:基本上在每个 UNIX 上,如果它返回 EINTR ,则close() 不能重试。不要waitpid()像 for或那样为 close 放置一个 EINTR 重试循环read()。有关详细信息,请参阅此页面:http ://austingroupbugs.net/view.php?id=529在 linux、Solaris、BSD 等上,重试close()不正确。HP-UX 是我能找到的唯一一个需要这个的通用(!)系统。

read()EINTR 对andselect()等的含义waitpid()与对.的含义非常不同close()。对于大多数呼叫,您在 EINTR 上重试,因为您要求在哪些阻塞中完成某些事情,如果您被打断,则意味着它没有发生,所以您再试一次。对于close(),您请求的操作是从 fd 表中删除一个条目,这是瞬时的,没有错误,并且无论close()返回什么都将始终发生。[*]close()阻塞的唯一原因是有时,对于特殊语义(如TCP linger),它可以等到 I/O 完成后再返回。如果 close 返回 EINTR,这意味着您要求它等待但它不能。但是,fd 仍然关闭;你只是失去了等待的机会。

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

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

于 2013-03-14T15:12:36.777 回答