重点 第九章 虚拟内存



第九章-----虚拟内存
       系统中的进程共享CPU和主存资源,但存储器空间是有限的 
    虚拟内存(virtual memory) 将用户逻辑内存和物理内存分开。这在现有物理内存有限的情况下,为程序员提供了巨大的虚拟内存。 
1.物理和虚拟地址
        计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每字节都有一个唯一地物理地址(Physical Address, PA)。第一个字节的地址为0,接下来的字节地址为1,再下一个为2,以此类推。给这种简单的结构,CPU访问内存的最自然的方式就是使用物理地址。我们把这种方式称为 物理寻址
       早期的PC使用物理地址 ,然而现代处理器使用的是一种称为 虚拟寻址的寻址形式。如下图:
       
1、 使用虚拟寻址的过程:
1、CPU通过生成一个虚拟地址(Virtual Address, VA)来访问主存,这个虚拟地址在被送到内存之前先转换成适当的物理地址。2、 将一个虚拟地址转换为物理地址的任务叫做地址翻译 。 
3、CPU芯片上叫做内存管理单元(Memory Management Unit, MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。

虚拟地址空间:CPU从一个有N=2^n个地址的地址空间中生成虚拟地址

物理地址空间:对应系统中物理内存的M个字节

主存中的每个字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址

2、连续内存分配

给进程分配一块不小于指定大小的连续的物理内存区域.

内存碎片:空闲内存不能被利用,使内存利用率降低


当申请6个字的虚拟内存时,虽然堆中有6个空闲的字但却是在两个空闲块中的,因此必须向内核请求额外的连续的6个字的虚拟内存。


外部碎片:分配单元之间的未被使用内存

内部碎片:分配单元内部的未被使用内存,取决于分配单元大小是否要取整

2.1 动态内存分配

使用动态内存分配器分配虚拟内存空间比mmap和munmap要简便和移植性强

动态内存分配器维护着一个进程的虚拟内存区域——堆,对于每个进程,内核维护着一个变量brk,它指向堆的顶部

分配器将堆视为不同大小的块的集合,每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的

malloc函数:
程序可以通过调用malloc函数从堆中分配块


malloc函数返回一个指针,指向一个至少包含size字节的块。(一个字是4个字节)

程序通过调用free函数释放已分配的块


ptr指向一个已分配块的起始位置。

使用动态内存分配的原因: 通常直到程序实际执行时才知道某些数据结构的大小。

连续内存分配的缺点:分配给程序的物理内存必须连续、存在外碎片和内碎片,内存利用率较低

3、非连续内存分配:

特点:允许一个程序使用非连续的物理地址空间、允许共享代码与数据、支持动态加载和动态链接

3.1 段式存储管理(以一个段作为一个分配单位(块),较大)

段地址空间的逻辑视图:


段访问机制:

3.2页式存储管理(以一个页作为一个分配单位(块),较小):

3.2.1虚拟内存作为缓存的工具
 1、 虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每字节都有一个唯一地虚拟地址,作为到数组的索引。
2、磁盘上数组的内容被缓存在主存中。和存储器层次结构中其它缓存一样,磁盘(较低层)上的数据被分割成块,这些块作为磁盘和主存(较高层)之间的传输单元。
3、VM系统通过将虚拟内存分割为称为虚拟页(Virtual Page, VP)的大小固定的块来处理这个问题。每个虚拟页的大小为P=2^p个字节。
4、类似地,物理内存被分割为物理页(Physical Page,PP),大小也为P字节(物理页也被称为页帧)。
      一个地址空间的大小由表示最大地址所需要的位数来描述的。例如,一个包含N=2^n 个地址虚拟地址空间就叫做一个n位地址空间。现代系统通常支持32位或者64位虚拟地址空间。
      
     在任意时刻,虚拟页面的集合都分为三个不相交的子集:
      ● 未分配的:VM系统还未分配(或者创建)的页。未分配的块没有任何数据和它们相关联,因此也就不占用任何磁盘空间。
      ● 缓存的:当前已缓存在物理内存中的已分配页。
      ● 未缓存的: 未缓存在物理内存中的已分配页。
3.2.2.DRAM缓存的组织结构
      在存储层次结构中,DRAM缓存的位置对它的组织结构有很大的影响。回想一下,DRAM比SRAM要慢大约10倍,而磁盘要比DRAM慢大约100 000多倍。一次DRAM缓存中的不命中比起SRAM缓存中的不命中要昂贵的多,这是因为DRAM缓存不命中要由磁盘来服务,而SRAM缓存不命中通常是由基于DRAM的主存来服务的。而且,从磁盘的第一个扇区读取第一个字节的时间开销比起读这个扇区中连续的字节慢大约100 000倍。
      因为大的不命中处罚和访问第一个字节的开销,虚拟页往往很大,通常是4KB~2MB。
3.2.3.页表
      虚拟内存系统必须有某种方法来判定一个虚拟页是否缓存在DRAM中的某个地方。如果是,系统还必须确定这个虚拟页存放在哪个物理页。如果不命中,系统必须判断这个虚拟页存放在磁盘的哪个位置,在物理内存中选择一个牺牲页,并将虚拟页从磁盘复制到DRAM中,替换这个牺牲页。
      这些功能是由软硬件联合提供的,包括操作系统软件、MMU(内存管理单元)中的地址翻译硬件和一个存放在物理内存中叫做页表的数据结构,页表将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表。操作系统负责维护页表内容,以及在磁盘与DRAM之间来回传送页。
      下图展示了一个页表的基本组织结构。页表就是一个页表条目(Page Table Entry, PTE)的数组。虚拟地址空间中的每个页中一个固定偏移量处都有一个PTE。假设每个PTE都由一个有效位和一个n位地址字段组成的。
有效位表明了该虚拟页当前是否被缓存在DRAM中。
如果设置了有效位,那么地址字段就表示DRAM中相应的物理页的起始地址。
如果没有设置有效位,那么一个空地址表示这个虚拟页还未分配。若不是空地址,这个地址就指向该虚拟页在磁盘上的起始位置
      
 上图展示了一个有8个虚拟页和4个物理页的系统的页表。
1、四个虚拟页(VP1、VP2、VP4和VP7)当前被缓存在DRAM中。
2、两个页(VP0和VP5)还未被分配。
3、而剩下的页(VP3和VP6)已经被分配了,但是当前还未被缓存。
      ● 页命中
      考虑一下当CPU想要读包含在VP2中的虚拟内存的一个字时会发生什么,VP2被缓存在DRAM中。使用地址翻译技术,地址翻译硬件将虚拟地址作为一个索引来定位PTE2,并从内存中读取它。因为设置了有效位,那么地址翻译硬件就知道VP2是缓存在内存中的了。所以它使用PTE中的物理内存地址(该地址指向PP1中缓存页的起始位置),构造出这个字的物理地址。
       
       ● 缺页
       在虚拟内存的习惯说法中,缓存不命中称为缺页
      下图展示了在缺页之前我们的示例页表的状态:
CPU引用了VP3中的一个字,VP3并未缓存在DRAM中。
1、地址翻译硬件从内存中读取PTE3,从有效位推断出VP3未被缓存,并且触发一个缺页异常。
2、缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,在此例中就是存放在PP3中的VP4。
3、如果VP4已经被修改了,那么内核就会将它复制回磁盘 
4、无论哪种情况,内核都会修改VP4的页表条目,反映出VP4不再缓存在主存中这一事实。

5、  接下来,内核从磁盘复制VP3到内存中的PP3,更新PTE3,随后返回。

       
    6、当异常处理程序返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。但是现在,VP3已经缓存在主存中了,那么页命中也能由地址翻译硬件正常处理了。下图展示了在缺页之后我们的示例页表的状态。
       
3.2.4 置换算法的功能
当出现缺页异常,需调入新页面而内存已满时,置换算法帮助选择被置换的物理页面

目标

1、尽可能减少页面的调入调出次数

2、把未来不再访问或短期内不访问的页面调出

置换算法的分类:

1、最优置换算法(OPT)

基本思路:置换在未来最长时间不访问的页面

   算法实现:

   1、缺页时,计算内存中每个逻辑页面的下一次访问时间

   2、选择未来最长时间不被访问的页面(实际过程中无法预估)

   3、理想算法

2、先进先出算法(FIFO)

     基本思路:选择在内存驻留时间最长的页面进行置换

     实现:

      1、维护一个记录所有位于内存中的逻辑页面链表

      2、链表元素按驻留内存的时间排序,链首最长,链尾最短

      3、出现缺页时,选择链首页面进行置换,新页面加到链尾

3、最近最久未使用算法(LRU)

    基本思路:选择最长时间没有被引用的页面进行置换

    如某些页面长时间未被访问,则它们在将来还可能会长时间不会访问

    实现:

    1、缺页时,计算内存中每个逻辑页面的上一次访问时间

    2、选择上一次使用到当前时间最长的页面(最优置换算法的一种近似)

可能的实现方法:

页面链表

1、系统维护一个按最近一次访问时间排序的页面链表,链表首节点是最近刚刚使用过的页面,链表尾节点是最久未使用的页面

2、访问内存时,找到相应页面,并把它移到链表之首

3、缺页时,置换链表尾节点的页面

活动页面栈

1、访问页面时,将此页号压入栈顶,并将栈内相同的页号抽出

2、缺页时,置换栈底的页面

页面请求中的缺页率计算

答:缺页率 = (页面置换次数+分配给该进程的物理块数)/要访问的页面总数;

缺页数=页面置换次数+分配给该进程的物理块数;

注意: 
1)要访问的页面总数:不是数值最大,而是看要访问的总次数,例如某程序访问以下页面0、1、4、2、0、2、6、5、1、2、3、

2、1、2、6、2、1、3、6、2,总共20个数,则要访问的页面总数是20,并不是6;

2)由于进程开始时,都会给该进程分配一定数量的物理块,当物理块充足时,直接将要访问的页面添加进物理块中就好,并不算页面置换次数;

3)页面置换次数:当前要访问的页面不在内存中,且,物理块已经被占满,没有物理块可用,就需要将物理块中的页面按照一定的算法将不用的页面换出内存,把要访问的页面换进内存中。

技巧:在算缺页率时,可以假设分配给进程的物理块为0,则每进入一个页面就算一次缺页,这样就把分配给该进程的物理块数合并到页面置换次数中,方便记忆。当把物理块占完以后,再考虑要不要从物理块中换出内存。


举例:

若进程访问页面的次序是1、2、3、6、4、7、3、2、1、4、7、5、6、5、2、1,采用最佳置换算法,情况如下图:


3.2.5、虚拟内存作为内存管理的工具

操作系统为每个进程提供了一个独立的页表,即一个独立的虚拟地址空间。

注意:可以将多个虚拟页面映射到同一个共享物理 页面上

如下图所示:

进程i 页表中的虚拟页面VP2,和进程j 页表中的虚拟页面VP1映射到同一个物理页面上,称为共享页面。

3.2.6.地址翻译

         形式上来说,地址翻译是一个N元素的虚拟地址空间(VAS)的元素和一个M元素的物理地址空间(PAS)中元素之间的映射,
                                                            
       这里
              
      下图展示了MMU(内存管理单元)如何利用页表来实现这种映射
1、CPU中的一个控制寄存器,页表基址寄存器(Page Table Base Register, PTRB)指向当前页表。
2、n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(Virtual Page Offset, VPO)和一个(n-p)位的虚拟页号(Virtual Page Number, VPN)。
3、MMU利用VPN来选择适当的PTE。例如,VPN 0选择PTE 0,VPN 1选择PTE 1,以此类推。
4、将页表条目中物理页号(Physical Page Number,PPN)和虚拟地址中的VPO串联起来,就得到相应的物理地址。
       
注意:因为物理页面和虚拟页面都是P字节的,所以物理页面偏移PPO和VPO是相同的。

页面命中完全是由硬件来处理的。但缺页要求硬件和操作系统内核协作完成。

  • 利用TLB(快表)加速地址翻译    

 在MMU中包含一个关于PTE的小缓存,称为翻译后备缓冲器(TLB)

TLB是一个小的,虚拟寻址的缓存,其中的每一行都保存着一个由单个PTE组成的块。


用于组选择和行匹配的索引和标记字段是从虚拟地址的虚拟页号中提取出来的。

如果TLB有T=2^t个组,那么TLB索引是由VPN的t个最低位组成的,剩余的位组成TLB标记。

           ● 多级页表 
         用来压缩页表的常用方法是使用层次结构的页表。
        用一个具体的示例是最容易理解这个思想的:
1、假设32位虚拟地址空间被分为4KB的页,而每个页表条目都是4字节。
2、还假设在这一时刻,虚拟地址空间有如下形式:内存的前2K个页面分配给了代码和数据,接下来6K个页面未分配,再接下来的1023个也未分配,接下来的1个页面分配给了用户栈。
下图展示了如何为虚拟空间构造一个两级也表层次结构:
        
4.Linux虚拟内存区域
        下图记录了一个进程中虚拟内存区域的内核数据结构。内核为系统中的每个进程维护一个单独的任务结构(源代码中的task_struct)。任务结构中的元素包含或者指向内核运行该进程所需要的所有信息(例如,PID、指向用户栈的指针、可执行目标文件的名字,以及程序计数器)。
       
      任务结构中的一个条目指向mm_struct,它描述了虚拟内存的当前状态。我们感兴趣的两个字段是pgd和mmap,其中pgd指向第一级页表(页全局目录)的基址,而mmap指向一个vm_area_structs(区域结构)的链表,其中每个vm_area_structs都描述了当前虚拟地址空间的区域。当内核运行这个进程时,就将pgd存放在CR3控制寄存器中。
      一个具体区域结构包含下面的字段:
      ●   vm_start: 指向这个区域的起始处。
      ●  vm_end: 指向这个区域的结束处。
     ●  vm_prot: 描述这个区域内包含的所有页的读写许可权限。
     ●  vm_flags:描述这个区域内的页面是与其他进程共享的,还是这个进程私有的(还描述了其他一些信息)。
     ●  vm_next: 指向链表中的下一个区域结构。
5.内存映射
     Linux通过将虚拟内存区域与磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射
使用mmap函数的用户级内存映射

Linux 使用mmap函数来创建新的虚拟内存区域,并将对象映射到这些区域中


1、mmap要求内核创建一个新的虚拟内存区域,最好从地址start开始

2、并将文件描述符fd指定的对象的一个连续的片映射到这个新的区域

3、连续的对象片大小为length个字节,从距文件开始处偏移量为offerset字节的地方开始。


使用munmap函数删除虚拟内存区域


munmap删除从start开始的length个字节的虚拟内存区域。接下来如果对已删除的区域引用会导致段错误。

 6 C程序中常见的与内存有关的错误

1、间接引用坏指针

在进行的虚拟地址空间中有较大一部分没有映射到任何有意义的数据,如果我们试图间接引用一个指向这些空间的指针,将会导致段错误。

2、读未初始化的内存

虽然bss内存位置总是被加载器初始化为0,但是对于堆内存却不是这样的,常见的错误就是假设对内存被初始化为0:


3、引用指针,而不是它指向的对象


4、误解指针运算

忘记了指针的算术运算是以他们指向的对象的大小为单位进行的,而这种大小单位并不一定是字节

函数目的是通过扫描一个int 数组,找到val第一次时,指向它的指针。


5、引用不存在的变量


6、引起内存泄漏


小结:



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值