在 Linux 中替换活动文件的最佳方法(mv/cp)?

在 Linux 中替换活动文件的最佳方法(mv/cp)?

我正在编写一个脚本,用于更新活动服务的文本包含/conf 文件。该脚本首先将更改写入临时文件。完成后,要用临时文件替换包含文件,考虑到服务处于活动状态,最好使用cpmv还是echo >其他?我不清楚它如何与保留文件句柄的程序一起工作。

如果答案是如果某些东西持有文件句柄则不可能,那么下一个最佳方法。假设服务只是打开、读取、关闭。替换文件的最安全方法是什么?

mv差点忘了,我使用 PHP 编写脚本,使用内置的 php 移动/复制方法与使用 bash /有什么区别cp

答案1

分析

当程序(例如,相关服务)获得文件句柄后,该句柄将引用文件系统中保存该文件的 inode 号所标识的同一文件。文件的路径名无关紧要。

如果文件在文件系统中被重命名或移动,路径名将发生变化。如果文件被取消链接(删除)或移动到另一个文件系统(此操作实际上是复制,然后取消链接),路径名将消失。这些都不重要。即使链接计数降至零(意味着文件系统中不再有指向该文件的路径名),句柄仍将保持有效。只有当链接计数为零且没有任何东西使用该文件时,文件系统才会真正删除该文件。

whatever … > the_file如果您使用或向文件写入内容cp foo the_file,则文件的内容将发生变化,但“文件”仍指具有未更改的 inode 号的文件。旧句柄将引用已修改的文件。持有句柄的程序将能够立即看到更改。

这种方法的一个大问题是程序可能会偶然对处于无效状态的文件进行操作。我所说的“无效”是指“根据程序逻辑无效”,而不是“文件系统损坏”。例如,>首先截断文件,因此其大小为零,然后才写入内容。程序可能会发现文件为空,但根据程序的逻辑,它永远不会为空。通常,您可能会覆盖或修改而不截断或在最后截断(到所需大小),因此在某些时候文件可能一半是旧的,一半是新的。无论如何,程序无法轻易知道文件在任何给定时刻是否完整且有效。当涉及到配置文件时,一个明智的方法是允许程序假设文件有效,而不是以这种方式更新配置。

另一种更新方式是使用mv。当您执行mv foo the_file并且 和 都foo位于the_file同一文件系统中时,the_file会被原子地替换foo,结果是foo名称下的前者的内容the_file

这里的“原子性”是指如果任何程序the_file在任何时候重新打开,那么它将获得一个文件句柄,该句柄将指向旧内容或新内容。无法发现混合状态或其他损坏的内容,因为此类内容永远不存在。

将其视为硬链接foothe_file(此操作将the_file文件名从其旧 inode 切换到 的 inode foo),然后取消链接foo。在这种情况下,“原子地”并不意味着您无法发现foo和更新的目标文件同时存在。它只意味着the_file始终是全部旧的或全部新的,没有中间状态。

笔记:

因为filename 现在与另一个 inode(在取消链接之前指向的the_fileinode)相关联,所以之前的所有文件句柄仍然指向旧文件。即使没有路径名,旧文件仍然存在于文件系统中。它可以被读取、写入;它可以增长。它是一个单独的文件,与您现在看到的不同。它与之前的不同之处就像它与之前的不同之处一样。foothe_filemvthe_filethe_filefoo

这意味着如果以这种方式进行更新,服务打开的配置文件将是徒劳的,除非服务通过路径重新打开它并再次读取配置。

当更新而不切换到另一个 inode(例如使用cp)时,您需要某种机制来告诉服务停止使用该文件(这可能包括也可能不包括关闭该文件)并告诉它稍后继续使用该文件。否则,服务可能会使用文件的某些中间状态。最基本的机制就是停止服务并在准备就绪时启动。服务可能支持某种机制来在不停止的情况下执行此操作,但我不会依赖它。

一般来说,允许中间状态存在于文件系统中是有风险的。如果发生电源故障或其他中断(例如 rash Ctrl+ C),则最终可能会得到无效文件。我已经将“无效”解释为“根据程序逻辑无效”,而不是“文件系统损坏”,但当时我们讨论的是文件只是暂时无效,最终会有效的情况。现在我们讨论的是无效状态仍然存在的情况,因为原本要将一个有效状态转变为另一个有效状态的过程在中间终止了。

mv允许您避免无效状态。mv您肯定需要某种机制来告诉服务按路径重新加载文件;否则它将坚持使用旧文件。最基本的机制是重新启动服务。服务可能支持替代方案,例如sshd在 时重新读取其配置SIGHUP,而无需重新启动。这是正确的方法。

注意很多取决于服务的行为方式:

  • 该服务可能会读取文件一次,自行配置,并且永远不会再读取,除非重新启动或被要求(如果支持)。
  • 或者它有时可能会出于某种原因使用相同的文件句柄再次读取该文件。
  • 或者有时可能会因为某种原因而重新打开并重新读取。
  • 它可能会监视文件并在文件更改时重新读取它。通常,很难判断更改何时完成,所以我认为这种方法是有缺陷的。
  • 服务可能会监视文件名,并在文件名开始指向另一个 inode 时重新读取它。这适用于mv更新方法,但仅适用于单个文件。如果配置可以存储在许多文件中,则服务应仅在需要时重新读取它。您mv可以原子地更新任何单个文件,但不能整体更新多文件配置。

明确答案

替换文件最安全的方法是什么?

当然mv是在同一个文件系统内。但是,如果您希望服务无需重新启动即可处理新文件,则需要服务支持。我不知道您的服务如何表现。

使用内置 PHP 移动/复制方法与 Bash mv/有什么区别cp

(旁注:您在 Bash 中调用mvcp是与 Bash 无关的独立可执行文件。)

我不懂 PHP,无法回答这个问题。我思考没有理由说它的 move 方法不是原子的,但我可能错了。我希望这个答案中的信息能对你自己的研究有所帮助。现在你知道要注意什么了。

答案2

这里有两个概念:文件名和文件数据。

如果文件被删除rm,则文件会丢失其名称,但其数据不会释放,直到最后一个打开它的程序停止。但是,如果文件以独占方式打开,则无法对其进行操作。

因此,如果无法删除该文件,一种已知的解决方法是重命名它。

在您的情况下,当其他程序不断打开该文件时,您可能会发现自己删除了该文件,但尚未复制新文件,因此其他程序会发现该文件丢失。

如果文件丢失不是问题,我会这样做:

  • 将文件重命名为某个临时名称
  • 复制替换文件到其位置
  • 删除临时名称。

如果此方法不适合您,您将需要使用信号量机制。您可以使用 锁文件 命令作为两个程序之间的锁。此示例来自以下man页面:

...
lockfile important.lock
...
access_"important"_to_your_hearts_content
...
rm -f important.lock
...

相关内容