计算机原理(四)



2. 虚拟存储器

 

虚拟存储器我们一般也称为虚拟内存(和Windows中的虚拟内存不是一个概念,但是有关联),它的基本思想是:

  • 每个进程都有自己的地址空间;
  • 每个地址空间被分为多个块,每个块称为页,每个页有连续的地址空间;
  • 这些页被映射到物理内存,但不是所有也都在内存中程序才能运行;
  • 当使用的页不在物理内存中时,由操作系统负责载入相应的页;

 

在实模式下,CPU将偏移地址和段寄存器,基址寄存器等进行计算得到的实际的物理地址。 而在保护模式下,引入了虚拟内存的概念,在虚拟内存中使用的地址称为虚拟地址(线性地址),虚拟地址通过MMU将虚拟地址映射为物理地址,然后送到总线,进行内存访问。这里最关键的就是虚拟地址的映射。

 

 

2.1 分页

 

对于虚拟内存来说,是对物理内存的抽象,整个虚拟内存空间被划分成了多个大小固定的页(page),每个页连续的虚拟地址,组合成了一个完整的虚拟地址空间。同样,操作系统也会把物理内存划分为多个大小固定的块,我们称为页框(page frame),它只是操作系统为了方便管理在逻辑上的划分的一个数据结构,并不存放实际内存数据,但是我们可以简单的认为它就是内存。这样一个虚拟内存的page就可以和一个物理内存的page frame对应起来,产生映射关系。

 

关于一个虚拟页的大小,现在的操作系统中一般是512B-64K(准确的说是地址范围大小,而非容纳数据的大小)。但是内存页的大小会对系统性能产生影响,内存页设得太小,内存页会很多,管理内存页的数组会比较大,耗内存。内存页设大了,因为一个进程拥有的内存是内存页大小的整数倍,会导致碎片,即申请了很多内存,真正用到的只有一点。目前Windows和Linux上默认的内存页面大小都是4K。

 

从上图我们也可以看出,虚拟内存的页和物理内存的页框并不一定是一一对应的,虚拟内存的大小和系统的寻址能力相关,也就是地址线的位数,而物理内存的页框数取决于实际的内存大小。所以可能只有一部分页有对应的页框,而当访问的数据不在物理内存中时就会出现缺页,这个时候操作系统会负责调入新的页,也就是建立新的映射。这样就不需要一次把程序全部加载到内存。

 

 

 

2.1.1 虚拟页是什么?

 

很多人会有一个疑问,虚拟页到底是实际存在的还是虚拟的?我们知道内存中存放的是执行文件的代码和数据,而程序在运行前,它的数据和代码是存放在这个程序的可执行文件中的(比如.exe和.so),而在运行时需要把可执行文件加载到内存。所以我们把这个硬盘上的文件也划分为4K大小的页(逻辑上划分,实际是加载过程中加载器完成的),这就是虚拟页里面实际的东西。但是程序在运行是可能会申请内存,这个时候需要新的虚拟页来映射,所以我们可以得知虚拟页应该有3种状态:

  1. 已映射:虚拟页面被创建已经被加载到物理内存,和物理页之间存在映射关系。
  2. 未映射:虚拟页面被创建,但是没有被加载到内存或已经被调出内存,和物理页面之间没有映射关系,当需要使用时调入内存建立映射。
  3. 未创建:虚拟页面没有被创建,可能是因为还没有访问到此页面所以没有加载或者是调用macllo来分配内存,只有在运行是才会被创建。

 

 

2.1.2 存储器映射

 

加载应用程序到内存时,因为和虚拟地址有关,我们需要把应用程序文件和虚拟内存关联起来,在Linux中称为存储器映射,同时提供了一个mmap的系统调用来实现次功能。文件被分成页面大小的片,每一片包含一个虚拟页面的内容,因为页面调度程序是按需求调度,所以在这些虚拟页面并没有实际的进入内存,而是在CPU发出访问请求时,会创建一个虚拟页并加载到内存。我们在启动一个进程时,加载器为我们完成了文件映射的功能,所以此时我们的执行文件又可以称为映像文件。实际上加载器并不真正负责从磁盘加载数据到内存,因为和虚拟内存建立了映射关系,所以这个操作是虚拟内存自动进行的。 正是有了存储器映射的存在,使得我们可以很方便的将程序和数据加载到内存中。

 

 

2.1.3 交换分区

 

当CPU请求一个虚拟页是,虚拟页会被创建并加载到内存,而页面调度算法可能在页面休眠或在内存满的情况下更具调度算法将虚拟页交换出去,在适当的时候可能被交换回来。这个时候就需要一个区域来存放被交换出来的虚拟页,这个区域称为交换分区。 这个分区在Linux中称为swap分区,而在Windows中我们称为虚拟内存(注意这里和我们谈到的虚拟内存技术不是一回事)。

 

以前电脑内存很小,特别是玩一些游戏时经常会提示内存不足,网上一般会告诉你增大你的虚拟内存(交换分区),这样一来在内存不足的时候可以存放更多交换出来的虚拟页,看起来好像内存变大了一样。从这方面来说Windows把他叫虚拟内存(交换分区)也是很正确的。  交换分区虽然也是硬盘的一部分,但是交换分区没有普通的文件系统,这样消除了将文件偏移转换为页地址的开销。但是过于频繁的交换页面,IO操作会导致系统性能下降。但是在内存不足时可以保证系统 正常运行。当然这也和交换分区的大小有关。

 

而如今,一般使用的电脑都已经4G,8G内存了,对于普通需求来说足够大了。所以虚拟页会长时间存在与内存中而不被交换出去。所以我们可以禁用掉交换分区,以便提高性能。对于Windwos 从Vista开始有一个Superfetch的内存管理机制,而linux有Preload与之类似。这种内存机制会将用户经常用的应用的部分虚拟页提前加载到内存,当用户使用时就无需在从硬盘加载。而当应用休眠或关闭时,也不会将这些虚拟页交换出去。


如下图就是Windows 8上内存使用情况,其中最左灰色部分是给BIOS和硬件保留的内存映射区域;绿色为操作系统,驱动以及用户进程使用的内存;橙色表示已经修改的内存也,当交换出来时需要先写回到硬盘;而蓝色部分5G内存则是用来缓存了未激活进程的数据和代码页;最后剩余的3M才是空闲内存。 当活动进行需要更多内存时会优先使用可用部分,当可用部分没有内存可用时,会释放一部分备用区域的内存。


 


2.2 页表

 
 

上面我们看到当实际物理内存小于虚拟内存时,会存在缺页以及页面交换等问题。此时操作系统会处理这些事情,是的这一切对于程序来说是透明的,它们不知道发生了什么,只知道自己可以使用全部的虚拟内存空间。而对于操作系统来说,它们需要负责一切,需要知道程序的那些页在实际内存中,那些不在。于是出现了页表,就是用来记录虚拟内存的页和物理页框之间的映射关系。MMU也正是利用页表来进行虚拟地址和物理地址的转换。

上面这张图是一张虚拟内存页和物理内存页框之间通过页表的映射关系,其中虚拟页面从VP0-VP7,物理页为PP0-PP3,我们从图中可以得到几点信息:

  1. 不是所有的虚拟内存页都加载到了物理内存中(VP3,VP6未映射状态);
  2. 不是所有虚拟内存页都被创建(VP0,VP5未被创建)
  3. 所有的虚拟内存页在页表中都有一项纪录,我们称为PTE(Page Table Entry);
  4. 虚拟内存的页是存放在磁盘上的;
  5. 页表纪录需要占用内存空间;
  6. MMU通过页表,将虚拟地址转换为物理地址;

 

这里可能会有疑问,为什么VP5没有被创建?虚拟页不是应该连续的吗?这就涉及到内存分段,程序编译和加载一些列问题了,这个会在介绍程序加载时解释。

 

 

最后我们看下页表中PTE的结构,一个PTE大小是32位,系统在操作页表时则会根据这些属性进行相应操作。

 

  • P:存在标志(1表示当前页是加载到了物理内存中)
  • W:读写标志(0时表示只读)
  • U/S: 用户/超级用户(0时表示用超级用户权限)
  • PWT:连续写入
  • PCD:禁用缓存
  • A:访问过
  • D:脏位(1表示被写过)
  • PAT:页面属性索引表
  • G:全局标志(TLB中使用)
  • Avail:方便操作系统使用


 


2.3 虚拟地址转换



通过上图我们来分析一下虚拟地址转换的过程:

  1. CPU送出要访问的虚拟地址,地址的结构是【页号+页内地址】;
  2. 页表存放在内存中,页表的地址和长度信息则存放在一个页表专用的寄存器中;通过读取寄存器的信息获得页表的起始地址;
  3. 将虚拟地址的页号与页表其实地址相加可以得到页表的实际地址
  4. 通过页表的映射项目,可以得到对应的物理内存页的号码
  5. 通过物理页号和业内偏移地址就能得到实际要访问的物理地址

 

当然,如果访问过程中出现缺页,会产生一个中断,然后操作系统会载入需要的页面并进行映射(设置页表),最后返回物理页号得到物理内存。从上面的过程我们可以知道,每次进行地址变化,MMU都要访问内存。回忆8086地址变换时是不需要访问内存的,于是虚拟地址的转换会影响系统性能,但是相当于虚拟内存带来的好处,这点代价还是值得的。

 


  

2.4 页表分级



在IA-32平台上,地址线为32位,所以最大的寻址范围是4G,那么最多能够支持使用4GB的内存(内存按字节编码)。那么对于虚拟内存来说,它的地址范围为4G(0x00000000 ~ 0xFFFFFFFF),而一个内存页的大小是4K,那么一个程序虚拟内存空间中有1048576个页(实际上进程可访问的虚拟地址范围没有4G,Linux是3G,Windows是2G或3G)。

从上面我们知道每个虚拟页都会在页表中有一个PTE,每个PTE为32位,那么对于一个进程至少需要4MB的内存来维护自己的页表;而一个系统中可能存在多个进行,仅仅维护页表这一项就需要消耗比较多内存。但实际上很多PTE项并没有映射到对应的物理页,这就造成了浪费。

 

有人会说那我们就动态建立页表,在映射时才增加这一项。但是从虚拟地址转换我们可以看到,找到PTE是通过PT首地址+页号得到的,所以页表PTE必须是连续的,但我们又知道并不是所有的虚拟页都会马上被创建,在访问是就会出现问题,比如VP0-VP8中的VP5没有被创建,当访问页号是5时,就会错误的访问到VP6。所以为了解决页表占用内存过多的问题,引入了分级页表。注意分级页表也需要CPU硬件提供支持。

 


 

2.4.1 二级页表

 

 

上图是Linux系统上二级页表的示意图。与一级页表不同的是,多增加了一层目录,虚拟地址的组成变为了【目录地址+页表地址+页内偏移】。其中页内偏移地址为12位,页表(PGT)地址为10位,页表目录(PGD)地址为10位。因为总过是32位,他们表示的PTE的个数是不变的。同样,PEG的每一个项目也有自己的结构。

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值