现代操作系统(三)
三.内存管理
分层存储器体系:MB级别的快速、昂贵、易失的高速缓存(cache);GB级别的速度与价格适中、易失的内存;TB级别的低速、廉价、非易失的磁盘存储;USB等可移动存储设备
操作系统中管理分层存储器体系的部分称为存储管理器
无存储器抽象
直接访问物理内存,所以想在内存中同时运行两个程序是不可能的,因为新的程序会覆盖前一个程序在相同位置的所有内容
在不使用存储器抽象的情况下运行多个程序
操作系统只需要把当前内存中所有内容保存到磁盘中,然后把下一个程序读入到内存中再运行即可。只要在某一个时间内存中只有一个程序,那么就不会发生冲突
特殊硬件在没有交换功能情况下,也可以并发运行多个程序,只是需要静态重定位(将加载地址加到每一个程序地址上,会减慢加载速度)
一种存储器抽象:地址空间
1.地址空间的概念
地址空间是一个进程可用于寻址内存的一套地址集合,每个进程都有一个自己的地址空间,并且这个地址空间独立于其他进程的地址空间(除了共享空间)
基址寄存器和界限寄存器
动态重定位:简单地把每个进程的地址空间映射到物理内存的不同部分
每个CPU都配置基址寄存器和界限寄存器
当一个程序运行时,程序的起始物理地址装载到基址寄存器,程序的长度装载到界限寄存器
每次一个进程访问内存,CPU硬件会在把地址发送到内存总线前,自动把基址值加到进程发出的地址值上;同时,它检查程序提供的地址是否大于等于界限寄存器的值
缺点:每次访问内存都需要进行加法和比较运算
2.交换技术
把一个进程完整调入内存,使该进程运行一段时间,然后把它存回磁盘。空闲进程主要存储在磁盘上,所以当他们不运行时就不会占用内存
内存紧缩
交换在内存中产生了多个空闲区,通过把所有的进程尽可能向下移动,有可能将这些小的空闲区合成一大块。但是会耗费大量CPU时间
进程运行时增长
当换入或者移动进程时,为它分配额外的内存(换出到磁盘时,只交换进程实际使用的内存中的内容)
如果有两个可增长的段,一个自上向下增长,一个自下向上增长,在这两者之间可以供这两个段使用
3.空闲内存管理
1)使用位图的存储管理
每个分配单元对应于位图中的一位
分配单元的大小是一个重要的设计因素。分配单元越小,位图越大
缺点:在决定把一个占有K个分配单元的进程调入内存时,存储管理器必须搜索位图,在位图中找到有K个连续的0串,比较耗时
2)使用链表的存储管理
链表中每个结点都包含以下域:空闲区或者进程的指示标志、起始地址、长度和指向下一个结点的指针
首次适配算法:存储管理器沿着段链表进行搜索,直到找到一个足够大的空闲区,然后分成一部分供进程使用,另一部分形成新的空闲区。速度很快,尽可能减少搜索链表节点
下次适配算法:类似首次适配算法,不同点在于每次找到合适的空闲区时都记录当前的位置,以便下次寻找空闲区时从上次结束的地方开始搜索,而不用从头开始搜索。性能略低于首次适配算法
最佳适配算法:搜索整个链表,找出能够容纳进程的最小的空闲区。试图找出一个最接近实际需要的空闲区,以最好的匹配请求和可用空闲区。性能比首次适配算法要慢,而且会浪费更多的内存(因为会产生大量无用的小空闲区)。如果进程和空闲区使用不同的链表,则可以按照大小对空闲区链表排序,以便提高最佳适配算法的速度
快速适配算法:他为那些常用大小的空闲区维护单独的链表。寻找一个指定大小的空闲区十分快速,但是和所有按大小排序的方案一样,有一个共同缺点——在一个进程终止或者被换出时,寻找它相邻块并查看是否可合并的过程都是耗时的。如果不进行合并,内存将很快分裂出大量进程无法使用的小空闲区
虚拟内存
基本思想:每个程序拥有自己的地址空间,被分割成许多块,每一块称为一页或页面。每一页有连续的地址范围。这些页被映射到物理内存,但不是所有的页都必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件执行必要的映射;当程序引用到一部分不在物理内存的地址空间时,由操作系统将缺失的部分装入物理内存并重新执行失败的指令
分页
由程序产生的地址称为虚拟地址,它们构成了一个虚拟地址空间。在没有虚拟内存的计算机上,系统直接将虚拟内存送到内存总线上,读写操作使用具有同样地址的物理内存字;在使用虚拟内存的情况下,虚拟地址不是被直接送到内存总线上,而是被送到内存管理单元(MMU),MMU把虚拟地址映射为物理内存地址
虚拟地址空间按照固定大小划分成为页面,物理内存中对应的单元称为页框
RAM和磁盘之间的交换总是以整个页面为单元进行的
在实际的硬件中,会有异味用来记录页面在内存中的实际存在情况
当加载未映射的页面:MMU注意到该页面并没有被映射,于是CPU陷入到操作系统,这个陷阱成为缺页中断或者缺页错误,操作系统找到一个很少使用的页框并把它的内容写入磁盘,随后把需要访问的页面读到刚才回收的页框中,修改映射关系,然后重新启动引起陷阱的指令
页表
虚拟地址被分为两部分:虚拟页号(高位)和偏移量(低位)
虚拟页号可用作页表的索引,用来找到该虚拟页面对应的页表项。由页表项可以找到页框号,然后把页框号拼接到偏移量的高位端,以替换掉虚拟页号,形成送往内存的物理地址
页表的目的是把虚拟页面映射成页框
页表项的结构
阴影 | 高速缓存禁止位 | 访问位 | 修改位 | 保护位 | 在/不在位 | 页框号 |
---|
最重要的是页框号,映射的目的就是找到这个值
在/不在位:1表示有效可以使用;0表示不在内存,会引起缺页中断
保护:指出一个页允许什么类型的访问
修改:在写入一个页面时自动设置;如果一个页面被修改过,则必须写回磁盘
访问:帮助操作系统在缺页中断时选择要淘汰的页面
最后一位用于禁止高速缓存
加速分页过程
虚拟地址到物理地址的映射必须非常快
如果虚拟地址空间很大,页表也会很大
转换检测缓冲区
基于这样一种观察:大多数程序总是对少量的页面进行多次的访问
所以设计出一种小型的硬件设备,将虚拟地址直接映射到物理地址,而不必直接访问页表,称为转换检测缓冲区(TLB)或者是相联存储器、快表
软件TLB管理
针对大内存的页表
多级页表:通过分级的方式避免把全部的页表都保存在内存中
倒排页表:实际内存中的每个页框对应一个表项,而不是每个虚拟页面对应一个表项,缺点是从虚拟地址到物理地址的转换会变得很困难,同样使用TLB解决困难
页面置换算法
最优页面置换算法
在缺页中断发生时,有些页面在内存中,其中有一个页面(包含紧接着的下一条指令的那个页面)将很快被访问,其他页面也可能在10、100、1000条指令后才会被访问,每个页面都可以用在该页面首次被访问前所要执行的指令数作为标记。
最优页面置换算法规定应该置换标记最大的页面
最近未使用页面置换算法(Not Recently Used)
随机的从类编号最小的非空类中挑选一个页面淘汰。优点在于易于理解和能够有效被实现,虽然性能不是最好的。
使用R位和W位:当启动进程时,它的所有的页面的两个位都被设置为0,R位被定期的(比如每次时钟中断时)清零,以区分最近没有被访问的页面和被访问的页面
先进先出页面置换算法
由操作系统维护一个所有当前在内存中的页面的链表,最新进入的页面放在表尾,最早进入的页面放在表头。当发生缺页中断时,淘汰表头的页面并把新调入的页面放在表尾
第二次机会页面置换算法
对先进先出算法进行改进,检查最老页面的R位。如果R位是0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是1,就将R位清0,并把该页面放在链表的尾端,修改它的装入时间就像刚装入的一样,然后继续搜索
第二次机会算法就是寻找一个最近的时钟间隔内没有被访问过的页面
时钟页面置换算法
一个更好的办法是把所有的页面都保存在一个类似钟面的环形链表中,一个表针指向最老的页面。当发生缺页中断时,算法首先检查表针指向的页面,如果它的R位是0就淘汰该页面,并把新的页面插入这个位置;如果R位是1就清除R位并把表针前移一个位置。如此重复。
最近最少使用页面置换算法(Least Recently Used)
在缺页中断发生时,置换未使用时间最长的页面
用软件模拟LRU
一种可能的方案是NFU(Not Frequently Used,最不常用)算法。该算法将每个页面与一个软件计数器相关联,计数器的初值为0。每次时钟中断时,由操作系统扫描内存中所有的页面,将每个页面的R位加到它的计数器上。这个计数器大体上跟踪了每个页面被访问的频繁程度。发生缺页中断时,则置换计数器值最小的页面。该算法的主要问题在于从来不忘记任何事情
优化:使用老化算法。首先在R位被加进之前先将计数器右移一位;其次。将R位加到计数器最左端的位而不是最右端的位。发生缺页中断时,将置换计数器值最小的页面。
工作集页面置换算法
请求调页策略:页面是在需要时被调入的,而不是预先装入
局部性原理:在进程运行的任何阶段,它都只访问较少的一部分页面
一个进程正在使用的页面的集合称为它的工作集。如果整个工作集都被装入到了内存中,那么进程在运行到下一阶段之前,不会产生很多缺页中断。
若每执行几条指令就发生了一次缺页中断,那么就称这个程序发生了颠簸。
跟踪进程的工作集,以确保在让进程运行以前,它的工作集就已经在内存中。该方法称为工作集模型,其目的在于大大减少缺页中断率。在进程运行前就预先装入其工作集页面也称为预先调页。工作集是随着时间变化的
工作集时钟页面置换算法
分页系统中的设计问题
局部分配策略和全局分配策略
局部算法可以有效地为每个进程分配固定大小的内存片段。全局算法在可运行进程之间动态的分配页框,因此分配给每个进程的页框数也是随时间变化的
负载控制
一些进程需要更多的内存,但是没有进程需要更少的内存。在这种情况下,没有方法能够在不影响其他进程的情况下满足那些需要更多内存进程的需要。唯一现实的解决方案就是暂时从内存中去掉一些进程
将一部分进程交换到磁盘,并释放他们所占有的所有页面
页面大小
内部碎片:分配的页面没有全部使用
分离的指令空间和数据空间
共享页面
只读的页面可以共享,但是数据页面不能共享
共享库
当一个共享库被装载和使用时,整个库并不是一次性的读入内存,而是根据需要,以页面为单位装载,因此没有被调用到的函数是不会被装载到内存中的
需要解决的小问题:使用相对定位
内存映射文件
进程可以通过发起一个系统调用,将一个文件映射到其虚拟地址空间的一部分。在多数实现中,在映射共享的页面时不会实际读入页面的内容,而是在访问页面时才会被每次一页的读入,磁盘文件则被当做后备存储
清除策略
保存一定数目的页框供给比使用所有内存并在需要时搜索一个页框有更好的性能。分页守护进程至少保证了所有的空闲页框是干净的,所有空闲页框在分配时不必急着写回磁盘
虚拟内存接口
有关实现的问题
与分页有关的工作
缺页中断处理
指令备份
锁定内存中的页面
后备存储
策略和机制的分离
分段
每个段由一个从0到最大的线性地址序列构成
因为每个段都构成了一个独立的地址空间,所以它们可以独立的增长或减小而不会影响到其他的段。段是一个逻辑实体
纯分段的实现
页面是定长的而段不是。在系统运行一段时间后内存被划分成许多块,一些块包含着段,一些则成为了空闲区,这种现象称为外部碎片
分段和分页的结合:MULTICS
一个地址由两部分构成:段和段内地址。段内地址又进一步分为页号和页内的字
- 根据段号找到段描述符
- 检查该段的页表是否在内存中
- 检查所请求虚拟页面的页表项
- 把偏移量加到页面的起始地址上
- 最后进行读或写操作
分段和分页的结合:INTEL X86