内存管理<原理篇>(九、页帧和其他问题)

9.1 帧分配

本来以为上一篇写了就结束了,想不到后面还有一个重要的知识点,就是帧分配。

所谓的帧分配:就是操作系统一共有93个空闲帧和2个进程,我们怎么个分配法?

9.1.1 帧的最小数

帧分配策略受到多方面的限制。

例如:

  1. 所分配的帧不能超过可用帧的数量(除非有页面共享)
  2. 必须分配至少最小数量的帧。

第一个很明显,分配帧是肯定不能超过可用帧,要不然用啥呢?

第二个为啥需要至少最小数量的帧?因为分配给每个进程的帧数量的减少,缺页错误率也增加,从而减慢进程执行。此外,若在执行指令完成之前发生缺页错误,应重新启动指令。因此,必须有足够的帧来容纳任何单个指令可以引用的所有不同的页面。(隐私就是执行这条指令可能需要用到多个页面,比如一个指令页,一个数据页,就需要至少两个页帧,如果只有一个页帧,不就加载了这个,另一个又缺页了么,这就是个死循环)

课程上也介绍了,最小帧数由计算机架构定义。有些计算机体系结构允许多级的间接寻址,就怕这个寻址寻到另一个页面,另一个页面也是寻址,再次寻到另一个页面,这样就需要很多个页面了。(跟多重指针一样)

所以,可以限制一个指令只能最多16级的间接引用。当发生第一次间接引用时,计算器被设置为16,这样每间接访问一个页面,计算器就减减,直到计算机器递减为0,就会出现一个陷阱(应该是错误)。

9.1.2 分配算法

  1. 平均分配

我们第一个想到的肯定是平均分配。

在n个进程中分配m个帧,我们使用平均分配,每个进程有m/n帧。

例如:有93个帧和5个进程,那么每个进程将获得18个帧,剩余3帧可以用作空闲帧缓冲池。

  1. 比例分配

我们都知道平均分配一般都是理想化,如果有一个进程需要内存比较大,一个进程需要内存比较小,这样平均分配,确实不靠谱。所以又进化出了一个比例分配。

比例分配就是有点像我们学习的加权分配。

a i = s / ∑ ( s i ) ∗ m ( m 是 可 用 帧 数 ) a_i = s/\sum(s_i) * m (m是可用帧数) ai=s/(si)m(m)

这个看公司就明白是加权平均。

如果可用帧数是62,一个进程需要10K内存,一个进程需要127K,我们按加权平均。

小内存进程分到4帧,大进程分到57帧,这样比较合理。

  1. 注意:对于平均分配或比例分配,高优先级进程与低优先级进程同样处理。当然,如果需要高优先级进程加速执行,我们可以根据进程优先级和内存大小来组合分配。

9.1.3 全局分配与局部分配

还记得我上一节提到的,我们置换算法只置换我们当前进程的页面,还是置换整个系统的页面,这一节就介绍了。

全局置换:允许一个进程从所有帧的集合中选择一个置换帧。

局部置换:要求每个进程只从它自己分配的帧中选择。

9.1.5 非均匀内存访问

有一些计算机使用的是非均匀内存访问(NUMA)系统,CPU和内存不在一个主板上,并且毫无例外的,他们要慢于内存和CPU位于同一个主板的。

9.1.6 页面大小

对于现有机器,操作系统设计人员很少可以选择页面大小。然而,对于正在设计的新机器,必须对最佳页面大小做出决定。

其实并没有最佳页面大小,而是有很多因素影响页面大小:

  1. 减少页面大小增加了页面的数量,从而增加了页表的大小
  2. 较小的页面可以更好的使用内存,减少内存碎片
  3. 读取或写入页面所需时间,小页面速度会更快
  4. 大页面会减少缺页中断

9.1.7 分离的指令空间和数据空间

有一些计算机为指令(程序正文)和数据设置分离地址空间,分别称为I空间和D空间。两种地址空间都可以进行分页,而且相互独立。

9.2 系统抖动

如果一个进程的调页时间多于它的执行时间,那么这个进程就在抖动。

在这里插入图片描述

这里CPU利用率是按多道程序来绘制的。随着多道程序的增加,CPU利用率也增加,虽然增加的更慢,直到达到最大值。如果多道程序还要进一步增加,那么系统抖动就开始了,并且CPU利用率急剧下降。此时,为了提高CPU利用率并停止抖动,必须降低多道程序。

这是因为多道程序增加之后,使用的内存也增加,导致其他进程的内存开始缺页,缺页之后就请求调页,如果请求了调页后,又可能导致其他进程缺页,然后其他进程又调页,这样形成了颠簸。

可以通过局部置换算法优先级置换算法可以限制系统抖动。

如果一个进程开始抖动,那么由于采用局部置换,它不能从另一个进程中获取帧,而且也不能导致后者抖动。但是这样也不能完全解决问题,因为这样会导致大多数时间都在等待请求调页,缺页错误的平均等待时间也会增加,有效访问时间也会增加。

9.3 工作集

工作集模型是基于局部性假设的,这个模型采用参数Δ定义工作集窗口。它的思想是检查最近Δ个页面引用。这最近Δ个页面引用的页面集合称为工作集。

如果一个页面处于活动使用状态,那么它处在工作集中。如果它不再使用,那么它最后一次引用的Δ时间单位后,会从工作集中删除。因此工作集是程序局部的近似。

9.4 预调页面

纯请求调页的一个明显特性是,当进程启动时,发生大量的缺页错误。这种情况源于试图将最初局部调到内存。在其他时候也可能会出现同样情况。比如,当换出进程重新启动时,它现在的所有页面都在磁盘上,而且每个页面都会通过缺页错误而调进内存。

预调页面试图阻止这种大量的最初调页。这种策略是同时调进所需的所有页面。有的操作系统对于小文件采用预调页面。

例如:对于采用工作集模型的系统,可以为每个进程保留一个位于工作集内的页面列表。当一个进程必须暂停时(由于I/O的等待或空闲帧的缺少),它的进程工作集也应该记住。

当这个进程被重启时(由于I/O已经完成或已有足够的可用空闲帧),在重启之前自动调入它的整个工作集。

当然也是有缺点的,采用预调页的成本是否小于处理相应缺页错误的成本,通过预调页面而调进内存的许多页面也有可能没有使用。

9.5 有关实现问题

都是一大篇文字,真的让我头大。

9.5.1 与分页有关工作

  1. 进程创建时:

创建一个进程时,操作系统要确定程序和数据在初始时有多大,并为它们创建一个页表。操作系统还要在内存中为页表分配空间并对其初始化。

  1. 进程被换出:

页表不需要驻留在内存中,另外操作系统要在磁盘交换区分配空间,以便在一个进程换出时在磁盘上有放置的空间。

  1. 调度进程执行时:

必须为新进程重置MMU,刷新TLB,以清除以前的进程遗留的痕迹。新进程的页表必须成为当前页表,通常是把指向这个页表的指针赋值给硬件寄存器就可以了。

  1. 缺页中断时:

操作系统必须通过读硬件寄存器来确定是哪个虚拟地址造成了缺页中断。通过该信息,它需要计算哪个页面,并在磁盘上对该页面定位。它必须找到合适的页框来存放新页面,必要时还要置换老的页面,然后把所需的页面读入页框。最后还要回退程序计数器,使程序计算器指向引起缺页中断的指令,并重新执行该指令。

  1. 进程终止时:

操作系统必须释放进程的页表。页面和页面在硬盘上所占用空间。如果某些页面是与其他进程共享的,当最后一个使用他们的进程终止时候,才可以释放内存和磁盘上的页面。

9.5.2 缺页中断处理

现在终于可以讨论缺页中断发生的细节了,缺页中断发生时的事件顺序如下:

  1. 硬件陷入内核,在堆栈中保存程序计数器。大多数机器讲当前指令的各种状态信息保存特殊的CPU寄存器中。

  2. 启动一个汇编代码例程保存通用寄存器和其他易失的信息,以免被操作系统破坏。这个例程将操作系统作为一个函数来调用。

  3. 当操作系统发现一个缺页中断时,尝试发现需要哪个虚拟页面。通常一个硬件寄存器包含了这一信息,如果没有的话,操作系统必须检索程序计数器,取出这条指令,用软件分析这条指令,看看它在缺页中断时正在做什么。

  4. 一旦知道了发生缺页中断的虚拟地址,操作系统检查这个地址是否有效,并检查存取与保护是否一致,如果不一致,向进程发出一个信号或杀掉该进程。如果地址有效且没有保护错误发生,系统则检查是否有空闲页框。如果没有空闲页框,执行页面置换算法寻找一个页面来淘汰。

  5. 如果选择了页框”脏”了,安排该页写回磁盘,并发生一次上下文切换,挂起产生缺页中断的进程,让其他进程运行直至磁盘传输结束。无论如何,该页框被标记为忙,以免因为其他原因而被其他进程占用。

  6. 一旦页框"干净"后(无论是立刻还是在写回磁盘后),操作系统查找所需页面在磁盘上的地址,通过磁盘操作将其装入,该页面正在被装入时,产生缺页中断的进程仍然被挂起,并且如果有其他可运行的用户进程,则选择另一个用户进程运行。

  7. 当磁盘中断发生时,表面该页已经被装入,页表已经更新可以反映它的位置,页框也被标记为正常状态。

  8. 恢复发生缺页中断指令以前的状态,程序计数器重新指向这条指令。

  9. 调度引发缺页中断的进程,操作系统返回调用它的汇编语言例程。

  10. 该例程恢复寄存器和其他状态信息,返回到用户空间继续执行,就好像缺页中断没有发生过一样。

9.5.3 指令备份

当程序访问不在内存中的页面时,引起缺页中断的指令会半途停止并引发操作系统的陷阱。在操作系统取出所需的页面后,它需要重新启动引起陷阱的指令。但这并不是一件容易实现的事。(到底容易不容易,还真不知道,哈哈哈。)

幸运的是,在某些计算机上,CPU的设计者们提供了一种解决方法,就是通过使用一个隐藏的内部寄存器。在每条指令执行之前,把程序计数器的内容复制到该寄存器。这些机器可能会有第二个寄存器,用来提供哪些寄存器已经自动增加或自动减少以及增减的数量等信息。通过这些信息,操作系统可以消除引起缺页中断的指令所造成的所有影响,并使指令可以重新开始执行。如果该信息不可用,那么操作系统就要找出所发生的问题从而设法来修复它。

9.5.4 锁定内存中的页面

通过上面的缺页中断处理,我们就知道了,在我们请求调页的情况下,我们会把交换空间的数据读取到内存中,但是如果分页算法是全局算法,我们用的页面是有可能(虽然机会很小)被选中换出内存,这样不就鸡飞蛋打了,所以需要把这个内存页面所锁住,就不会再被其他进程影响到了。

9.5.5 后备存储

在前面讨论过的页面置换算法中,我们已经知道了如何选择换出内存的页面。但是却没有讨论当页面被换出时会存放在磁盘上的哪个位置,现在我们讨论一下磁盘管理相关问题。

在磁盘上分配页面空间的最简单的算法是在磁盘上设置特殊的交换分区,甚至从文件系统划分一块独立的磁盘,大多数unix是这样处理的。在这个分区里没有普通的文件系统,这样就消除了将文件偏移转换成地址的开销,取而代之的是,始终使用相应分区的起始块号。

  1. 静态交换区

在这里插入图片描述

当一个进程启动时,留出与这个进程一样大的交换区块,剩余的为总空间减去这个交换分区。(进程的虚拟内存是4G,然后unix的交换空间跟内存一样大?那这怎么放的下几个进程?难道只是放有使用的部分的??)。进程结束后,会释放其磁盘上的交换区。交换区是怎么形式组织的,这个后面再说,感觉是不是也是以空闲列表的形式,猜测的啊。

因为我们是进程整个页面对应的,所以要写回一个页面时,主要将虚拟地址空间中页面的偏移量加到交换区开始地址,就可以找出在哪了。

但是这种方式,在装载的时候其实也是有两种情况的:

(1)将整个进程映像复制到交换区,以便随时可以将所需内容装入。

(2)将整个进程装入内存,并在需要时换出

这里就得出我们的结论了,最好正文、数据和堆栈分别保留交换区,并且允许这些交换区在磁盘上多于一个块。(就不用整个都考到交换区,并且数据段可能会增长)

  1. 动态备份页面

在这里插入图片描述

这种就是事先不做处理,在页面换出的时候为其分配磁盘空间,并在换入时回收磁盘空间,这样内存中的进程不必固定于任务交换空间。其缺点是内存中每个页面都要记录相应的磁盘地址。换言之,每个进程都必须有一张表,记录每一个页面在磁盘上的位置。

Windows就没有固定的交换分区,所以使用的是正常文件系统中的一个或多个较大、事前定位的文件。

还是继续做优化:每个进程的程序正文一般都是只读的,所以在内存资料紧张的时候,是可以把程序也移除内存的,需要的时候,再去读。(我记得之前说的安卓系统好像就是这样做的)

9.5.6 策略和机制的分离

这一节主要是介绍这个整体代码的分离思路,目前有没看到linux内核的源码,所以这一节先不写了,反正缺页请求的流程就是这样了,所有目前就先不做这一节的笔记了。

9.6 总结

没想到这一节写了这么多,并且也没想到,最后的9.5介绍了挺多硬核的东西,本来之前按原计划是开始看内核代码,不过现在计划有变,内存的内核源码以后在看,虚拟内存的原理篇倒是都看完了。后面还有很多知识要看,继续加油。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值