9

Linux 的“man close”警告(SVr4、4.3BSD、POSIX.1-2001):

不检查 close() 的返回值是一个常见严重的编程错误。上一次 write(2) 操作的错误很有可能在最后的 close() 中首先报告。关闭文件时不检查返回值可能会导致数据无声丢失。这在 NFS 和磁盘配额中尤其明显。

我可以相信这个错误很常见(至少在应用程序中;我不是内核黑客)。但在今天或过去三年的任何时候,它有多严重?尤其是:

有没有一个简单的、可重复的例子来说明这种无声的数据丢失?甚至是在 close() 期间发送 SIGKILL 之类的人为操作?

如果存在这样的示例,是否可以更优雅地处理数据丢失而不是仅仅

printf("Sorry, dude, you lost some data.\n");?

4

2 回答 2

9

[H] 这有多严重,今天还是过去三年的任何时候?

典型应用程序处理数据。他们消耗一些输入,并产生结果。因此,有两种一般情况下close()可能会返回错误:关闭输入(只读?)文件时,以及关闭刚刚生成或修改的文件时。

close()返回错误的已知情况特定于将数据写入/刷新到永久存储。特别是,操作系统通常会在本地缓存数据,然后再实际写入永久存储(at close()fsync()fdatasync());这在远程文件系统中很常见,这也是手册页上提到 NFS 的原因。

关闭只读输入文件时,我从未遇到过错误。我能想到的在现实生活中使用任何常见文件系统可能发生的所有情况都是发生灾难性故障的情况,例如内核数据结构损坏。如果发生这种情况,我认为close()错误不能是某些事情严重错误的唯一迹象。

在远程文件系统上写入文件时close(),如果本地网络容易出现故障或只是丢弃大量数据包,则时间错误非常普遍。作为最终用户,我希望我的应用程序在写入文件时告诉我是否有错误。通常与远程文件系统的连接完全断开,写入新文件失败的事实是用户的第一个指标。

如果不检查close()返回值,应用程序就会对用户撒谎。它将指示(如果不是其他情况,则缺少错误消息)该文件被正确写入,而实际上它不是,并且应用程序被告知如此;该应用程序只是忽略了该指示。如果用户像我一样,他们会对应用程序非常不满。

问题是,用户数据对您来说有多重要?大多数当前的应用程序程序员根本不在乎。Basile Starynkevitch(在对原始问题的评论中)是绝对正确的;检查close()错误不是大多数程序员费心去做的事情。

我认为这种态度是应受谴责的;无视用户数据的骑士。

不过,这是很自然的,因为用户无法明确指出哪个应用程序损坏了他们的数据。根据我的经验,最终用户通常会归咎于操作系统、硬件、开源或免费软件,或者本地 IT 支持;因此,对于程序员来说,没有压力,社会或其他方面的压力。因为只有程序员才知道诸如此类的细节,而大多数程序员并不关心,所以没有改变现状的压力。

(我知道上面说的话会让很多程序员讨厌我的胆量,但至少我是诚实的。我指出这样的事情得到的典型反应是,这种情况非常罕见,它会是浪费资源来检查这一点。这可能是真的.. 但我愿意花更多的 CPU 周期并向程序员多付几个百分点,如果这意味着我的机器实际上可以更可预测地工作,并告诉我是否它失去了情节,而不是默默地破坏了我的数据。)

有没有一个简单的、可重复的例子来说明这种无声的数据丢失?

我知道三种方法:

  1. 使用 U 盘,在决赛之后write()但在close(). 不幸的是,大多数 USB 记忆棒的硬件无法承受这种情况,因此您最终可能会将 USB 记忆棒变砖。根据文件系统的不同,您的内核也可能会出现恐慌,因为大多数文件系统都是在假设这永远不会发生的情况下编写的。

  2. 设置 NFS 服务器,并通过使用 iptables 丢弃 NFS 服务器和客户端之间的所有数据包来模拟间歇性数据包丢弃。确切的场景取决于服务器和客户端、挂载选项和使用的版本。但是,使用两个或三个虚拟机设置测试台应该相对容易。

  3. 使用自定义文件系统来模拟一次写入错误close()。当前的内核不允许您强制卸载 tmpfs 或环回挂载,只有 NFS 挂载,否则这很容易通过在最终写入之后但在close(). (如果该文件系统上有打开的文件,当前内核简单地拒绝 umount。)对于应用程序测试,创建一个 tmpfs 的变体,close()如果文件模式表明它是可取的(例如,其他可写但不是其他-可读或其他可执行文件,即。-??????-w-)将非常容易且安全。它实际上不会损坏数据,但如果内核在关闭时间报告(风险)数据损坏,它可以很容易地检查应用程序的行为。

于 2013-09-27T20:13:28.517 回答
7

调用POSIXclose()可能会导致errno被设置为:

  1. EBADF: 错误的文件号
  2. EINTR: 中断的系统调用
  3. EIO: I/O 错误(从 POSIX 规范第 6 期开始)

不同的错误表示不同的问题:

  1. EBADF表示编程错误,因为程序应该跟踪哪些文件/套接字描述符仍处于打开状态。我认为测试此错误是一项质量管理措施。

  2. EINTR似乎是最难处理的,因为尚不清楚传递的文件/套接字描述符在函数返回后是否有效(在 Linux 下它可能不是:http ://lkml.org/lkml/2002/7/ 17/165)。观察到这个错误,您或许应该检查程序处理信号的方式。

  3. EIO如手册页中所述,预计仅在特殊条件下出现。但是,至少正因为如此,应该跟踪此错误,就好像它很可能发生时确实出了问题一样。

总而言之,这些错误中的每一个都至少有一个被捕获的充分理由,所以就去做吧!;-)

可能的具体反应:

  1. 就稳定性而言,忽略一个EBADF可能是可以接受的,但不会发生错误。如前所述,修复您的代码,因为程序似乎并不真正知道它在做什么。

  2. 观察到EINTR可能表明信号正在疯狂运行。这不好。一定要寻找根本原因。由于尚不清楚描述符是否已关闭,因此请尽快重新启动系统。

  3. 遇到EIO一个绝对可能表明所涉及的硬件*1出现严重故障。然而,在强烈建议关闭系统之前,可能值得简单地重试该操作,尽管同样的问题适用于EINTR不确定描述符是否真的关闭了。如果它确实关闭了,再次关闭它是一个坏主意,因为它可能已经被另一个线程使用。尽快进行关机和硬件*1更换。


*1 硬件在这里更广泛地理解:NFS 服务器充当磁盘,因此EIO可能只是由于服务器或网络配置错误或 NFS 连接中涉及的任何原因。

于 2013-09-27T17:51:56.830 回答