内存管理两部曲之虚拟内存管理

传统存储管理存在的问题

虚拟内存这个东西他为什么会出现?他出现的背景是什么?

前文 内存管理两部曲之物理内存管理 提到:随着用户程序功能的增加,进程所需要的内存空间越来越大,进程空间很容易就突破了物理内存的实际大小,导致进程无法运行。

因此,为了解决内存不足的情况,缓和大程序与小内存之间的矛盾,扩充内存容量势在必行。

可以从物理和逻辑两方面来考虑扩充内存容量,物理扩容没啥技术含量,需要我们研究的自然是如何从逻辑上扩充内存容量。

所谓逻辑扩充,就是说实际上物理内存的容量没有发生改变,但是它能装的东西却变多了,使得用户看来似乎有一个比实际内存大得多的内存。

对内存的逻辑扩充技术主要有三种:覆盖技术、交换技术、以及虚拟内存(Virtual Memory),也称为虚拟存储器。事实上,这些逻辑扩充技术的核心理念都是一致的,研究的都是将哪个进程(或进程的某部分)暂时从内存移到外存(磁盘),以腾出内存空间供其他进程(或进程的某部分)占用。

覆盖(Overlay)和交换(Swapping)这两种存在于早期操作系统中的逻辑扩充技术现在已经成为历史,这里就简单介绍下:

前文说过,早期操作系统仅将内存空间分成两块:系统区(用于存放操作系统相关数据)和用户区(用于存放用户进程相关数据,内存中只能有一道用户程序,用户程序独占整个用户区空间,显然,内存空间容不下某个用户程序的现象常会发生。

覆盖技术(Overlay)的基本思想就是:程序运行时并非任何时候都要访问程序及数据的所有部分(尤其是大程序),因此可以把用户空间(内存)分成一个固定区和一个或多个覆盖区。

将程序经常活跃的部分放在固定区,其余部分按调用关系进行分段:首先将那些即将要用的段放在覆盖区,其他段放在外存(磁盘),在需要调用前由用户来安排特定的系统调用将这些放在外存中的段调入覆盖区,替换覆盖区中原有的段。

覆盖技术的缺点显而易见并且可以说是让人无法接受的,那就是覆盖技术是把解决内存空间不足的问题交给了用户。操作系统仅仅为用户提供将覆盖段调入内存的系统调用,但是必须由用户自己来说明覆盖哪个段、调入哪个段。

合着我用个电脑还得算着怎么才能让我的程序不崩溃?

OK,可以看出来,覆盖技术其实是用在同一个作业/进程的不同段之间的,那么不同的作业/进程之间怎么办呢?

这就是交换技术的适用场景。

交换技术(Swapping)的基本思想是:空闲进程/作业主要存储在外存(磁盘)上,当其中某个进程/作业需要运行的时候,就将其从磁盘中完整地调入内存,使该进程运行一段时间,然后再把它返回磁盘。所以说当进程/作业不运行的时候它们是不会占用内存的。


事实上,覆盖和交换技术分别解决了传统存储管理(物理内存管理)中存在的某个问题:

  • 覆盖技术打破了作业/进程必须一次性全部装入内存后才能开始运行(一次性)的限制
  • 交换技术打破了一旦作业被装入内存,就会一直驻留在内存中,直至作业运行结束(驻留性)的限制

当然了,Anyway,这两种逻辑扩充技术已经成为历史,虚拟内存技术才是目前的主流,它综合了这两种古老技术的特点,单枪匹马解决了传统存储管理中存在的这两个问题。

什么是虚拟内存

有了上述交换技术的铺垫,理解起虚拟内存来也就不那么陌生了。

当然了,在此之前,我一定要着重声明的是,不要把虚拟内存当作一个实际存在的东西,它是一门技术!和交换技术覆盖技术一样是一门用来逻辑扩充内存空间的技术!

虚拟内存技术基于一个非常重要的原理,局部性原理

1)时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量的循环)

2)空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问(因为很多数据在内存中都是连续存放的,并且程序的指令也是顺序地在内存中存放的)

基于这个局部性原理,在一个程序装入内存的时候,可以只将这个程序中很快会用到的部分装入内存,暂时用不到的部分仍然留在外存(磁盘),并且程序可以正常执行;

而在程序执行过程中,当 CPU 所需要的信息不在内存中的时候,由操作系统负责将所需信息从外存(磁盘)调入内存,然后继续执行程序;

如果调入内存的时候内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。

以上,就是虚拟内存技术。

如何实现虚拟内存技术

可以看见,虚拟内存允许一个作业/进程分多次调入内存,那如果采用连续分配方式,不方便实现,所以虚拟内存技术的实现是建立在不连续分配管理方式之上的。

传统的基本分页管理、基本分段管理、基本段页式管理和虚拟内存技术结合,分别称为请求分页管理(页式虚存系统)、请求分段管理(段式虚存系统)、请求段页式管理(段页式虚存系统)。

这几个概念非常容易混淆,其实很容易区分,记住这句话就 OK,摘自百度百科:

如果不具备请求调页、页面置换的功能,则称为基本分页管理(或称为纯分页管理),它不具有支持实现虚拟内存的功能,它要求把每个作业(进程)全部装入内存后方能运行。

请求分段存储管理也差不多,它建立在分段存储管理之上,但增加了请求调段、段置换功能。

请求调页、页面置换 和 请求调段、段置换概念差不多,这里以请求调页和页面置换为例解释下。

  • 在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存(磁盘)调入内存,然后继续执行程序(操作系统要提供请求调页的功能, 将内存中缺失的页面从磁盘调入内存 );
  • 若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到磁盘(操作系统要提供页面置换的功能, 将暂时用不到的页面换出磁盘)。

具体来说,在页式虚存系统中,每当 CPU 要访问的页面不在内存时,就会产生一个缺页中断,然后由操作系统的缺页中断处理程序来处理中断。此时,缺页的这个进程/作业就会被阻塞住,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。

  • 如果内存中有空闲块,则为该进程分配一个空闲块,将所缺的页面装入这个块中,并修改页表中相应的页表项。
  • 如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存,未修改过的页面不用写回外存。

可以看出来,这并不是一个简单的过程,基本分页管理中的简单页表已经无法胜任这样的工作。

我们还是先来回顾下基本分页管理的页表,它只有页号和块号两个字段:

请求分页管理的页表自然是会复杂不少的:

1)为了实现 “请求调页” 功能,操作系统需要知道每个页面是否已经调入内存,如果还没调入,那么也需要知道该页面在磁盘中存放的位置。

2)而当内存空间不够时,要实现 “页面置换” 功能,操作系统需要通过某些指标来决定到底换出哪个页面,有的页面没有被修改过,就不用浪费时间写回磁盘;有的页面修改过,就需要将磁盘中的旧数据覆盖。因此,操作系统也需要记录各个页面是否被修改的信息。

为此,请求分页管理的页表中添加了 4 个字段:

  • 状态位:该页面是否已调入内存
  • 访问字段:可记录该页面最近被访问过几次,或记录上次访问该页面的时间,供页面置换算法换出页面时参考
  • 修改位:该页面调入内存后是否被修改过
  • 外存地址:该页面在外存中的存放地址

页面置换算法也是一个很重要的内容,本来应该在这篇文章里一起写的,But 想到 “页面置换” 问题不仅仅是在虚拟内存中存在,在计算机设计的其他领域也会同样发生(比如多数计算机都会把最近使用过的 32 字节或者 64 字节存储块保存在一个或多个高速缓存中,当这些高速缓存存满后就必须选取一些块丢弃掉,以此来存入最新的使用过的存储块),所以决定后续单开一篇文章。

🎉 关注公众号 | 飞天小牛肉,即时获取更新

  • 博主东南大学硕士在读,携程 Java 后台开发暑期实习生,利用课余时间运营一个公众号『 飞天小牛肉 』,2020/12/29 日开通,专注分享计算机基础(数据结构 + 算法 + 计算机网络 + 数据库 + 操作系统 + Linux)、Java 技术栈等相关原创技术好文。本公众号的目的就是让大家可以快速掌握重点知识,有的放矢。关注公众号第一时间获取文章更新,成长的路上我们一起进步
  • 并推荐个人维护的开源教程类项目: CS-Wiki(Gitee 推荐项目,现已累计 1.7k+ star), 致力打造完善的后端知识体系,在技术的路上少走弯路,欢迎各位小伙伴前来交流学习 ~ 😊
  • 如果各位小伙伴春招秋招没有拿得出手的项目的话,可以参考我写的一个项目「开源社区系统 Echo」Gitee 官方推荐项目,目前已累计 800+ star,基于 SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... 并提供详细的开发文档和配套教程。公众号后台回复 Echo 可以获取配套教程,目前尚在更新中。
posted @ 2021-05-31 17:15  飞天小牛肉  阅读(498)  评论(0编辑  收藏  举报