3.操作系统中的存储管理

内存的层次结构?

(寄存器::L1高速缓存::L2高速缓存)::L3高速缓存::内存::磁盘

地址空间?

物理地址空间:直接和硬件相对应,即硬件所能支持的地址空间,譬如内存条所支持的内存大小
逻辑地址空间:一个运行程序所拥有的地址范围,在程序看来,地址空间就是一个一维的线性地址空间

物理地址的生成?

在CPU中有一个叫MMU(Memory Management Unit)内存管理单元的区域,用于将逻辑地址转换成物理地址,具体过程如下:
1.CPU的ALU(arithmatic and logic unit)要执行一条指令,需要拿到这个地址的内容,那CPU就要发出指令从内存中取出这条指令,取的时候带的参数就是这条指令的逻辑地址
2.CPU的MMU组件就从自己的维护的映射表中查找是否有现成的映射关系,如果有直接去取,如果没有,就去内存中找
3.从逻辑地址定位到物理地址的转换规则是由操作系统维护的,在内存中找到这条指令后,返回给CPU
4.CPU取出这条指令,然后执行

操作系统如何保证运行程序之间内存空间的相对独立性?

操作系统为每个程序设置了逻辑地址空间的基址和界限,在CPU根据逻辑地址去内存中找指令时,操作系统会对逻辑地址进行检验,如果超过了为这个程序设定的界限,则是非法访问了内存,不允许

连续物理内存分配

1.内存碎片问题
  • 即内存中不能被利用的地址空间
  • 内部碎片:在分配单元中未被使用的内存
  • 外部碎片:在分配单元未被使用的内存
2.分配策略
  • 首次适配:为了分配n个字节,使用第一个(从零地址往下找)可用的空闲块(这个空闲快比n个字节要大)(容易产生大量外空间块
  • 最优适配:为了分配n个字节,从内存中未分配的空闲块中找出和n个字节大小相差最小的空闲块进行分配(容易产生几乎不可用的空间块)
  • 最差适配:为了分配n个字节,从内存中找到一个跟要分配内存大小差距最大的一个内存块进行分配(破坏了大空闲块以致大分区无法被分配)
    (并不是这里的3个算法哪个更好,这里只是简单的内存分配,程序分配需要大的块也需要小的块,谈不上最优)
3.压缩式与交换式碎片整理
压缩式(compact):

就是通过已分配内存块的挪动(重置)来使未分配的内存块放到一起
问题?

1.什么时候进行重置
  • 不能在程序运行的时候进行挪操作,可以在程序阻塞,等待,挂起的时候挪
2.内存重置开销大不大?
  • 内存拷贝的开销还是很大的
交换式(swap)

就是抢占等待的程序,回收他们的内存——把他们从内存挪到硬盘,数据并没有丢失,只是从内存挪到硬盘了
1.选择哪一个程序换出呢(对于连续内存分配来说,换入换出的单位是程序,不是程序片段)
2.什么时候进行换入换出
3.换入换出的开销
(这些问题都是使用连续物理内存分配要解决的,有的还没找到好的解决方式时,这种分配方式已经过时了)

非连续内存分配

0.为什么使用非连续内存分配?
  • 因为使用连续的内存分配总是不可避免的产生碎片问题从而降低了内存的利用率,使用非连续的内存分配就能避免这一问题产生
非连续内存分配的优点:
  • 更好的内存利用率
  • 允许代码和数据共享
  • 支持动态加载和动态连接
非连续内存分配的缺点:
  • 缺点就是分配管理本身,所谓管理就是如果把逻辑地址转化成物理地址
  • 使用软件的解决方法开销很大,因为对于每一条指令都要使用软件进行映射,开销太大啦
  • 常见的硬件解决方案有:分段分页

1.分段

分段寻址方案(根据应用程序的运行特点进行分割,是有意义的分割)
  • 程序中逻辑地址本身是连续的,但我们可以根据程序运行的特点对逻辑地址进行分段,譬如把逻辑地址分为:堆、运行栈、程序数据、程序txt段(库、用户代码),然后把这些段分别放到不同的物理地址区域中(这就是分段映射了),以不同的操作方式的权限进行操作
如何进行分段映射呢?
  • 在编写和编译应用程序的时候,内存被抽象为了一维的逻辑地址,当程序加载到内存的时候,不同的段已经映射到不同的物理内存块中了,一个段对应一个内存块
  • 程序访问物理内存需要一个2维的二元组(段号,段内偏移),逻辑地址就分成了两部分,即段号和段内偏移
  • 有两种具体的实现:段寄存器+地址寄存器(X86)、单地址
  • 一条逻辑地址怎么转化成物理地址呢?逻辑地址有两部分组成:段号+偏移。首先,根据段号去查段表(由操作系统维护,①逻辑地址段号和物理地址块的对应关系,即这个段号对应的内存快的起始地址②不同的段长度大小不同,这个信息也是放到段表里面的),然后看一下偏移地址是否超出了该段的限制,内存访问异常,正常的话是段表对应物理地址起始地址加上偏移地址得该条逻辑地址对应的物理地址
  • 段表在程序运行之前就应该由操作系统建立好,怎么建跟硬件有很强的联系(知道到这就行了)

2.分页

分页寻址方案(页的大小不变,而且是没有意义的切割)

1.分页和分段一样,都需要一个页号(段号)和页内偏移(段内偏移)

2.如果进行页映射呢?

  • 对逻辑地址空间和物理地址空间进行分页,分页的大小一样,都是2的幂次方
  • 逻辑页(Page)和物理页(Frame:页帧):物理地址被分为相同大小的内存块,一个物理地址由两部分组成:帧号帧内偏移
  • 那一条逻辑地址如何转换成物理地址呢?物理地址=2的S次方*f+o,其中:S为页帧的比特位数,f是帧号,o为页内偏移
  • OK,页内偏移和帧内偏移是一样的,关键是逻辑地址中的页号和帧号不一定相同,这就需要一个页表了,建立起逻辑页号和物理页帧的对应关系
  • 页表是由操作系统建立维护的,每个运行的程序都有一个页表
  • 逻辑地址分的页是连续的,映射到物理页帧的时可能不连续了就:不是所有的页在一开始就有对应的页帧,毕竟逻辑地址空间可以大于物理地址空间

3.页表(PageTable)

  • 页表概述
    1.每个运行的程序都有一个页表,而且页表是动态变化的,逻辑地址中页号想要找到其对应的物理地址帧号,需要两个东西:页表基地址(存放在页表基地址寄存器中,存两部分内容,一个是页表基址,一个是页表长度)、页号
    2.每个页表相当于一个大数组,索引是页号,值是帧号
    3.不是所有的页号都对应一个页帧号
    4.页表是位于内存上的,所以,每次寻址都要访问两次内存 ???
问题:

1.页表可能会很大,因为越发展可用的地址空间越大,如果每页1024字节,一个64位机器的页表内存都放不下
2.访问效率问题:页表是放到内存中的,一次寻址都要访问两次内存

如何处理这些问题呢?

1.(解决效率问题)可以通过缓存,把常用的页表对应关系缓存到离CPU较近的地方,先找缓存,缓存没有了再找实际的页表
2.(解决空间问题)间接访问,可以把大的空间拆分成较小的空间

  • 快表(TLB(Translation Look-aside Buffer):转换后备缓冲区位于CPU的MMU单元)
    1.要缓存的内容就是页表中的内容
    2.先在TLB中找页帧号,如果找到了就直接去内存中寻址访问,如果没找到,不得不查页表,寻址后并且把页表更新到TLB中
    3.由于程序的运行具有空间和时间的局部性,快表中页表被miss掉的可能性在10%左右
  • 二级、多级页表(牺牲时间换空间,牺牲的时间又可以通过TLB来缓解)
    其实无论使用二级还是多级页表,都只是对大页表进行了拆分,减少了大页表占据连续的大的内存空间的可能性,而本身并不能缓解大页表占据大内存的情景,能想到的避免大的页表占据大的内存的策略就是不一下子把程序的全部页表都加载到内存,即使用虚拟内存的技术
  • 二级页表
    将逻辑地址段再拆分,分为三段:一级页表段+二级页表段+段内偏移,根据一级页表段查看一级页表(存放的是对应二级页表的基址),根据二级页表段查看实际对应的内存页帧号,然后这个页帧号加上对应的帧内(页内)偏移就是实际的物理地址
  • 多级页表
    二级页表的推广,也是将逻辑地址再分,和二级页表很像,只是换成多级页表了
  • 反向页表(整个系统只有一个页表)
    不让页表和逻辑地址空间大小相对应,而和物理地址空间相对应(逻辑地址空间的增长速度大于物理地址空间的增长速度)
    所谓反向是指:通常的页表是用逻辑地址的页号去索引物理地址的页帧号,也就是页表是和逻辑地址相关联的;而反向是使用物理地址的页帧号去索引逻辑地址的页号,那这个页表就只和物理地址的大小有关了,不会因逻辑地址空间很大而导致了占据很大内存的大页表
    *问题也很明显,如何去根据逻辑地址查找对应的物理地址呢?
    有很多中实现方案,但是,比较好的方案是『基于哈希的查找方案』,实现是:定义一个哈希函数,输入为Page Number输出为Frame Number

虚拟内存技术

1.起因

程序规模的增长速度远大于存储器容量的增长速度

  • 要让更多、更大的程序运行在有限的硬件内存上
    所以就把硬盘的一部分当成虚拟内存,程序运行的时候,并不把程序的全部内容加载到内存中,只把部分的要用的加载,等用到的时候,没有了再从硬盘加载

2.覆盖技术(虚存技术产生之前)

  • 如果程序太大,超过了内存的容量,可以采用**手动覆盖(Overlay)**的技术,只把需要的指令和数据保存到内存当中,这里的手动是指需要程序员编写程序的时候自己处理内存的管理
  • 根据程序运行时函数之间的调用关系来对程序进行拆分,相互之间没有调用关系的分开,当然有一个程序是常驻内存的,用来管理这个覆盖技术,可以把函数之间的调用关系使用一棵树的形式表示出来,然后同一级别的树位于同一覆盖区(回想以下老师上课讲的例子)
  • 问题:
    1.程序员在编写程序的时候多了额外的考虑和负担(主要)
    2.覆盖本身的开销

3.交换技术(虚存技术产生之前)

  • 如果程序太多,超过了内存的容量,可以采用自动交换(Swapping)技术,把暂时不运行的程序送到外存中去,由操作系统完成,但是成本很高,因为每次换入换出都是整个程序(主要问题就是交换的粒度太大:整个程序 )
  • 这种技术是早期Unix使用的
  • 交换式的连续内存分配一样有相似的问题:
    1.什么时候进行交换?
    2.交换的策略:换谁进来,换谁出去?
  • 整个过程由操作系统完成,对程序员是透明的

4.虚存技术

  • 工作情况
    基于程序的局部性原理,程序运行时没有必要把程序的全部内容加载到内存,只需把程序当前运行所需的部分片段装入内存即可,其余部分留在硬盘上。
    程序运行时,如果他索要访问的页(段)已经在内存了,OK,继续执行下去,如果没有,就会产生一个缺页中断,由OS把请求页(段)调入内存,如果内存已满,则使用特定的页(段)置换策略,换出一部分页(段)出来,再将所需页(段)加载到内存,重新执行被中断的指令,以使程序继续运行

  • 程序的局部性原理
    1.时间局部性:如果一条指令被执行了,则不久以后该条指令可能会再次执行,如果某个数据被访问了,则不久以后,该条数据可能会在被访问
    2.空间局部性:一旦程序访问了某个存储单元,则其附近的存储单元在不久之后也将被访问

  • 基本特征
    1.内存变大了(加上了硬盘呀)
    2.部分交换
    3.空间本身的不连续性

  • 虚拟页式内存管理
    1.大多数的虚拟内存管理系统都采用了页式存储管理技术。即在页式存储管理的基础上增加了请求调页页面置换的功能
    2.跟一般的页表相比,页表表项增加了一些东西,以满足虚拟内存的实现,譬如增加了

      		驻留位:表示该页是在内存则为1在外存则为0;  
      		保护位(不是1位):读写保护位;  
      		修改位:该页是否被修改过; 
      		访问位:该页是否被访问过(用于置换算法)
    
局部页面置换算法
  1. 最优页面置换算法(OPT:Optimal Page Transfer)
    要换出的页面是将永久不使用的或者将来最长时间内不会使用的
    是一种理论上的算法,因为将来哪些页面将被调入是无法预知的,但是可以用来评价其他页面置换算法。
  2. 先进先出算法(FIFO:First In First Out)
    要换出的页面是最早进入内存的页面,即在内存中驻留时间最久的页面要被淘汰。
    最简单,但是缺页率略高,因为这种置换是没有实际意义的,完全是按照时间来的,并没有考虑到程序运行时的动态特征
  3. 最近最久未使用算法(LRU,Least Recently Used)
    要换出的页面是最近最少使用的页面。对于内存中的每个页面,都设置一个访问字段,用来记录自上次访问之后所经历的时间T,当要淘汰一个页面时,选择T最大的页面淘汰,即选择未使用时间最长的页面换掉。
    虽然算法较好,但是支持该算法所需要的硬件支持也很多,实现起来比较麻烦,成本较高。在实际应用中使用的是近似LRU算法,而Clock算法就是一个近似LRU算法。
  4. 时钟页面置换算法(Clock)
    这里的Clock并不是轮询的意思,而是在查找置换页面的时候像一个钟表
    首先为每个页面添加一个访问位,再将所有的页面通过链接指针连成一个循环队列,当某页被访问的时候,该页的访问位设置为1(硬件完成的,当然也可以用软件实现);置换算法在这个循环队列中使用FIFO的方式选择要换出的页面,如果该页面的访问位是0,直接换出,如果是1,置为0并且接着往下找
    对于时钟页面置换算法,有一个其增强的算法即增强时钟页面置换算法(又叫二分机会法)
    这样使脏页总能在一次时钟扫描中留下来,在时钟页面置换的基础上又利用了dirty bit(修改位)位,其置换策略为:
    在这里插入图片描述
    为什么要使脏页先留一下,对于非脏页(没有被修改的页)直接释放就行,不用写回硬盘,对于脏页,还要写会硬盘,成本较高。
  5. 最不常用算法(LFU:Least Frequently Used)
    为每个页面记录一段时间内被访问的频率,要换出的是访问频率最低的页面,可以使用和LRU一样的一套硬件来实现LFU,同样实现成本较高。
    问题很多,首先使用最不常用来淘汰页面本身就有点问题
  6. Belady现象
    FIFO算法出现的现象
    操作系统为每个程序分配的可用物理内存往往是小于程序运行所需的逻辑地址空间的,使用虚拟内存技术运行改程序,在程序运行的过程中肯定会产生缺页中断,通常来说呢,分配给该程序的可用物理内存越大,缺页率应该越低,而Belady现象指的是对于某些页面置换算法而言,被分配的物理内存大了,缺页率也跟着变大的现象
  7. 抖动现象(颠簸)
    产生原因:由于系统中运行的进程太多,导致进程在运行的时候大部分时间用于页面的换入/换出,而无法做一些实际有效的工作,导致CPU的利用率急剧下降到很低并趋于0的状况,这种状况称为『抖动』
  8. LRU、FIFO和Clock的比较
    LRU和FIFO本质上都是先进先出的策略,只是LRU考虑了程序的动态运行特征,对于程序中访问的到的页面,从栈中取出来重新放到栈顶,而FIFO则没有按照程序的运行进行动态的调整
    而Clock算法是对LRU的近似
    如果程序没有很好的局部性访问特征,Clock和LRU都可能退化成FIFO算法
全局页面置换算法
  • 工作集模型
    (背景知识:根据程序运行的局部性原理,程序在运行期间其对页面的访问是不均匀的,不同段的时间内可能集中在不同的几个页面内)
    所谓工作集:是指在某段时间间隔内,进程运行实际要访问的页面的集合
  • 工作集页置换算法
    虽然程序需要少量的几页就能运行,但是为了较少的产生缺页,将程序的全部工作集装入内存,然而我们并不知道未来将要访问到工作集到底是哪些页面,只能根据最近的时间内的行为作为未来时间内行为的近似
    具体就是因为工作集窗口随着运行时间在向后平移,OK,那平移之后不在工作集中的页要换出去,然而并不是因为内存不够用了,而是因为不在工作集了,内存可能还很够用
  • 缺页率置换算法
    缺页率=缺页次数/访问总数
    缺页率高的时候增加工作集的长度,缺页率低的时候减少工作集的长度
    OK,怎么增加怎么减少呢?
    1.首先设定一个初始的窗口的大小
    2.缺页率是一个区间的值
    3.可以设定这样一个值,如果两次缺页中断中间访问的次数大于该值,表示缺页率较低了,这时候要减少工作集的长度,减少的页是在这个区间中没有被访问到的页;如果两次中断中间隔得访问次数的间隔小于该值,表示缺页率略高,要扩容,就把缺的页加到当前工作集中
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值