关于虚拟地址和物理地址的映射,最开始人们使用的是一种叫做分段(Segmentation)的方法,基本思路是把一段与程序所需要的同等大小的虚拟空间映射到某段物理空间。
例如程序A需要 10MB 内存,虚拟地址的范围是从 0X00000000 到 0X00A00000,假设它被映射到一段同等大小的物理内存,地址范围从 0X00100000 到 0X00B00000,即虚拟空间中的每一个字节对应于物理空间中的每一个字节。
程序运行时,它们的对应关系如下图所示:
当程序A需要访问 0X00001000 时,系统会将这个虚拟地址转换成实际的物理地址 0X00101000,访问 0X002E0000 时,转换成 0X003E0000,以此类推。
这种分段的方法很好地解决了不同程序地址不隔离的问题,同时也能够在程序中使用固定的地址。
如上图所示,程序A和程序B分别被映射到了两块不同的物理内存,它们之间没有任何重叠,如果程序A访问的虚拟地址超出了 0X00A00000 这个范围,系统就会判断这是一个非法的访问,拒绝这个请求,并将这个错误报告给用户,通常的做法就是强制关闭程序。
请看下面的代码:
#include <stdio.h>
int main() {
char *str = (char*)0XF0000000; //使用数值表示一个明确的地址
printf("%s\n", str);
return 0;
}
这段代码不会产生编译和链接错误,但是运行程序时,为了输出字符串,printf() 需要访问虚拟地址为 0XF0000000 的内存,但是该虚拟地址是被操作系统占用的(后续会讲解),程序没有权限访问,会被强制关闭,如下图所示:
虚拟内存无论被映射到物理内存的哪一个区域,对于程序员来说都是透明的,我们不需要关心物理地址的变化,只需要按照从地址 0X00000000 到 0X00A00000 来编写程序、放置变量即可,程序不再需要重定位。
分段思想对内存区域的映射是以整个程序为单位的,如果物理内存不足,被换入换出到磁盘的是整个程序,这样势必会导致大量的磁盘读写操作,严重影响运行速度,所以这种方法还是显得粗糙,粒度比较大。
现代计算机都使用一种比分段粒度更加小的映射方法——分页(Paging),下节我们就会讲解。