POSIX/UNIX:如何可靠地关闭文件描述符

13

问题:

在一个close()系统调用失败并返回EINTR或EIO时,文件是否已经关闭是不确定的。 (http://pubs.opengroup.org/onlinepubs/9699919799/) 在多线程应用程序中,重新尝试关闭可能会关闭其他线程打开的无关文件。不重新尝试关闭可能导致无法使用的打开文件描述符增加。一种干净的解决方案可能涉及对新关闭的文件描述符进行fstat()调用和相当复杂的锁定机制。 此外,将所有的open/close/accept/... 调用与单个互斥锁串行化可能是一种选项。

这些解决方案没有考虑到库函数可能以不可控的方式自己打开和关闭文件,例如,一些实现std::thread::hardware_concurrency()的函数在/proc文件系统中打开文件。

如 [file.streams] C++ 标准部分所述的文件流不是一种选择。

在存在多个线程的情况下,是否有简单可靠的机制来关闭文件?


编辑:

常规文件: 虽然大多数时间不会积累无法使用的打开文件描述符,但两个条件可能会引发该问题: 1. 一些恶意软件以高频率发出信号 2. 在缓存刷新之前丢失连接的网络文件系统。

套接字:根据Stevens/Fenner/Rudoff的说法,如果在引用连接套接字的文件描述符上设置了SO_LINGER选项,并且在close()期间计时器到期之前FIN-ACK关闭序列完成,则close()将作为通用程序的一部分失败。 Linux没有显示这种行为,但FreeBSD确实如此,并且还将errno设置为EAGAIN。我理解,在这种情况下,是否失效文件描述符是不确定的。测试该行为的C++代码:http://www.longhaulmail.de/misc/close.txt 那里的测试代码输出对我来说看起来像是FreeBSD中的竞态条件,如果不是,为什么呢?

在调用close()期间,您可能需要考虑阻止信号。


1
相关链接:https://dev59.com/jVwY5IYBdhLWcg3wAj09 - alk
2
@VictorDyachenko POSIX 规定在 EINTR 和 EIO 两种情况下,fd 的状态是未指定的。 - P.P
3
在Linux上,即使“失败”,close函数也会无条件地使其参数文件描述符无效。Linus曾在邮件列表中提到过这一点。 - Petr Skocik
3
这是邮件的存档:http://yarchive.net/comp/linux/close_return_value.html - Petr Skocik
2
请参考以下内容:不检查close()的返回值有多严重?如何关闭文件?系统调用被信号中断仍需完成等等(我找不到我想要找的那个)。基本上,你卡住了。你必须将文件描述符视为已关闭 - 除非它是由文件描述符创建函数返回的(在这种情况下,你可以安全地假设关闭是OK的),否则永远不要再使用它。 - Jonathan Leffler
显示剩余2条评论
3个回答

8

3

这个问题没有实际解决方案,因为POSIX根本没有解决这个问题。

不重试关闭可能导致无法使用的打开文件描述符堆积。

尽管听起来像是合理的担忧,但我从来没有看到过由于失败的 close() 调用而导致的这种情况发生。

一个干净的解决方案可能涉及对刚关闭的文件描述符进行调用 fstat() 和相当复杂的锁定机制。

不是真的。当 close() 失败时,文件描述符的状态是未指定的。所以,您不能可靠地使用它来调用 fstat()。因为文件描述符可能已经被关闭了。在这种情况下,您正在向 fstat() 传递一个无效的文件描述符。或者另一个线程可能已经重新使用了它。在这种情况下,您将错误的文件描述符传递给 fstat()。或者文件描述符可能已经被失败的 close() 调用损坏了。

当进程退出时,所有打开的描述符都将被刷新和关闭。所以,这并不是一个实际上的问题。有人可能会争论在长时间运行的进程中,close() 失败太频繁会成为问题。但是根据我的经验,我从来没有看到过这种情况,并且 POSIX 也没有提供任何替代方案。

基本上,除了报告发生问题之外,您无法做太多事情。


我能想到的唯一潜在解决方案是在调用close()之前调用fstat(),如果close()调用失败,则再次调用fstat()并尝试确定是否为描述符另一端的相同文件/对象。但这仅适用于您知道没有其他线程可能打开相同的文件/对象的情况。正如您已经注意到的那样,这不是一个很大的问题 - 我从未见过close()调用失败。 - Andrew Henle

2
为了缓解任何问题,请明确同步文件:
  1. (如果您正在操作 FILE * ,请先调用 fflush()来确保用户空间缓冲区被清空到内核。)
  2. 调用文件描述符上的 fsync(),将有关文件的任何内核数据和元数据刷新到磁盘。
这些步骤可以在出现错误时重试而不必担心额外的问题。之后,在某些操作系统上中断关闭时可能会泄漏文件描述符或句柄,但这可能是一个较小的问题,特别是如果您检查对您重要的操作系统的行为(我怀疑在大多数相关操作系统中都没有问题)。
此外,一旦文件和数据已刷新,关闭期间被中断的机会就会小得多,因为关闭实际上不应该接触磁盘。如果您仍然遇到EIO或EINTR错误,则只需(可选)记录并忽略它,因为做其他事情可能会带来更多的伤害。这不是一个完美的世界。

1
“flush the file”是什么意思?这是文件描述符I/O,而不是文件流I/O;因此没有用户级别的缓冲区需要刷新。 - Jonathan Leffler
@JonathanLeffler 编辑的答案,更好了吗? - hyde

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接