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

POSIX/UNIX: How to reliably close a file descriptor

问题:

在因 EINTR 或 EIO 失败的 close() 系统调用之后,不确定文件是否已关闭。 (http://pubs.opengroup.org/onlinepubs/9699919799/) 在多线程应用中, 重试关闭可能会关闭其他线程打开的不相关文件。不重试关闭可能会导致无法使用的打开文件描述符堆积起来。一个干净的解决方案可能涉及在新关闭的文件描述符上调用 fstat() 和一个相当复杂的锁定机制。 此外,使用单个互斥锁序列化所有 open/close/accept/... 调用可能是一种选择。

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

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() 期间发出阻塞信号。

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

Not retrying the close may result in unusable open file descriptors piling up.

尽管这听起来像是合理的担忧,但我从未见过由于 close() 次呼叫失败而发生这种情况。

A clean solution might involve invoking fstat() on the freshly closed file descriptor and a quite complex locking mechanism.

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

当进程退出时,所有打开的描述符都将被刷新并关闭。所以,这不是一个实际问题。有人可能会争辩说,在 close() 经常失败的漫长 运行 过程中,这将是一个问题。但我已经看到这种情况发生在我的经验中,POSIX 也没有提供任何替代方案。

基本上,除了报告问题发生之外,您对此无能为力。

要缓解任何问题,请显式同步文件:

  1. (如果您正在对 FILE* 进行操作,首先对其调用 fflush() 以确保用户 space 缓冲区已清空到内核。)
  2. 在文件描述符上调用 fsync(),将有关该文件的任何内核数据和元数据刷新到磁盘。

这些你可以重试错误而不用担心。在那之后,在某些操作系统上可能会泄漏文件描述符或中断关闭句柄可能是一个小问题,特别是如果您检查对您很重要的操作系统的行为(我怀疑大多数相关操作系统都没有问题)。

此外,一旦文件和数据被刷新,在关闭过程中被中断的可能性就会小得多,因为关闭实际上不应该接触磁盘。如果您确实得到了 EIO 或 EINTR,只需(可选)记录并忽略它,因为做任何其他事情可能弊大于利。这不是一个完美的世界。

此问题已在 POSIX 的下一期中修复;不幸的是,在最近的 TC2 中它的变化太大了。参见 the final accepted text for Austin Group Issue #529