cat*_*cat 5 system-calls hard-link inode rename write
X
在 Unix 上安全、原子地写入文件的正常方法是:
Y
。rename(2)
Y
到 X
在两个步骤中,我们似乎除了X
“就地”更改之外什么也没做。
它可以防止竞争条件和意外数据丢失(X
被破坏但Y
不完整或被破坏)。
这样做的缺点(在这种情况下)是它不会写入X
就地引用的 inode ;rename(2)
makeX
引用一个新的 inode 编号。
当X
链接计数> 1(显式硬链接)的文件时,现在它不像以前一样引用相同的inode,硬链接已损坏。
消除缺点的明显方法是就地写入文件,但这不是原子的,可能会失败,可能导致数据丢失等。
有没有办法像原子一样做到rename(2)
但保留硬链接?
也许将Y
(临时文件)的 inode 编号更改为与 相同X
,并为其X
命名?一个 inode 级别的“重命名”。
这将有效地写入X
withY
的新内容引用的 inode ,但不会破坏其硬链接属性,并且会保留旧名称。
如果假设的 inode“重命名”是原子的,那么我认为这将是原子的并且可以防止数据丢失/竞争。
您有一个(大部分)详尽的系统调用列表here。
您会注意到没有“替换此 inode 的内容”调用。修改该内容总是意味着:
第 4 步可以提前完成。还有一些快捷方式,例如pwrite,它直接在指定的偏移量处写入,结合步骤 #2 和 #3,或分散写入。
另一种方法是使用内存映射,但它会变得更糟,因为写入的每个字节都可能独立发送到底层文件(从概念上讲,好像每次写入都是 1 字节write
调用)。
? 关键是您可以拥有的最佳场景仍然是 2 个操作:一个write
和一个truncate
。
无论您执行它们的顺序如何,您仍然需要冒另一个过程来弄乱中间的文件并最终导致文件损坏。
正如您所指出的,这就是为什么规范方法是创建一个新文件的原因,您知道您是唯一的作者(您甚至可以通过组合O_TMPFILE
和来保证这一点linkat
),然后以原子方式将旧名称重定向到新文件。
还有其他两种选择,但都以某种方式失败:
它通过设置特殊的标志组合来拒绝其他进程访问文件。听起来像是这项工作的工具,对吧?然而:
警告:强制锁定的 Linux 实现是不可靠的。
自 Linux 4.5 起,强制锁定已成为可选功能。这是完全删除此功能的第一步。
这只是合乎逻辑的,因为 Unix 总是避免使用锁。它们容易出错,并且不可能覆盖所有边缘情况并保证不发生死锁。
它是使用fcntl系统调用设置的。然而,它只是建议性的,大多数程序只是忽略它。
事实上,它只适用于在多个协作的进程之间管理共享文件的锁。
有没有办法像 rename(2) 那样以原子方式进行但保留硬链接?
不。
inode 是低级的,几乎是一个实现细节。很少有 API 承认它们的存在(我相信stat
调用系列是唯一的)。
无论您尝试做什么,都可能依赖于滥用 Unix 文件系统的设计或只是对其要求过高。
这可能是XY 问题吗?