http://blog.csdn.net/sergeycao/article/details/8228613
可执行文件只有被装载进内存以后才能被CPU执行。
接下来我们看一下可执行文件装载的本质到底是什么。
1. 首先来了解一下进程虚拟地址空间。
程序和进程的区别:程序是一个静态的概念,他就是一些预先编译好的指令和数据的集合;而进程则是一个动态的概念,它是程序运行时的一个过程,很多时候吧动态库叫做运行时(Runtime)也是有一定的含义的。
每个程序被运行起来以后它将拥有自己的独立的虚拟地址空间 (Virtual Address Space),这个虚拟地址空间的大小由计算机的硬件平台决定,具体来说就是由CPU的位数来决定的。如:32位:0~232 - 1,即我们所说的4GB的虚拟空间大小;64位:0~264 - 1,即17179869184GB。以32位的CPU为例,4GB的虚拟地址空间,那么我们的程序是否可以任意使用呢?很遗憾,不行!
因为程序运行的时候处于操作系统的监管下,操作系统为了达到监控程序运行等一系列目的,进程的虚拟空间都在操作系统的掌握之中。进程只能使用那些操作系统分配给进程的地址,如果访问未经允许的空间,操作系统就会捕获这些访问,将进程的这些访问当做非法操作,强制进程结束。
那么这4GB的进程虚拟地址空间到底是怎样的分配状态呢?以Linux OS为例,它将整个4GB化为两个部分:
a. 从0XC0000000到0XFFFFFFFF,共1GB,被OS本身用去了
b. 从0X00000000到0XBFFFFFFF,共3GB,留给进程使用的。
即使是上面的“3GB”,也只是“原则上”可以给进程使用的,事实上进程并不能完全使用这3GB的虚拟空间,其中一部分是预留给其他用途的。(稍后会提到)
32位的CPU下,程序使用的空间能不能超过4GB呢?这个问题应该从两个角度来看,首先,问题里面的“空间”如果指的是虚拟地址空间,那么答案是“否”,因为32位的CPU只能使用32位的指针,它最大的寻址范围是0到4GB。如果“空间”指的是计算机的内存空间,那么答案是“是”。Intel自从1995年代Pentium Pro CPU开始采用了36位的物理地址,也就是可以访问高达64GB的物理内存。扩展至36位地址线之后,Intel修改了页映射的方式,使得新的映射方式可以访问到更多的物理内存。扩展的物理地址空间对于普通应用程序来说是感觉不到它的存在的,因为这主要是OS的事,在应用程序里,只有32位的虚拟地址空间。那么应用程序如何使用这些大于常规的内存空间呢?一个很常见的方法就是OS提供一个窗口映射的方法,吧这些额外的内存映射到进程地址空间来。像Linux OS采用mmap()系统调用来实现。
2. 装载的方式
程序执行时所需要的指令和数据必须在内存中才能够正常运行。当然,最简单的办法就是将程序运行所需要的全部资源全部装入内存。但是这样会造成内存的浪费。研究发现程序运行是有局部性原理的,所以比较好的解决方法就是将程序最常用的部分驻留在内存中,而将一些不太常用的数据存放在磁盘里,这就是动态装入 的基本原理。
覆盖装入 (Overlay)和页映射 (Paging)是两种典型的动态装载方法,它们都利用了程序的局部性原理。动态装入的基本思想就是用到哪个模块就将哪个模块装入内存,如果用不到则暂时不装入,存放到磁盘。
a. 覆盖装入(Overlay)
利用时间换取空间,此处不多涉及。
b. 页映射(Paging)
页映射是将内存和所有磁盘中的数据和指令按照“页”为单位划分成若干个页,以后所有的装载和操作的单位就是页。利用换入换出机制(如FIFO,LUR等)即可完成。
3. 从OS角度看可执行文件的装载
a. 进程的建立
从OS的角度来看,一个进程最关键的特征是它拥有独立的虚拟地址空间,这使得它有别于其他进程。很多时候一个程序被执行的同时都伴随着一个新的进程的创建,那么我们就来看看这种最通常的情形:创建一个进程,然后装载相应的可执行文件并且执行。在有虚存储的情况下,上述过程最开始需要做三件事情:
- 创建一个独立的虚拟地址空间
- 读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系
- 将CPU的指令寄存器设置成可执行文件的入口地址,启动运行
首先是创建虚拟地址空间。我们知道一个虚拟空间由一组页映射函数将虚拟空间的各个页映射至相应的物理空间,那么创建一个虚拟空间实际上并不是创建空间而是创建映射函数所需要的相应的数据结构。
读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系。上面一步的页映射关系是虚拟空间到物理内存的映射关系,这一步所做的是虚拟空间与可执行文件的映射关系。
将CPU的指令寄存器设置成可执行文件的入口地址,启动运行。这步是最简单的一步,OS通过设置CPU的指令寄存器将控制权转交给进程,由此进程开始执行。这一步看似简单,实际上OS层面上比较复杂,它设计内核堆栈和用户堆栈的切换,CPU运行权限的切换。不过从进程的角度看这一步可简单的认为OS执行了一条跳转指令,直接跳转到可执行文件的入口地址。
从OS的角度看,经过上面的步骤,其实可执行文件的真正指令和数据都没有被装入内存中。OS只是通过可执行文件都不的信息建立起可执行文件和进程虚存之间的映射关系而已。