对于 POSIX,R.. https://stackoverflow.com/questions/13357019/how-to-know-if-a-linux-system-call-is-restartable-or-not/14431867#14431867's 回答相关问题 https://stackoverflow.com/questions/13357019/how-to-know-if-a-linux-system-call-is-restartable-or-not/14431867#14431867非常清晰简洁:close()
是不可重新启动的特殊情况,不应使用循环。
这让我感到惊讶,所以我决定描述我的发现,然后是我的结论和最后选择的解决方案。
这并不是一个真正的答案。将此视为一位程序员同事的意见,包括该意见背后的推理。
POSIX.1-2001 http://pubs.opengroup.org/onlinepubs/007904975/functions/close.html and POSIX.1-2008 http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html描述可能出现的三种可能的 errno 值:EBADF
, EINTR
, and EIO
。之后的描述符状态EINTR
and EIO
is “未指定”,这意味着它可能已关闭,也可能未关闭。EBADF
表示fd
不是有效的描述符。换句话说,POSIX.1明确建议使用
if (close(fd) == -1) {
/* An error occurred, see 'errno'. */
}
无需任何重试循环来关闭文件描述符。
(就连奥斯汀集团缺陷#519 http://austingroupbugs.net/view.php?id=529#c1200R..提到的,无助于恢复close()
错误:未指定在执行后是否可以进行任何 I/OEINTR
错误,即使描述符本身保持打开状态。)
对于 Linux 来说,close()
系统调用定义在文件系统/open.c https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/fs/open.c, with __do_close()
in 文件系统/文件.c https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/fs/file.c管理描述符表锁定,以及filp_close()
早在文件系统/open.c https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/fs/open.c照顾细节。
综上所述,描述符条目被无条件地从表中删除first,然后是特定于文件系统的刷新(f_op->flush()
),然后是通知(dnotify/fsnotify 挂钩),最后删除任何记录或文件锁。 (大多数本地文件系统,如 ext2、ext3、ext4、xfs、bfs、tmpfs 等,没有->flush()
,因此给定一个有效的描述符,close()
不能失败。只有 ecryptfs、exofs、fuse、cifs 和 nfs 有->flush()
据我所知,Linux-3.13.6 中的处理程序。)
这确实意味着在 Linux 中,如果特定于文件系统的写入错误发生->flush()
处理程序期间close()
, 没有办法重试;文件描述符总是关闭的,就像 Torvalds 所说的那样。
自由BSD手册页描述了完全相同的行为。
既没有也不Mac OS X https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/close.2.html close()
手册页描述了描述符在出现错误时是否关闭,但我相信它们共享 FreeBSD 的行为。
对我来说很明显,安全关闭文件描述符不需要或不需要任何循环。然而,close()
仍可能返回错误。
errno == EBADF
表示文件描述符已经关闭。如果我的代码意外地遇到这种情况,对我来说,这表明代码逻辑中存在重大错误,进程应该正常退出;我宁愿我的进程死掉也不愿产生垃圾。
任何其他errno
值指示最终确定文件状态时出现错误。在 Linux 中,这绝对是与将任何剩余数据刷新到实际存储相关的错误。特别是,我可以想象ENOMEM
如果没有空间缓冲数据,EIO
如果数据无法发送或写入实际设备或介质,EPIPE
如果与存储的连接丢失,ENOSPC
如果存储已经满并且没有保留未刷新的数据,等等。如果该文件是日志文件,我会让进程报告失败并正常退出。如果文件内容在内存中仍然可用,我将删除(取消链接)整个文件,然后重试。否则我会向用户报告失败。
(请记住,在 Linux 和 FreeBSD 中,在错误情况下您不会“泄漏”文件描述符;即使发生错误,它们也保证会被关闭。我假设我可能使用的所有其他操作系统都有相同的行为。)
从现在开始我将使用的辅助函数将类似于
#include <unistd.h>
#include <errno.h>
/**
* closefd - close file descriptor and return error (errno) code
*
* @descriptor: file descriptor to close
*
* Actual errno will stay unmodified.
*/
static int closefd(const int descriptor)
{
int saved_errno, result;
if (descriptor == -1)
return EBADF;
saved_errno = errno;
result = close(descriptor);
if (result == -1)
result = errno;
errno = saved_errno;
return result;
}
我知道上面的内容在 Linux 和 FreeBSD 上是安全的,并且我认为它在所有其他 POSIX-y 系统上也是安全的。如果我遇到不是这样的版本,我可以简单地将上面的版本替换为自定义版本,并将其包装在合适的版本中#ifdef
因为那是。维持这种情况的原因errno
不变只是我编码风格的一个怪癖;它使短路错误路径更短(重复代码更少)。
如果我要关闭包含重要用户信息的文件,我将执行fsync() or fdatasync() http://man7.org/linux/man-pages/man2/fdatasync.2.html在关闭之前。这确保了数据到达存储,但与正常操作相比也会造成延迟;因此我不会对普通数据文件这样做。
除非我会unlink()ing http://man7.org/linux/man-pages/man2/unlink.2.html关闭的文件,我会检查closefd()
返回值,并采取相应的行动。如果我可以轻松地重试,我会的,但最多一次或两次。对于日志文件和生成/流式传输的文件,我仅警告用户。
我想提醒读到这里的人我们无法制造出完全可靠的东西;这是不可能的。我们能做的,也是我认为应该做的,是detect当发生错误时,尽可能可靠。如果我们可以轻松地并且以可忽略的资源使用重试,我们应该这样做。在所有情况下,我们都应该确保(有关错误的)通知传播给实际的人类用户。让人们担心在重试操作之前是否需要完成其他可能复杂的操作。毕竟,许多工具仅用作较大任务的一部分,而最佳行动方案通常取决于该较大任务。