mtr*_*eur 3 rename concurrency files portability
假设我在 pathname 中有一些非目录(文件,命名管道/套接字,等等),在 pathname 中/tmp/foo
有一些其他非目录/tmp/bar
。然后两个(或更多)进程开始并发执行:
流程一:
unlink('/tmp/foo') /* or rename('/tmp/foo', '/tmp/removed') */
unlink('/tmp/bar') /* or rename('/tmp/bar', '/tmp/removed') */
Run Code Online (Sandbox Code Playgroud)
进程二(依此类推)执行以下操作:
link('/tmp/foo', '/tmp/bar')
Run Code Online (Sandbox Code Playgroud)
据我了解,进程二不可能成功(要么link(2)
尝试而/tmp/foo
仍然存在,在这种情况下/tmp/bar
也存在所以它必须失败EEXIST
,或者/tmp/foo
消失所以必须失败ENOENT
)。
但是这种直觉依赖于假设unlink(2)
和/或rename(2)
系统调用在它们的解链效果中本质上是顺序的,所以我正在寻找我的理解的验证:是否有任何类似 *nix 的系统,其内核允许两个unlink(2)
和/或rename(2)
调用成功,但同时会导致link(2)
成功以及(无论是由于重新排序的取消链接/tmp/foo
和/tmp/bar
,而不是抽象/隐藏,从过程调用link(2)
,或通过通过一些其他古怪的竞争状态/错误)?
我已阅读联机帮助页unlink(2)
,rename(2)
以及link(2)
Linux和一些BSD系统,并为这些功能的POSIX规范。但经过仔细考虑,我认为它们实际上没有包含任何关于此事的令人放心的内容。至少rename(2)
,我们承诺如果目标已经存在(操作系统本身的错误除外),则它会被原子替换,但没有别的。
我已经看到声称多个同时执行的rename(foo, qux)
will 原子地和可移植地除了一个重命名之外都失败了ENOENT
- 所以这是有希望的!我只是不确定这是否可以扩展到在相同情况下也link(foo, bar)
失败ENOENT
。
我意识到这是“无法证明否定”的情况之一 - 我们最多只能注意到没有证据表明link(2)
存在允许进程二成功的类似 *nix 的系统。
因此,我正在寻找的答案是尽可能多地涵盖类似 *nix 的系统(至少是 Linux、OS X 和各种 BSD,但理想情况下还有专有的仍在使用的系统,如 Solaris 10)-来自对这些系统和这一系列狭窄问题(原子/有序文件系统操作)足够熟悉的人,他们有信心(尽可能多的现实)他们知道像前面提到的 Mac 这样的问题OS X rename(2)
-not-actually-atomic bug 如果它们存在于他们熟悉的平台上。这会给我足够的信心,我认为它以一种足够便携的方式工作,可以依赖。
这不是一个“X/Y 问题”的问题——没有潜在的问题可以通过向我介绍各种锁定/IPC 机制或其他解决这些特定系统调用如何交互的不确定性的方法来回答:我特别想要想知道是否可以依靠上述系统调用在当今实际使用的 *nix-like 系统中按预期进行可移植的交互。
查看POSIX等标准以获得可移植性保证。在实践中,大多数符合 POSIX 的系统与规范有细微的偏差,但一般来说,您可以依赖规范中给出的保证。大多数现代 unice 都符合规范,即使它们尚未经过正式测试。他们可能需要在POSIX模式来运行,例如,设置POSIXLY_CORRECT=1
bash或确保/usr/xpg4/bin
领先的/bin
,并/usr/bin
在PATH
Solaris上。
Single Unix v2(POSIX 的旧扩展)有这样的说法link
:
该
link()
函数将自动为现有文件创建一个新链接,并且文件的链接计数加一。
关于rename
:
如果new参数命名的链接存在,则将其删除并将旧的重命名为new。在这种情况下,在整个重命名操作过程中,名为 new 的链接将对其他进程保持可见,并且将引用操作开始前由new或old引用的文件。
POSIX 明确指出,如果目标存在,则其替换必须是原子的。但是,它没有状态的重命名本身必须是原子,即存在时间当两个没有一点老和新的参考文件有问题,或当同样没有。实际上,这些属性在 unix 系统上是正确的,至少对于本地文件系统是这样。
此外,操作顺序是有保证的:在 C 中,;
保证顺序执行;在 sh 中,;
/newline 保证顺序执行(如 do&&
等);其他编程语言也提供类似的保证。所以在
unlink("/tmp/foo");
unlink("/tmp/bar");
Run Code Online (Sandbox Code Playgroud)
保证没有时间点/tmp/foo
存在但不存在/tmp/bar
(假设/tmp/bar
最初存在)。因此并发进程执行link("/tmp/foo", "/tmp/bar")
不会成功。
请注意,原子性并不能保证弹性。原子性是关于实时系统上可观察到的行为。在文件系统的上下文中,弹性是关于在系统崩溃的情况下发生的事情。许多文件系统为了性能牺牲了弹性,所以如果执行unlink("foo"); unlink("bar");
被中断(当前目录在磁盘存储上),它可能bar
会被删除并foo
保留在后面。
当操作发生在不同的客户端上时,一些网络文件系统提供较少的保证。NFS 的旧实现因此而臭名昭著。我认为现代实现更好,但我没有现代 NFS 的经验。