问题

问题

X在 Unix 上安全、原子地写入文件的正常方法是:

  1. 将新文件内容写入临时文件Y
  2. rename(2) YX

在这两个步骤中,我们似乎除了X“就地”更改之外什么也没做。

它可以防止竞争条件和意外数据丢失(已X损坏但Y不完整或已损坏)。

X这样做的缺点(在本例中)是它不会就地写入引用的 inode ;rename(2)使X引用一个新的索引节点号。

X一个文件的链接计数> 1(显式硬链接)时,现在它不再像以前那样引用相同的inode,硬链接已损坏。

消除该缺点的明显方法是就地写入文件,但这不是原子的,可能会失败,可能会导致数据丢失等。

是否有某种方法可以像原子一样完成此操作rename(2)但保留硬链接?

也许可以将 (临时文件) 的 inode 编号更改Y为与 相同X,并为其X命名?inode 级别的“重命名”。

X这将有效地写入with的新内容引用的 inode Y,但不会破坏其硬链接属性,并保留旧名称。

如果假设的索引节点“重命名”是原子的,那么我认为这将是原子的并且可以防止数据丢失/竞争。

答案1

问题

您有一个(大部分)详尽的系统调用列表这里

您会注意到没有“替换此索引节点的内容”调用。修改该内容始终意味着:

  1. 开幕file 来获取文件描述符。
  2. 选修的 寻找到所需的写入偏移量
  3. 写作到文件。
  4. 选修的 截断旧数据,如果新数据较小。

第4步可以提前完成。还有一些快捷方式,例如写入,直接在指定的偏移处写入,结合步骤#2和#3,或者散写

另一种方法是使用内存映射,但情况会变得更糟,因为写入的每个字节都可能独立发送到底层文件(概念上就好像每次写入都是 1 字节write调用)。

→ 重点是,您可以拥有的最佳场景仍然是 2 个操作: 1write和 1 truncate

无论您以什么顺序执行它们,您仍然面临另一个进程弄乱中间文件并最终导致文件损坏的风险。

解决方案

正常溶液

正如您所指出的,这就是为什么规范的方法是创建一个新文件,您知道您是唯一的编写者(您甚至可以通过组合O_TMPFILE和来保证这一点linkat),然后自动将旧名称重定向到新文件。

还有其他两个选项,但是都以某种方式失败:

强制锁定

它通过设置特殊的标志组合来拒绝其他进程的文件访问。听起来像是这项工作的工具,对吧?然而:

  • 它必须在文件系统级别启用(它是安装时的一个标志)。
  • 警告:Linux 的强制锁定实现是不可靠的。

    从 Linux 4.5 开始,强制锁定已成为可选功能。这是完全删除此功能的第一步。

这是合乎逻辑的,因为 Unix 一直避免使用锁。它们很容易出错,并且不可能覆盖所有边缘情况并保证没有死锁。

咨询锁定

它是使用福康特尔系统调用。然而,它只是建议性的,大多数程序都会忽略它。

事实上,它只适用于管理多个协作进程之间共享文件的锁。

结论

有没有某种方法可以像 rename(2) 一样以原子方式执行此操作,但保留硬链接?

不。

索引节点是低级别的,几乎是一个实现细节。很少有 API 承认它们的存在(我相信stat调用系列是唯一的)。

无论你尝试做什么,都可能依赖于滥用 Unix 文件系统的设计,或者只是对它要求太多。

这可能有点像XY-问题

相关内容