#操作系统
与“分页”最大的区别就是——离散分配时所分配的地址空间的基本单位不同。
- 进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址。
- 内存分配规则:以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻。
由于是按逻辑功能模块划分,用户编程更方便,程序的可读性更高。
分段系统的逻辑地址结构由段号(段名)和段内地址(段内偏移量)所组成。
- 段号(segment number)的位数决定了每个进程最多可以分几个段;
- 段内(offset)地址位数决定了每个段的最大长度是多少。
程序分多个段,各段离散地装入内存,为了保证程序能正常运行,就必须能从物理内存中 找到各个逻辑段的存放位置。为此,需为每个进程建立一张段映射表,简称“段表”(segment table),即图中的黑框部分。
段表中的每个段对应一个段表项,其中记录了该段在内存中的起始位置(base,又称 “基址”)和段的长度(limit)。虽然内存中的每个段的大小不同,但是各个段表项的长度是相同的(很容易理解,记录这些段信息的每一行的长度是相同的)。
【例子】
某系统按字节寻址,采用分段存储管理,逻辑地址结构为段号(segment number) 16位, 段内地址(offset) 16位,因此用16位,即可表示最大段长(max limit)。物理内存大小为4GB(可用32位表示整个物理内存地址空间)。
因此,可以让每个段表项占 16(offset)+32(base)= 48位,即6B(48bit)。由于段表项长度相同,因此段号可以是隐含的,不占存储空间(数组根据地址求序号)。若段表存放的起始地址为 M
,则 K号段对应的段表项存放的地址为 M + K*6
。
计算机中存储进程的分段信息,位于分段寄存器(segment registers)中。
- 根据逻辑地址得到段号(segment No.)、段内地址(Offset)。
- 判断段号是否越界,越界,则中断;
- 查询段表(segment table),找到对应的段表项。
- 检查段内地址是否超过段长(limit), 越界,则中断;
- 根据偏移,计算得到物理地址。
段 | 页 | |
---|---|---|
存储层次 | 段是信息的逻辑单位。 一个段通常包含着一组属于一个逻辑模块的信息。 | 页是信息的物理单位。 |
目的 | 分段的主要目的是更好地满足用户需求。 | 分页的主要目的是为了实现离散分配,提高内存利用率。 |
使用者可见 | 分段对用户是可见的,用户编程时需要显式地给出段名。 | 分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。 |
大小 | 段的长度却不固定,决定于用户编写的程序。 | 页的大小固定且由系统决定。 |
维度 | 分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。 | 分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址。 |
需要单独提出来的是,分段比分页更容易实现信息的共享和保护。不能被修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的。可修改的代码是不能共享的。
优点 | 缺点 | |
---|---|---|
分页管理 | 内存空间利用率高,不会产生外部碎片,只会有少量的页内碎片 | 不方便按照逻辑模块实现信息的共享和保护 |
分段管理 | 很方便按照逻辑模块实现信息的共享和保护 | 如果段长过大,为其分配很大的连续空间会很不方便。另外,段式管理会产生外部碎片 |
分段管理中产生的外部碎片也 可以用“紧凑”(Compress,压缩)技术来解决,只是需要付出较大的时间代价。
段页式综合两种方式。将进程按逻辑模块分段(segment),再将各段分页(paging,如每个页面4KB),再将内存空间分为大小相同的内存块(page frame,页框/页帧/物理块),进程前将各页面分别装入各内存块中。
段页式系统的逻辑地址结构由段号、页号、页内地址(页内偏移量)组成。如上图。
- 段号(Segment No.)的位数决定了每个进程最多可以分几个段
- 页号(Page No.)位数决定了每个段最大有多少页
- 页内偏移量(Page Offset)决定了页面大小、内存块大小是多少。
“分段”对用户是可见的,程序员编程时需要显式地给出段号、段内地址。而将各段“分页”对用户是不可见的。系统会根据段内地址自动划分页号和页内偏移量。因此段页式管理的地址结构是二维的。 段页式存储,囊括了段表和页表。
- 每个段对应一个段表项,每个段表项由段号、页表长度、页表存放块号(页表起始地址)组成。每个段表项长度相等,段号是隐含的。
- 每个页面对应一个页表项,每个页表项由页号、页面存放的内存块号组成。每个页表项长度相等,页号是隐含的。
- 根据逻辑地址得到段号、页号、页内偏移量;
- 判断段号是否越界;
- 查询段表,找到对应的段表项;
- 检查页号是否越界;
- 根据页表存放块号、页号查询页表,找到对应页表项;
- 根据内存块号、页内偏移量得到最终的物理地址;
- 访问目标内存单元。
传统存储管理方式,包含上述的连续分配(单一连续、固定分区、动态分区)、非连续分配(分页、分段、段页式),具有明显的确定。
- 一次性:作业必须一次性全部装入内存后才能开始运行。这会造成两个问题:
- 作业很大时,不能全部装入内存,导致大作业无法运行;
- 当大量作业要求运行时,由于内存无法容纳所有作业,因此只有少量作业能运行,导致多道程序并发度下降。
- 驻留性:一旦作业被装入内存,就会一直驻留在内存中,直至作业运行结束。 事实上,在一个时间段内,只需要访问作业的一小部分数据即可正常运行,这就导致了内存中会驻留大量的、暂时用不到的数据,浪费了宝贵的内存资源。
- 时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据 被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量的循环) 。
- 空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。 (因为很多数据在内存中都是连续存放的,并且程序的指令也是顺序地在内存中存放的)。
基于局部性原理,
- 在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。
- 在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
- 若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
因此,在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存。
请求分页存储管理(demand paging):
- 在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
- 若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
请求页表项,除了内存块号外,增加了四个字段:
- 状态位,表示是否已调入内存。
- 访问字段,可记录最近被访问过几次,或记录上次访问的时间,供置换算法选择换出页面时参考。
- 修改位,页面调入内存后是否被修改过。
- 外存地址,页面在外存中的存放位置。
在请求分页系统中,每当要访问的页面不在内存时,便产生一个缺页中断,然后由操作系统的缺页中断处理程序处理中断。
- 此时缺页的进程阻塞,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。
- 如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项。
- 如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存。未修改过的页面不用写回外存。
缺页中断是因为当前执行的指令想要访问的目标页面未调入内存而产生的,因此属于内中断(fault),一条指令在执行期间,可能产生多次缺页中断。例如,copy A to B
,即将逻辑地址A中的数据复制到逻辑地址B,而A、B属于不同的页面,则有可能产生两次中断。
- 现在,如果 CPU 想要访问进程 P 的第 P2 页,首先它将在页表中搜索该页。
- 由于页表不包含此页面,因此它将是一个陷入或页面错误 。一旦生成陷入并发生上下文切换,控制权就会转到操作系统。
- 操作系统会将进程置于就绪/阻塞状态。操作系统系统现在将在后备存储或辅助内存中搜索该页面。
- 然后,操作系统将从后备存储中读取页面并将其加载到主内存中。
- 接下来,操作系统将相应地更新页表条目。
- 最后,从操作系统中收回控制权,并恢复进程的执行。
页面置换算法,Page Replacement Algorithms,在使用分页进行内存管理的操作系统中,需要页面替换算法来决定当新页面进入时需要替换哪个页面。
最佳置换算法(OPT,Optimal):每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。
最佳置换算法可以保证最低的缺页率,但实际上,只有在进程执行的过程中才能知道接下来会访问到的是哪个页面。操作系统无法提前预判页面访问序列。因此,最佳置换算法是无法实现的。
先进先出置换算法(FIFO):每次选择淘汰的页面是最早进入内存的页面。 实现方法:把调入内存的页面根据调入的先后顺序排成一个队列,需要换出页面时选择队头页面即可。 队列的最大长度取决于系统为进程分配了多少个内存块。
Belady 异常——当为进程分配的物理块数增大时,缺页次数不减反增的异常现象。
只有 FIFO 算法会产生 Belady 异常。
另外,FIFO算法虽然实现简单,但是该算法与进程实际运行时的规律不适应,因为先进入的页面也有可能最经常被访问。因此,算法性能差。
最近最久未使用置换算法(LRU,least recently used):每次淘汰的页面是最近最久未使用的页面 实现方法:赋予每个页面对应的页表项中,用访问字段记录该页面自上次被访问以来所经历的时间t。 当需要淘汰一个页面时,选择现有页面中 t 值最大的,即最近最久未使用的页面。
该算法的实现需要专门的硬 件支持,虽然算法性能好, 但是实现困难,开销大。
最佳置换算法性能最好,但无法实现;先进先出置换算法实现简单,但算法性能差;最近最久未使用 置换算法性能好,是最接近OPT算法性能的,但是实现起来需要专门的硬件支持,算法开销大。 时钟置换算法是一种性能和开销较均衡的算法,又称CLOCK算法,或最近未用算法(NRU,Not Recently Used)
简单的CLOCK 算法实现方法:
- 为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列。当某页被访问时,其访问位置为1。
- 当需要淘汰一个页面时,只需检查页的访问位。 如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描(第二轮扫描中一定会 有访问位为0的页面,因此简单的CLOCK 算法选择一个淘汰页面最多会经过两轮扫描)
简单的时钟置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过, 就不需要执行I/O操作写回外存。
只有被淘汰的页面被修改过时,才需要写回外存。 因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。
在其他条件都相同时,应优先淘汰没有修改过的页面,避免I/O操作。这就是改进型的时钟置换算法的思想。 修改位=0,表示页面没有被修改过;修改位=1,表示页面被修改过。
为方便讨论,用(访问位,修改位)的形式表示各页面状态。如(1,1)表示一个页面近期被访问过, 且被修改过。
算法规则:
- 将所有可能被置换的页面排成一个循环队列
- 第一轮:从当前位置开始扫描到第一个(0, 0)的帧用于替换。本轮扫描不修改任何标志位。
- 第二轮:若第一轮扫描失败,则重新扫描,查找第一个(0, 1)的帧用于 替换。本轮将所有扫描过的帧访问位设为0
- 第三轮:若第二轮扫描失败,则重新扫描,查找第一个(0, 0)的帧用于 替换。本轮扫描不修改任何标志位
- 第四轮:若第三轮扫描失败,则重新扫描,查找第一个(0, 1)的帧用于 替换。
由于第二轮已将所有帧的访问位设为0,因此经过第三轮、第四轮扫描一 定会有一个帧被选中,因此改进型CLOCK置换算法选择一个淘汰页面最多会进行四轮扫描。
算法 | 算法规则 | 优缺点 |
---|---|---|
OPT | 优先淘汰最长时间内不会被访问的页面 | 缺页率最小,性能最好; 但无法实现 |
FIFO | 优先淘汰最先进入内存的页面 | 实现简单;但性能很差, 可能出现Belady异常 |
LRU | 优先淘汰最近最久没访问的页面 | 性能很好;但需要硬件支持,算法开销大 |
CLOCK(NRU) | 循环扫描各页面 第一轮淘汰访问位=0的,并将扫描过的页面访问 位改为1。若第一轮没选中,则进行第二轮扫描。 | 实现简单,算法开销小; 但未考虑页面是否被修改过。 |
改进型CLOCK(改进型NRU) | 若用(访问位, 修改位)的形式表述,则第一轮:淘汰(0, 0) 第二轮:淘汰(0, 1),并将扫描过的页面访问位 都置为0 第三轮:淘汰(0, 0) 第四轮:淘汰(0, 1) | 算法开销较小,性能也不错 |
驻留集,Resident set size,RSS。指请求分页存储管理中给进程分配的物理块的集合。 在采用了虚拟存储技术的系统中,驻留集大小一般小于进程的总大小。 若驻留集太小,会导致缺页频繁,系统要花大量的时间来处理缺页,实际用于进程推进的时间很少; 驻留集太大,又会导致多道程序并发度下降,资源利用率降低。所以应该选择一个合适的驻留集大小。
- 固定分配,操作系统为每个进程分配一组固定数目的物理块,在进程运行期间不再改变。即,驻留集(RSS)大小不变。
- 可变分配,先为每个进程分配一定数目的物理块,在进程运行期间,可根据情况做适当的增加或减少。 即,驻留集(RSS)大小可变。
- 局部置换:发生缺页时只能选进程自己的物理块进行置换。
- 全局置换:可以将操作系统保留的空闲物理块分配给缺页进程,也可以将别的进程持有的物理块置换到外存,再分配给缺页进程。
系统为每个进程分配一定数量的物理块,在整个运行期间都不改变。若进程在运行中发生缺页,则只能从该进程在内存中的页面中选出一页换出,然后再调入需要的页面。
这种策略的缺点是:很难在刚开始就确定应为每个进程分配多少个物理块才算合理(采用这种策略的系统可 以根据进程大小、优先级、或是根据程序员给出的参数来确定为一个进程分配的内存块数)。
刚开始会为每个进程分配一定数量的物理块。操作系统会保持一个空闲物理块队列。当某进程发生缺页时,从空闲物理块中取出一块分配给该进程;若已无空闲物理块,则可选择一个未锁定的页面换出外存,再将该物理块分配给缺页的进程。
采用这种策略时,只要某进程发生缺页, 都将获得新的物理块,仅当空闲物理块用完时,系统才选择一个未锁定的页面调出。被选择调出的页可能是系统中任何一个进程中的页,因此这个被选中的进程拥有的物理块会减少,缺页率会增加。
刚开始会为每个进程分配一定数量的物理块。当某进程发生缺页时,只允许从该进程自己的物理块中选出一个进行换出外存。如果进程在运行中频繁地缺页,系统会为该进程多分配几个物理块,直至该进程缺页率趋势适当程度;反之,如果进程在运行中缺页率特别低,则可适当减少分配给该进程的物理块。
对比两种可变分配方式:
- 可变分配全局置换:只要缺页就给分配新物理块。
- 可变分配局部置换:要根据发生缺页的频率来动态地增加或减少进程的物理块。
刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出外存,这种频繁的页面调度行为称为抖动,或颠簸,Thrashing。产生抖动的主要原因是进程频繁访问的页面数目高于可用的物理块数(分配给进程 的物理块不够)。
- 为进程分配的物理块太少,会使进 程发生抖动现象。
- 为进程分配的物理块太多,又会降低系统整体的并发度,降低某些资源的利用率。
为了研究为应该为每个进程分配多 少个物理块,Denning 提出了进程 “工作集”的概念。
- 驻留集(Resident set):指请求分页存储管理中给进程分配的内存块的集合。
- 工作集(Working set):指在某段时间间隔里,进程实际访问页面的集合。
操作系统会根据“窗口尺寸”(variable-window size)来算出工作集。 工作集大小可能小于窗口尺寸,实际应用中,操作系统可以统计进程的工作集大小,根据工作集大小 给进程分配若干内存块。如:窗口尺寸为5,经过一段时间的监测发现某进程的工作集最大为3,那么 说明该进程有很好的局部性,可以给这个进程分配3个以上的内存块即可满足进程的运行需要。 一般来说,驻留集大小不能小于工作集大小,否则进程运行过程中将频繁缺页。