简介
本篇从操作系统的角度,整理下整个可执行文件的装载过程。
目前的操作系统对可执行文件的装载基本都采用页映射的动态装入方式。所谓页映射方式是指将内存和磁盘中的数据和指令均以“页”为单位进行装载操作。
可执行文件装载
一个程序被执行最通常的情形是,创建一个进程,然后装载相应的可执行文件,最后程序运行。这样的一个过程,主要涉及到以下四件事情:
1) 创建独立的虚拟地址空间
每个程序被运行起来,它都会拥有自己独立的虚拟地址空间,虚拟空间的大小取决于CPU位数,即硬件的寻址空间大小,比如32位CPU寻址空间4GB,即虚拟空间也有就4GB,这个说烂了,不多说。
值得说明的是,在Linux下,创建虚拟空间仅仅只是简单地分配一个页目录,页映射关系要等到后续通过发生页错误进行实际装载才进行设置。(这里说的是进程虚拟空间与实际物理内存的映射关系)
2) 读取可执行文件头,建立虚拟空间与可执行文件的映射关系
程序执行通过发生页错误进行实际装载的过程,是由操作系统从物理内存分配一个物理页,然后再将“所需要页”的内容读取到该物理页中,之后再设置虚拟空间中的“虚拟页”与物理内存中“物理页”的映射关系。所以,第二步骤的作用就是,虚拟空间中的“虚拟页”需要知道它“所需要页”在可执行文件中的位置,从而操作系统可以顺利将可执行文件中的“所需要页”的内容读取到“物理页”中。这一步是整个装载过程的核心。
3) 将CPU的指令寄存器设置成可执行文件的入口地址,启动运行
操作系统将控制权转交给进程,进程由此开始执行。控制权转交的过程实质就是操作系统执行跳转指令,跳转到可执行文件文件头中的入口地址。
4) 可执行文件指令和数据的装载
上述步骤执行完成后,进程开始执行。执行过程中,进程发现需要执行的虚拟页上的指令或数据为空,于是通过产生页错误产生中断,CPU再将控制权交由操作系统,由操作系统的页错误处理程序处理。这里主要是通过查询第二步所建立的虚拟页与可执行文件的映射关系,找到虚拟页映射的内容在执行文件中的位置,然后操作系统分配一个物理页并读取可执行文件的内容,之后建立“物理页”与“虚拟页”的关系,最后控制权交还进程继续执行。随着进程的执行,页错误不断产生,直至满足进程执行需求。
在Linux下,一般是通过调用exec系列的系统调用进行装载指定的ELF文件。
进程虚拟空间分布
操作系统通过给进程空间划分出一个个VMA (Virtual Memory Area) 来管理进程虚拟空间。它的基本原则是将相同权限、相同映象文件映射成一个VMA。
一个进程基本可以分为代码VMA、、数据VMA、堆VMA、栈VMA。如图:
杂散知识点:
ELF文件被映射至虚拟空间,是以页为单位。这样,一个页面上可能映射一个ELF文件中多个段的内容,这些内容主要以段权限为组合合并成一个页面。这些组合包括:
1) 以代码段为代表的可读可执行段
2) 以数据段和BBS段为代表的可读可写段
3) 以只读数据段为代表的只读段
一个或多个相似属性的段合并,就是通常意义上的英文单词“Segment”;如果单单只对于一个段,对应英文单词“Section”。
“Segment”的概念是从装载的角度将ELF文件的各个相同属性的段归并而成。在将目标文件链接成可执行文件时,链接会尽量把相同权限属性的段分配在同一空间;“Section”的概念则是从链接的角度审视单个ELF文件格式中的单个段。
操作系统会将进程运行的环境信息,比如系统环境变量、进程运行参数保存到进程虚拟空间的栈中,即进程栈的初始化。我们熟知的main()函数的argc / argv两个参数即保存在这里。

被折叠的 条评论
为什么被折叠?



