让我分解一下。
当您运行可执行文件,系统调用的顺序执行,最显着的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保持不变。
在两个不同的文件系统之间,发生两件事:
rm
实际上只是unlink()
从目录树中访问给定的文件,因此具有目录的写许可权将使您有足够的权利从该目录中删除任何文件。
现在,为了好玩,想象一下当您在两个文件系统之间移动文件而又没有unlink()
源文件的许可时会发生什么?
那么,该文件将首先复制到目标(read()
,write()
),然后unlink()
将由于权限不足失败。因此,该文件将保留在两个文件系统中!