在某些情况下,文件租约可以解决根本问题——确保原子内容更新——但前提是每个读取器和写入器都为每个快照打开和关闭文件。
因为传统的复制-更新-重命名序列也存在类似的限制,所以文件租用解决方案可能也适用于 OP。
有关详细信息,请参阅man 2 fcntl 租赁 和 管理信号 部分。该进程必须与文件具有相同的所有者,或者具有CAP_LEASE 功能(通常通过文件系统功能授予该进程)。超级用户进程(以 root 身份运行)默认具有该功能。
这个想法是,当进程希望对文件进行“原子”更改时,它会获取文件的写租约。 只有在没有其他进程打开文件的情况下才会成功。 如果另一个进程试图打开文件,则租约持有者会收到一个信号,并且有长达租约中断时间(大约一分钟,通常) 降级租约(或干脆关闭文件);在此期间,开启者将阻塞。
请注意,没有办法转移开场白。情况是打开器已经拥有底层 inode 的句柄(因此访问检查和文件名解析已经发生);只是在租约被释放或破坏之前内核不会将其返回给用户空间进程。
但是,您的租约所有者可以将当前内容的副本创建到一个临时文件中,同时获得一个写租约,然后将其重命名为目标文件名。这样,每个(一组)打开程序都可以获取文件内容的句柄,就像它们在打开时一样;如果他们进行任何修改,它们将是“私有的”,并且不会反映在原始文件中。由于底层 inode 不再由任何文件名引用,因此当它们(打开它的最后一个进程)关闭它时,inode 被删除并将存储释放回文件系统。 Linux 页面缓存也很好地缓存了此类访问,因此在许多情况下,“临时复制文件”甚至从未命中实际存储介质(除非存在内存压力,即非页面缓存目的所需的内存)。
纯粹的“原子修改”不需要任何类型的副本或重命名,只在必须对读者显示为原子的写入集期间持有租约。
请注意,写入租约通常会阻塞,直到没有其他进程再打开文件,因此这种基于租约的原子更新可以发生的时间受到限制,并且不能保证始终可用。 (例如,您可能有一个惰性进程,它只是保持文件打开,偶尔轮询它。如果您有这样的进程,这种基于租约的方法将不起作用——但复制重命名方法也不起作用。 )
此外,租约仅适用于本地文件。
如果您需要基于记录的原子性,只需使用基于 fcntl 的记录锁,并让所有读取器为他们想要原子访问的区域获取读锁,所有写入器为要访问的区域获取写锁已更新,因为记录锁是建议性的(即,不阻止读取或写入,仅阻止其他记录锁)。