如何关闭文件?

时间:2014-03-24 07:08:58

标签: c linux multithreading signals posix

经过多年的经验,我觉得与Posix和平相处。

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

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

NO。

     

以上是

     
    

(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并默认生成这样的循环,正是因为它不安全。

这是一个标准的库bug,对吗?在C ++应用程序关闭的每个文件上。

编辑:为了避免在一些大师出现回答之前引起太多警报,我应该注意到close似乎只允许在特定情况下阻止,也许没有曾经适用于常规文件。我不清楚所有细节,但如果没有EINTRclose选择某事,您就不应该fcntl setsockopt看到{{1}}。然而,这种可能性使得通用库代码更加危险。

1 个答案:

答案 0 :(得分:7)

对于POSIX,R..&#39; s answer to a related question非常简洁明了:close()是一个不可重启的特殊情况,不应该使用循环。< / p>

这对我来说是令人惊讶的,所以我决定描述我的发现,然后是我的结论和最终选择的解决方案。

这不是一个真正的答案。考虑这更像是一个程序员的意见,包括这种观点背后的推理。


POSIX.1-2001POSIX.1-2008描述了可能出现的三种可能的错误值:EBADFEINTREIOEINTREIO之后的描述符状态为&#34;未指定&#34; ,这意味着它可能已关闭,也可能未关闭。 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中定义,fs/file.c中的__do_close()管理描述符表锁定,filp_close()返回f_op->flush() 3}}处理细节。

总之,描述符条目从表中无条件地删除 first ,然后是文件系统特定的刷新(->flush()),然后是通知(dnotify / fsnotify hook),最后通过删除任何记录或文件锁。 (大多数本地文件系统,如ext2,ext3,ext4,xfs,bfs,tmpfs等,都没有close(),所以给定一个有效的描述符,->flush()不能失败。只有ecryptfs,exofs,fuse ,据我所知,cifs和nfs在Linux-3.13.6中有->flush()个处理程序。)

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

FreeBSD fs/open.c手册页描述了完全相同的行为。

close()OpenBSD close()手册页都没有描述描述符是否在出现错误时被关闭,但我相信它们共享FreeBSD行为。


我似乎很清楚,安全关闭文件描述符不需要或不需要循环。但是,errno == EBADF可能仍会返回错误。

errno表示文件描述符已经关闭。如果我的代码意外地遇到了这个,那么对我来说它表明代码逻辑中存在重大错误,并且该进程应该优雅地退出;我宁愿我的过程死也不会产生垃圾。

任何其他ENOMEM值表示最终确定文件状态时出错。在Linux中,它肯定是将任何剩余数据刷新到实际存储的错误。特别是,我可以想象EIO以防无法缓冲数据,EPIPE如果数据无法发送或写入实际设备或媒体,ENOSPC如果连接如果存储已满,对未刷新的数据没有预留,则存储丢失#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中,你没有&#34;泄漏&#34;错误情况下的文件描述符;即使发生错误,它们仍然会被关闭。我假设我可能会使用所有其他操作系统使用方式相同。)

我从现在开始使用的辅助函数将类似于

#ifdef

我知道上面的内容在Linux和FreeBSD上是安全的,我认为它在所有其他POSIX-y系统上都是安全的。如果我遇到一个不是,我可以简单地用自定义版本替换上面的内容,将其包装在适合该操作系统的errno中。保持closefd()不变的原因只是我编码风格的一个怪癖;它使短路错误路径更短(重复代码更少)。

如果我要关闭包含重要用户信息的文件,我会在关闭之前对其进行Mac OS X。这确保了数据到达存储器,但与正常操作相比也导致延迟;因此,我不会为普通数据文件做这件事。

除非我将fsync() or fdatasync()封闭文件,否则我会检查{{1}}返回值,并采取相应措施。如果我可以轻松重试,我会,但最多一次或两次。对于日志文件和生成/流式文件,我只警告用户。

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