赞
踩
在没有虚拟内存的时候,那个时候的计算机,程序指令所访问的内存地址就是物理内存地址. 也就是不得不把程序的全部装进内存当中,然后运行。物理内存其实就是插在计算机主板内存槽上的实际物理内存,CPU可以直接进行寻址.。物理内存的容量是固定的,但是寻址空间却取决于cpu地址线条数,如32位机,则寻址空间为2^32 = 4G,所以最大支只持4G的寻址空间,即使插了8G的内存条也只能使用4G内存。
直接使用物理地址进行寻址会出现以下几个问题。
Linux系统会给当前每一个进程分配一个2^32位大小(4G)的一块空间,这块空间就叫做进程的虚拟地址空间。
IBM公司关于虚拟的解释:
它存在,你看得见,它是物理的
它存在,你看不见,它是透明的
它不存在,你看得见,它是虚拟的
它不存在,你看不见,它被删除
接下来分别阐述每块空间的作用
从0x00000000到0x08048000的这段空间是预留的,是不能访问的,例如对空指针进行访问程序就会崩溃
程序运行时产生的指令就放在.text段(代码段)。这一段同时也保存了只读数据(read only data),例如char* p = "hello world"
中的"hello world"
,在这里,当想进行*p = 'a'
操作时可以通过编译但运行会崩溃(在比较新的c++编译器中不允许用普通指针指向常量字符串),因为该段是只能读但不能写的。
.data存放了初始化了的且初始化的值不为0的数据
.bss存放未初始化及初始化为0的数据
打印未初始化的全局变量会看到值为0,因为存放于.bss段,操作系统会将.bss段的数据全部赋值为0
堆(heap),在《深入理解计算机系统》中的名称是运行时堆(由malloc创建),也就是说这里的堆空间是暂时没有的,当程序运行,new或malloc之后才会分配堆内存,由低地址向高地址增长
加载共享库,也就是动态链接库,Windows下是*.dll,Linux下是*so
stack 函数运行或产生线程时,每一个函数/线程独有的栈空间,由高地址向低地址增长的
命令行参数和环境变量,命令行参数如main函数传参,环境变量如搜索头文件、库文件时默认的路径
每个进程创建加载的时候,会被分配一个大小为4G的连续的虚拟地址空间,虚拟的意思就是,其实这个地址空间是不存在的,仅仅是每个进程“认为”自己拥有4G的内存,而实际上,它用了多少空间,操作系统就在磁盘上划出多少空间给它,等到进程真正运行的时候,需要某些数据并且数据不在物理内存中,才会触发缺页异常,进行数据拷贝。
更准确一点的说,系统将虚拟内存分割为称为虚拟页(Virtual Page,VP)的大小固定的块,每个虚拟页的大小为P = 2^p字节,类似地,物理内存被分割为物理页(Physical Page,PP),大小也为P字节(物理页也称为页帧/页框(page frame))。
在任意时刻,虚拟页面都分为互不相交的三种:
这个示例展示了一个有8个虚拟页的小虚拟存储器,虚拟页0和3还没有被分配,因此在磁盘上还不存在。虚拟页1、4和6被缓存在物理存储器中。页2、5和7已经被分配了,但是当前并未缓存在主存中。
操作系统向进程描述了一个完整的连续的虚拟地址空间供进程使用,但是在物理内存中进程数据的存储采用离散式存储(提高内存利用率),但是其实虚拟内存和物理内存之间的关系并不像上图中那样直接,其中还需要使用页表映射虚拟地址与物理地址的映射关系,并且通过页表实现内存访问控制。
页表是一种特殊的数据结构,存放着各个虚拟页的状态,是否映射,是否缓存.。进程要知道哪些内存地址上的数据在物理内存上,哪些不在,还有在物理内存上的哪里,这就需要用页表来记录。页表的每一个表项分为两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)。当进程访问某个虚拟地址,就会先去看页表,如果发现对应的数据不在物理内存中,则发生缺页异常。
图中展示了一个页表的基本组织结构,页表就是一个页表条目(Page Table Entry,PTE)的数组,每个PTE由一个有效位(valid bit)和一个地址组成,有效位表明了该虚拟页当前是否存在于物理内存中,如果有效位是1,该PTE中就会存储物理内存中相应的物理页的起始地址。如果有效位是0,且PTE中的地址为null,这表示这个虚拟页还未被分配,而如果有效位是0且PTE中有地址,那么这个地址指向该虚拟页在磁盘上的起始位置。
上图的示例展示了一个有8个虚拟页和4个物理页的系统的页表,四个虚拟页(VP1、VP2、VP4和VP7)当前存储于物理内存中,两个页(VP0和VP5)还未被分配(也就是什么都没存的虚拟内存,在磁盘和物理内存中都不存在这个空间),而剩下的页(VP3和VP6)已经被分配了,但是还未缓存进物理内存(也就是存在于磁盘上)。
在上面的过程中,CPU读包含在VP1中的一个数据时,地址翻译硬件将虚拟地址作为一个索引找到页表中的PTE 2,然后再从PTE 2中保存的物理地址从真正的物理内存中读到这个数据,在有效位为1的PTE中成功找到对应的物理页就称之为页命中。
而当试图访问一个有效位为0,但PTE中又保存了地址的虚拟内存中的数据时(也就是VP3和VP6的情况,数据保存在磁盘中),就是DRAM缓存不命中,一般将这种状况称为缺页异常(page fault)。触发缺页异常后,系统会调用内核中的缺页异常处理程序,该程序会选择一个牺牲页(牺牲页的选择有具体的算法,在这里不做讨论),在此例中就是存放在PP3中的VP4,内核将修改后的VP4重新拷贝回磁盘,并且修改VP4中的页表条目,将有效位改成0,反映出VP4不再存在于物理内存中这一事实。接下来,内核从磁盘拷贝VP3到存储器中的PP3,更新PTE3,随后返回。当异常处理程序返回时,它会重新启动导致缺页的指令,再次从试图访问该虚拟地址开始,这时有效位是1,于是正常页命中,从物理地址中读取内存。
虚拟内存的工作原理
当一个进程试图访问虚拟地址空间中的某个数据时,会经历下面两种情况的过程:
Linux将虚拟空间分为若干个大小相等的存储分区,Linux将这样的分区称为页。为了换入换出方便,物理内存页按页的大小划分为若干块。由于物理内存中的块空间时用来容纳虚拟页的容器,所以物理内存中的块叫做页框,页与页框是Linux实现虚拟内存技术的基础。
在Linux中页和页框的大小一般为4KB,根据系统和应用的不同页和页框的大小会有所变化。
物理内存和虚拟内存被分为页和页框后,其存储单元原来的地址都被自然地分为两段,这两段各自代表着不同的含义:高位段分别叫做页框码和页码用于识别页框和页的编码,低位段分别叫做页框偏移量和页内偏移量是存储单元在页框和页内的地址编码。
处理器遇到的地址都是虚拟地址,虚拟地址和物理地址都分为页码(页框码)和偏移值两部分,在由虚拟地址转化为物理地址的过程中,偏移值不变。而页码和页框码之间的映射就在一个映射记录表(页表)中。
虚拟内存的好处:
扩大地址内存 。
内存保护:每个进程运行在各自的虚拟内存地址空间,互相不能干扰对方,虚存还对待定的内存地址提供写保护,可以防止代码或数据被恶意篡改。
公平内存分配。采用虚拟内存之后,每个进程都相当 于有同样大小的虚拟内存空间。
当进程进行通信时,可以采用虚存共享的当时实现。
当不同的进程使用同样的代码时,比如库文件中的代码,物理内存中可以只存储一份这样子的代码,不同的进程只需要把自己的虚拟内存映射过去就行,节省了内存。
虚拟内存很 适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中,当一个程序等待它的一部分读入内存时,可以把cpu交给另一个进程使用,在内存中可以保留多个进程,系统并发度提高。
在程序需要分配连续的内存空间的时候,只需要在虚拟内存空间分配连续空间,而不需要实际物理内存的连续空间,可以利用碎片。
虚拟内存的坏处:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。