彻底搞懂虚拟内存,虚拟地址,虚拟地址空间

程序经过编译后,变成了可执行的文件,可执行文件主要包括代码和数据两部分,代码是只读的,数据则是可读可写的 。
可执行文件由操作系统加载到内存中,交由CPU去执行,现在问题来了,CPU怎么去访问代码和数据?,访问的方式经历过四个阶段:
1.直接访问
2.段基址+段偏移地址
3.段选择子+段偏移地址
4.虚拟地址
现代操作系统采用的是虚拟地址,这也是本篇文章阐述的重点,但虚拟地址是由1~3阶段发展而来的,所以也有必要阐述1~3三种访问方式 。
直接访问直接访问很好理解,程序经过编译后,生成了可执行文件,编译器给每行数据和代码分配了一个唯一的地址,如下图

彻底搞懂虚拟内存,虚拟地址,虚拟地址空间

文章插图
可执行文件
【彻底搞懂虚拟内存,虚拟地址,虚拟地址空间】如上图可执行文件中1000~1024之间的地址,加载到内存后,内存的地址也是1000~1024,在可执行文件中分配的唯一地址就是内存中的物理地址,这就叫直接访问,直接定访问干脆直接,没有那些弯弯绕 。
当时问题也不少,例如同一个可执行文件不能同时执行,它们的物理地址一样,冲突了,必须一个接一个,还有就是可执行文件的物理地址已经固定了,如果想在其它物理地址运行,必须地重新编译,生成新的物理地址 。
可见直接定位是计算机发展早期的产物,早期没有那么多的程序要运行,程序都是一个接一个地去执行的,因此早期这种定位比较简单,直接高效 。
段基址+段偏移地址随着多任务需求的来临,现在内存中要并发运行多个程序,虽然采用直接定位把每个不同的程序放在不同的内存段中,勉强可以满足,但是一个可执行文件不能同时运行多个,另外程序必须在固定的物理地址运行,灵活性大大减弱,调度起来也是非常麻烦,因此CPU设计师和操作系统开发人员发明了段基址+段偏移地址 。
Inter 8086处理器就是采用这种定位方式,我们知道可执行文件主要分为数据段和内存段,如下图
彻底搞懂虚拟内存,虚拟地址,虚拟地址空间

文章插图
 
由上图红色部分可知,0,4,8就是相对于数据段的偏移地址,0,4,8,12是相对于代码段的偏移地址 。
在可执行文件中,一个段的偏移地址是固定的,无论可执行文件加载到内存的什么位置,这个偏移地址是固定的 。
当可执行文件加载到内存时,先在内存中分配一个数据段和代码段,这两个段理论上可以不挨着,一般情况下,代码段和数据段是挨着的,代码段和数据段在内存中都有一个起始地址,这个起始地址就叫做段基址,这个段基址是放在段寄存器里,例如代码段基址放在CS寄存器,数据段基址放在DS寄存器,当然还有其他的段例如栈段,栈段刚开始大小为0,随着程序的运行入栈,出栈,这个栈段在不断扩展,当然,咋们主要说的是数据段和代码段,栈段只是简单带过 。
假设可执行文件被加载到了内存中,如下图
彻底搞懂虚拟内存,虚拟地址,虚拟地址空间

文章插图
 
如上图所示,代码段被布局到以0x00600000为起始地址的内存中,数据段被布局到以0x00601000为起始地址的内存中 。
当CPU开始执行代码段的第一条指令时,会将代码段的起始地址放入到段寄存器中,此时CS代码段寄存器中存储的就是0x00600000,然后开始从起始地址处开始执行第一条代码指令,此时把代码指令的偏移地址放入到IP寄存器中,IP寄存器存储的就是0,所以CPU要定位一条代码指令时通过CS:IP的方式定位的,如下图所示
彻底搞懂虚拟内存,虚拟地址,虚拟地址空间

文章插图
定位指令
当CPU执行到0x00600000处的代码指令时,该指令为MOV AX,[0],该指令的意思是把地址0处的数据存储到AX寄存器,这个0就是数据段的偏移地址,此时CPU会将数据段的起始地址加入到DS段寄存器中,然后将数据段寄存器的值+偏移地址即0x00601000+0=0x00601000定位到了数据123,然后将123存储到AX寄存器中 。
彻底搞懂虚拟内存,虚拟地址,虚拟地址空间

文章插图
定位数据
上述过程就是【段基址+段偏移地址】的定位方式,之所以把起始地址加入到寄存器中,也是为了后续再执行指令或者获取数据时,可以直接从寄存器获取,加快CPU执行的速度 。
段选择子+段偏移地址【段选择子+段偏移地址】与【段基址+段偏移地址】有些相似之处,之所以采用【段选择子+段偏移地址】主要是为了安全,原来的【段基址+段偏移地址】方式,程序员可以直接跳转到其他代码段和数据段,没有任何限制,安全性全依赖于程序员的职业操守和水平,因此CPU设计者就发明了【段选择子+段偏移地址】 。


推荐阅读