2021-05-14 现代操作系统 《现代操作系统 第4版》第3章 内存管理——总结上

  • 计算机的存储体系 :内存是计算机很重要的一个资源,因为程序只有被加载到内存中才可以运行;此外,CPU所需要的指令与数据也都是来自内存的。可以说,内存是影响计算机性能的一个很重要的因素。

  • 操作系统内存管理:总的来说,操作系统内存管理包括物理内存管理虚拟内存管理
    1、物理内存管理:包括程序装入等概念、交换技术、连续分配管理方式和非连续分配管理方式(分页、分段、段页式)。
    2、虚拟内存管理虚拟内存管理包括虚拟内存概念请求分页管理方式、页面置换算法页面分配策略工作集和抖动

  • 现代操作系统使用分层存储器体系
    操作系统中管理分层存储器体系的部分称为存储管理器(memory manager),即记录那些内存是正在使用的,那些内存是空闲的;在进程需要时为其分配内存,在进程是用完后释放内存。
    1、高速缓存(cache):若干兆(MB)、快速、昂贵、且易失性、的高速缓存。
    问:为什么要有缓存呢?
    答:内存小,但是交换速率比内存快很多,所以它的出现是为了解决CPU处理速度与内存读写速度不匹配的矛盾。因为CPU的运算速度比在内存的读写速度快很多,CPU要花费很长的时间等待从内存读到的数据以及向内存写入数据。
    2、(主)内存(RAM):数千兆(GB)、速度与价格适中、且同样易失性、的内存。
    在这里插入图片描述

    3、磁盘:几兆兆(TB)、低俗、廉价、非易失性的磁盘。

  • 无存储抽象
    早期计算机没有存储器抽象,每个程序都直接访问物理内存。内存的管理也非常简单,除去操作系统所用的内存(操作系统存放在内存中) 之外,全部给用户程序使用。 想要在内存中同时运行两个程序是很困难的(容易同时访问一个内存地址)。
    1、操作系统在RAM中(大型机)
    2、操作系统在ROM中(笔记本,嵌入式)
    3、操作系统在RAM中,位于ROM中的设备驱动程序为BIOS(基本输入输出系统)
    在这里插入图片描述

  • 问:在不适用存储器抽象的情况下是否可以运行多个程序??
    可以
    再问为什么
    操作系统只需要把当前内存中所有内容保存到磁盘文件中(交换方式),然后把要运行的程序读入到内存中运行即可(只要在某一个时间内存中也只有一个程序,那么就不会发生冲突)。
    再问:还有别的方式吗?
    答:有。 通过硬件,比如(IBM360):内存划分为内存块(2KB),每个块分配一个“4位的保护键”,存在CPU的特殊寄存器中。每个进程都拥有程序状态字寄存器,其中存放着一个4位码,当进程需要访问相应内存块时,需要让其内存块的保护键和PSW中的4位码有联系才可。(这样就防止了进程间,进程和OS间的互相干扰)。但是缺点就是:虽然两个程序(进程)不会相互影响,但是当他们同时进入内存,他们各自的指令可能会互相调用,而这就是因为他们都用了绝对物理地址,这时我们需要一套私有的本地地址进行内存寻址
    再问:什么叫私有的本地地址进行内存寻址
    :通过静态重定位:就是把当前程序(进程)的第一条指令的直接物理地前加上当前在内存中的地址,依此类推,但是会减装载速度,并不是很好。
    再加一句:计算世界的发展总是重复历史,虽然现在直接引用物理地址很少见,但是在嵌入式和智能卡中也是很常见的。

  • 无内存抽象带来的问题
    1、用户程序可以访问任意内存,容易破坏操作系统,造成崩溃(可以使用特殊的硬件进行保护比如IBM360)
    2、同时运行多个程序特别困难
    改进:随着计算机技术发展,要求操作系统支持多进程的需求,所谓多进程,并不需要同时运行这些进程,只要它们都处于 ready 状态,操作系统快速地在它们之间切换,就能达到同时运行的假象。每个进程都需要内存,Context Switch 时,之前内存里的内容怎么办?简单粗暴的方式就是先 dump 到磁盘上,然后再从磁盘上 restore 之前 dump 的内容(如果有的话),但效果并不好,太慢了!

  • 一种内存抽象:地址空间
    问: 为什么要出现这种内存抽象
    :因为在无内存抽象中,当多个程序同时处于内存中并互不影响,有两个敌人:保护和重定位。通过硬件保护(IBM360)可以解决第一个敌人“保护”,但是无法解决重定位(静态重定位大大降低了效率)!-------------这时!“地址空间”横空出世!
    再问那什么是地址空间呢
    一种新的存储器抽象(被面试官打死)!嗯!想了想:我可不想被挂,那应该是什么呢?毕竟是存储器抽象,当然是为了寻址,那么就是----一个进程可用于寻址内存的一套地址集合。类比于:进程—创造了一类抽象的CPU以运行程序
    再问:给我举几个地址空间的应用
    :比如:端口,IP地址,域名(eg:.com),电话号码,身份证号码等!
    再问通过什么方法,可以让每个程序有不同的物理地址
    答:动态重定位,通过给每个CPU配置两个特殊的硬件寄存器,基址寄存器和界限寄存器:当要运行的程序(进程)装载到内存的时候,无法重定位。是因为当一个程序运行时,程序的每个起始物理地址装载到基址寄存器中,程序的长度装载到界限寄存器中比如当进程访问内存时,需要相应的内存地址,都会自动先加上基址寄存器的内容,然后通过界限寄存器看是否超过这个程序的长度
    问:那基址寄存器和界限寄存器有什么缺点么
    答:每次访问内存都需要进行加法和比较运算

  • 交换技术
    交换技术的由来
    :因为通过使用特殊硬件寄存器“基址寄存器”,“界限寄存器”,把所有的进程都放入到内存(RAM)中是不切实际的,因为所有的进程要远远大于内存的容量。
    那交换技术是什么?
    :简单来说,就是用不到这个进程的时候存到磁盘,运行的时候完整调入到内存。
    那交换技术有什么问题呢?
    :交换技术在内存中会产生很多的空闲区,我们可以通过将内存中的所有进程往下移,不过这样的话要耗费大量的CPU时间,因为要使用基址寄存器和界限寄存器重新定址。
    那如果在这种情况下类似于java,需要动态分配内存怎么办?
    :首先如果相邻区为空闲区,我们可以把该空闲区分配给这个进程,如果不相邻,我们可以把这个进程移动到更大的空闲区或者把别的进程交换回磁盘,不过这样并不好。
    那么怎么进一步改善呢???
    :当移入或移动进程 时,我们给它分配一些额外的内存,但我们把它换算出去时,不要把额外的内存换算出去。
    在这里插入图片描述

  • 问:以上都算动态分配内存,那OS怎么进行管理呢?
    答:通过两个方法跟踪内存使用情况,分别是位图和空间区链表。(后者在后面讲解)
    问:那位图是什么?
    答:位图就是我们把内存划分成大小不等的分配单元,然后每个分配单元的使用状况分别用0,1表示,然后放到对应的位图中。但是缺点是:当我们通过位图的长0串查找一块内存区时,可能会造成跨越字的边界。
    在这里插入图片描述
    :那空间区链表是什么?
    答:通过位图我们知道了分配单元,然后在空闲区链表中的每个节点,我们可以存放一个进程也可以两个进程间的一块空闲区(看上图)。通过链表我们可以知道其删插操作的复杂度很低,因此可以一定程度上解决了内存碎片问题

  • 虚拟内存
    虚拟内存的由来
    :前面的抽象虽然满足了一定的多进程的要求,比如交换技术,但是它需要进程运行的时候需要完整调入到内存,有时候这也是一种缺陷,那么我们进一步改进,这就是覆盖技术。
    那交换技术的进一步改进是什么
    :将一个程序分为多个块,基本思想是先将块0加入内存,块0执行完后,将块1加入内存。依次往复,这个解决方案最大的问题是需要程序员去程序进行分块,这是一个费时费力让人痛苦不堪的过程。后来这个解决方案的修正版就是虚拟内存
    :那你说说虚拟内存的思想:
    每个进程有独立的地址空间,内存被分为大小相等的多个块,称为(Page).每个页都是一段连续的地址。这些页被映射到物理地址内存,但不是所有的页都必须在内存中才能运行程序。当程序引用到一部分 不在物理内存中的地址空间时,由硬件立即执行必要的映射。当映射到一部分不在物理内存中的地址空间时,由OS负责将缺失的部分装入物理内存再次执行刚才的指令。
    :我还是没懂虚拟内存到底用来干嘛的?
    答:虚拟内存是将一个进程独立的整个地址空间用相对较小的单元映射到物理内存(也就是进程的一部分映射到物理内存中)

  • 那下面我们就讲一讲怎么通过虚拟内存进行离散分配吧!!!!!!

  • 问: 虚拟地址怎么用,MMU是做什么的?
    由程序生成的地址称为虚拟地址,它们构成了一个虚拟地址空间。在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上;但是在使用虚拟地址的情况下,虚拟地址不是直接送到内存总线上,而是被送到内存管理单元(MMU),MMU把虚拟地址映射为物理内存地址

  • 分页
    问:虚拟地址空间怎么划分的?页面是什么?页框是什么?
    答:虚拟地址空间按照固定大小划分为页面,在物理内存中映射的单元称为页框。业面和页框的大小通常都是一样得,但实际中可以是512字节到1GB,也就是4MB~1GB。业面是RAM和磁盘之间的交换总是以整个页面为单元进行的。
    在这里插入图片描述

    问:什么时候用大一点的页面?什么时候用小一点的业面呢?
    答:---------------------------------------容我想想。
    问:为什么页面的大小都是2的整数次幂?
    答: 因为我们需要用MMU中的页表来进行映射,将虚拟页面映射到物理地址(页框)中,我们需要的都是二进制映射,所以是2的整数次幂。
    问:虽然通过MMU,可以把虚拟页面映射到物理内存的页框中,但是这并没有解决虚拟地址比物理地址大的问题,如果部分虚拟页面没有被映射到物理内存中,这时候程序调用,怎么办呢?
    :当MMU注意到该页面没有被映射,于是CPU陷入到OS,这个陷阱称为“缺页中断”或“缺页错误”。OS会找到一个很少使用的页框把它的内容写入磁盘(如果它不在磁盘),随后把这个要访问的页面读到刚才的页框中,在MMU中修改映射关系,重新重新启动引起陷阱的指令。
    问:具体MMU怎么映射的呢?
    :看图!起始地址+偏移量!
    在这里插入图片描述

  • 页表
    问:页表在哪里?干什么用的?能否简介表达一下虚拟内存?
    **答:**虚拟地址==虚拟页号(高位部分)+偏移量(低位部分)。 页表在MMU中,MMU一般在CPU中,是为了通过页表将虚拟内存映射成物理内存。一般我们规定虚拟页号作为页表的索引,然后找出相应的页框号,也就是说在虚拟内存到物理内存,我们只映射的是页面号—>页框号,偏移量不变(如下图) 也就是说虚拟地址比物理地址多一位
    在这里插入图片描述
    问:那你说一说页表项的结构
    答:在这里插入图片描述

    • 页框号:物理内存的基本单位的索引号
    • 在/ 不在位”:表示虚拟内存中页面是否映射到了物理地址的页框,如果没有则访问会产生缺页中断。
    • 保护位:指出一个页允许访问什么类型,一般有三位,读,写,执行。
    • 修改位:写入时由硬件自动设置修改位,有利于OS重新分配页框是非常有用的。如果页面被修改过,也就是修改位有值(变"脏"),那么需要把它写回磁盘。
    • 访问位:有利于发生缺页中断时,选择要被淘汰的页面。
    • 高速缓存禁止位:当把虚拟内存映射到寄存器中,而不是物理内存,这时就不需要将值先经过缓存,而是直接读取数据(让我想到了volatile)。
  • 问:再说一遍虚拟内存到底干什么的?
    沉思了一下。虚拟内存就是将虚拟地址空间分解成页,并且将每一个页映射到物理内存的某个页框或(暂时)解除映射。

  • 问: 既然你知道了虚拟内存,以及作用,那你说说我们在运用虚拟内存映射的时候需要注意什么的?
    :我想,应该注意的是,虚拟地址映射到物理地址的时候要非常快并且如果虚拟地址空间很大,页表也会很大
    再问那怎么做到映射快呢?
    :我们加速分页过程就好了,但是怎么加呢?想了想,因为页表我们通常是MMU中,副本一般放在主存中。当启动一个进程时,OS把保存在内存中得进程页表的副本载入到寄存器中(如上图,放在MMU中)。优点:简单且在映射过程中不需要访问内存。缺点:页表很大的时候,代价很大,并且每次上下文切换(上下文切换就是不同线程的调度,也可以是任务的调度)的时候,都需要装载整个页表。(我自己想法:就是每个进程相应的页表都在主存中,当切换进程时,我们把这个内存中页表的副本考入到CPU中的寄存器MMU中就好了,下面的方法是不考入寄存器中,直接在内存中映射。)
    再问:还有别的方法加速分页过程吗?(一般用上面那种)
    :将整个页表都保存在内存中。不过我们需要一个指向页表起始位置的寄存器。这种设计在每次上下文切换时,只需要装载一个寄存器,但缺点就是:在执行每条指令时,都需要一次或者多次进行内存访问来完成页表项的读入,速度慢。

  • 这时候我们再想,这样加速分页过程真的符合实际么?
    大多数优化技术都是在内存中的页表开始的,比如一条1字节指令要把一个寄存器中的数据复制到另一个寄存器中,在不分页的情况下,这条指令只访问一次内存,也就是从内存中取出这条指令。但有了分页机制后,我们要多次访问内存。并且人们发现,人们总是多次访问少量的页面而不是所有的页面,因此我们设置了一种新的解决方法。
    这种帮助分页过程解决上下文切换的速度慢是通过什么?(TLB!!!!)
    :我们将引起分页慢的原因找到,也就是引用"页表"。常用的页面,我们将虚拟地址直接映射到物理地址,不用再访问页表,这时候我们就需要一种新的硬件设备“TLB(快表)” 。它通常也在MMU中,包含少量的表项,一般不会超过256个。
    TLB中包含什么?
    答:每个表项记录一个页面的相关信息,包括虚拟页号,该页对应的页框号,页面的修改位,保护码(读/写/执行权限)(除了虚拟页号都一一对应)。
    在这里插入图片描述
    问:TLB是如何工作的?
    :将一个虚拟地址放入到MMU中进行转换时,将虚拟页号与TLB中所有表项同时进行(并行)匹配,判断虚拟页面是否在其中,如果发现了就不必再去页表中查找,如果不在TLB中,则再去页表中查找(不一定)。然后在TLB中淘汰一个表项(当这个表项被清除出TLB时,我们要把其中的修改位复制到内存中的相应页表项中),然后把这个新找到的页表项(这样我们下次用这个表项,直接在TLB中查找就行了)代替它(当这个页表项装入到TLB中,我们要在内存中来取相应的值)。

  • 问:TLB失效后,一定要去页表中查找么?
    :现在几乎所有的页面管理都是在软件中实现,也就是说当TLB失效后,我们生成一个TGB失效并将问题交给操作系统解决。当OS找到这个页面,然后从TLB中删除一个项,接着装载一个新的项,然后再执行先前出错的指令。(这一切几条指令就能执行)
    再问:软件TLB怎么管理?
    答: 当TLB很大时(64表项)可减少失效率,TLB的软件管理变得非常有效。这种方法的最主要的好处是获得了一个非常简单的MMU,这在CPU芯片上为高速缓存以及其他改善性能的设计腾出了相当大的空间。
    再问:如何优化软件TLB管理机制呢?
    答:我们想减小TLB失效的同时,又要在发生TLB失效时减少处理开销。OS有时候能”预先“知道哪些页面可能下一步被用到,所以会提前装载到TLB中。
    再问:如何应对”TLB失效“呢?
    答: 无论是用硬件还是软件处理“TLB失效”,目的都是找到页表并执行索引操作以定位要访问的页面
    软件做这样的搜索造成的问题:页表可能不在TLB中(说明MMU中有TLB,TLB中放页表,比如TLB在内存中),这样会导致额外的TLB失效(因为可能TLB还要映射到页表中),不过我们可以通过在内存中的固定位置来维护一个大的TLB表项的软件高速缓存(该页面一直保存在TLB中)(来方便映射页表?),一般4KB,通过首先检查软件高速缓存,操作系统能实质性的减少TLB失效。
    再问:“TLB失效”后有什么区别吗?
    答: 软失效和硬失效软失效: 页面不在TLB中,但是在内存中,这时候更新一下T LB就好,花费几纳秒完成操作。硬失效: 就是页面本身不在TLB中,也不在内存中,这时候需要一次磁盘存储,大约需要几毫秒。在页表结构中查找相应的映射称为页表映射
    再问:实际中只有这两种TLB失效么?
    答: 实际中当然不可能只有这两种T LB失效。比如多线程多进程中,当我们要查找的页面在TLB和页表中都没有,说明内存中没有,但这时候别的进程把这个页面已经从磁盘中调回来了,这就是"次要缺页错误"。而如果内存的确没有,也就是严重缺页错误==硬失效。而最后一种属于程序错误,程序可能访问了一个非法地址,不需要向TLB中添加映射。这时候OS会通过“段错误”来终止程序。除了最后一种,其他缺页情况都会被硬件或OS以降低性能为代价而自我修复

  • 针对大内存的页表:
    问:大内存的页表的作用?
    答:TLB只是加快了虚拟地址到物理地址的转换。但是怎么处理巨大的虚拟地址空间呢,这时候就引入了多级页表,倒排页表!
    问:多级页表是什么?
    答: 多级页表就是把虚拟地址分解,我们可以把其看成一层一层的页目录,最底层就是最开始间说到的页表,每个页面以4KB为单位,这样就可以不用把全部页表一直保存在内存中(因为每个进程都有相应的虚拟内存,也就是相应的虚拟地址,都有自己的页表,都存放在内存中,但是如果虚拟地址太大,不可能把页表一直保存在页表中,我们可以通过顶级页表一级一级往下存)。我们通过一个32位的虚拟地址来进行分析。在这里插入图片描述
    再问:有没有别的方法处理大的“虚拟空间”?
    答: 倒排页表! 实际内存中的每个页框对应一个表项,而不是每个虚拟页面对应一个表项。 这样就解决了大量的空间,尤其是虚拟地址空间比物理内存大的多的时候这样的,但也有不足的地方,就是虚拟地址到物理地址的变化会变得很困难。
    这样我们在查找物理地址的时候,我们需要反向查找虚拟地址,也就是必须对每一次内存访问操作,都要执行一次倒排页表
    再问:对于倒排页表,有什么合适的改进方法吗?它又适用于哪里呢?
    答: 它更适用于64位机器中,即使使用了大页面,页表项的数量还是很强大的。对于改进方法,我们可以通过TLB来实现,让TLB记录所有频繁使用的页面,这样常用的虚拟地址就转换成内存地址非常快了。但是当TLB失效的时候,我们依旧需要用软件搜索整个倒排页表。我们可以通过一个散列表来进行搜索,用虚拟地址散列。当前所有内存中具有相同散列值的虚拟页面被连接在一起。当TLB失效,我们通过散列表查找虚拟地址对应的页框号(也就是实际内存地址),然后添加到TLB中。
    在这里插入图片描述

  • 页面置换算法:
    问: 在前面,我们考虑了发生“缺页中断”时,比如虚拟地址没有映射到实际内存地址,这时候内存中没有相应的内存,我们需要从磁盘中调入,但是内存满了,所以这时候我们应该将内存中的一个页面置换出内存(也就是说,进程运行时,若其访问的页面不在内存而需将其调入,但内存已无空闲空间时,就需要从内存中调出一页程序或数据,送入磁盘的对换区)。这时候我们要考虑两个问题:
    1、换取哪个页面呢?按理说我们应该置换不常使用的页面
    2、置换的时候是直接覆盖这个页面呢,还是先把这个页面回写到磁盘呢?这时候我们要看“页面”的修改位,如果修改了,也就是“变脏”了,那这时候我们要先写回磁盘。
    那请问怎么知道这个页面置换算法好不好呢?
    答: 如果有较低的页面更换频率,这就算好。
    再问:那请问有什么页面置换算法呢?
    答:

    • 最佳(Optimal, OPT)页面置换算法:所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的页面更换频率。但由于人们目前无法预知进程在内存下的若千页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现

    • 最近未使用(NRU)(Not Recently Used)页面置换算法: 系统为每一个页面设置2个状态位,当页面被访问(读或写)时设置R位,当页面被写入(即修改)时设置M位。这些位包含在每个页表项中。每次访问内存都更新这些位,因此由硬件来设计它们。不过一旦设置某位为1,它就保持1直到OS将其复位。比如我们通过两个“R”和“M"位来构造 1个简单的页面置换算法:当启动一个进程,所有页面的两个位都由OS设置成0,R被(时钟中断)定期清0,以区别最近有没有被 访问的页面。不清楚M位,是为了决定是否需要写回磁盘。NRU的思想:淘汰一个没有被访问的已修改页面要比淘汰一个频繁使用的“干净”页面好(因为如果频繁使用,那么页面更换频率较高)

    • 先进先出(FIFO)页面置换算法:(相当于队列) 优先淘汰最早进入内存的页面,也就是在内存中存储时间最久的页面。该算法实现简单,只需把调入内存的页面根据先后次序链接成队列,设置一个指针总指向最早的页面。但该算法与进程实际运行时的规律不适应,因为在进程中,有的页面经常被访问 (不实用)。

    • 第二次机会置换算法(FIFO页面置换算法的改进): ,因为最早进入内存的页面,可能被经常访问,这时候如果置换出去是不明智的,这时候我们可以检查下它的“R”位**,如果该页面被访问过,也就是R1,那么我们将它清零,也就是R0,并且把页面放到链表(队列)的尾端,然后继续搜索。

    • 时钟页面置换算法:(第二次机会置换算法的改进):我们每次把最早进入内存的页面,并且被访问的,修改R==0,移动到链表最后,虽然这样有效,但是降低了效率,并且不是必要的,这时候我们需要把所有的页面都保存在一个类似钟面的环形链表中(这就相当于一个时钟)在这里插入图片描述

    • 最近最少使用页面置换算法(LRU,Least Recently Used)选择最近最长时间未访问过的页面予以淘汰,它认为过去一段时间内未访问过的页面,在最近的将来可能也不会被访问。该算法为每个页面设置一个访问字段,来记录页面自上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰(虽然可以实现,但是代价太高,因为需要在内存中维护一个所有页面的链表。最近最多使用的在表头,最近最少使用的在表尾每次访问内存都必须更新整个链表,并且每次排列链表也是很费时的)。

    • 问:那怎么解决这个问题呢?

    • 答:
      ①、 我们使用特殊的硬件实现LRU,要求硬件有一个64位计数器C,它在每条指令执行完成后自动加1,每个页表项有一个容纳这个计数器值的域,每次访问后,将当前的C值保存到被访问页面的页表项中。一旦缺页中断,OS将检查所有页表项中得计数器的值,找到值最小的一个页面,然后就是最近最少使用的页面。然后还有别的计数方法。
      ②、如图
      在这里插入图片描述
      ③、用 软件模拟LRU(NFU): 前面两种LRU算法虽然在理论上可以实现,但并不是所有计算机都有这种硬件。要使用软件实现,一种可能的方法称为最不常用算法(NFU-Not Frequently Used)(NFU) 该算法将每个页面与一个软件计数器相关联,计数器初值为0.每次时钟中断时,由操作系统扫描内存中所有的页面,将每个页面的R位(访问位,它的值是0或1)加到它的计数器上。这个计数器大体上跟踪了各个页面被访问的频繁程度。发生缺页中断时,则置换计数器值最小的页面。
      缺点:NFU不会忘记任何事情,比如一个页面可能开始被频繁使用,计数器值很高。但是过一段时间后,它不在经常被访问了,但是被经常访问的其他页面计数器总是比他小,结果是将有用的页面置换走了。所以,我们需要对此进行改进,修改以后的算法称为**老化算法。**首先,在R位被加进之前先将计数器右移一位(类似于权重问题);其次,将R位加到计数器最左端而不是最右端。 发生缺页中断时,将置换计数器值最小的页面。
      老化算法的缺点,老化算法的计数器只有有限位数,因此限制了其对过往页面的记录。如果两个页面的计数器都是0,只能随机挑选一个进行置换,但实际上,可能其中一个被访问过,只不过时间超出了位数。例如,计数器只能记录8位,但是页面1在9个时钟滴答前被访问过,页面2在1000个时钟滴答前被访问过,可是他们的计数器都是0。

  • 工作集页面置换算法:(就是在一个进程需要的所有页面内进行置换)
    问:请问什么是工作集?什么是局部性行为?什么叫颠簸?
    答: 在单纯的分页系统里,刚启动进程时,在内存中并没有页面。在CPU试图读取第一条指令时就会产生一次缺页中断,使操作系统装入含有第一条指令的页面。其他由访问全局数据和堆栈引起的缺页中断通常会紧接着发生。一段时间后,进程需要的大部分页面都已经在内存了,进程开始在较少缺页中断的情况下运行。这个策略称为请求调页,因为页面是在需要时被调入的, 而不是预先装入。大部分进程在工作时表现出了一种局部性访问行为,即在进程运行的任何阶段,它都只是访问较少的一部分页面。而进程当前正在使用的页面的集合被称作它的工作集。 显而易见,如果整个工作集都被装入到了内存中,那么进程在运行到下一运行阶段之前,不会产生很多缺页中断。但若内存太小而无法容纳整个工作集,那么进程在运行过程中就会产生大量的缺页中断。若每执行几条指令程序就发生一次缺页中断,就称这个程序发生了颠簸
    问:什么是工作集模型?为什么要有工作集模型?
    答: 通俗来说,就是在多进程中,我们每运行一个程序,都需要从磁盘调入到内存,运行完再调回磁盘。每次调入内存,都要重新处理“缺页中断”,浪费时间。因此!!不少分页系统设法去跟踪进程工作集,以确保让进程运行以前它的工作集就在内存中,这就是工作集模型,目的就是为了减少缺页中断频率。这就叫做“预先调页”。
    (说白了,工作集模型 == 把工作集预先调入到内存中
    问:那你可以“定量”的说说什么是工作集模型么?
    答: 我们用w(k,t),来表示工作集,意思就是任意时刻t,最近所有k次所访问的页面。
    在这里插入图片描述

    问:那怎么如何实现工作集模型呢?
    答: OS必须跟踪哪些页面在工作集中,以此确定工作集模型,然后根据工作集模型推出信息置换算法。
    问: 通过w(k,t)我们可以发现,通过确定k的值,页面集合也就是工作集就被确定了。那你能告诉我怎么确定工作集么?
    答: 向后找最近k次的内存访问(没有被使用过)。设想有一个长度为k次的移位寄存器,每进行一次内存访问就左移一位然后再最右端插入刚才所访问过的页面号移位寄存器中的k个页面号的集合就是工作集。理论上,当缺页中断发生时,只要读出移位寄存器中的内容,并排序删除重复页面后,结果就是工作集。然而,维护寄存器并在缺页中断时处理它所需要的开销很大,因此该技术没有被使用过。
    问:这种技术不被使用,那用什么技术确定工作集呢?
    答:既然不能考虑k,那我们可以考虑t呀,我们定义在过去t秒实际运行时间中它所访问的页面的集合。

  • 工作集页面置换算法:
    基本思路是找出一个不在工作集中的页面并淘汰它。在页表中,每个表项至少包含了两条信息:上次使用该页面的近似时间R(访问)位。其他的域如页框号、保护位、M(修改)位在该算法中不需要,被忽略(用空白域表示)。在这里插入图片描述
    假设使用硬件来设置R(访问)位和M(修改)位。同样,假设在每个时钟滴答中,有一个定期的时钟中断会用软件的方法来清除R位。每当缺页中断发生时,扫描页表以找出一个合适的页面淘汰之。
    在处理每个表项时,需要检查R位
    R==1,就把当前的实际时间替换上次使用时间。
    R==0,说明没被访问,并且生存时间(上次被访加粗样式问到现在的生存时间)>┏,移出这个页面。
    R==0且生存时间<=┏,则需要记住当前时间,作为置换的备选。
    注意:存在一种情况,所有页面的R位都为1,即都在工作集中,这时需要随机选择一个页面淘汰。最好选择一个没有被修改过的干净页面,因为这样可以直接被淘汰,而不需要将该页面在写回磁盘,可以节省时间。

  • 工作集时钟页面置换算法(类似于时钟页面置换算法):
    工作集页面置换算法的缺点:当缺页中断发生后,需要扫描整个页表才能确定被淘汰的页面,所以基本工作集算法比较费时。有一种改进算法,基于时钟算法,并且使用了工作集信息,称为WSClock(工作集时钟)算法
    最初,该表是空的当装入第一个页面时,把它加到该表中。随着更多的页面的加入,它们形成一个环。每个表项包含来自基本工作集算法的上次使用时间,以及R(访问)位和M位(修改位,图中由每个表项上面的空白域表示)。与时钟页面置换算法一样,每次缺页中断时,首先检查指针指向的页面。如果R被置位1,该页面在当前工作集中,不该被淘汰。然后把该页面R置为0,指针指向下一个页面,重复该算法。当指针指向的页面R=0时,同样是比较生存时间与t。若生存时间较大,并且页面是干净的,就淘汰该页面,并把新页面放在其中。如果该页面被修改过,不能立即申请页框,为了避免写磁盘操作引起的进程切换,指针继续向前走,找到一个旧的并干净的页面。存在一种可能,所有的页面因为磁盘I/O在某个时钟周期被调度。为了降低磁盘阻塞,所以需要设置一个值n,目的是限制最大只允许写回n个页面。一旦到达该限制,不允许调度新的写操作
    如果指针经过一圈并返回起始点,存在两种情况:
    1、至少调度了一次写操作
    对于这种情况,指针仅仅是不停地移动,寻找一个干净页面。既然已经调度了一个或多个写操作,最终一定会有某个写操作完成,并且它的页面会被标记为干净。置换遇到的第一个干净页面,这个页面不一定是第一个被调度写操作的页面。因为磁盘驱动程序可能把写操作重排序。
    2、没有调度过写操作
    对于这种情况,所有页面都在工作集中,否则将至少调度一个写操作。简单的方法是随便选择一个干净页面淘汰,所以在上一步的扫描过程中,需要记录所有干净页面的位置。如果不存在干净页面,那么就把当前页面写回磁盘。

  • 算法总结在这里插入图片描述

    • FIFO算法:是通过维护链表来记录它们装入内存的顺序。淘汰最老的页面,虽然该页面仍在使用,但并不是一个好选择。
    • 第二次机会算法:是对FIFO的改进,它在移出页面前先检查该页面是否正在被使用,如果没有被使用则放到链表尾部,重新计算
    • 时钟算法:是第二次机会算法的改进,不需要放到链表尾部,直接形成环就好了。FIFO->第二次机会算法->时钟算法
    • LRU:很优秀的算法,但是一般需要硬件实现。
    • NFU:LRU+通过计算器实现,但是计算器虽然能统计访问的次数,但是不能说明在什么时间访问的,如果是很早之前就不好了。
    • 老化算法:可以更有效的实现LRU,我认为更是NFU的改进,也是计算访问量,只不过通过右移的方法对不同时间的访问量加了权值,越靠近现在的,权值越大。 LRU->NFU->老化算法
    • 最后两种都使用了工作集:工作集算法有合理的性能,但它的实现开销较大,工作集时钟算法是它的变体,不仅具有良好的性能,还能更好实现。
    • 总结:老化算法、工作集时钟算法最重要,性能最好!
  • 与分页有关实现问题
    1、 分页相关工作的时间段:进程创建时,调度进程执行时,缺页中断时和程序终止时。
    问:进程创建时需要做什么?
    答:当进程创建的时候,OS需要根据程序和数据的大小创建页表,然后在内存中为页表分配空间并初始化,只有运行进程的时候,才要求页表在内存中。在磁盘交换空间分配空间,因为当进程不用需要置换出时,需要磁盘有放置此进程的空间,然后程序和数据还要对磁盘交换区初始化,这样发生缺页中断,可以调入回去。因为进程都有进程表,所以页表和磁盘交换区的信息存储在进程表中。
    问:调度进程执行时需要做什么?
    答: 重置MMU,刷新TLB(说明TLB在MMU中,而页表不一定,大部分在内存)。
    问:缺页中断时需要做什么?
    答:读硬件寄存器->哪个虚拟地址造成了缺页中断->是哪个页面->磁盘上定位该页面->在内存中找合适的页框,并置换老的页面,然后把所需的页面读入页框->回退到程序计数器使其能重新执行发生缺页中断的指令
    问:进程退出时需要做什么?
    答:OS必须释放页表,页面和页面在硬盘上所占用的空间,当然要注意共享页面,是要等所有进程都不需要了,才能释放。

  • 缺页中断处理:
    问:麻烦说一说缺页中断发生的细节以及时间顺序
    答:
    1、(保存信息) 硬件陷入内核,在堆栈中保存程序计数器(为了回退并重新执行引发缺页中断的指令)。大多数机器将当前指令的各种状态信息保存在特殊的CPU寄存器中。
    2、(进一步保存信息)启动一个汇编代码例程保存通用寄存器和其他易失的信息,以免被操作系统破坏。这个例程将操作系统作为一个函数来调用。
    3、(找引发缺页中断的页面)当操作系统发现一个缺页中断时,尝试发现需要哪个虚拟页面。通常一个硬件寄存器(读硬件寄存器)包含了这一信息,如果没有的话,操作系统必须检索程序计数器,取出这条指令,用软件分析这条指令,看看它在缺页中断时正在做什么。
    4、(检查这个虚拟地址是否有效) 一旦知道了发生缺页中断的虚拟地址,操作系统检查这个地址是否有效,并检查存取与保护是否一致。如果不一致,向进程发出一个信号或杀掉该进程。如果地址有效且没有保护错误发生,系统则检查是否有空闲页框。如果没有空闲页框,执行页面置换算法寻找一个页面来淘汰。
    5、 (置换老的”脏“的页框时的措施) 如果选择的页框“脏”了,安排该页写回磁盘,并发生一次上下文切换,挂起产生缺页中断的进程,让其他进程运行直至磁盘传输结束。无论如何,该页框被标记为忙,以免因为其他原因而被其他进程占用。
    6、(置换老的”脏“的页框刷新到磁盘之后) 一旦页框“干净”后(无论是立刻还是在写回磁盘后),操作系统查找所需页面在磁盘上的地址,通过磁盘操作将其装入。该页面被装入后,产生缺页中断的进程仍然被挂起,并且如果有其他可运行的用户进程,则选择另一个用户进程运行。
    7、 (更改页面映射关系) 当磁盘中断发生时,表明该页已经被装入,页表已经更新可以反映它的位置,页框也被标记为正常状态。
    8、(回到第一步的前半部分,回退,重新执行引发缺页中断的指令) 恢复发生缺页中断指令以前的状态,程序计数器重新指向这条指令。
    9、(因为置换脏的页框,上下文切换,别的进程在运行,所以这时候需要返回引发缺页中断的进程) 调度引发缺页中断的进程,操作系统返回调用它的汇编语言例程。
    10、(回到第一步的后半部分作用,不过恢复的是寄存器) 该例程恢复寄存器和其他状态信息。*

  • 指令备份:
    当程序访问不在内存中的页面时,将引起页面失效的指令停止并产生OS的陷阱。在OS系统取出所需的页面后,我们需要重新启动引起陷阱的指令,但这并不容易。
    解决方案:使用隐藏的内部寄存器复制程序计数器的内容

  • 锁定内存中的页面:
    虚拟内存和I/O通过微妙的方式相互作用着。如果分页算法是全局算法,包含I/O缓冲区的页面会有很小的机会(但不是没有)被选中换出内存。
    问:那为什么包含I/O缓冲区的页面很少被选中换出内存?
    答:(保证正在做I/O操作的页面不会被移出内存。)当一个进程刚刚通过系统调用从文件或其他设备中读取数据到其地址空间中的缓冲区。在等待I/O完成时,该进程被挂起。另一进程被允许进行。但是!如果I/O设备正处在对该页面进行DMA传出的过程中,将这个页面移出会导致部分数据写入它们所属的缓冲区,而部分数据被写入到最新装入的页面。 解决方法就是:锁住正在进行I/O操作的页面。或者在”内核缓冲区“中完成所有I/O操作,再把数据复制到用户页面。

  • 后备储存
    思考:当我们页面置换后,应该把置换出的页面存放在磁盘上的哪个位置?
    想了想: 在磁盘上分配页面空间最简单的方法就是在磁盘上设置特殊的交换分区,甚至从文件系统划分一块独立的磁盘。系统启动的时候,交换分区为空,需要在内存中给出交换分区的起始和大小,然后在交换分区中创建与该进程同样大的分区,然后进程结束后,会释放其磁盘上的交换区。交换分区以空闲块列表的形式组织。每个进程对应的是交换区的磁盘地址,也就是进程映射保存的地方(这些信息记录在进程表)(回写地址 = 虚拟地址的偏移量+交换区的开始地址)
    面试官: 那如果进程在启动后增大了怎么办?
    答: 一般情况下程序正文通常是固定的,但数据有时候会增长,堆栈也总是在随时增长。所以我们为此,可以把 正文,数据,堆栈分别保留交换区,并且允许交换区在磁盘上多于一个块。
    面试官: 还有没有别的解决方法?
    答: 我们也可以不事先给进程分配交换空间,但是每个页面都要记录相应的磁盘地址。也就是说,每个进程都额外有一张表,用来磁盘映射。
    在这里插入图片描述
    面试官:那如果没有磁盘分区可用呢?
    答: 这时候可以利用正常文件系统种得一个或多个较大的、事前定位的文件。因为每个进程的程序正文来自文件系统中某个(可执行)文件,这时候我们就可以把这个可执行文件用作交换区。但这时,我们可以想到更好的方法:一般程序都是只读的,当内存资源紧张,程序的页面不得不移出内存时,我们可以直接丢弃它们,在需要的时候再从可执行文件读入即可。共享库就是这样。

  • 策略和机制的分离(控制系统的复杂度):
    在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值