虚拟内存的背景

在基本的内存管理策略中,所有的策略的相同点都是:每个进程在执行之前需要完全处于内存中。那有没有一种方法可以不需要将进程所有页面加载到内存中就可以开始运行进程呢?有没有可能在进程需要某些页面时再将其调入进内存中呢?于是就有了虚拟内存技术。

而虚拟内存将用户逻辑内存和物理内存分开,使得编程人员不在担心有限的物理内存空间。

除此之外,虚拟内存允许文件和内存通过共享页而为多个进程共享,这将会有很多好处:

  • 通过将共享进程映射到虚拟地址空间中,系统库可以为多个进程所共享。
  • 虚拟内存允许一个进程创建一个内存区域,以便与其他进程共享,共享这个内存区域的进程认为:它是其虚拟地址空间的一部分,而事实这部分是共享的。
  • 当通过系统调用fork()创建进程时,可以共享页面,从而加快进程创建。

请求调页

在程序执行时,仅在程序需要时才加载页面,这种技术称为请求调页。对于请求调页的虚拟内存,页面只有在程序执行期间被请求时才被加载,那些从未访问的页面从不加载到物理内存中。

当程序执行时,他被交换到内存中,但是不是将整个进程交换到内存中,而是采用惰性交换器。惰性交换器除非需要某个页面,否则从不将他交换到内存中。

基本概念

当换入进程时,调页程序会猜测该进程被再次换出之前会用到哪些页,调页程序不是调入整个进程,而是把那些要使用的页调入内存,这样调页程序就避免读入那些不使用的页,也减少了交换时间和所需的物理内存空间。这中方法需要硬件的支持,以区分内存的页面和磁盘的页面。可以采用有效-无效位方案用于这一目的。当该位被设置为有效的时候,相关联的页面是合法的,并且存在于内存中;当该位被设置为无效时,页面无效(即不在进程的逻辑地址空间中),或有效但只在磁盘上。

如果进程试图访问标记为无效的页面时,对标记为无效的页面访问会产生缺页错误。分页硬件在通过页表转换地址时会注意到无效位被设置,从而陷入操作系统,处理缺页错误的流程大致如下:

  1. 检查这个进程的内部表,通常和PCB一起保存,以确定该引用是有效的还是无效的内存访问。
  2. 如果引用无效,那么终止进程,如果引用有效但是尚未调入内存,那么现在就应该调用。
  3. 找到一个空闲帧
  4. 调入一个磁盘操作,以将所需页面读到刚分配的帧。
  5. 当磁盘读取完成时,修改进程的内部表和页表,以指示该页现在处于内存中。
  6. 重新启动被陷阱中断的指令。该进程现在能访问所需的页面。

极端情况下,直接执行一个没有内存页面的进程,这种方案称为纯请求调页

请求调页的性能

我们需要计算请求调页内存的有效访问时间。对于没有出现缺页错误,有效访问时间就等于内存访问时间,如果出现缺页错误,那么就应该先从磁盘中读入相关的页面,在进行访问所需要的字。

设p为缺页错误的概率(0 ≤\leq≤p≤\leq≤ 1),内存访问时间为m,则有效访问时间为:

有效访问时间=(1-p) * m + p * 缺页错误时间

如果发生缺页错误,那么大致流程如下:

  1. 陷入操作系统
  2. 保存用户寄存器和进程状态
  3. 确定中断是否为缺页错误
  4. 检查页面引用是否合法,并确定页面的磁盘位置
  5. 从磁盘读入页面到空闲帧

    a. 在该磁盘队列中等待,直到读请求被处理

    b. 等待磁盘的寻道或延迟时间

    c. 开始传世磁盘页面到空闲帧
  6. 在等待时,将CPU分配给其他用户(CPU调度,可选)
  7. 收到来自I/O子系统的中断(I/O完成)
  8. 保存其他用户的寄存器和进程状态(如果执行了第6步)
  9. 确认中断是来自上述磁盘的
  10. 修正页表和其他表,以表示所需的页面现在已经在内存中。
  11. 等待CPU再次分配到本进程
  12. 恢复用户寄存器、进程状态和新页表,再次中心执行中断的指令。

总结下来,缺页错误的处理时间有三个主要组成部分:

  • 处理缺页错误中断
  • 读入页面
  • 重新启动进程

写时复制

当一个父进程通过fork()创建一个子进程的时候,fork()方法将会为子进程创建一个父进程地址空间的副本,并且复制属于父进程的页面。但是通常子进程在创建之后会立即调用exec(),父进程地址空间的复制没有必要。这时候我们可以通过写时复制的技术,允许父进程和子进程最初共享相同的页面来工作,这些共享页面标记为写时复制,如果任何一个进程写入共享页面,那么就创建共享页面的副本。

这里需要注意:只有可以修改的页面才需要标记为写时复制,不能修改的页面可以由父进程和子进程共享。

当采用写时复制复制来复制页面时,重要的是注意空闲页面的分配位置。通常情况下,OS为这类请求提供了一个空闲的页面池。操作系统分配这些页面通常采用按需填零的技术,按需填零页面在分配之前先填零,因此清除了以前的内容。

UNIX的多个版本提供了fork()的变种,即vfork()(虚拟内存fork),vfork()不同于写时复制的fork()。父进程被挂起,子进程使用父进程的地址空间,所以vfork()不采用写时复制。

页面置换

为了实现请求调页,需要解决两个问题:帧分配算法页面置换算法。如果有多个进程在内存中,则必须决定要为每个进程分配多少帧;并且当需要页面置换时,必须要选择置换的帧。

基本的页面置换

如果没有空闲帧,那么就查找当前不在使用的一个帧,并释放它:将其内容写到交换空间,并修改页表,以表示该页不在内存中。现在可以使用空闲帧,来保存进程出错的页面,大致的流程如下:

  1. 找到所需页面的磁盘位置
  2. 找到一个空闲帧

    a. 如果有空闲帧,那么使用它

    b. 如果没有空闲帧,那么就使用页面置换算法来选择一个牺牲帧

    c. 将牺牲帧的内容写到磁盘上,修改对应的页表和帧表
  3. 将所需页面读入(新的)空闲帧,修改页表和帧表
  4. 将发生缺页错误位置,继续用户进程

如果没有空闲帧,那么需要两个页面传输(一个调入,一个调出)。增加了缺页错误的处理时间和有效访问时间,可以采用修改位(或脏位)减少这种开销。当使用这种方法时,需要为每个页面或帧设置一个修改位,每当页面的任何字节被写入时,它的页面修改位会由硬件来设置,以表示该页面已被修改过。当选择一个页面进行置换时,检查它的修改位,如果该位已被设置,应将页面写入磁盘。如果修改为没有被设置,那么就不需要将内存页面写到磁盘。

FIFO页面置换

FIFO页面置换算法为每个页面记录了调到内存的时间,当必须置换页面时,选择最旧的页面。可以创建一个FIFO队列,来管理所有的内存页面。置换的是队列的首个页面,当需要调入页面到内存时,就将它加到队列的尾部。

引用串 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1
帧一 7 0 7 2 2 2 4 4 4 0 0 0 7 7 7
帧二 0 0 0 3 3 3 2 2 2 1 1 1 0 0
帧三 1 1 1 0 0 0 3 3 3 2 2 2 1

注意:FIFO置换算可能导致Belady异常

最优页面置换

该算法具有所有算法的最低的缺页错误率,并且不会遭受到belady异常。该算法在进行置换页面时会置换最长时间不会使用的页面

引用串 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1
帧一 7 0 7 2 2 2 2 2 7
帧二 0 0 0 0 4 0 0 0
帧三 1 1 3 3 3 1 1

但是最优置换算法需要引用串的未来进行判定,所以难以实现。

LRU页面置换

使用最近的过去作为不远将来的近似,那么可以将置换时间最长没有使用的页。这种方法称为最近最少使用算法。LRU置换将每个页面与它上次使用的时间关联起来,当需要置换页面的时候,LRU选择最长时间没有使用的页面。

引用串 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1
帧一 7 0 7 2 2 4 4 4 0 1 1 1
帧二 0 0 0 0 0 0 3 3 3 0 0
帧三 1 1 3 3 2 2 2 2 2 7

LRU置换算法的一个主要问题是:如何确定由上次使用时间定义的帧的顺序?

计数器

为每个页表条目关联一个使用时间域,并为CP添加一个逻辑时钟或计数器。每次内存引用都会递增时钟。每当进行时间引用时,时钟寄存器的内容会复制到相应的页面的页表条目的使用时间域。这样总是有每个页面的最后引用的时间,在置换时置换具有最小时间的页面。

堆栈

采用页码堆栈,每当页面被引用时,就从堆栈中移除并放在顶部,这样最近使用的页面总是在堆栈的顶部,最近最少使用的页面总是在堆栈的底部。因为需要从堆栈中间删除条目,所以可以采用具有首指针和尾指针的双向链表来实现。

LRU置换和最优置换算法属于同一类算法,称为堆栈算法绝不可能有Belady异常。证明:帧数为n的内存页面集合是帧数n+1的内存页面集合的子集。对于LRU置换,内存中的页面集合为最近引用的n个页面,如果帧数增加,那么这n个页面依旧是最近被引用的,因此仍然在内存中。

近似LRU页面置换

很少有计算机系统能够提供足够的硬件来支持真正的LRU算法,但是很多系统都通过引用位的形式提供一定的支持。每当引用一个页面时,它的页面引用位就被硬件置位。页表内的每个条目都关联一个引用位

额外引用位算法

为内存中的页表的每个页面保留一个8位的字节,定时器中断定期的将控制传到操作系统,操作系统将每个页面引用位移到其8位字节的高位,将其他位右移一位,并丢弃最低位。这些8位移位寄存器包含着最近8个时间周期内的页面使用情况。将这些8位字节即视为无符号整数,那么具有最小编号的页面是LRU替换页面。

第二次机会算法

在极端情况下,引用位的位数可以降至0,即只有引用位本身,这种算法就是第二次机会算法。是一种FIFO置换算法。

当选择了一个页面时,需要检查其引用位,如果值为0,那么就直接置换该页面;如果引用位为1,那么就给此页面第二次机会,并继续选择下一个页面。当一个页面获得第二次机会时,其引用位被清除,并且到达时间被设为当前时间。因此获得第二次机会的页面在所有其他页面被置换前不会被置换。

实现第二次机会算法的一种方式是采用循环队列:

增强型第二次机会算法

通过将引用位和修改位作为有序对,可改进第二次机会算法,存在四种可能的类型:

  • (0, 0)最近没有使用且没有修改的页面,最佳的页面置换
  • (0, 1)最近没哟使用但修改过的页面,在置换前需将页面写出
  • (1, 0)最近使用过但没有修改的页面,可能很快再次使用
  • (1, 1)最近使用过且修改过,可能很快再次使用且在置换前需将页面写出

为已修改的页面赋予更高级别,从而降低了所需的I/O数量。

基于计数的页面置换

可以为每个页面的引用次数保存一个计数器,基于计数的算法如下:

  • 最不经常使用(LFU)页面置换算法

    要求置换具有最小计数的页面。这种选择的原因是积极使用的页面应当具有大的引用计数。但是当一个页面在进程的初始阶段大量使用但是随后不在使用时会出现问题。由于被大量使用具有一个大的计数,即使不再需要却仍然保存在内存中。一种解决方法是:定期将计数右移1位,以形成指数衰减的平均使用计数。
  • 最经常使用(MFU)页面置换算法

    基于论点:具有最小计数的页面可能刚刚被引入并且尚未使用。

LFU和MFU置换不常用,这些算法的实现是昂贵的且不能很好的近似OPT(最优置换算法)。

参见于:《操作系统概念

OS之内存管理 --- 虚拟内存管理(一)的更多相关文章

  1. OS之内存管理 --- 虚拟内存管理(二)

    关于虚拟内存管理之前的请看:OS之内存管理 - 虚拟内存管理(一) 帧分配 每个进程对的最小帧数是由操作系统的体系结构决定的,但是最大帧数是由可用物理内存的数量决定的.所以在这之间,对于进程的帧的分配 ...

  2. linux内核内存分配(三、虚拟内存管理)

    在分析虚拟内存管理前要先看下linux内核内存的具体分配我開始就是困在这个地方.对内核内存的分类不是非常清晰.我摘录当中的一段: 内核内存地址 ============================ ...

  3. Linux的虚拟内存管理-如何分配和释放内存,以提高服务器在高并发情况下的性能,从而降低了系统的负载

    Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...

  4. 十问 Linux 虚拟内存管理 (glibc) (二)

    版权声明:本文由陈福荣原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/184 来源:腾云阁 https://www.qclo ...

  5. Linux虚拟内存管理(glibc)

    转:https://blog.csdn.net/tengxy_cloud/article/details/53067396 https://www.cnblogs.com/purpleraintear ...

  6. 《Tsinghua oc mooc》第8~10讲 虚拟内存管理

    资源 OS2018Spring课程资料首页 uCore OS在线实验指导书 ucore实验基准源代码 MOOC OS习题集 OS课堂练习 Piazza问答平台 暂时无法注册 第八讲 虚拟内存概念 为什 ...

  7. ucore操作系统学习(三) ucore lab3虚拟内存管理分析

    1. ucore lab3介绍 虚拟内存介绍 在目前的硬件体系结构中,程序要想在计算机中运行,必须先加载至物理主存中.在支持多道程序运行的系统上,我们想要让包括操作系统内核在内的各种程序能并发的执行, ...

  8. 一步一图带你深入理解 Linux 虚拟内存管理

    写在本文开始之前.... 从本文开始我们就正式开启了 Linux 内核内存管理子系统源码解析系列,笔者还是会秉承之前系列文章的风格,采用一步一图的方式先是详细介绍相关原理,在保证大家清晰理解原理的基础 ...

  9. SoC嵌入式软件架构设计II:没有MMU的CPU虚拟内存管理的设计和实现方法

    大多数的程序代码是必要的时,它可以被加载到内存中运行.手术后,可直接丢弃或覆盖其它代码. 我们PC然在同一时间大量的应用,地址空间差点儿能够整个线性地址空间(除了部分留给操作系统或者预留它用).能够觉 ...

随机推荐

  1. 2019.01.19 codeforces893F.Subtree Minimum Query(线段树合并)

    传送门 线段树合并菜题. 题意简述:给一棵带点权的有根树,多次询问某个点ppp子树内距离ppp不超过kkk的点的点权最小值,强制在线. 思路: 当然可以用dfsdfsdfs序+主席树水过去. 然而线段 ...

  2. MySQL批量修改表前缀

    error_reporting(0); $old_pre = 'tdr_'; // 原表前缀 $new_pre = 'db_'; // 新表前缀 // 配置连接 $db = new mysqli('1 ...

  3. 2014年的最后一个程序,却成为了2015年的第一个bug

    这个点不睡觉的程序员,要么就是在努力学技术,要么就是代码出bug了.而我,是后者.呵呵,2015了,觉还是要睡的

  4. 安卓hid驱动触摸屏

    在kernel/drivers/hid/ 目录下三个文件中添加usbtouch的pid vid, 文件分别是hid-multitouch.c .hid-ids.h.hid-core.c 具体如何添加可 ...

  5. idea配置github

    一.事先准备 1.安装Git Git下载: http://git-scm.com/downloads 最新版本是2.1.2 git登陆地址:https://github.com/ 2.注册GitHub ...

  6. 【慕课网实战】Spark Streaming实时流处理项目实战笔记四之铭文升级版

    铭文一级: 需求二:Agent选型:exec source + memory channel + logger sink# Name the components on this agenta1.so ...

  7. 笔记:CSS hack的学习与了解…

    更新时间:2015.05.12 兼容范围: IE:6.0+,FireFox:2.0+,Opera 10.0+,Sarari 3.0+,Chrome 参考资料: 各游览器常用兼容标记一览表: 标记  I ...

  8. linux grep进阶

    1.搜寻特定字符串 搜寻特定字符串很简单吧?假设我们要从刚刚的文件当中取得 the 这个特定字符串,最简单的方式就是这样: 那如果想要『反向选择』呢?也就是说,当该行没有 'the' 这个字符串时才显 ...

  9. 【python-ini】python读写ini文件

    [python-ini]python读写ini文件 本文实例讲述了Python读写ini文件的方法.分享给大家供大家参考.具体如下: 比如有一个文件update.ini,里面有这些内容:   1 2 ...

  10. python处理excel之读:xlrd模块

    # -*- coding:utf-8 -*- import xlrd path = r'D:/工作簿1(已自动还原).xlsx' # 打开excel文件读取数据 data = xlrd.open_wo ...