存储管理

本文深入探讨了计算机存储管理的核心概念,包括分层存储器体系、内存抽象、地址空间、动态重定位及虚拟内存等技术。重点阐述了交换技术、多级页表、页面置换算法以及如何优化分页系统性能。此外,文章还对比了分页与分段技术,揭示了它们在设计、功能和应用方面的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 内存是计算机中一种需要认真管理的重要资源。每个程序员都梦想拥有这样的内存:它是私有的、容量无限大的、速度无限快的,并且是永久性的存储器。但是目前的技术还不能为我们提供这样的内存。人们就提出了“分层存储器体系”的概念。操作系统中管理分层存储器体系的部分称为存储管理器。

最简单存储器抽象就是根本没有抽象。简单的就说就是直接采用物理地址,这样的组织,通常同一时刻只能有一个进程在运行。而且在没有内存抽象的系统中实现并行的一种方法是使用多线程来编程。但是这个没有被广泛应用,因为人们通常希望能够在同一时间运行没有关联的程序,而这正是线程抽象所不能提供的。而且在这种模式下运行程序,很容易导致程序崩溃。总之将物理地址暴露给进程会带来下面几个严重问题,第一,如果用户程序可以寻址到内存的每个字节,它们就可以很容易地破坏操作系统,第二使用这种模型,想要同时运行多个程序是很困难的。

所以为了保证多个应用程序同时处于内存中并且不互相影响,则需要解决两个问题:保护和重定位。那么一个更好的方法就是创建一个新的内存抽象:地址空间。就像进程的概念创造了一类抽象的CPU以运行程序一样,地址空间为程序创造了一种抽象的内存。采用了抽象的地址空间后,如何进行与物理地址进行映射呢?解决方案之一就是动态重定位,使用CPU配置的两个特殊硬件寄存器,叫基址寄存器和界限寄存器。前者存放程序起始的物理地址,后者存放程序的长度。然后采用跳转指令进行程序间的切换。但是使用基址寄存器和界限寄存器有个缺点是,每次访问内存都需要进行加法和比较运算。比较可以做的比较快,但是加法由于进位传递时间的问题,有时候会显得很慢。

如果计算机物理内存足够大,可以保存所有进程,那么之前提及的所有方案都或多或少是可行的。但实际上,所有进程所需要的RAM数量总和通常远远超出存储器能够支持的范围。有两种方式进行处理,简单的策略师交换技术,另一种策略是虚拟内存。前者是把一个进程完整调入内存,使该进程运行一段时间,然后把它存回磁盘。空闲进程主要存放在磁盘上,所以当它们不运行时就不会占用内存。后者是使程序在只有一部分调入内存的情况下运行。

关于交换在内存中会产生了多个空闲区(也叫空洞),需要进行内存紧缩,将小的空闲块合成一大块。如果进程创建时其大小固定不变,则分配很简单。但是如果进程的数据段可以增长,那么分配就会有问题。这时就需要动态的分配内存了,一般由两种方式跟踪内存使用情况:位图和空闲链表。按前者分配单元有一个重要的设计因素,分配单元越小,位图越大。因此内存的大小和分配单元的大小决定了位图的大小。后者是维护一个记录已分配内存段和空闲内存段的链表。那么关于对链表的管理有以下几种算法:

1)首次适配(FF)算法

2)下次适配(NF)算法

3)最佳适配(BF)算法

4)最差适配(WF)算法

5)快速适配(QF)算法

 第一种是从链表进行搜索,直到找到一个足够大的空闲区进行分配。第二种同第一种不同的是每次找到合适的空闲区时都记录当时的位置。以便在下次寻找空闲区时从上次结束的地方开始搜索,而不像第一种方式每次都是从头开始搜索。第三种是搜索整个链表,找出能够容纳进程最小的空闲区。第四种是为了避免第三种方式导致大量非常小的空闲区,总是分配最大的可用空闲区。最后一种是为那些常用大小的空闲区维护单独的链表,例如:有一个n项的表,该表第一项是指向4KB大小的空闲区,第二项是指向8KB大小的空闲区,第三项是指向12KB的空闲区,依次类推。

 

虚拟内存

尽管基址寄存器和界限寄存器可以用于创建地址空间的抽象,但是还有一个问题需要解决:管理软件的膨胀。这需要将程序分割成许多片段,称为覆盖。然后由系统进行实际的覆盖块换入换出操作,这样的方式称为虚拟内存。

虚拟内存的基本思想是:每个程序拥有自己的地址空间,这个空间被分割成多个块,每一块称作一页或页面。每一页有连续的地址范围。这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。而且虚拟内存特别适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中,当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。

大部分虚拟内存系统中都使用一种称为分页的技术。由程序产生的这些地址称为虚拟地址,它们构成了一个虚拟地址空间。而使用虚拟地址时,它不是被直接送到内存总线上,而是被送到内存管理单元(MMU),通过MMU将虚拟地址映射成物理地址。虚拟地址空间按照固定大小划分成称为页面(page)的若干单元。在物理内存中对应的单元称为页框(page frame)。页面和页框的大小通常是一样的。如何将虚拟地址映射成为物理地址呢?这就需要采用一种数据结构为页表的方式,那么虚拟地址被分为虚拟页号(高位部分)和偏移量(低位部分)两个部分。那么虚拟页号可用做页表的索引。从数学角度说,页表是一个函数,它的参数是虚拟地址,结果是物理页框号。下面是页表项结构图:

高速缓存禁止位

访问位

修改位

保护位

“在/不在”位

页框号

一般页表项大小通常是32位的。

高速缓存禁止位是用于I/O设备上的处理

访问位是用于页面淘汰的处理

修改位很简单,记录页面是否修改过

保护位也简单,就是指明页面类型,如:读、写、执行等。

“在/不在”位,表示该页表项是否有效,可以使用。

最后再次强调,虚拟内存本质上是用来创造一个新的抽象概念——地址空间,这个概念是对物理地址的抽象,类似于进程是对物理CPU的抽象。

如何提高分页的处理速度呢?这里需要考虑两个主要问题:

1)虚拟地址到物理地址的映射必须非常快。

2)如果虚拟地址空间很大,页表也会很大。

 对于第一个问题,如果执行一条指令需要1ns,页表查询必须在0.2ns之内完成。目前大多数计算机都采用了转换检测缓冲区(TLB),采用此方式,就避免了过多的去访问内存中的页表。TLB有两种方式一种是硬件MMU来处理(TLB包含在MMU设备中),一种是软件TLB管理。不过在硬件的中,TLB里的包含很少的表项,一般很少会超过64个。 另外就是关于TLB失效的处理,不管是硬件还是软件在处理,常见方法都是找到页表并执行索引操作以定位将要访问的页面。这里有两个概念要区分,一个软失效,一个硬失效,前者指页面访问在内存中而不在TLB的情况,后者指页面即不在内存中也不在TLB的情况。

对于第二个问题,一种方式是采用多级页表,会进一步对32位的虚拟地址进行划分,10位PT1域,10位PT2域和12位偏移量。PT1表示一级页表索引,PT2表示二级页表索引。对于32位的操作系统多级页表可以很好的发挥作用,但是对于64位的操作系统而言,那么情况就发生了彻底变化,解决的方式之一就是倒排页表方式(类似于一种散列捅)。

但是页面总是会无法命中,那么这时候就需要一种算法来处理这种缺页中断的情况。下面介绍相关的页面置换算法。

页面置换算法

最优页面置换算法,它无法实现,因为操作系统无法知道各个页面下一次将在什么时候被访问。

最近未使用页面置换算法(NRU),是每个页面设置两个状态位。当页面被访问时设置为R位,当页面被写入时设置M位。共有四种分类情况:

第0类:没有被访问,没有被修改。

第1类:没有被访问,已被修改。

第2类:已被访问,没有被修改。

第3类:已被访问,已修改。

可能第1类似乎不可能,但是第3类的R位被时钟中断清零时就成了第1类了。时钟中断不清除M位是因为在决定一个页面是否需要写回磁盘时将用到这个信息。该算法随机淘汰从类编号最小的非空类里的一个页面。另外性能不是最好,但是已经够用了。

先进先出置换算法(FIFO),维护一个当前所有内存中页面的链表,最新进入的页面放在表尾,最久进入的页面放在表头。因为它可能会吧经常使用的页面也置换出去,所以很少使用纯粹的FIFO算法。

第二次机会页面置换算法,是在FIFO算法的基础上,进行了修改:判断R位,如果是0,那么这个页面有老又没有被使用,可以立即换掉;如果是1,那么将R位清0,并把该页面放到链表的尾端。

时钟页面置换算法,是因为第二次机会页面置换算法,它经常要在链表中移动页面,既降低了效率又不是很有必要,一个更好的办法是把所有的页面都保存在一个类似钟面的环形链表中,一个指针指向最老的页面,发送缺页中断时,R位是0,那么就淘汰该页面,并把新的页面插入这个位置,然后把指针前移一个位置;如果R位是1就清除R位并把指针前移一个位置,重复这个过程直到找到R位为0的页面为止。

最近最少使用页面算法(LRU),在缺页中断时,置换未使用时间最长的页面。虽然LRU理论上可以实现,但是代价很高。一种实现方式也是维护一个所有页面的链表,最近最多使用的页面在表头,最近最少使用的页面在表尾。困难的是每次访问内存需要更新整个链表。需要做移动和删除操作非常费时。一种是硬件实现也非常耗时,硬件的实现需要维持一个初值为0的nXn的矩阵。当访问到页面k时,硬件首先把k行都设置成1,再把k列都设置成0。任何时刻二进制数值最小的行就是最近最少使用的。接着说明下软件的实现方式,一种方式是最不常用算法(NFU),该算法是将每个页面与一个软件计数器相关联,计数器的初值为0。每次时钟中断时,将每个页面的R位(它的值是1或0)加到它的计数器上。当缺页中断时,置换计数器最小的页面。该算法有个缺陷就是有时候将置换有用的页面而不是不再使用的页面。所以对该算法进行小小的修改,首先,在R位被加进之前将计数器右移一位;其次,将R位加到计数器的最左端的位而不是最右端的位。修改后的算法叫老化算法。该算法与LRU的区别,在于当有两个页面如何区分那个页面较早的时间访问以及那个页面在较晚的时间被访问。另一个是计数器的有限位数,这将限制了其对以往页面的记录。不过实践中,如果时钟滴答是20ms,8位一般是够用了。假设一个页面已经有160ms没有被访问过,那么它很可能并不重要了。

工作集页面置换算法,是根据测试发现,大部分进程不是需要把全部的页面在内存中,它们都表现出了一种局部性访问行为,即在进程运行的任何阶段,它都只访问较少的一部分页面。我们把 一个进程当前正在使用的页面的集合称为工作集。若每执行几条指令程序就发生一次缺页中断,那么就称这个程序发生了颠簸。工作集的基本算法是每个表项至少有两条信息:上次使用该页面的近似时间和R位。在处理时,都需要检查R位,如果它是1,就把当前实际时间写进页表项的“上次使用时间”域,以表示缺页中断发生时该页面正在被使用。它应该在工作集中。如果是0,那么表示在当前时钟滴答中,该页面还没有被访问过,则作为候选者被置换。为了知道它是否应该被置换,需要计算它的生存时间(即当前实际运行时间减去上次使用时间),然后与称为在过去的T秒(实际运行时间中进程访问过的页面集合,关于T秒的定义可以比作在过去的10ms中进程访问内存所用到页面的集合)做比较,大于就置换,反之则该页面仍然在工作集中。这样就要把该页面临时保留下来,但是要记录生存时间最长的页面。如果扫描完整个页面却没有找到合适被淘汰的页面,也就需要淘汰生存时间最长的页面了。

工作集时钟页面置换算法,类似于时钟页面置换算法,需要一个循环链表。这里就不细说了。

总结下置换算法各特点:

算法

注释

最优算法

不可实现,但可用作基准

NRU算法(最近未使用)

LRU的相对粗略的近似

FIFO算法

可能抛弃重要页面

第二次机会算法

FIFO有改善

时钟算法

现实的

LRU算法(最近最少使用)

很优秀,很难实现

NFU算法(最不经常使用)

LRU的相对粗略的近似

老化算法

非常近似LRU的有效算法

工作集算法

实现起来开销大

工作集时钟算法

好的有效算法

最好的两种算法是老化算法和工作集时钟算法,它们分别基于LRU和工作集。具有良好的页面调度性能,可以有效地实现。

分页系统中的设计问题

第一就是局部分配策略与全局分配策略,简单地说,就是进程间采用什么策略,进程内采用什么策略。第二个事负载控制,减少在内存中的进程,惟一的方式就是暂时从内存中去掉一些进程。只能采用交换的方式,交换是用来减少内存潜在的需求,而不是收回它的页面。第三个页面大小,设置太小分配频繁,设置太大导致空间浪费,产生内部碎片。第三个是分离指令空间和数据空间。第三个是共享页面,在大型多道程序设计系统中,几个不同的用户同时运行同一个程序很常见的。显然,由于避免了在内存中有一个页面的两个副本,共享页面效率更高。第四个是共享库,这个是用其他的粒度取代单个页面来实现共享。如果一个程序被启动两次,大多数操作系统会自动共享所有的代码页面,而在内存中只保留一份代码的副本。第五个是内存映射文件,其机制思想是:进程可以通过发起一个系统调用,将一个文件映射到其虚拟地址空间的一部分。在多数的实现中,在映射共享的页面时不会实际读入页面的内容,而是在访问页面时才会被每次一页地读入。第六个事清除策略,这样去管理和清除空闲页框。第七个是虚拟内存接口,之前是假定虚拟内存对进程和程序员都是透明的,但是可以通过此方式允许程序员对内存映射进行控制,其原因之一就是为了允许两个或多个进程共享同一部分内存(另外就是叫做分布式共享内存)。

有关实现的问题

与分页有关的工作有四个时段:进程创建时、进程执行时、缺页中断时和进程终止时。指令备份,当程序访问不在内存中的页面时,引起缺页中断的指令会半途停止并引发操作系统的陷阱。在操作系统取出所需的页面后,它需要重新启动引起陷阱的指令,但这并不是一件容易实现的事情。幸运的是,CPU的设计者通过一个隐藏的内部寄存器。在每条指令执行之前,把程序计数器的内容复制到该寄存器。锁定内存中的页面,这是对I/O操作有帮助的,当I/O操作在执行读写操作时,如果产生一个中断,那么这些进行数据写操作的相关页面进行锁住,使其无法置换出去。后备存储,我们已经知道了如何选择换出内存的页面。但是却没讨论当前页被换出时会存放在磁盘上的哪个位置。方法有二,一种是磁盘的交换区与内存中虚拟地址空间一样大,一种是动态分配,磁盘上没有固定地址,只保存交换出来的页面。最后是策略与机制分离,一个如何分离策略和机制的简单例子是,我们将存储管理系统被分为三个部分:1)一个底层MMU处理程序。2)一个作为内核一部分的缺页中断处理程序,3)一个运行在用户空间中的外部页面调度程序。这种实现的优势是有更多模块化代码和更好的适应性。

分段

之前讲都是将虚拟地址内存都是一维的,但是对于许多问题来说,有两个或多个独立的地址空间可能比只有一个要好得多。比如:一个编译器在编译过程中会建立许多表,其中可能包括:
1)源文件

2)符号表

3)所有整型量和浮点常量表

4)语法分析树

5)编译器内部的堆栈

下面通过对比来说明下分页与分段的区别

考察点

分页

分段

需要程序员了解正在使用这种

技术吗?

存在多少线性地址空间?

1

许多

整个地址空间可以超出物理存储器

的大小吗?

过程和数据可以被区分并分别被

保护吗?

其大小浮动的表可以很容易

提供吗?

用户间过程的共享方便吗?

为什么发明这种技术

为了得到大的线性

地址空间而不必购买

更大的物理存储器

为了使程序和数据

可以被划分为逻辑上

独立的地址空间并且

有助于共享和保护

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值