1 进程虚拟地址空间
每个程序被运行起来以后,它将拥有自己独立的虚拟地址空间(Virtual Address Space),这个虚拟地址空间的大小由计算机的硬件平台决定,具体地说是由CPU的位数决定的。硬件决定了地址空间的最大理论上限,即硬件的寻址空间大小,比如32位的硬件平台决定了虚拟地址空间的地址为0到 2 32 − 1 2^{32}-1 232−1,即0x00000000~0xFFFFFFFF,也就是我们常说的4GB虚拟空间大小:而64位的硬件平台具有64位寻址能力,它的虚拟地址空间达到了 2 64 2^{64} 264字节,即0x0000000000000000~0xFFFFFFFFFFFFFFFF,总共17179869184GB。
从程序的角度看,我们可以通过判断C语言程序中的指针所占的空间来计算虚拟地址空间的大小。一般来说,C语言指针大小的位数与虚拟空间的位数相同,如32位平台下的指针为32位,即4字节:64位平台下的指针为64位,即8字节。
在下文的介绍中以32位的地址空间为主,64位的与32位类似。
32位平台下的4GB虚拟空间程序不可以任意使用。因为程序在运行的时候处于操作系统的监管下,操作系统为了达到监控程序运行等一系列目的,进程的虚拟空间都在操作系统的掌握之中。进程只能使用那些操作系统分配给进程的地址,如果访问未经允许的空间,那么操作系统就会捕获到这些访问,将进程的这种访问当作非法操作,强制结束进程。我们经常在Windows下碰到令人讨庆的“进程因非法操作需要关闭”或Linux下的“Segmentation fault”很多时候是因为进程访问了未经允许的地址。
以Linux操作系统作为例子说明下这4GB的进程虚拟地址空间是怎样的分配状态。默认情况下,Linux操作系统将进程的虚拟地址空间做了如图6-1所示的分配:
整个4GB被划分成两部分,其中操作系统本身用去了一部分:从地址0xC00000000到0xFFFFFFFF,共1GB。剩下的从0x00000000地址开始到0xBFFFFFFF共3GB的空间都是留给进程使用的。那么从原则上讲,我们的进程最多可以使用3GB的虚拟空间,也就是说整个进程在执行的时候,所有的代码、数据包括通过C语言malloc等方法申请的虚拟空间之和不可以超过3GB。在现代的程序中,3GB的虚拟空间有时候是不够用的,比如一些大型的数据库系统、数值计算、图形图像处理、虚拟现实、游戏等程序需要占用的内存空间较大,这使得32位硬件平台的虚拟地址空间显得捉见时。当然一本万利的方法就是使用64位处理器,把虚拟地址空间扩展到17179869184GB。当然不是人人都能顺利地更换64位处理器,更何况有很多现有的程序只能运行在32位处理器下。那么32位CPU的平台能不能使用超过4GB的空间呢?这个问题我们将在后面的“PAE”一节中进行介绍。
上文提到这3GB的空间“原则上”是可以给进程使用的,但令人遗健的是,进程并不能完全使用这3GB的虚拟空间,其中有一部分是预留给其他用途的,我们在后面还会提到。
对于Windows操作系统来说,它的进程虚拟地址空间划分是操作系统占用2GB,那么进程只剩下2GB空间。2GB空间对些程序来说太小了,所以Windows有个启动参数可以将操作系统占用的虚拟地址空间减少到1GB,即跟Linux分布一样。方法如下:修改Windows系统盘根目录下的Boot.ini,加上“/3G”参数
1.1 PAE
2 装载的方式
程序执行时所需要的指令和数据必须在内存中才能够正常运行,最简单的办法就是将程序运行所需要的指令和数据全都装入内存中,这样程序就可以顺利运行,这就是最简单的静态装入的办法。但是很多情况下程序所需要的内存数量大于物理内存的数量,当内存的数量不够时,根本的解决办法就是添加内存。相对于磁盘来说,内存是昂贵且稀有的,这种情况自计算机磁盘诞生以来一直如此。所以人们想尽各种办法,希望能够在不添加内存的情况下让更多的程序运行起来,尽可能有效地利用内存。后来研究发现,程序运行时是有局部性原理的,所以我们可以将程序最常用的部分驻留在内存中,而将一些不太常用的数据存放在磁盘里面,这就是动态装入的基本原理。
覆盖装入(Overlay)和页映射(Paging)是两种很典型的动态装载方法,它们所采用的思想都差不多,原则上都是利用了程序的局部性原理。动态装入的思想是程序用到哪个模块,就将哪个模块装入内存,如果不用就暂时不装入,存放在磁盘中。
2.1 覆盖装入
省略 178
2.2 页映射
3 从操作系统角度看可执行文件的装载
3.1 进程的建立
182