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

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

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

编辑:我以为在 Mac 上不可能,但显然评论证实了这是可能的。也许只有在 Windows 上才不可能。感谢大家的回答。

答案1

让我来分解一下。

当你运行一个可执行文件时,会执行一系列系统调用,最值得注意的是fork()execve()

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

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

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

在新 exec() 开始运行自己的代码之前,实际上只有少数页面从磁盘加载到内存中。其他页面需求分页根据需要,当进程触及虚拟地址空间的这些部分时。(在开始执行用户空间代码之前预加载任何代码或数据页面只是一种性能优化。)


可执行文件由底层的 inode 标识。在文件开始执行后,内核通过 inode 引用(而不是文件名,如打开的文件描述符或文件支持的内存映射)保持文件内容完整。因此,您可以轻松地将可执行文件移动到文件系统的另一个位置,甚至移动到不同的文件系统。附带说明一下,要检查进程的各种状态,您可以查看/proc/PID(PID 是给定进程的进程 ID)目录。您甚至可以将可执行文件作为 打开/proc/PID/exe,即使它已从磁盘取消链接。


现在让我们深入挖掘一下这个举动:

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

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

  • 文件的内容首先复制到新位置,read()然后write()

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

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

现在为了好玩,想象一下当您在两个文件系统之间移动文件并且您没有unlink()来自源的文件的权限时会发生什么?

好吧,文件首先会被复制到目标位置(read()write()),然后unlink()会因权限不足而失败。因此,文件将保留在两个文件系统中!

答案2

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

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

我应该验证它,但我相信你也可以在 Mac OS X 文件系统上做到这一点。

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

我承认,我过于简单化了,但请去阅读维基百科上的“含义”部分,它比我做得好得多。

答案3

其他所有答案似乎都缺少一件事:一旦文件被打开并且程序持有打开的文件描述符,该文件将不是将被从系统中删除,直到该文件描述符被关闭。

删除引用的 inode 的尝试将被延迟,直到文件关闭:在相同或不同的文件系统中重命名不会影响打开的文件,无论重命名的行为如何,也不会明确删除或用新文件覆盖文件。弄乱文件的唯一方法是明确打开其 inode 并弄乱内容,而不是对目录进行重命名/删除文件等操作。

此外,当内核执行一个文件时,它会保留对该可执行文件的引用,这将再次阻止在执行期间对其进行任何修改。

所以最后即使好像您可以删除/移动组成正在运行的程序的文件,实际上这些文件的内容会保存在内存中,直到程序结束。

答案4

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

一旦启动了程序,其磁盘上的位就会受到保护以免被覆盖,但无需保护要重命名的文件,将其移动到同一文件系统上的其他位置(这相当于重命名文件)或移动到不同的文件系统(这相当于将文件复制到其他地方然后将其删除)。

删除正在使用的文件(因为进程在其上打开了文件描述符,或者因为进程正在执行它)不会删除文件数据(文件数据仍由文件 inode 引用,但仅删除目录条目,即可到达 inode 的路径)。

请注意,启动程序不会一次性将所有内容加载到(物理)内存中。相反,只会加载进程启动所需的最低限度的内容。然后,在进程的整个生命周期内按需加载所需的页面。这称为按需调页。如果内存不足,操作系统可以自由释放保存这些页面的内存,因此进程完全有可能从可执行 inode 多次加载同一页面。

Windows 无法实现这一功能的原因最初可能是因为底层文件系统 (FAT) 不支持目录条目与 inode 的拆分概念。NTFS 不再存在此限制,但操作系统设计已保留了很长时间,导致安装新版本的二进制文件时必须重新启动这一令人讨厌的限制,而 Windows 的最新版本不再存在这种情况。

相关内容