execve原理

execve原理

execve原理 #

execve 的作用是使当前进程执行一个新的可执行程序,它的原型如下所示:

#include <unistd.h>
int execve(const char* filename, const char* argv[],
          const char* envp[])

其中 execve 的第一个参数是可执行程序的文件名,第二个参数用来传递命令行参数,第三个参数用来传递环境变量。

execve 的执行步骤如下所示:

  1. 清空页表这样整个进程中的页都变成不存在了,一旦访问这些页,就会发生页中断;
  2. 打开待加载执行的文件在内核中创建代表这个文件的 struct file 结构;
  3. 加载和解析文件头文件头里描述了这个可执行文件一共有多少 section;
  4. 创建相应的vma vma 用来描述代码段,数据段,并且将文件的各个 section 与这些内存区域建立映射关系;
  5. 加载其他共享库文件如果当前加载的文件还依赖其他共享库文件,则找到这个共享库文件,并跳转到第 2 步继续处理这个共享库文件;
  6. 跳转到可执行程序的入口处执行。

execve 的实现并不负责将文件内容加载到物理页中,它只建立了文件 section,与内存区域的映射关系就结束了。真正负责加载文件内容的是缺页中断,接下来,我们就看看缺页中断是如何加载物理页的。

在 execve 的执行步骤中,内核为可执行程序创建一个 vma 结构体实例,然后将它的 vm_file 属性设成第 2 步所打开的文件,这就建立起了内存区域和文件的映射关系。这个内核区域的区间首地址、区间尾地址和控制权限,都是由第 3 步解析的信息决定的。例如.text 段被加载到的内存首地址,也就是链接时所决定的起始地址,它就决定了内存代码段的起始地址。

由于第 1 步把页表都清空了,这就导致 CPU 在加载指令时会发现代码段是缺失的,此时就会产生缺页中断。

Linux 内核用于处理缺页中断的函数是 do_no_page,如果内核检查,当前出现缺页中断的虚拟地址所在的内存区域 vma(虚拟地址落在该内存区域的 vm_start 和 vm_end 之间)存在文件映射 (vm_file 不为空),那就可以通过虚拟内存地址计算文件中的偏移,这就定位到了内存所缺的页对应到文件的哪一段。然后内核就启动磁盘 IO,将对应的页从磁盘加载进内存。一次缺页中断就这样被解决了。

可执行程序的加载不是一次性完成的,而是由缺页中断根据需要,将文件的内容以页为单位加载进内存的,一次只会加载一页。

Viewpoint #

From #

10 | 页中断:fork、mmap背后的保护神