物理内存与虚拟内存

一、 虚拟地址的由来
在早期的计算机中,要运行一个程序,会把这个程序全部都加载到内存,程序是直接运行到物理内存上的 。也就是说,程序运行时直接访问的就是实际的物理内存地址 。当计算机要运行某些程序时(运行中的程序称为进程),只要这些进程所需要的内存空间不超过计算机所拥有的物理内存空间,那么就不会出问题 。由于程序都是直接访问物理内存,所以一个进程是可以随意修改别的进程的内存数据 。如果某个"恶意"的进程修改了其它进程的内存数据,往往就会导致系统奔溃 。这种情况对我们来说是无法容忍的,因为我们希望使用计算机的时候,任意一个任务出现问题了,不要去影响其它任务的执行 。当在使用不同顺序去运行多个任务时,这些程序的运行内存地址是不确定的 。假如当时的计算机物理内存空间有128MB,操作系统的运行要78MB的物理内存空间,那么就剩余50MB的物理内存空间了,此时再运行一个任务A需要30MB的物理内存空间,若要再运行一个任务B,那么任务B所要求的物理内存空间不能超过20MB,否则就会出问题,下一次先运行任务B,再运行任务A,此时它们的地址和上次是不一样的 。而且在程序运行时,如果去窜改程序上运行的物理内存的数据的,轻则程序挂掉,重则计算机系统奔溃 。从上面可以看到,计算机的物理内存使用率是比较低下的,而且也是不安全的,轻微的一个失误都可能导致系统的奔溃,而且某个程序在运行时,并不需要运行所有的功能,只是某一时段运行某一功能 。在程序的执行过程中,也存在着大量在物理内存和硬盘之间的数据交换过程
二、分段
为了解决上述一系列问题,人们想到了一种有效的方法,增加一个程序与物理内存之间的中间层,利用一种间接的地址访问方法去访问物理内存 。按照这种方法,程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到物理内存地址上 。这样,只要操作系统处理好虚拟地址到物理内存地址的映射关系,就可以保证不同的程序最终访问的内存地址位于不同的区域,彼此不会存在地址重叠的现象,就可以达到物理内存地址被隔离的效果,进而保护计算机不会被"轻易"破坏 。比如在一个32位的windows操作系统中,当创建一个进程时,操作系统会为该进程分配一个4GB大小的虚拟进程地址空间 。之所以是4GB,是因为在32位的操作系统中,一个指针长度是4字节,而4字节指针的寻址能力是从0x00000000~0xFFFFFFFF,最大值0xFFFFFFFF表示的即为4GB大小的容量 。与虚拟地址空间相对的,还有一个物理地址空间,这个地址空间对应的是真实的物理内存 。如果你的计算机上安装了512MB大小的内存,那么这个物理地址空间表示的范围是0x00000000~0x1FFFFFFF 。当操作系统做虚拟地址到物理地址映射时,只能映射到这一范围,操作系统也只会映射到这一范围 。当进程创建时,每个进程都会有一个自己的4GB虚拟地址空间 。要注意的是这个4GB的地址空间是"虚拟"的,并不是真实存在的,而且每个进程只能访问自己虚拟地址空间中的数据,无法访问别的进程中的数据,通过这种方法实现了进程间的地址隔离 。那是不是这4GB的虚拟地址空间应用程序可以随意使用呢?很遗憾,在Windows系统下,这个虚拟地址空间被分成了4部分:NULL指针区、用户区、隔离区和内核区 。
1) NULL指针区(0x00000000~0x0000FFFF):如果进程中的一个线程试图操作这个分区中的数据,CPU就会引发非法访问 。它的作用是,当调用 malloc 等内存分配函数时,如果无法找到足够的内存空间,它将返回 NULL 。而不进行安全性检查 。它只是假设地址分配成功,并开始访问内存地址 0x00000000(NULL) 。由于禁止访问内存的这个分区,因此会发生非法访问现象,并终止这个进程的运行 。
2) 用户区(0x00010000~0xBFFEFFFF):这个分区中存放进程的私有地址空间 。一个进程无法以任何方式访问另外一个进程驻留在这个分区中的数据(相同exe,通过copy-on-write来完成地址隔离) 。(在windows中,所有.exe和动态链接库都载入到这一区域 。系统同时会把该进程可以访问的所有内存映射文件映射到这一分区) 。
3) 隔离区(0xBFFF0000~0xBFFFFFFF):这个分区禁止进入 。任何试图访问这个内存分区的操作都是违规的 。微软保留这块分区的目的是为了简化操作系统的现实 。
4) 内核区(0xC0000000~0xFFFFFFFF):这个分区存放操作系统驻留的代码 。线程调度、内存管理、文件系统支持、网络支持和所有设备驱动程序代码都在这个分区加载,该分区被所有进程共享 。


推荐阅读