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