为什么可以在Ubuntu中移动正在运行的程序?


24

我刚刚意识到我能够将正在运行的活动程序移至其他目录。以我的经验,这在MacO或Windows中是不可能的。它在Ubuntu中如何工作?

编辑:我认为这在Mac上是不可能的,但显然可以通过评论进行验证。也许只有在Windows上才有可能。感谢所有的答案。


2
几乎是跨站点的欺骗:stackoverflow.com/a/196910/1394393
jpmc26 '16

1
您不能rename(2)在OS X上运行可执行文件吗?会发生什么,您得到EBUSY什么?为什么不起作用?named(2)手册页没有ETXTBUSY针对该系统调用的文档,仅讨论EBUSY了目录重命名的可能性,因此我不知道POSIX系统甚至不允许重命名可执行文件。
彼得·科德斯

3
macOS应用程序还可以在运行时移动,而不会被丢弃。我认为某些应用可能在此之后出错,例如,如果它们将文件URL存储到二进制或捆绑资源的某个位置作为变量,而不是通过NSBundle等人生成此类URL。我怀疑这是macOS的POSIX合规性。
君士坦丁·萨鲁哈斯

1
它实际上按照Linux的意图工作,您应该知道自己在做什么。:P
userDepth '16

2
我想另一种方式来思考是,为什么就不是可能呢?仅仅因为Windows不允许您这样做,并不一定意味着由于流程的工作原理或根本原因根本不可能。
托马斯

Answers:


32

让我分解一下。

当您运行可执行文件,系统调用的顺序执行,最显着的fork()execve()

  • fork()创建调用进程的子进程,该子进程(主要是父进程的确切副本)都仍在运行相同的可执行文件(使用写时复制内存页,因此非常高效)。它返回两次:在父级中,它返回子级PID。在子进程中,它返回0。通常,子进程立即调用execve:

  • execve()将可执行文件的完整路径作为参数,并将调用过程替换为可执行文件。此时,新创建的进程将获得其自己的虚拟地址空间(即虚拟内存),并在其入口点开始执行(处于平台ABI的新进程规则所指定的状态)。

此时,内核的ELF加载器已将可执行文件的文本和数据段映射到内存中,就好像它使用了mmap()系统调用一样(分别具有共享的只读和私有读写映射)。BSS也像使用MAP_ANONYMOUS一样进行映射。(顺便说一句,我忽略了动态链接这里简单:动态链接open()S和mmap()一切都跳跃到主可执行文件的入口点之前的动态库)。

在new-exec()启动开始运行自己的代码之前,实际上只有几页从磁盘加载到内存中。如果/当过程触及其虚拟地址空间的那些部分时,将根据需要按需分页其他页面。(在开始执行用户空间代码之前预加载任何代码或数据页面只是性能优化。)


可执行文件由较低级别的索引节点标识。在开始执行文件之后,内核会通过inode引用而不是文件名来保持文件内容的完整性,就像打开文件描述符或文件支持的内存映射一样。因此,您可以轻松地将可执行文件移动到文件系统的其他位置,甚至移动到其他文件系统上。附带说明一下,要查看流程的各种统计信息,您可以查看/proc/PID(PID是给定流程的流程ID)目录。您甚至可以将可执行文件打开为/proc/PID/exe,即使该文件已从磁盘取消链接。


现在让我们深入研究一下:

在同一文件系统中移动文件时,执行的系统调用为rename(),只是将文件重命名为另一个名称,文件的inode保持不变。

在两个不同的文件系统之间,发生两件事:

  • 首先通过read()和将文件内容复制到新位置write()

  • 之后,使用取消该文件与源目录的链接unlink(),显然该文件将在新文件系统上获得一个新的inode。

rm实际上只是unlink()从目录树中访问给定的文件,因此具有目录的写许可权将使您有足够的权利从该目录中删除任何文件。

现在,为了好玩,想象一下当您在两个文件系统之间移动文件而又没有unlink()源文件的许可时会发生什么?

那么,该文件将首先复制到目标(read()write()),然后unlink()将由于权限不足失败。因此,该文件将保留在两个文件系统中!


5
您的虚拟和物理内存有些混乱。您对程序加载到物理内存的方式的描述不准确。exec系统调用根本不会将可执行文件的各个部分复制到物理内存,而只会加载启动进程所需的部分。之后,可能需要很长时间才能按需加载所需的页面。可执行文件字节是进程虚拟内存的一部分,可能会在整个过程中被读取,甚至可能再次读取。
jlliagre

@jlliagre编辑,希望现在已经澄清。谢谢。
heemayl

6
“该进程不再使用文件系统”语句仍然值得怀疑。
jlliagre

2
关于文件系统中给定文件不是由文件名直接标识的基本理解应该更加清楚。
托尔比约恩Ravn的安德森

2
在您的更新中仍然不准确。该mmapunmap系统调用不是用来装载和卸载点播页面,页面由内核访问时,他们生成页面错误,当OS感觉RAM将得到更好的用于别的网页都从内存中卸载加载。这些加载/卸载操作不涉及系统调用。
jlliagre

14

好吧,这很简单。让我们以一个名为/ usr / local / bin / whoopdeedoo的可执行文件为例。那只是对所谓的inode(Unix文件系统上文件的基本结构)的引用。标记为“使用中”的索引节点。

现在,当您删除或移动文件/ usr / local / whoopdeedoo时,唯一被移动(或擦除)的是对索引节点的引用。索引节点本身保持不变。基本上就是这样。

我应该进行验证,但是我相信您也可以在Mac OS X文件系统上执行此操作。

Windows采用不同的方法。为什么?谁知道...?我对NTFS的内部并不熟悉。从理论上讲,所有使用对文件名的内部结构的引用的文件系统都应该能够做到这一点。

我承认,我过于简化了,但是请阅读Wikipedia上的“含义”部分,该部分比我做的要好得多。


1
好吧,如果您在Windows中使用快捷方式来启动可执行文件,也可以擦除该快捷方式,如果您想像这样比较它的话,也许吗?= 3

2
不,这就像擦除符号链接一样。在其他注释的某处,该行为是由于FAT文件系统的传统支持所致。这听起来可能是一个原因。
颚theshark

1
这不会有什么特别与索引节点。NTFS使用MFT记录来跟踪文件状态,而FAT为此使用目录条目,但是Linux从用户的角度来看仍然与这些文件系统以相同的方式工作。
罗斯兰

13

其他所有答案似乎都缺少的一件事是:一旦打开文件并且程序保存了打开的文件描述符,则在关闭该文件描述符之前,不会从系统中删除该文件。

尝试删除引用的索引节点将被延迟,直到文件关闭:在相同或不同文件系统中重命名不会影响打开的文件,而与重命名行为无关,也不会显式删除或覆盖新文件。弄乱文件的唯一方法是显式打开文件的inode并弄乱内容,而不是通过对目录的操作(如重命名/删除文件)来进行处理。

此外,当内核执行文件时,它会保留对可执行文件的引用,这将再次防止在执行过程中对其进行任何修改。

因此,最后即使您看起来能够删除/移动组成正在运行的程序的文件,实际上这些文件的内容仍保留在内存中,直到程序结束。


1
这个不对。execve()不返回任何FD,它仅执行程序。因此,例如,如果您运行tail -f /foo.log它们,则它们是/proc/PID/fd/<fd_num>与可执行文件本身关联的FD()tailfoo.log但与可执行文件本身tail(而不是其父文件)关联。单个可执行文件也是如此。
heemayl

@heemayl我没有提到,execve所以我看不出这有什么关系。一旦内核开始执行文件,尝试替换该文件将不会修改内核将要加载的程序,以渲染点模拟。如果要在可执行文件运行时对其进行“更新”,则可以execve在某个时候调用以使内核重新读取该文件,但是我不认为这有多重要。关键是:在可执行文件停止运行之前,删除“正在运行的可执行文件”并不会真正触发任何数据删除。
巴库里

我说的是这部分,如果程序在开始执行后就由单个可执行文件组成,则程序可以独立于目录中的任何更改而正常运行:在相同或不同文件系统中重命名不会影响打开的处理程序,您在说的是execve()在这种情况下,如果不涉及FD,则大约为FD。
heemayl

2
您不需要文件句柄即可拥有对该文件的引用-映射页面也已足够。
西蒙·里希特

1
Unix没有“文件句柄”。 open()返回文件描述符,这是heemayl在此处与之讨论execve()。是的,正在运行的进程对其可执行文件具有引用,但这不是文件描述符。即使它完成munmap()了其可执行文件的所有映射,可能仍会有一个引用(在/ proc / self / exe中反映)阻止了索引节点的释放。(如果从从未返回的库函数中执行此操作,则可能不会崩溃。)顺便说一句,截断或修改正在使用的可执行文件可能会给您带来好处ETXTBUSY,但可能会起作用。
彼得·科德斯

7

在Linux文件系统中,当您移动文件时,只要它不跨越文件系统边界(读取:位于同一磁盘/分区上),您所要做的就是将..(父目录)的inode 移至新位置的inode。。实际数据根本没有在磁盘上移动,只是在指针上没有移动,因此文件系统知道在哪里可以找到它。

这就是为什么移动操作如此快速的原因,并且可能是为什么移动一个正在运行的程序没有问题的原因,因为您实际上并不在移动程序本身。


您的答案似乎暗示将二进制可执行文件移动到另一个文件系统将影响从该二进制文件启动的正在运行的进程。
jlliagre

6

这是可能的,因为移动程序不会影响通过启动程序启动的正在运行的进程。

启动程序后,将保护其磁盘位不被覆盖,但是无需保护要重命名的文件,将其移动到同一文件系统上的其他位置(等效于重命名文件或将其移动)到另一个文件系统,相当于将文件复制到其他位置然后将其删除。

删除正在使用的文件,要么是因为某个进程打开了文件描述符,要么是因为某个进程正在执行该文件,但不会删除文件数据,该数据仍由文件inode引用,只会删除目录项,即可以从其到达inode的路径。

请注意,启动程序不会一次将所有内容加载到(物理)内存中。相反,仅加载启动过程所需的严格最低要求。然后,在整个过程中按需加载所需的页面。这称为需求分页。如果RAM不足,则OS可以自由释放保存这些页面的RAM,因此进程很有可能从可执行索引节点中多次加载同一页面。

Windows无法实现的原因最初可能是由于基础文件系统(FAT)不支持目录项与inode的分离概念。NTFS不再存在此限制,但操作系统设计已保留了很长时间,这导致安装新版本的二进制文件时不得不重新启动的讨厌限制,而最新版本的Windows不再是这种情况。


1
我相信较新版本的Windows可以替换正在使用的二进制文件而无需重新启动。
托尔比约恩Ravn的安德森

@ThorbjørnRavnAndersen我不知道为什么所有更新仍然需要重新启动:(
Braiam '16

1
@Braiam他们没有。仔细看看。即使可以更新二进制文件,内核也无法(据我所知),并且需要重新启动才能用较新的版本替换。这对于大多数操作系统内核都是有效的。聪明的人不是我写kpatch为Linux可修补运行时,Linux内核-见en.wikipedia.org/wiki/Kpatch
托尔比约恩Ravn的安德森

@ThorbjørnRavnAndersen我的意思是“所有Windows更新”
Braiam

@Braiam是的-我也是。请仔细看。
托尔比约恩Ravn的安德森

4

基本上,在Unix及其类似版本中,文件名(包括指向该文件的目录路径)用于在打开文件时关联/查找文件(执行文件是一种以某种方式打开文件的方式)。在那一刻之后,将确定文件的身份(通过其“ inode”),并且不再受到质疑。您可以删除文件,重命名文件,更改其权限。只要任何进程或文件路径在该文件/ inode上都有一个句柄,它就会一直存在,就像进程之间的管道一样(实际上,在历史悠久的UNIX中,管道一个无名的inode,其大小恰好适合inode中的“直接块”磁盘存储参考(大约10个块)。

如果您在PDF文件上打开了PDF查看器,则可以删除该文件并打开一个具有相同名称的新文件,并且只要打开旧的查看器,仍然可以很好地访问旧文件(除非它可以主动监视)文件系统,以便在文件以其原始名称消失时发出通知)。

需要临时文件的程序可以以某种名称打开这样的文件,然后在它仍处于打开状态时立即将其删除(或更确切地说是其目录条目)。之后,无法再通过名称访问该文件,但是任何具有打开的文件描述符的进程仍可以访问该文件,并且如果此后程序意外退出,则该文件将被删除并自动回收存储。

因此,文件的路径不是文件本身的属性(实际上,硬链接可以提供几个不同的此类路径),仅在打开文件时才需要,而对于已经打开该文件的进程不需继续访问。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.