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

n0.*_*.ob 24 kernel files directory

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

编辑:我认为这在 Mac 上是不可能的,但显然随着评论的验证是可能的。也许在 Windows 上是不可能的。感谢所有的答案。

hee*_*ayl 32

让我分解一下。

当您运行可执行文件,系统调用的顺序执行,最显着的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()-ing 目录树中的给定文件,因此拥有对该目录的写权限将使您有足够的权利从该目录中删除任何文件。

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

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

  • “该进程不再使用文件系统”的说法仍然值得怀疑。 (6认同)
  • 您有点混淆虚拟内存和物理内存。您对程序加载到物理内存的方式的描述不准确。exec 系统调用根本不会将可执行文件的各个部分复制到物理内存,而只会加载启动进程所需的部分。之后,可能需要很长时间才能按需加载所需的页面。可执行文件字节是进程虚拟内存的一部分,可能会被读取,并且可能会在进程的整个生命周期中再次读取。 (5认同)
  • 文件系统中给定的文件不是直接通过文件名来标识的,这个基本理解应该就清楚多了。 (2认同)
  • 您的更新中仍然不准确。`mmap` 和 `unmap` 系统调用不用于按需加载和卸载页面,内核在访问页面时加载页面会产生页面错误,当操作系统认为 RAM 会更好时从内存中卸载页面用于别的东西。这些加载/卸载操作不涉及系统调用。 (2认同)

jaw*_*ark 14

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

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

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

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

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

  • 不,这就像擦除符号链接一样。在其他评论中的某处指出,该行为是由于对 FAT 文件系统的旧支持。这听起来像是一个可能的原因。 (2认同)

Bak*_*riu 13

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

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

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

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

  • 您不需要文件句柄来引用文件——映射页面也足够了。 (2认同)

小智 7

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

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


jll*_*gre 6

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

一旦程序启动,它的磁盘位被保护不被覆盖,但不需要保护要重命名的文件,移动到同一文件系统上的不同位置,这相当于重命名文件,或移动到不同的文件系统,这相当于将文件复制到别处然后将其删除。

删除正在使用的文件,无论是因为进程在其上打开了文件描述符,还是因为进程正在执行它,都不会删除文件数据,文件数据仍由文件 inode 引用,但只会删除目录条目,即可以到达 inode 的路径。

请注意,启动程序不会立即将所有内容加载到(物理)内存中。相反,仅加载进程启动所需的严格最小值。然后,在流程的整个生命周期中按需加载所需的页面。这称为需求寻呼。如果 RAM 不足,操作系统可以自由释放保存这些页面的 RAM,因此进程很可能多次从可执行 inode 加载相同的页面。

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