第三章 存储管理
1.概述
分层存储体系: 计算机有若干兆(MB) 快速、 昂贵且易失性的高速缓存(cache) , 数千兆(GB) 速度与价格适中且同样易失性的内存, 以及几兆兆(TB) 低速、 廉
价、 非易失性的磁盘存储, 另外还有诸如DVD和USB等可移动存储装置。 操作系统的工作是将这个存储体系抽象为一个有用的模型并管理这个抽象模型。
2. 无存储器抽象
没有内存抽象,直接面对物理内存。
用户程序出现的错误可能摧毁操作系统, 引发灾难性后果。
2.1 在不使用内存抽象的情况下运行多道程序
操作系统只需要把当前内存中所有内容保存到磁盘文件中, 然后把下一个程序读入到内存中再运行即可。
缺陷: 两个程序都装入内存时,因为都使用了绝对地址,因此代码中地址跳转指令会出问题。 我们希望每个程序都使用一套私有的本地地址来进行内存寻址。
因此采用 静态重定位技术修正,但是减慢装载速度。 而且, 它要求给所有的可执行程序提供额外的信息来区分地址和常数。
3. 一种存储器抽象--地址空间
要保证多个应用程序同时处于内存中并且不互相影响, 则需要解决两个问题:保护和重定位。
地址空间为程序创造了一种抽象的内存。 地址空间是一个进程可用于寻址内存的一套地址集合。 每个进程都有一个自己的地址空间, 并且这个地址空间独立于其他进程的地址空间。
要解决的核心问题: 不同进程地址空间对应的物理空间不重复的问题。
3.1 基址寄存器和界限寄存器
动态重定位。每次将程序的起始物理地址装载到基址寄存器中, 程序的长度装载到界限寄存器中。 下一个程序在其后进行同样的装载。
当执行硬件指令的时候,指令的地址直接叠加在基址上,获得实际物理地址。
缺点: 每次访问内存要做加法和比较运算,慢。
3.2 交换技术
把所有进程一直放在内存里会引起内存超载。 解决方法: 交换技术 、虚拟内存
交换技术: 把 一个进程调入内存,运行一段时间后存回磁盘。
在交换的过程中,会产生多个空闲区(空洞)。通过将所有进程向下移动可以合并小的空闲区,该技术称为(内存紧缩)。
内存紧缩的缺点: 极其耗时。
3.3 空闲空间管理
使用位图管理:
把内存划分为细粒度的分配单元,每个分配单元用位图的一个bit去记录使用(使用、空闲)。分配单元越大,位图越小,但进程大小不是位图的整数倍,则会浪费空间。
使用链表管理:
维护一个记录已分配内存段和空闲内存段的链表. 链表中的每一个结点都包含以下域:空闲区(H) 或进程(P) 的指示标志、 起始地址、 长度和指向下一结点的指针。
链表管理中的内存分配方法:
首次适配(first fit) 算法。 存储管理器沿着段链表进行搜索, 直到找到一个足够大的空闲区,除非空闲区大小和要分配的空间大小正好一样, 否则将该空闲区分为两部分, 一部分供进程使用, 另一部分形成新的空闲区。
下次适配(next fit) 算法。 它的工作方式和首次适配算法相同, 不同点是每次找到合适的空闲区时都记录当时的位置。 以便在下次寻找空闲区时从上次结束的地方开始搜索, 而不是像首次适配算法那样每次都从头开始。
最佳适配(best fit) 算法。 最佳适配算法搜索整个链表(从开始到结束) , 找出能够容纳进程的最小的空闲区。 最佳适配算法试图找出最接近实际需要的空闲区, 以最好地区配请求和可用空闲区, 而不是先拆分一个以后可能会用到的大的空闲区。
最差适配(worst fit) 算法, 即总是分配最大的可用空闲区, 使新的空闲区比较大从而可以继续使用 。
4. 虚拟内存
每个程序拥有自己的地址空间, 这个空间被分割成多个块, 每一块称作一页或页面(page) 。 每一页有连续的地址范围。 这些页被映射到物理内存, 但并不是所有的页都必须在内存中才能运行程序。 虚拟内存使得整个地址空间可以用相对较小的单元映射到物理内存。
页表
虚拟页号可用做页表的索引, 以找到该虚拟页面对应的页表项。 由页表项可以找到页框号(如果有的话) 。 然后把页框号拼接到偏移量的高位端, 以替换掉虚拟页号, 形成送往内存的物理地址。 页表的目的是把虚拟页面映射为页框。
在任何分页式系统中, 都需要考虑两个主要问题:
1)虚拟地址到物理地址的映射必须非常快。
2)如果虚拟地址空间很大, 页表也会很大。
1.转换检测缓冲区
硬件首先通过将该虚拟页号与TLB中所有表项同时(即并行) 进行匹配, 判断虚拟页面是否在其中。 如果发现了一个有效的匹配并且要进行的访问操作并不违反保护位, 则将页框号直接从TLB中取出而不必再访问页表。
如果MMU检测到没有有效的匹配项时, 就会进行正常的页表查询。 接着从TLB中淘汰一个表项, 然后用新找到的页表项代替它。 这样, 如果这一页面很快再被访问, 第二次访问TLB时自然将会命中
类似一个路由表,保存常用的,实时更新.
针对大内存的页表
多级页表
倒排页表
在实际内存中每一个页框有一个表项, 而不是每一个虚拟页面有一个表项。
每次都遍历一遍表项(数量与叶框相同) // 或者将常用的存TLB ,当发生缺页中断时,还是要遍历一遍。
4. 页面置换算法
当发生缺页中断时, 操作系统必须在内存中选择一个页面将其换出内存, 以便为即将调入
的页面腾出空间。 如果要换出的页面在内存驻留期间已经被修改过, 就必须把它写回磁盘以更
新该页面在磁盘上的副本;如果该页面没有被修改过(如一个包含程序正文的页面) , 那么它
在磁盘上的副本已经是最新的, 不需要回写。
1) 最优页面置换算法(无法实现)
在缺页中断发生时, 其他页面则可能要到10、 100或1000条指令后才会被访问, 每个页面都可以用在该页面首次被访问前所要执行的指令数作为标记。最优页面置换算法规定应该置换标记最大的页面。 (离当前访问页面距离最远的先调到磁盘)
2) 最近未使用页面置换算法
当页面被访问(读或写) 时设置R位;当页面(即修改页面) 被写入时设置M位。NRU(Not Recently Used, 最近未使用) 算法随机地从类编号最小的非空类中挑选一个页面淘汰之。
这个算法隐含的意思是, 在最近一个时钟滴答中(典型的时间是大约20ms) 淘汰一个没有被访问的已修改页面要比淘汰一个被频繁使用的“干净”页面好。
3) 先进先出页面置换算法
最新进入的页面放在表尾, 最久进入的页面放在表头。 当发生缺页中断时, 淘汰表头的页面并把新调入的页面加到表尾。
4) 第二次机会页面置换算法
FIFO算法可能会把经常使用的页面置换出去, 为了避免这一问题, 对该算法做一个简单的修改:检查最老页面的R位。 如果R位是0, 那么这个页面既老又没有被使用, 可以立刻置换掉;如果是1, 就将R位清0, 并把该页面放到链表的尾端, 修改它的装入时间使它就像刚装入的一样, 然后继续搜索。
5) 时钟页面置换算法(二次机会算法在数据结构上的优化)
尽管第二次机会算法是一个比较合理的算法, 但它经常要在链表中移动页面, 既降低了效率又不是很有必要。 一个更好的办法是把所有的页面都保存在一个类似钟面的环形链表中, 一个表针指向最老的页面。
6) 最近最少使用页面置换算法
在前面几条指令中频繁使用的页面很可能在后面的几条指令中被使用。 反过来说, 已经很久没有使用的页面很有可能在未来较长的一段时间内仍然不会被使用。 这个思想提示了一个可实现的算法:在缺页中断发生时, 置换未使用时间最长的页面。 这个策略称为LRU(Least Recently Used, 最近最少使用) 页面置换算法。