问题描述
我刚刚意识到我可以将正在运行的活动程序移动到不同的目录。根据我的经验,这在 MacOs 或 Windows 中是不可能的。它在 Ubuntu 中是如何工作的?
编辑:我认为这在 Mac 上是不可能的,但显然随着评论的验证是可能的。也许在 Windows 上是不可能的。感谢所有的答案。
最佳思路
让我分解一下。
当您运行可执行文件时,会执行一系列系统调用,最显著的是 fork()
和 execve()
:
-
fork()
创建调用进程的子进程,它(大部分)是父进程的精确副本,两者仍然运行相同的可执行文件(使用 copy-on-write 内存页,所以它很有效)。它返回两次:在父进程中,它返回子进程 PID。在子进程中,它返回0。通常,子进程会立即调用execve: -
execve()
将可执行文件的完整路径作为参数,并用可执行文件替换调用进程。此时,新创建的进程获得自己的虚拟地址空间,即虚拟内存,并从其入口点开始执行(处于平台 ABI 的新进程规则指定的状态)。
此时,内核的 ELF 加载器已将可执行文件的 text and data segments 映射到内存中,就好像它使用了 mmap()
系统调用(分别具有共享只读和私有读写映射)。 BSS 也像 MAP_ANONYMOUS 一样映射。 (顺便说一句,为了简单起见,我在这里忽略了动态链接:动态链接器 open()
s 和 mmap()
s 在跳转到主可执行文件的入口点之前是所有动态库。)
在 newly-exec()ed 开始运行自己的代码之前,实际上只有几页从磁盘加载到内存中。如果/当进程触及其虚拟地址空间的那些部分,则根据需要进一步的页面是 demand paged in。 (在开始执行 user-space 代码之前预加载任何页面的代码或数据只是一种性能优化。)
可执行文件由较低级别的 inode 标识。文件开始执行后,内核通过 inode 引用保持文件内容完整,而不是通过文件名,例如打开文件描述符或 file-backed 内存映射。因此,您可以轻松地将可执行文件移动到文件系统的另一个位置,甚至是不同的文件系统。作为旁注,要检查进程的各种统计信息,您可以查看 /proc/PID
(PID 是给定进程的进程 ID)目录。您甚至可以将可执行文件打开为 /proc/PID/exe
,即使它已与磁盘取消链接。
现在让我们深入挖掘移动:
当您在同一文件系统中移动文件时,执行的系统调用是 rename()
,它只是将文件重命名为另一个名称,文件的 inode 保持不变。
而在两个不同的文件系统之间,会发生两件事:
-
文件内容先复制到新位置,由
read()
和write()
-
之后,使用
unlink()
取消文件与源目录的链接,显然该文件将在新文件系统上获得一个新的 inode。
rm
实际上只是 unlink()
– 从目录树中获取给定文件,因此拥有对该目录的写权限将使您有足够的权利从该目录中删除任何文件。
现在为了好玩,想象一下当您在两个文件系统之间移动文件并且您无权从源 unlink()
文件时会发生什么?
那么,文件首先会被复制到目的地( read()
, write()
),然后由于权限不足, unlink()
将失败。因此,该文件将保留在两个文件系统中!!
次佳思路
嗯,这很直接。让我们以一个名为 /usr/local/bin/whoopdeedoo 的可执行文件为例。这只是对所谓的 inode(Unix 文件系统上文件的基本结构)的引用。是被标记为 “in use” 的 inode。
现在,当您删除或移动文件 /usr/local/whoopdeedoo 时,唯一移动(或擦除)的是对 inode 的引用。 inode 本身保持不变。基本上就是这样。
我应该验证它,但我相信您也可以在 Mac OS X 文件系统上执行此操作。
Windows 采用了不同的方法。为什么?谁知道…?我不熟悉 NTFS 的内部结构。从理论上讲,所有使用文件名内部结构引用的文件系统都应该能够做到这一点。
我承认,我过于简化了,但是请阅读维基百科上的 “Implications” 部分,它比我做得好得多。
第三种思路
所有其他答案中似乎缺少的一件事是:一旦打开文件并且程序持有打开的文件描述符,该文件将不会从系统中删除,直到该文件描述符关闭。
尝试删除引用的 inode 将被延迟,直到文件关闭:在相同或不同的文件系统中重命名不会影响打开的文件,独立于重命名的行为,也不会显式删除或用新文件覆盖文件。弄乱文件的唯一方法是显式打开其 inode 并弄乱内容,而不是通过对目录进行操作,例如重命名/删除文件。
此外,当内核执行文件时,它会保留对可执行文件的引用,这将再次防止在执行期间对其进行任何修改。
因此,即使看起来您可以删除/移动构成正在运行的程序的文件,但实际上这些文件的内容仍会保留在内存中,直到程序结束。
第四种思路
在 Linux 文件系统中,当您移动文件时,只要它不跨越文件系统边界(读取:保持在同一磁盘/分区上),您所做的只是将 ..
(父目录)的 inode 更改为新的 inode地点。磁盘上的实际数据根本没有移动,只是指针,以便文件系统知道在哪里找到它。
这就是移动操作如此快速的原因,并且可能是为什么移动正在运行的程序没有问题,因为您实际上并没有移动程序本身。
第五种思路
这是可能的,因为移动程序不会影响通过启动它启动的正在运行的进程。
一旦程序启动,它的 on-disk 位被保护不被覆盖,但不需要保护要重命名的文件,移动到同一文件系统上的不同位置,这相当于重命名文件,或移动到一个不同的文件系统,相当于把文件复制到别处然后删除。
删除正在使用的文件,无论是因为进程在其上打开了文件描述符,还是因为进程正在执行它,都不会删除文件数据,文件数据仍由文件 inode 引用,但只会删除目录条目,即可以到达 inode 的路径。
请注意,启动程序不会立即将所有内容加载到(物理)内存中。相反,仅加载进程启动所需的严格最小值。然后,在整个流程生命周期中按需加载所需的页面。这称为需求寻呼。如果 RAM 不足,操作系统可以自由释放保存这些页面的 RAM,因此进程很可能多次从可执行 inode 加载相同的页面。
Windows 无法实现的原因最初可能是因为底层文件系统 (FAT) 不支持目录条目与 inode 的拆分概念。 NTFS 不再存在此限制,但 OS 设计已保留很长时间,导致在安装新版本的二进制文件时必须重新启动的令人讨厌的限制,而最近版本的 Windows 已不再存在这种情况。
第六种思路
基本上,在 Unix 及其同类中,文件名(包括指向它的目录路径)用于在打开文件时关联/查找文件(执行文件是以某种方式打开文件的一种方式)。在那一刻之后,文件的身份(通过其 “inode”)被建立并且不再被质疑。您可以删除文件,重命名它,更改其权限。只要任何进程或文件路径在该文件/inode 上有一个句柄,它就会一直存在,就像进程之间的管道一样(实际上,在历史悠久的 UNIX 中,管道是一个无名的 inode,其大小正好适合inode 中的 “direct blocks” 磁盘存储引用,类似于 10 个块)。
如果您在 PDF 文件上打开了 PDF 查看器,您可以删除该文件并打开一个同名的新文件,只要旧查看器处于打开状态,它仍然可以访问旧文件(除非它主动观看)文件系统,以便通知文件何时以其原始名称消失)。
需要临时文件的程序可以以某个名称打开这样的文件,然后在它仍然打开时立即删除它(或者更确切地说是它的目录条目)。之后该文件将无法再通过名称访问,但任何具有该文件的打开文件描述符的进程仍然可以访问它,如果之后程序意外退出,该文件将被删除并自动回收存储。
因此,文件的路径不是文件本身的属性(实际上,硬链接可以提供多个不同的此类路径),并且仅在打开文件时需要,而不需要已打开文件的进程继续访问。