关于POSIX,
R..对相关问题的
回答非常清晰简洁:
close()
是一个不可重启的特殊情况,不应使用循环。
这让我感到惊讶,所以我决定描述我的发现,接着在结尾给出我的结论和选择的解决方案。
这并不是真正的答案。把它看作是一位程序员的意见,包括该意见背后的推理。
POSIX.1-2001和POSIX.1-2008描述了可能发生的三个errno值:EBADF
,EINTR
和EIO
。在EINTR
和EIO
之后,描述符状态为“未指定”,这意味着它可能已经关闭,也可能没有关闭。EBADF
表示fd
不是有效的描述符。换句话说,POSIX.1明确建议使用。
if (close(fd) == -1) {
/* An error occurred, see 'errno'. */
}
没有任何重试循环来关闭文件描述符。
(即使是Austin Group的
defect #519 R..提到的,也无法帮助从
close()
错误中恢复:它未指定在
EINTR
错误后是否可能进行任何I/O,即使描述符本身仍然打开。)
在Linux中,
close()
系统调用在
fs/open.c中定义,
__do_close()
在
fs/file.c中管理描述符表锁定,而
filp_close()
则在
fs/open.c中处理详细信息。
总之,描述符条目首先被无条件地从表中删除,然后进行特定于文件系统的刷新(
f_op->flush()
),接着是通知(dnotify/fsnotify hook),最后删除任何记录或文件锁。(大多数本地文件系统如ext2、ext3、ext4、xfs、bfs、tmpfs等都没有
->flush()
,因此只要有一个有效的描述符,
close()
就不会失败。据我所知,只有ecryptfs、exofs、fuse、cifs和nfs在Linux-3.13.6中具有
->flush()
处理程序。)这意味着在Linux中,如果在特定于文件系统的
->flush()
处理程序中发生写入错误,
close()
期间
没有重试的方法;文件描述符总是关闭,就像Torvalds所说的那样。
FreeBSD的
close()
手册页面描述了完全相同的行为。
无论是OpenBSD还是Mac OS X的close()
手册都没有描述在出现错误时描述符是否关闭,但我相信它们共享FreeBSD的行为。
我认为并不需要循环来安全关闭文件描述符。但是,close()可能仍会返回错误。
errno == EBADF 表示文件描述符已经关闭。如果我的代码意外遇到这种情况,则表明代码逻辑存在重大错误,进程应该优雅地退出;我宁愿让我的进程死亡也不要产生垃圾。
任何其他errno值都表示在完成文件状态时发生了错误。在Linux中,这绝对是与刷新任何剩余数据到实际存储相关的错误。特别是,如果没有缓冲数据的空间,则可以想象出ENOMEM,如果无法将数据发送或写入实际设备或介质,则为EIO,如果与存储的连接丢失,则为EPIPE,如果存储已经满了而没有保留未刷新的数据,则为ENOSPC等。如果文件是日志文件,则应该让进程报告失败并优雅地退出。如果文件内容仍然在内存中,则应删除(unlink)整个文件并重试。否则,我会向用户报告失败。
请记住,在Linux和FreeBSD中,在出现错误的情况下不会“泄漏”文件描述符;即使发生错误,它们也保证会被关闭。我假设我可能使用的所有其他操作系统都是以同样的方式运作。
从现在开始我将使用类似以下的辅助函数
#include <unistd.h>
#include <errno.h>
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系统上也是安全的。如果我遇到一个不安全的系统,我可以简单地用适当的
#ifdef
为该OS包装一个自定义版本来替换上述内容。这个保持
errno
不变的原因只是我的编码风格的怪癖;它使得错误路径的短路更短(重复代码更少)。
如果我关闭了一个包含重要用户信息的文件,我会在关闭之前执行
fsync()
或fdatasync()
以确保数据已经存储,但这也会导致与正常操作相比的延迟;因此,我不会为普通数据文件执行此操作。
除非我将
unlink()
关闭的文件,否则我将检查
closefd()
的返回值,并根据情况采取行动。如果可以轻松重试,我会重试,但最多只能重试一两次。对于日志文件和生成/流式传输文件,我只会警告用户。
我想提醒所有阅读到这里的人,我们无法做出完全可靠的任何东西;这是不可能的。我们能做的,也应该做的,是尽可能可靠地检测出错误。如果我们可以轻松地并且使用资源很少地重试,那就应该这样做。在所有情况下,我们都应该确保将有关错误的通知传播给实际的人类用户。让人类担心在重新尝试操作之前是否需要执行其他可能复杂的操作。毕竟,很多工具只是作为更大任务的一部分使用,而最佳行动方案通常取决于该更大任务。
close
不会被调用。为了谨慎起见,应该显式地调用fflush。但是,根据您提供的链接,也许您指的是close
而不是fclose
? - Jim Balterclose
,而不是 stdio 或 iostreams,它们只是一个典型的用例和一个可靠的示例来进行工作。 - Potatoswatterclose
函数都无法可靠地关闭一个文件而不是零个或两个,除非深入研究特定于平台的文档。这有点糟糕。 - Potatoswatter