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

System call interrupted by a signal still has to be completed

本文关键字:信号 中断 系统调用      更新时间:2023-10-16

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

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

我能想到的是:

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

谁能建议一种更好/更优雅/标准的方式来处理这种情况?

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

一些系统调用是可重新启动的, 这意味着如果中断内核将重新启动调用, 如果在安装信号处理程序时使用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) 羊群(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

假设你追求较短的代码,你可以尝试这样的东西:

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

郑重声明:基本上在每个 UNIX 上,如果close()返回 EINTR,则不得重试不要waitpid()read()那样放置 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 上的阻塞系统调用内,那么......这要看情况。