物理地址和虚拟地址
把主存看成是由连续字节单元组成的大数组,并且用物理地址(PA)来标识每个数组的单元。CPU需要加载存储器中一个字都时候,就指定这个字的物理地址的首地址,从而将存储器中的数据返回给CPU,通过物理地址来访问存储器的方式就是物理寻址。所以很直观,物理寻址方便很多,然而对于系统来说,直接物理寻址对存储器的管理很不合理。
所以有了虚拟地址(VA),通过虚拟地址访问存储器的方式就叫做虚拟寻址,其实这种说法不是很恰当,因为虚拟地址最终还是会被翻译(这个翻译由硬件和系统协同完成的)成物理地址,从而访问存储器,虚拟地址和物理地址是一一对应的
可以看到,最终访问内存的还是物理地址(PA)。
虚拟存储器
虚拟存储器(即虚拟内存),是由N个字节组成的,它存在于磁盘上(注意,是磁盘上,不是存储器内存上)。另外物理存储器和虚拟存储器都是用页来作为磁盘和内存的传输单元。但是CPU是从内存上取数据啊,而虚拟存储器是存在在磁盘上,因此虚拟存储器必须清楚它的哪些页是已分配(里面的数据是有效的)的,哪些是缓存在内存中(将数据从磁盘中拷贝到CPU能较高速访问物理存储器)的,哪些是已分配但是未被缓存到内存中的(数据是有效的且暂时只存在磁盘中,当需要的时候再缓冲)。
神奇的页表
虚拟存储器如何知道它每个页的分配情况呢?页表(PT)从而诞生了,每一个虚拟存储器都有它自己独有的页表,页表为虚拟地址和物理地址的全相联提供了可能。
因为有页表的存在,所以没有必要把虚拟存储器(再声明,是存在在磁盘上的)的所以页都缓存在内存当中,即便CPU访问该虚拟存储器的页不存在在内存当中,那么系统会通过查表,把需要的页从磁盘当中拷贝到内存当中,这里就涉及了页面调度的复杂算法了(页面置换算法)。
有了页表,CPU也不放心
页命中的情况:如果虚拟页已经缓存到了物理存储器那么好办,直接从物理存储器中直接读取数据就好了。
还是这张图,来看看简单的命中的情况。假如CPU给出的虚拟地址,地址翻译硬件进行查表,通过虚拟地址定位到了PTE 2(PTE就是页表的条目,上面有注释)的位置,发现它的有效位是1,证明里面的数据是有效的且数据已经缓存到了DRAM当中。所以取出PTE 2中的物理存地址来访问DRAM,可以看到它指向了DRAM中的VP 0,目标查找成功,OVER!!
页不命中(缺页)的情况:
如果CPU给出的虚拟地址指向了尚未缓冲的地址。地址翻译硬件发现PTE 5中数据是有效的,但它并没有从磁盘中缓存到DRAM中。地址翻译硬件发现某个CPU需要的虚拟页没有缓存到DRAM中,就会触发一个缺页的异常(产生一个中断),那么对应的缺页异常处理程序就会启动。首先会选择已缓存到DRAM的一个牺牲页,把它拷贝回虚拟存储器的某个位置,再将命中的虚拟页缓存到DRAM当中。
这时候,异常处理过程返回,重新返回到导致缺页异常的指令,重新执行,目标命中,OVER!!另外,经常缺页会降低程序执行的效率,不命中的处罚还是存在的。
进程离不开虚拟存储器
现在来谈谈操作系统中的进程。操作系统为每个进程都分配了一个页表,也就是说每个一个进程都有虚拟内存,独立的虚拟地址空间。
可以看到,每个进程都有一张页表,同一个进程的内存空间在虚拟存储器中可以是不连续的,因为虚拟存储器和页表映射加上缺页缓存的机制让进程“拥有”一个“连续”的空间提供了可能,可实际上,是很有可能是不连续的。CPU提取数据的时候,都会先给出的是虚拟地址,无论页命中还是不命中,最后由地址翻译硬件翻译出来的物理地址都不会有错。
在刚开始学c语言的时候经常用到函数scanf和printf等等一些函数,他们在自己进程中有一个虚拟地址与之对应,这样看起来好像每个进程内scanf和printf是分家的,实际上,由于虚拟地址的映射,每个进程中的这些库函数地址都映射到了物理地址的同一个位置,这就节省了很多的空间,从而又不影响进程的独立性。
------------------------------------------------------------------------------------------------------------------------
操作系统普遍采用虚拟内存管理(Virtual Memory Management)机制,这需要处理器中的MMU(Memory Management Unit,内存管理单元)提供支持,现在简要介绍MMU的作用。
首先引入两个概念,虚拟地址和物理地址。如果处理器没有MMU,或者有MMU但没有启用,CPU执行单元发出的内存地址将直接传到芯片引脚上,被内存芯片(以下称为物理内存,以便与虚拟内存区分)接收,这称为物理地址(Physical Address,以下简称PA),如下图所示。
如果处理器启用了MMU,CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address,以下简称VA),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将VA映射成PA,如下图所示。
MMU将VA映射到PA是以页(Page)为单位的,32位处理器的页尺寸通常是4KB。例如,MMU可以通过一个映射项将VA的一页0xb7001000~0xb7001fff映射到PA的一页0x2000~0x2fff,如果CPU执行单元要访问虚拟地址0xb7001008,则实际访问到的物理地址是0x2008。物理内存中的页称为物理页面或者页帧(Page Frame)。虚拟内存的哪个页面映射到物理内存的哪个页帧是通过页表(Page Table)来描述的,页表保存在物理内存中,MMU会查找页表来确定一个VA应该映射到什么PA。
操作系统和MMU是这样配合的:
- 操作系统在初始化或分配、释放内存时会执行一些指令在物理内存中填写页表,然后用指令设置MMU,告诉MMU页表在物理内存中的什么位置。
- 设置好之后,CPU每次执行访问内存的指令都会自动引发MMU做查表和地址转换操作,地址转换操作由硬件自动完成,不需要用指令控制MMU去做。
我们在程序中使用的变量和函数都有各自的地址,程序被编译后,这些地址就成了指令中的地址,指令中的地址被CPU解释执行,就成了CPU执行单元发出的内存地址,所以在启用MMU的情况下,程序中使用的地址都是虚拟地址,都会引发MMU做查表和地址转换操作。
为什么要使用虚拟地址?
1)程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
2)程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存没有命中时,内存管理器会将最近最不常用的物理内存页(通常大小为 4 KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
3)不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程使用的物理内存。