【问题标题】:How to close a file?如何关闭文件?
【发布时间】:2014-05-01 10:23:55
【问题描述】:

经过多年的经验,我对 Posix 感到很平静。

然后我读到了大约 2002 年来自 Linus Torvalds 的 this 消息:

int ret;
do {
    ret = close(fd);
} while(ret == -1 && errno != EBADF);

没有。

以上是

(a) 不便携

(b) 不是现行做法

“不可移植”部分来自以下事实(正如有人指出的那样 out),内核确实关闭 FD 的线程环境 如果出现错误,FD 可能已被(内核)有效地重用于 其他一些线程,第二次关闭FD是一个BUG。

不仅循环直到EBADF 不可移植,而且任何循环都是由于竞争条件,如果我没有将这些事情视为理所当然,我可能会注意到这种情况。

但是,在 GCC C++ 标准库实现中,basic_file_stdio.cc,我们有

    do
      __err = fclose(_M_cfile);
    while (__err && errno == EINTR);

这个库的主要目标是 Linux,但它似乎没有注意到 Linus。

据我了解,EINTR 仅发生在系统调用 blocks 之后,这意味着内核在开始任何被中断的工作之前收到了释放描述符的请求。所以没有必要循环。确实,SA_RESTART 信号行为不适用于close,并默认生成这样的循环,正是因为它不安全。

那么这是一个标准库错误,对吧?在 C++ 应用程序关闭的每个文件上。

编辑:为了避免在某些专家给出答案之前引起过多的恐慌,我应该注意close 似乎只在特定情况下被允许阻止,也许从来没有适用于常规文件。我不清楚所有细节,但如果不通过fcntlsetsockopt 选择加入某些东西,您不应该从close 看到EINTR。然而,这种可能性使通用库代码更加危险。

【问题讨论】:

  • 另见 stackoverflow.com/q/13356497/153285 我在哪里获得了链接。
  • 我认为 EINTR 将来自隐式 fflush,在这种情况下不会调用 close。为谨慎起见,应该明确调用 fflush。但是,鉴于您的链接,也许您的意思是 close 而不是 fclose
  • @JimBalter 啊,好点子。而fclose 并没有在内部处理这个问题,因为它假装是一个系统调用。我认为这可能就是答案……但我们仍然需要验证这个循环不能导致两个文件被关闭。
  • @JimBalter 是的,我的问题是关于 close,而不是 stdio 或 iostreams,它们只是一个规范的用例和一个可靠的工作示例。
  • @JimBalter 所以。似乎没有办法可靠地关闭一个文件,而不是零或两个,在包括 Linux 在内的任何平台上使用 C 或 C++ 标准库,或使用 Posix close 而不深入研究特定于平台的文档。这有点糟糕。

标签: c linux multithreading signals posix


【解决方案1】:

关于 POSIX,R..answer to a related question 非常清晰简洁:close() 是不可重启的特例,不应使用循环。

这让我很惊讶,所以我决定描述我的发现,然后是我的结论和最后选择的解决方案。

这不是一个真正的答案。这更像是一个程序员同行的意见,包括该意见背后的原因。


POSIX.1-2001POSIX.1-2008 描述了三个可能出现的 errno 值:EBADFEINTREIOEINTREIO 之后的描述符状态是"unspecified",这意味着它可能已关闭,也可能未关闭。 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 挂钩),最后是删除任何记录或文件锁定。 (大多数本地文件系统,如 ext2、ext3、ext4、xfs、bfs、tmpfs 等,都没有 ->flush(),因此给定一个有效的描述符,close() 不会失败。只有 ecryptfs、exofs、fuse、cifs 和据我所知,nfs 在 Linux-3.13.6 中有 ->flush() 处理程序。)

这确实意味着在 Linux 中,如果文件系统特定的 ->flush() 处理程序在 close() 期间发生写入错误,没有办法重试;文件描述符总是关闭的,就像 Torvalds 说的那样。

FreeBSD close() 手册页描述了完全相同的行为。

OpenBSDMac OS X 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()。这确保了数据到达存储,但与正常操作相比也会导致延迟;所以我不会对普通的数据文件这样做。

除非我将unlink()ing 关闭文件,否则我将检查closefd() 返回值,并采取相应措施。如果我可以轻松地重试,我会,但最多一次或两次。对于日志文件和生成/流式传输的文件,我只警告用户。

我想提醒所有读到这里的人,我们无法让任何东西完全可靠;这是不可能的。我们能做的,在我看来应该做的,是在发生错误时尽可能可靠地检测。如果我们可以轻松地使用可忽略的资源重试,我们应该这样做。在所有情况下,我们都应该确保将通知(关于错误)传播给实际的人类用户。让人们担心在重试操作之前是否需要完成其他一些可能很复杂的操作。毕竟,许多工具仅用作较大任务的一部分,而最佳的行动方案通常取决于该较大任务。

【讨论】:

  • 仅介绍和免责声明就值得 +1 :) 。到目前为止对我来说带回家的教训是:1)避免裸露的 fd 并具体说明正在关闭的内容,例如预测特定的令人惊讶的语义。 2) 始终针对 NFS 故障进行测试。系统管理员需要可靠性,但使用它会自取其辱。也许为了获得最佳可靠性,日志文件等的管理应该检测 NFS 文件系统并透明地生成本地镜像。
猜你喜欢
  • 2016-12-03
  • 1970-01-01
  • 2016-01-19
  • 2015-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-14
相关资源
最近更新 更多