如何关闭文件?

2024-02-16

经过多年的经验,我对 Posix 感到很安心。

然后我读了this https://lkml.org/lkml/2002/7/17/1652002 年左右,Linus Torvalds 发来的消息:

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

NO.

以上是

(a) 不可携带

(b) 非现行做法

“不可移植”部分来自这样一个事实:(正如有人指出的 out),一个线程环境,其中内核does关闭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++ 应用程序关闭过的每个文件上。

EDIT:为了避免在某些大师给出答案之前引起太多恐慌,我应该注意:close http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html似乎只允许在特定情况下阻止,也许这些都不适用于常规文件。我不清楚所有细节,但你不应该看到EINTR from close不选择某事fcntl or setsockopt。然而,这种可能性使通用库代码更加危险。


对于 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当发生错误时,尽可能可靠。如果我们可以轻松地并且以可忽略的资源使用重试,我们应该这样做。在所有情况下,我们都应该确保(有关错误的)通知传播给实际的人类用户。让人们担心在重试操作之前是否需要完成其他可能复杂的操作。毕竟,许多工具仅用作较大任务的一部分,而最佳行动方案通常取决于该较大任务。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何关闭文件? 的相关文章

随机推荐