锁定执行文件:Windows可以,Linux不行。为什么?

92

我注意到在Windows上执行文件(.exe或.dll)时,它会被锁定,无法删除、移动或修改。

而Linux则不会锁定正在执行的文件,您可以删除、移动或修改它们。

为什么Windows要锁定而Linux不用呢?锁定有什么好处吗?


7
有一个叫做WhoLockMe的实用工具,它会在资源管理器的右键菜单中添加一个菜单项,可以显示锁定给定文件的进程。当你遇到奇怪的文件锁定错误时非常有用。编辑:我知道这并不回答问题,但我认为在这种情况下它足够有用,值得作为一个独立的回答(而不是只是一个评论)。 - JesperE
8个回答

114

Linux拥有引用计数机制,因此您可以在文件正在执行时删除它,并且只要某些进程(先前打开它的进程)仍然保留了对它的打开句柄,它就会继续存在。删除文件时,该文件的目录条目被移除,因此无法再次打开它,但已经使用此文件的进程仍然可以使用它。一旦所有使用此文件的进程终止,该文件将自动删除。

Windows没有这种功能,因此它被迫锁定该文件,直到所有从中执行的进程都已完成。

我认为Linux的行为更可取。可能存在一些深层次的架构原因,但我发现最具说服力的主要(简单)原因是,在Windows中,有时不能删除文件,您不知道为什么,只知道某个进程正在使用它。而在Linux中,永远不会出现这种情况。


2
请注意,您可以在Windows中使用Process Explorer等工具来查看哪个进程正在使用文件/文件夹。 - J c
18
在支持Linux行为的实际原因是,您可以在系统运行时更新操作系统和其他软件,而无需重新启动,甚至可以在不重新启动的情况下切换运行的内核。尽管这很困难,但对于需要最大正常运行时间的应用程序才会进行此操作。 - joelhardi
7
Windows没有这个功能......您确定吗?NT内核基于使用句柄和引用来对对象进行引用计数。 - Adam Mitz
14
Windows实际上有3个比特位可以设置,用来定义另一个进程在文件打开时可以做什么。如果设置了"FILE_SHARE_DELETE"比特位,则可以在文件打开时删除文件。我不认为PE加载器(负责加载EXE和DLL文件)会设置这个比特位。句柄是引用计数的,在最后一个句柄被释放时文件会被删除,但与Unix的区别在于,当发生这种情况时,NT会阻止创建具有相同名称的新文件。 - asveikau
2
Comonad所说的完全是错误的,NTFS当然使用硬链接,并且一直在使用,符号链接是在Windows Vista中添加的。同样完全错误的是Windows不使用引用计数,它确实使用,只需阅读像CreateFile这样的API,其中清楚地说明了文件何时可删除等情况。而且这也是问题的真正答案的适当位置:CreateFile有一个名为dwShareMode的参数,它控制打开文件的强制锁定并让应用程序决定。默认值是独占锁定... - Thorsten Schöning
显示剩余7条评论

32
据我所知,Linux在运行时确实会锁定可执行文件 -- 但是,它锁定的是inode。这意味着您可以删除"文件",但是inode仍然存在于文件系统中,未更改,您真正删除的只是一个链接。
Unix程序经常使用这种文件系统思维方式,创建临时文件,打开它,删除名称。您的文件仍然存在,但名称已被释放供其他人使用,没有其他人可以看到它。

一直都是这样吗?有什么例子吗? - Mez
4
查询谷歌关于“Unix安全临时文件”,你会找到足够的描述来展示这种技术是众所周知和常用的。虽然我没有具体的例子可提供,但我敢说任何注重安全的应用程序使用临时文件都会这样做。 - Dave Sherohman

29

Linux会锁定文件。如果你试图覆盖正在执行的文件,你将会得到“ETXTBUSY”(文本文件忙)错误。但是你可以删除该文件,并且当最后一个对它的引用被移除时,内核将删除该文件。(如果机器没有干净地关闭,则这些文件是当检查文件系统时产生“已删除的i节点具有零d时间”消息的原因。它们没有完全被删除,因为运行的进程仍在引用它们,现在他们是。)

这样做有一些重要的优点,您可以通过删除可执行文件、替换它然后重新启动进程来升级正在运行的进程。甚至init也可以像这样升级,替换可执行文件,发送一个信号,它就会重新exec ()自身,而无需重新启动。(这通常由软件包管理系统自动完成作为其升级的一部分)

在Windows下,替换正在使用的文件似乎是一个很大的麻烦,通常需要重新启动以确保没有正在运行的进程。

可能会出现一些问题,例如,如果你有一个非常大的日志文件,并删除了它,但忘记告诉正在记录该文件的进程重新打开该文件,它将继续保持引用,你会想知道为什么你的磁盘没有突然获得更多的可用空间。

你也可以在Linux下使用这个技巧来处理临时文件。打开文件,删除它,然后继续使用该文件。当你的进程退出(无论出于什么原因——甚至是电源故障),该文件将被删除。

像lsof和fuser这样的程序(或者只是在/proc//fd中四处查看)可以向你展示哪些进程打开了没有名称的文件。


6
我认为Linux / Unix不使用相同的锁定机制,因为它们是作为多用户系统从头构建的,这意味着可能会有多个用户使用同一文件,甚至可能是出于不同的目的。
锁定的优势在哪里?嗯,它可能会减少操作系统需要管理的指针数量,但现在节省的量非常微不足道。我能想到的最大的优点是:你可以减少一些用户可见的歧义。如果用户A正在运行一个二进制文件,而用户B删除它,那么实际文件必须一直保留,直到用户A的进程完成。然而,如果用户B或任何其他用户在文件系统中查找它,他们将无法找到它,但它将继续占用空间。对我来说并不是一个很大的问题。
我认为主要是关于与Windows文件系统的向后兼容性的问题。

在这里,“Windows”指的是Windows NT系列。它被设计为单用户Windows 3.11的多用户后继者。相比之下,Unix是Multics的单用户后继者。 - MSalters

6
我认为你对Windows的理解过于绝对了。通常情况下,它不会为可执行文件的代码部分分配交换空间。相反,它会锁定可执行文件和DLL文件。如果需要再次使用已丢弃的代码页,它们会被重新加载。但是使用/SWAPRUN参数,这些页面将保存在交换空间中。这用于CD或网络驱动器上的可执行文件。因此,Windows不需要锁定这些文件。
对于.NET,请查看“Shadow Copy”(影像副本)。

2
在文件中执行的代码是否应该被锁定是一个设计决策,微软只是决定进行锁定,因为在实践中它有明显的优势:这样你就不需要知道哪个版本的哪个代码被哪个应用程序使用。这是 Linux 默认行为的一个主要问题,大多数人都会忽略它。如果系统范围的库被替换,你很难知道哪些应用程序使用了这些库的代码,大多数情况下,你能得到的最好结果就是包管理器知道一些使用这些库的用户,并重新启动它们。但这仅适用于一般和众所周知的事物,例如 Postgres 及其库等。更有趣的场景是,如果你针对某些第三方库开发自己的应用程序,并且这些库被替换了,因为大多数情况下,包管理器根本不知道你的应用程序。这不仅仅是本地 C 代码或类似的问题,几乎所有东西都可能发生:只需使用带有 mod_perl 和一些 Perl 库的 httpd,并使用包管理器安装这些 Perl 库,因为任何原因让包管理器更新这些 Perl 库。它不会重新启动你的 httpd,仅仅是因为它不知道依赖关系。有很多类似这个例子的情况,因为任何文件都可能包含在内存中由任何运行时使用的代码,想想 Java、Python 和所有这些东西。
所以有一个很好的理由认为默认情况下锁定文件可能是一个不错的选择。当然,你不需要同意这个理由。
那么微软做了什么呢?他们简单地创建了一个API,使得调用应用程序有机会决定是否锁定文件,但他们决定这个API的默认值是为第一个调用应用程序提供独占锁。看看CreateFile周围的API及其dwShareMode参数。这就是为什么你可能无法删除某些应用程序正在使用的文件的原因,因为它根本不关心你的用例,使用了默认值,因此被Windows锁定了一个文件。
请不要相信有人告诉你Windows不使用HANDLE的引用计数或不支持硬链接等,这是完全错误的。几乎每个使用HANDLE的API都记录了其关于引用计数的行为,你可以在几乎任何关于NTFS的文章中轻松阅读到它确实支持硬链接,并且一直都支持。自Windows Vista以来,它还支持符号链接,并通过提供API来读取给定文件的所有硬链接等来改进对硬链接的支持。
此外,您可能只是想查看用于描述文件的结构,例如Ext4NTFS之间的区别,它们有很多共同点。两者都使用extent的概念,将数据与文件名等属性分开,而inode几乎只是另一个名称,用于早期但类似的概念。即使维基百科也在其文章中列出了这两个文件系统。
在网络上,关于Windows文件锁定与其他操作系统的比较存在很多不必要的恐慌,就像碎片整理一样。通过简单地阅读维基百科,可以排除其中的一些恐慌。

但这就是我的观点:决定使用哪个DLL版本是DLL地狱的一部分。如果您想要区别于Windows的某些老奇怪行为,可以称其为“依赖地狱”。无论如何,默认锁定可执行文件对于管理共享依赖项没有帮助。那些依赖于特定文件的东西甚至在尝试升级时可能都没有运行;没有额外的安全性。共享依赖项有两种选择:自由共享,这会在尝试运行它时导致某些东西遭到破坏,或者包管理器,这会阻止您安装一些东西。 - jpmc26
这个问题与决定使用哪个EXE或DLL没有任何关系,而是关于默认情况下会发生什么以及原因。你正在讨论完全不同的话题。锁定是在决定执行哪个EXE或DLL后由第一个用户(在这个例子中是Windows本身)使用,以获得一定程度的额外控制,并告诉其他人有关该控制的信息。当然,不允许“其他人”删除或写入Windows需要的文件并将它们锁定,是一种额外协调的机制。 - Thorsten Schöning
重点是“其他人”不知道很多事情,只是忽略了许多问题的复杂性。你的例子中,来自某个可能根本没有运行的DLL的代码就是这样一个例子,因为没有人知道。你可以选择忽略这些问题,只需放置新文件即可,或者关心复杂性,微软决定关心。这是一个任意的决定,你不需要遵循,但它并不总是错误的,仅仅因为你喜欢忽略事情。 - Thorsten Schöning
但微软的决定并没有减少复杂性。一旦二进制文件被加载到内存中,持有文件锁并不能解决任何问题,反而会同时创建问题,因为它使用户难以进行更改。 - jpmc26
1
一些EXE或DLL通常不是“在内存中”,而是默认映射到其中。映射需要文件内容可用,因此默认情况下随意替换被认为是不安全的,应该知道自己在做什么。如果惊讶于被锁定的EXE或DLL,则显然不是这种情况。另一方面,所有其他文件默认只被锁定,而不是必须的,因此应用程序可以根据其用例决定是否允许您进行写入或删除操作。应用程序开发人员应该比任意用户更了解如何使用他们的文件以及哪些操作是安全的。 - Thorsten Schöning
显示剩余10条评论

0

当运行时,可执行文件会逐步映射到内存中。这意味着只有在需要时才会加载可执行文件的部分。如果在所有部分被映射之前将文件交换出去,可能会导致严重的不稳定性。


0

NT变体有

openfiles

命令,它将显示哪些进程在哪些文件上具有句柄。但是,它需要启用系统全局标志“维护对象列表”。

openfiles /local /?

告诉您如何执行此操作,并且还会因此产生性能损失。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接