在非事务性文件系统中实现原子文件写入

maf*_*afu 4 filesystems file-io transactions atomicity

许多常见的文件系统不提供原子操作,但是在某些情况下以原子方式写入文件非常重要。我试图为这个问题提出解决方案。

我做了以下假设:

  • 使用中的文件系统支持inode级别的原子操作(例如NTFS)。这意味着移动删除是原子的。
  • 只有程序本身才能访问文件。
  • 一次只有一个程序实例,它以单线程方式运行。
  • 为简单起见,每次都写入整个文件内容(即截断写入)。

这就留下了以下问题:写入文件时,程序可能会中断,并且文件中只剩下部分内容要写入。

我提出以下过程:

  1. 写新的内容到一个临时文件
  2. 将原始文件“ 原始”移动到一个临时位置备份
  3. 移动
  4. 删除备份

新建文件和备份文件与原始文件是有区别的(例如,它们的前缀可以不同,或者可以在同一卷上的单独目录中)。同时,它们的名称应直接映射到相应的原始名称(例如,只需使用相同的文件名)。

但是,这还不能使操作原子化。该过程可能会中断步骤1、2、3或4:

  1. 留下一个可能不完整的New
  2. 移动是原子的,但是现在缺少目标文件。这两个新的备份存在并且是完整的。
  3. Move是原子的,但是有一个未使用的Backup。在被替换的内容
  4. 删除是原子的。

使用先前的假设2和3,程序必须在崩溃后重新启动。在启动过程中,它应执行以下恢复检查:

  • 如果 存在“ 新建”,但“ 备份”不存在,则在步骤1或之后崩溃。删除“ 新建”,因为它可能不完整。
  • 如果存在“ 新建”并且“ 备份”也存在,那么我们在步骤2之后崩溃。继续执行步骤3。
  • 如果备份存在,但新的不也一样,我们一步后坠毁3.第4步继续。

仅使用原子操作的恢复过程本身将在中断后继续从中断处继续进行。

我相信这个想法可以确保对单个程序进行原子写入。这些问题仍然存在:

  • 当使用同一程序的多个实例时,恢复过程会干扰其他程序中当前正在进行的文件写入。
  • 仅读取但不写入的外部程序通常会获得正确的结果,但是如果同时对请求的条目执行写操作,则它们可能会错误地找不到任何条目。

可以通过使用策略(例如,检查其他实例,并拒绝对其他用户的目录访问)来解决这些问题(先前的假设未包括在内)。

最后,我的问题是:这样做有意义吗,或者过程中存在缺陷?是否有任何问题阻止这种方法在实践中使用?

小智 5

您应该假设只有一件事,重命名文件是原子操作

因此,执行以下步骤将确保更正(至少在像OS这样的unix上)

  1. 将新内容写入临时文件中
  2. 重命名临时文件为原始名称

这样,如果应用程序在重启时崩溃,则无需额外的代码即可获取旧内容或新内容。

  • @mafutrct:不正确;重命名可以覆盖(至少在 Unix 上)。 (2认同)