操作系统——第5章 存储管理(上)

第5章 存储管理(上)

基本概念

虚拟存储器

虚拟存储示意

  1. 进程执行过程中,大部分程序和数据并不需要经常访问,这是就可以把这部分不需要经常访问的内容放到外存中去

  2. 进程中的目标代码、数据等的虚拟地址组成的虚拟空间称为虚拟存储器。编译链接程序会把源程序编译后链接到一个以0地址为起始地址的线性或多维空间中。每个进程都会有这样的一个虚拟空间

  3. 上述的这种链接包括静态和动态两种,静态链接在程序执行前完成,动态链接则在程序执行过程中根据需要进行

  4. 源程序经过编译链接,其中的相关的数据、代码等地址都变换到自身的虚拟空间中,以相对位置的形式存在,这整个过程与语言系统的设计有关;虚拟空间需要对应到物理内存,这个过程称为地址变换由操作系统完成

  5. 地址变换涉及两个方面的内容:虚拟空间的划分、虚拟地址到内存地址的映射(即地址变换)

    对于虚拟空间的划分,不同机器会有所不同。一个进程的虚拟空间会被分为多个部分用作不同用途,例如划分为进程空间与系统空间

地址变换
静态地址重定位

在虚拟空间程序执行之前由装配程序完成地址映射工作。事实上就是直接将程序中的指令或数据的虚拟地址转换为对应的物理内存地址,这由装配程序完成,无需硬件支持

显然,若使用静态重定位,程序一旦装入内存之后就不能再移动,需要在程序执行前将有关部分全部装入内存,因此这种方式无法实现虚拟存储器

另外,若使用静态重定位,就必须占用连续的内存空间,不利于程序段的共享

静态地址重定位

从上图中,可以发现程序虚拟空间被转换到了一串连续的物理内存,另外,代码中的虚拟地址也会被转换

动态地址重定位

在程序执行过程中,CPU访问内存之前,将要访问的地址转换为实际的物理内存地址。依靠硬件地址变换机构完成

使用基地址寄存器BR与虚拟地址寄存器VR(都可以有多个)。将程序段装入内存时,将其要占用的内存区首地址放在BR中;访存前,将要访问的虚拟地址放在VR中

动态重定位可以对内存进行非连续分配,利于程序段的分享,并且可以实现虚拟存储器,即程序段的最大长度与内存外存容量总和有关,而不是受内存容量限制

内存的利用机制和与外存的数据传输
内外存的数据交换

显然,要实现内存的扩充,内外存需要在程序的执行过程中不断交换数据。交换分为用户程序自己控制操作系统控制两种方式

用户程序控制

覆盖就是用户程序控制内外存数据交换的例子,用户需要了解程序的结构并指定各程序段调入内存的先后顺序。覆盖技术不能实现虚拟存储器

操作系统控制

操作系统控制又可以分为交换、请求调入和预调入两种方式

  1. 交换方式:操作系统将处于等待态的进程换出内存,将等待时间已发生、处于就绪态的进程换入内存

  2. 请求调入方式:程序执行时,若遇到不在内存中的程序段和数据段,操作系统会自动从外存将相关内容调入内存

  3. 预调入方式:操作系统预测即将会用到的程序段和数据段部分,在用到前的适当时机将相关内容调入内存

事实上,交换方式是根据资源限制与程序当前是否能执行来判断换入换出,并没有根据程序运行的需要处理,因此还是不能实现虚拟存储器。请求调入和预调入方式能实现虚拟存储器

内存的分配与回收

给每一个进程分配内存空间,在进程执行结束后回收其占用的内存。设计分配与回收方法时,需要注意以下几点

  • 分配结构:供分配程序使用的记录内存使用情况的数据结构

  • 放置策略:即选择内存空闲区以存放调入内存的程序与数据

  • 交换策略:当某程序或数据需要调入内存,但内存无空闲区时,决定将内存中的那些程序或数据换出以腾出空间

  • 调入策略:决定外存中的程序与数据何时以何种方法换入内存

  • 回收策略:决定回收的时机以及内存空闲区如何调整

内存信息的共享与保护

用户程序段、系统程序段、数据段可供不同用户进程共享

为了防止不同进程不对其他进程的程序和数据段产生干扰与破坏,需要采取保护措施。保护可以分为硬件法、软件法、软硬件结合法三种

上下界保护法

这是一种硬件保护法,会为每个进程设置一对上下界寄存器。程序执行过程中,会对每次访存操作进行访址合法性检查,若要访问的内存地址不在上下界寄存器规定的范围内,则产生访址越界中断

保护键法

这是一种软件保护方式。在这种方法下,会为每个被保护存储块分配一个单独的保护键。同时,程序状态字中也会有相应的保护键开关字,与存储块的保护键进行匹配。显然,可以将这种方法设计为对存储块的读和写分别进行保护

保护键法

界限寄存器和CPU工作模式相结合

这是一种软硬件结合保护方式。界限寄存器规定用户态进程能访问的内存部分;核心态进程可以访问整个内存地址空间

分区存储管理

为了运行进程,需要为每个进程分配一块大小合适的存储区。按分区时机,可将分区管理方法分为固定分区、动态分区两种

固定分区法

会把内存固定地划分为一些大小不等的区域。具体划分的规则由操作系统或操作员决定。划分完成后,在整个执行过程中保持不变,使用分区说明表记录分配情况

分区说明表

上图中就使用固定分区法,用分区说明表就可以得知各段的位置及内存的分配情况。另外,可以发现操作系统常驻于内存,位于内存的开始

动态分区法

在作业执行前不建立分区,作业处理过程中创建的分区可随需求改变大小。此处就涉及到了对内存进行分配和释放的管理算法。

在动态分区法中,同样需要使用分区说明表记录内存的分配情况。除此之外,还将内存中空闲可用部分记录为可用表自由链;将请求内存的作业/进程记录为资源请求表

动态分区用的表

上图中的可用表和自由链表示的含义是相同的。在可用表中,表项分别为分区号、本分区长度、本分区起始地址

在自由链中,利用每个空闲区的头部几个存储单元存放本分区长度与下一分区起始地址,另外,有一个链首指针指向第一个空闲区

分区的分配与回收

对于固定分区法来说,由于分区大小是固定的,从前往后遍历分区说明表,直到找到一个够大的分区可以分给当前待分配内存作业/进程,然后简单地修改分区说明表的表项就可以完成分区的分配与回收

但对于动态分区法来说,就需要用一定的算法完成空间的分配与回收。显然,在分配和回收的过程中,动态分区法所用的算法需要

  • 先在可用表或自由链中找到一块够用的空闲内存区

  • 然后更新可用表或自由链

  • 进程完成后,需要对重新空出来的分区进行链接合并

  • 再更新可用表或自由链

下面的FF、BF、WF三种算法中:

  • FF在搜索速度上性能最佳,且尽可能利用了低地址空间

  • BF找到的空闲区是最佳的,也就是说找到的是最小够分配的空闲区

  • WF最不容易留下碎片空闲区

最先适应算法(first fit)

要求可用表或自由链按起始地址递增排列。算法执行过程:

  • 从前往后遍历可用表或自由链,直到找到一个够大的分区

  • 在这个分区中划出作业/进程需要的大小分配给用户,并合并可合并的分区

  • 按照分配的情况修改可用表或自由链

这种算法容易在低地址处产生碎片,主存空间利用率会降低

最佳适应算法(best fit)

要求可用表或自由链按空闲区大小递增排列。算法执行过程与上一种最先适应算法一致

解决了大作业的分配问题,但也容易产生太小而不可利用的空闲区,降低主存空间利用率

最坏适应算法(worst fit)

要求可用表或自由链按空闲区大小递减排列。同样和前面两种算法一样从头开始搜索第一个足够当前作业/进程请求的大小进行划分

不会产生过多碎片,但是由于大的空闲区会先被划分,会影响大作业的分配

分区回收时的拼接

作业/进程完成时,管理程序会将其占用的分区重新放回可用表或自由链。在这个回收的过程中,可以将一些满足条件的小分区进行拼接,组成一个大的分区

空闲区拼接

在上面的四种情况中,回收释放的分区可以与已经存在的空闲区进行合并

  • 情况a中,上下空闲区和回收释放的分区三者合并

  • 情况b中,回收释放的分区和上空闲区合并

  • 情况c中,回收释放的分区和下空闲区合并

  • 情况d中,回收释放的分区没得合并,单独作为一个新的表项插入可用表或自由链中

分区存储管理中的几个注意点
  1. 分区存储管理这种方式事实上和前面提到过的地址变换方式没有直接的关联。其实,动静态地址变换都能用于分区存储管理

  2. 在动态分区法中,都以“可用表或自由链”表示。这是因为在动态分区法中,分区大小不固定,若使用静态地址变换,无法很好地修改代码中的访存地址

  3. 分区存储管理显然无法实现各分区之间的信息共享

覆盖技术

用于扩充内存的一种方法。由于一个程序不需要一开始就将其所有指令和数据都装入内存,不会同时运行的程序不需要同时存在于内存中

可以将程序分为若干相对独立的程序段,在需要先运行的程序段执行完成后,再调入后运行的程序段覆盖前面的程序段

但是,要使用这种方法,程序员需要人工将程序划分为不同程序段,并规定好各段执行和覆盖的顺序

显然,覆盖技术在同一个作业/进程内进行操作

交换技术

同样是一种扩充内存的方法。会先将内存某部分的程序或数据写入外存的交换区,再从交换区换入指定的程序或数据,并运行之

显然,负责交换的管理进程需要与外存设备管理进程进行通信,一般通过设备缓冲队列进行。交换进程发送给外存设备进程的信息中包括:分区号、分区基址、分区长度及方向、外存交换区中的起始地址

页式管理

在上述的分区管理中,各分区在内存中都是连续存放的,当某些需要同时在内存中的程序来说,很可能面临内存空间不足的情况,仍受内存大小限制

基本概念

对于进程的虚拟空间,将其划分成若干大小相同的页(page)。如此,进程的虚拟地址就可以视为由两部分组成,前半段视为页号、后半段视为页内地址

例如一般来说,Linux下给进程的初始虚拟空间为4G、页的大小为4K。那么,共有 4 G / 4 K = 2 20 4G/4K=2^{20} 4G/4K=220个页,需要20位来表示;由于是按字节编址的,每个页内的地址需要 4 K / 1 = 2 12 4K/1=2^{12} 4K/1=212。因此,进程的虚拟空间地址编排如下

页地址划分

对于物理内存空间,按与页相同的大小划分为块(也称为页面page frame、片),一个块可以为多个进程共享

在某个进程运行的过程中,在它使用自己的虚拟空间时,似乎地址是连续的,但是与各个页对应的物理内存中的块可以是非连续分散的

此时,显然需要一张表来记录虚拟空间中页和物理内存中块的对应关系,这张表称为页表

为了记录和管理物理内存块,静态页式管理中使用了三种表

页表

在内存中占有固定的存储区,大小由进程/作业的大小决定。其中记录了页号和块号的对应关系。

显然,由于每个进程都有自己的虚拟空间,每个进程至少要有一个页表,也就是一个系统中会有很多页表

请求表

用来确定作业/进程虚拟空间的各页在内存中的实际对应位置,表中包括的信息:进程号、页表起始地址、页表长度、需要请求的页面数等

一个系统中只需要有一个请求表

存储页面表

用于记录内存中各块是否已经被分配出去,内存中还剩多少页面未分配。存储页面表一般有两种表示形式,位示图及空闲页面链,一个系统中只需要有一个存储页面表

位示图是内存中划分出的一块固定区域,其每个bit代表一个块是否已被分配,0代表未分配、1代表已分配

空闲页面链利用每个空闲块首部的空间。整个链的第一个空闲块首部存放当前空闲页面总数和指向下个空闲块的指针,后面的空闲块首部只存放指向下个空闲块的指针。显然,空闲页面链无需占据额外的内存空间

页表、请求表、存储页面表

静态页式管理

在作业/进程开始执行之前,将其需要的程序段和数据全部调入内存的各个块中,并通过页表及硬件地址变换机构实现虚拟地址到物理内存地址的转换

显然,若使用这种静态的管理方式,当可用块数不足时,进程/作业只能等待;并且,进程/作业的大小仍受块数量的限制(也就是内存大小的限制)

内存块分配回收机制

完成一次分配的步骤

  1. 在请求表中获得要请求内存的及其需要的块数

  2. 在存储页面表中查看空闲块是否足够本次分配。若足够,则先分配页表表项并填写页号

  3. 再根据某种查找算法找到空闲块,在该表项中填写页面号

作业/进程执行完成后,将页表中的对应项删除,同时更新存储页面表,即可完成页面的回收

地址变换方式

同样的,在静态页式管理中,地址变换是由硬件地址变换机构自动完成的,通过查找页表完成逻辑地址和实际内存物理地址的对应。由于页号与页面号往往是从小到大连续编址的,乘上页面大小就可以得到实际位置

在这整个过程中,需要控制寄存器来存放要访存的进程的页表起始地址页表长度

假设页面大小为1K,虚地址空间中的页和物理内存中的页面都是按地址从小到大连续编号的(即第一块为01023、第二块为10242047…)

假设某进程中有个指令MOV AX, [2500],即将虚拟地址2500处的内容传送到寄存器AX中,这条指令本身存的位置假设为100,该进程页表如下

静态页式管理举例

整个执行过程如下

  1. 该进程被调度,此时进行访存,从请求表中得到该进程的页表起始地址和页表长度

  2. 虚地址100经过地址变换机构,变成了页号和页内偏移地址(0, 100)

  3. 查页表,得到0号页对应2号页面,得到了页面号与页内偏移地址(2, 100),访存物理地址 2 × 1 K + 100 = 2148 2\times1K+100=2148 2×1K+100=2148得到这条指令

  4. 执行指令,需要获得虚地址为2500处的内容,同样经过地址变换机构,变成页号与页内偏移地址(2, 452)

  5. 查页表,发现2号页对应8号页面,得到页面号与页内偏移地址(8, 452),要访问的实际物理地址为 8 × 1 K + 452 = 8644 8\times1K+452=8644 8×1K+452=8644

页表存在内存的某段固定位置上,若指令本身就需要访存,则至少访存两次,有点慢。可以在地址变换机构中加入高速联想存储器以存放常用的页及其对应页面号,这个表称为快表,会先尝试在快表中查询需要的表项

事实上,在这个过程中可以发现的是,静态页式管理只是单纯地将虚地址的页与物理内存地址的块对应起来,若对应的块中没有内容,整个过程都没有意义。也就是说,静态页式管理依赖“在作业/进程开始执行之前,将其需要的程序段和数据全部调入内存的各个块中”的机制

动态页式管理

动态页式管理分为请求页式管理和预调入页式管理两种方式。这两种方式都只会把经常反复执行与调用的装入内存,其他部分在执行过程中动态调入。这样子就解决了能运行的程序大小受限于内存大小的问题

请求页式管理:执行时发现所需要的数据或指令不在内存中,则发生缺页中断,此时系统将外存中的相应页面调入内存

预调入页式管理:根据外存中页的调入顺序计算估计这些页的执行/被访问顺序,按此顺序调入调出内存

显然,由于执行的进程的程序和数据并不会在运行前全部调入内存,很可能在需要使用时,内容不在内存,需要到外存中取得并调入内存。另外,在调入时,若内存已满,则需要按照一定算法将内存中的页面淘汰。这两种情况分别通过调整页表结构和置换算法处理

动态页式管理示意图

动态页式管理的页表

为了记录页面是否在内存中,以及配合之后的置换算法,调整页表结构如下图

动态页式管理页表

  • 页号和页面号的含义不变;

  • 中断位为1表示该页面不在内存,即需要发起缺页中断

  • 当页面不在内存时,需要存储该页面在外存的起始地址

  • 改变位用于记录该页面在从外存调入后,在内存中是否有修改,若有修改,之后在被淘汰时需要先存到外存中,否则用新调入的页面直接覆盖即可

  • 为什么不需要记录在外存中的长度呢?显然,每个页面大小都是一样的,当然不用记录

地址变换

和静态页式管理的地址变换相同,也是由硬件变换机构自动完成的,若变换机构发现该页不在内存中,则发起缺页中断,中断处理程序完成读取外存至内存以及内存满时的置换算法工作,这是软件完成的

置换算法

用于当内存满时,还要从外存换入页面到内存的情况。置换算法会选择一个认为是被访问概率最低的页,将其移出内存

置换算法非常重要,若选用不合适的算法,会产生抖动现象:刚换出的页面马上就又换入内存,系统中页面调度非常频繁,调入调出的开销极大

此处定义缺页率为缺页次数与访问页的总次数之比

随机淘汰算法

即随机选择一个内存中的页面淘汰

轮转法

即循环换出内存可用区内一个可换出的页面,不管该页面是刚换进的还是已经存在于内存很长时间了

先进先出FIFO算法

每次都选择最先调入内存的页面换出,即选择存在于内存时间最久的换出。但是FIFO算法可能出现Belady现象,见下面的详解

Belady现象分配的页面数增多,缺页次数反而增加

按照直觉,对于同一个进程来说,其能够使用的物理内存块数越多,越不容易发生缺页中断(当该进程的所有程序和数据都能放到内存中时则不会发生缺页中断)。但是,对于FIFO算法来说,有时会出现Belady现象。

例如某进程有5页,其访问各页的顺序为(称为访问串)0,1,2,3,0,1,4,0,1,2,3,4,现计算分配3个和4个页面时的缺页率

Belady举例图1

分配3个页面时,缺页率为9/12=75%

Belady举例图2

分配4个页面时,缺页率为10/12=83.3%

最近最久未使用LRU算法

选择最近一段时间内,最长时间没有使用过的页面淘汰换出。若是要完全实现此算法,每次需要淘汰时都要找出最久未被使用的页面,开销非常大。一般使用LRU的近似算法——LFU、NUR、Clock等

最不经常使用LFU算法

使用访问的频次近似代替LRU中的时间,在页表中添加一列作为“访问计数器”,某一页面被访问时,将该页面的访问计数器加一

当需要淘汰时,选择计数器值最小的页面换出,同时将所有计数器清零

最近未使用NUR算法

使用“最近是否访问过”标记近似代替LRU中的时间,只需要在页表中增加一列作为0/1的标记位。当某页面被访问时,将此标记位置1。系统周期性地将所有标记位清零

当需要淘汰时,从标记位为0的页面中任选一个换出

时钟Clock算法

是对NUR算法的一种改进,消除了“任选一个”的不确定性

除了在页表中增加一列作为0/1标记位记录最近是否被访问外,还增加一列存放指针,将内存中的所有页面构成一个循环链表。当某页面被访问时,将该页面在页表中的标记位置1

在页表中,还需要一个额外的指针(称为替换指针

当需要置换时,执行以下步骤

  1. 将替换指针沿链表向后推进一个位置

  2. 若替换指针所指表项标记位为0,则选择该页面换出;否则,将当前替换指针所指表项标记位置为0,然后回到第一步

Clock算法的一种改进

考虑到将修改过的页面换出时,需要写到外存花费较多时间,因此结合页面是否被修改过得到以下四种页面(A表示访问标记位、M表示修改位,按淘汰优先级从高到低列出)

  1. A=0且M=0,最佳

  2. A=0且M=1,不是很好

  3. A=1且M=0,该页面可能即将被访问

  4. A=1且M=1,开销最大

在替换指针循环寻找要淘汰的页面时:

  • 第一轮先扫描第一类,找到则将该页面换出

  • 第二轮扫描第二类,找到则将页面换出

  • 将所有的标记位置为0

  • 第三轮扫描第三类(原来的第三类事实上已经变成了第一类),找到则将页面换出

  • 第四轮扫描第四类(原来的第四类事实上已经变成了第二类),找到则将页面换出

理想型淘汰OPT算法

选择将来再也不会使用到的,或是下一次访问离当前最远的页面淘汰

此算法是基于“已知进程的访问串”(即知道进程执行时访问页的顺序)的前提,由于无法预测未来,这种算法显然无法实现

二级页表与多级页表

页表有两个特点,同时也是两个缺陷

  • 每个页表在物理内存中是连续存放的

  • 某进程开始执行,其页表必须全部调入内存

当每个进程的虚拟空间很大时,其页表也会很大,若按照原来页表的思路,单单页表就会占用大量内存,而且是很大一段连续的内存

此处以一个进程的虚拟空间为 2 64 2^{64} 264、页面大小为4K为例,计算下一个页表最多有多大(需要注意的是,内存管理中最小单位为B,即以 2 8 2^8 28为单位使用内存空间)

  1. 一个页表最多可以有的页数为 2 64 4 × 2 10 = 2 52 \frac{2^{64}}{4\times2^{10}}=2^{52} 4×210264=252

  2. 页表中一个表项的大小至少为 ⌈ log ⁡ 2 8 2 52 ⌉ = 7 \lceil\log_{2^8}{2^{52}}\rceil=7 log28252=7字节

  3. 一个页表占用的总空间为 7 × 2 52 = 28 P 7\times2^{52}=28P 7×252=28P字节,即28PB

  4. 一个页表占用的块数为 28 × 2 50 4 × 2 10 = 7 T \frac{28\times2^{50}}{4\times2^{10}}=7T 4×21028×250=7T

将页表分成多级索引,即设计页表的页表(即二级页表)、甚至页表的页表的页表(即三级页表)等可以解决上述的两个问题

页式管理的缺陷

页式管理相较于分区管理,已经解决了很多问题,但仍有一些问题

  1. 地址变换都需要硬件变换机构,成本增加

  2. 有缺页中断,系统开销增加

  3. 有抖动现象的风险

  4. 同一个页中可能存有两个不同的过程,每个页面在逻辑上并不完整,无法进行共享

  5. 一般只能进行静态链接

  6. 一个页表最多可以有的页数为 2 64 4 × 2 10 = 2 52 \frac{2^{64}}{4\times2^{10}}=2^{52} 4×210264=252

  1. 页表中一个表项的大小至少为 ⌈ log ⁡ 2 8 2 52 ⌉ = 7 \lceil\log_{2^8}{2^{52}}\rceil=7 log28252=7字节

  2. 一个页表占用的总空间为 7 × 2 52 = 28 P 7\times2^{52}=28P 7×252=28P字节,即28PB

  3. 一个页表占用的块数为 28 × 2 50 4 × 2 10 = 7 T \frac{28\times2^{50}}{4\times2^{10}}=7T 4×21028×250=7T

将页表分成多级索引,即设计页表的页表(即二级页表)、甚至页表的页表的页表(即三级页表)等可以解决上述的两个问题

页式管理的缺陷

页式管理相较于分区管理,已经解决了很多问题,但仍有一些问题

  1. 地址变换都需要硬件变换机构,成本增加

  2. 有缺页中断,系统开销增加

  3. 有抖动现象的风险

  4. 同一个页中可能存有两个不同的过程,每个页面在逻辑上并不完整,无法进行共享

  5. 一般只能进行静态链接

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值