问题
假设我在 pathname 处有一些非目录(文件、命名管道/套接字等),/tmp/foo
在 pathname 处有一些其他非目录/tmp/bar
。然后两个(或更多)进程开始并发执行:
流程一的作用是:
unlink('/tmp/foo') /* or rename('/tmp/foo', '/tmp/removed') */
unlink('/tmp/bar') /* or rename('/tmp/bar', '/tmp/removed') */
流程二(依此类推)执行以下操作:
link('/tmp/foo', '/tmp/bar')
据我了解,进程二不可能成功(要么在仍然存在的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)
,还是通过其他一些奇怪的竞争条件/错误)?
目前的理解
我已经阅读了Linux 和一些 BSD 的unlink(2)
、rename(2)
、 和手册页,以及这些函数的 POSIX 规范。link(2)
但经过仔细考虑,我认为它们实际上并没有在这个问题上包含任何令人放心的内容。至少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)
如果它们存在于他们熟悉的平台上,则不是实际上的原子错误。这将使我有足够的信心,相信它的工作方式是我认为的,并且以足够便携的方式进行依赖。
最后说明
这不是一个“X/Y 问题”问题 - 没有任何根本问题可以通过向我介绍各种锁定/IPC 机制或其他解决这些特定系统调用如何交互的不确定性的方法来回答:我特别想要了解是否可以依靠上述系统调用在当今实际使用的 *nix 类系统中按预期进行可移植交互。
答案1
查看标准,例如POSIX以保证可移植性。实际上,大多数符合 POSIX 标准的系统与规范有微小的偏差,但一般来说,您可以依赖规范中给出的保证。大多数现代 unice 都遵守该规范,即使它们尚未经过正式测试。它们可能需要在 POSIX 模式下运行,例如POSIXLY_CORRECT=1
使用 bash 设置或确保其在Solaris 上/usr/xpg4/bin
处于领先地位/bin
。/usr/bin
PATH
单一 Unix v2(POSIX 的旧扩展)有这样的说法link
:
该
link()
函数将自动为现有文件创建一个新链接,并且文件的链接计数加一。
关于rename
:
如果链接命名为新的参数存在,它被删除并且老的重命名为新的。在这种情况下,在整个重命名操作过程中,名为 new 的链接对其他进程保持可见,并且将引用由新的或者老的在手术开始之前。
POSIX 明确规定,如果目标存在,则其替换必须是原子的。然而,它并没有声明重命名本身必须是原子的,即不存在两者同时发生的时间点。老的和新的参考有问题的文件,或者当两者都没有时。实际上,这些属性在 UNIX 系统上是正确的,至少在本地文件系统上是这样。
此外,操作的顺序是有保证的:在C中,;
保证顺序执行;在 sh 中,;
/newline 保证顺序执行(如 do&&
等);其他编程语言也提供类似的保证。所以在
unlink("/tmp/foo");
unlink("/tmp/bar");
保证不存在/tmp/foo
存在但不存在的时间点/tmp/bar
(假设/tmp/bar
最初存在)。因此并发进程执行link("/tmp/foo", "/tmp/bar")
无法成功。
请注意,原子性并不能保证弹力。原子性是关于实时系统上可观察的行为。在文件系统的上下文中,弹性是指系统崩溃时发生的情况。许多文件系统为了性能而牺牲了弹性,因此如果 的执行unlink("foo"); unlink("bar");
被中断(当前目录位于磁盘存储上),则可能bar
会被删除并foo
保留在后面。
当操作发生在不同客户端上时,某些网络文件系统提供的保证较少。较旧的 NFS 实现因此而臭名昭著。我认为现代的实现更好,但我没有现代 NFS 的经验。