本节讲述操作系统内存机制,包括物理内存寻址、交换空间机制和虚拟内存机制
内存管理的需求
进程内存寻址的要求
重定位
在操作系统中,所谓的“内存地址”分为:
-
逻辑地址(虚地址):CPU所生成的地址
-
物理地址(实地址):内存单元所看到的地址
在多道程序设计中,内存空间被多个进程所共享,通常情况下,程序员事先不知道有什么其他程序会驻留在内存中。而且程序需要通过交换技术在内存和存储间移动,故需要一套将逻辑地址转换成物理地址,使得程序进程和操作系统使用相对固定的逻辑地址的同时可以灵活使用物理地址的机制,称为重定向
- 重定位(地址转换):把逻辑地址转换为物理地址
- 静态重定位
- 地址转换工作在进程执行前一次完成;
- 无须硬件支持,易于实现,但不允许程序在执行过程中移动位置。
- 动态重定位
- 地址转换推迟到最后的可能时刻,即进程执行时才完成;
- 允许程序在主存中移动、便于主存共享、主存利用率高。
- 静态重定位
保护
- 保护操作系统不受用户进程所影响
- 保护用户进程不受其他用户进程所影响
为做到以上两点:
-
一般策略:
- 用户进程不可访问操作系统的任何部分
- 进程通常不能跳转到另一个进程的任何指令,一般也不可以访问其它进程的数据区
-
存储键保护
-
系统将主存划分成大小相等的若干存储块,并给每个存储块都分配一个单独的保护键(锁);
-
在程序状态字PSW中设置有保护键字段,对不同的作业赋予不同的代码(钥匙);
-
钥匙和锁相配才允许访问
-
-
界限寄存器(下图)
-
上、下界防护:硬件为分给用户作业的连续的主存空间设置一对上、下界,分别指向该存储空间的上、下界
-
基址、限长防护:基址寄存器存放当前正执行者的程序地址空间所占分区的始址,限长寄存器存放该地址空间的长度,用户内存地址 0 ≤ A ≤ L ( 限长 ) 0 \le A \le L(\text{限长}) 0≤A≤L(限长)
-
共享
某些场合,允许多个进程访问内存的同一部分。
- 多个进程执行同一个程序,允许每个进程访问该程序的同一个副本
- 合作完成同一个任务的进程(甚至线程)可能需要共享访问相同的数据结构
组织
逻辑组织
- 组织为一维线性空间且地址空间由一系列字节或字组成
物理组织
- 两级存储结构:内存和外存
- 大容量的外存可以用于长期存储程序和数据
- 内存则用于保存当前使用的程序和数据内存和外存之间信息流的组织
- 两级存储器间的信息流的组织
覆盖和交换
覆盖技术
覆盖技术的思想 : 将程序分为多个段(多个模块)。常用的段常驻内存,不常用的段在需要时调入内存。
内存中分为一个“固定区”和若干个“覆盖区”。需要常驻内存的段放在“固定区”中,调入后就不再调出(除非运行结束)不常用的段放在“覆盖区”,需要用到时调入内存,用不到时调出内存。
必须由程序员声明覆盖结构,操作系统完成自动覆盖。缺点:对用户不透明,增加了用户编程负担。覆盖技术只用于早期的操作系统中,现在已成为历史。
交换技术
交换(对换)技术的设计思想: 内存空间紧张时,系统将内存中某些进程暂时换出外存,把外存中某些已具备运行条件的进程换入内存(进程在内存与磁盘间动态调度)。之前讲的中级调度(内存调度)就是为这个服务的。
虚拟内存技术就是为了适应在有交换条件下的内存调度而设计。
内存分区
内存管理的主要技术
内存管理的主要要求是将处理器装入内存去执行。
在几乎所有的现代多道程序设计系统中,都有交换技术管理其的虚拟内存方案。这个下一节会讲。仅考虑单一的内存结构(无视虚拟内存机构的存在),有以下几种内存管理技术。
PS:外部碎片:内存被分配段以外的碎片
内部碎片:被分配的内存中未被进程使用的内存部分
固定分区
大多数内存管理方案假定操作系统占据内存的某些部分,其余部分则供多个用户进程使用。
- 大小相等的分区
- 程序可能太大而不能放到一个分区中,从而必须使用覆盖技术设计程序,使得在特定时候程序的有且只能有一个部分需要放在内存内。
- 内存的利用率非常低,会有内部碎片。任何再小的程序都需要占据一个完整的分区。由于装入的数据块小于分区大小,因而导致分区内部存在空间浪费,这种现象称为内部碎片(internal fragmentation)。
- 大小不相等的分区
- 可缓解上述问题,但不能完全解决。
放置算法
- 大小相等的分区
- 只要存在可用的分区,进程就可以装入分区。
- 若所有分区都被处于不可运行状态的进程所占据,则选择其中一个进程换出,为新进程让出空间。
- 大小不等的分区
- 把每个进程分配到能够容纳它的最小分区。
- 每个分区维护一个调度队列,用于保存从这个分区换出的进程。
- 为所有进程只提供一个队列。
- 把每个进程分配到能够容纳它的最小分区。
固定分区的缺陷
-
分区的数目在系统生成阶段已经确定,限制了系统中活动(未挂起)进程的数目。
-
分区大小在系统生成阶段事先设置,小作业不能有效地利用分区空间。适用于知道所有作业的内存需求的情况下。
固定分区的用途
早期的多道操作系统,如IBM的OS/MFT
动态分区
动态分区的分区长度和数目是可变的,当进程被装入内存时,系统会给它分配一块和它所需容量完全相等的内存空间。
动态分区的数据结构
采用的数据结构(主要有两种)
空闲分区表:每个空闲分区对应一个表项;表项包含分区号、分区大小、分区起始地址等信息
**空闲分区链**:每个分区的起始部分和末尾部分分别设置**前向指针和后向指针**。起始部分处还可以记录分区大小等信息动态分区的缺点:内存碎片
\
如上图所示,动态分区过程中,若一个进程结束/被换出,而后续的进程没有被换出的进程大,就会留下不易利用的小内存碎片。
外部碎片解决方法
-
压缩:操作系统不时移动进程使得进程占用的空间连续并使得空闲空间连成一片。
- 费时且浪费处理器时间
- 需要操作系统有动态重定位能力
-
放置算法:
- 首次适配(First Fit)
- 从开始扫描内存,选择大小足够的第一个可用块;
- 具体操作:空闲分区以地址递增的次序排列,每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区
- 缺点:每次都从链头开始查找,可能导致低地址部分出现很多小的空闲区,而每次分配查找时,又需要经过这些分区,所以增加了查找的开销
- 优点:这种每次都需要从头查找的规则,也决定了当低地址部分有更小的分区可以满足时,会更有可能用到小分区,而把大分区保留下来。也就是它隐含了最佳适应算法的优点
- 下次适配/临近适配(Next Fit)
- 首次适配的变种,每次分配时从未分配区的上次扫描结束处顺序查找,选择下一个大小足够的可用块。用于解决首次适配方法重复适配的问题
- 具体操作:空闲分区以地址递增的次序排列(可以排成一个循环链表),每次分配内存时从上次查找结束位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区
- 最佳适配(Best Fit)
- 选择与要求的大小最接近的块。
- 具体操作:空闲分区按容量递增次序链接,每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区,此时这个分区能满足要求且最小
- 最差适配(Worst Fit)
- 选择符合要求大小的最大容量的块。
- 首次适配(First Fit)
伙伴系统
伙伴系统是一种固定分区和可变分区折中的主存管理算法。
分配原理
可用于分配的整个空间被视为一个大小为2U的块,若请求的大小s满足2U-1﹤s≤2U,则分配整个空间,否则,该块被分成两个大小相等的伙伴2U-1。
如果有2U-2﹤s≤2U-1 ,则给该请求分配两个伙伴中的任何一个,否则,其中一个伙伴又被分成两半……该过程一直继续,直到产生大于或等于s的最小块。
伙伴分配树
非连续内存分配方式
在连续分配管理方式中,程序被装入时是需要连续的空间的。也就是说一个1GB的进程想要装入内存就必须要找到连续的1GB空间,即便有1GB的空间,但只要是不连续的,那也无能为力
如果允许将一个进程分散地装入到许多不相邻的分区中,那内存的利用率不就能得到很大的提升了吗? 这也就是本节所涉及的主要内容——非连续分配方式 ,主要有以下三种
- 基本分页存储管理
- 基本分段存储管理
- 段页式存储管理
分页
基本思想:把内存分成一个个相等小分区,再按照分区大小把进程拆分成一个个小部分
内存被划分成大小固定相等的块(页框),且块相对比较小,每个进程也被分成同样大小的块(页)。
操作系统以页框为单位为各个进程分配内存空间,进程的每个页面分别放入一个页框中,也即它们对应的关系。各个页面不必连续存放,也不必遵循先后顺序,可以放到不相邻的各个页框中
- 进程中称为页的块可以指定到内存中称为页框的可用块。
- 分页与固定分区的区别
- 分页技术的分区相当小
- 一个程序可以占据多个分区,虚拟分区空间巨大
- 一个程序占据的多个分区不需要是连续
分页的地址转换
借鉴连续分配管理方式中的动态重定位
前面说过,如果采用连续分配管理方式,那么可以利用动态重定位实现逻辑地址到物理地址的转换
动态重定位思想:模块在内存中的起始地址+目标内存单元相对于起始位置的偏移量
如果采用动态重定位,那么装入模块装入后,其逻辑地址不会发生变化,地址转换在指令执行时才会进行
具体来说,系统会设置一个**重定位寄存器,它存放的是装入模块的起始位置**。 如下,在装入模块中,指令1的作用是“往逻辑地址为80的存储单元中写入数据1”,接着当CPU执行到指令1后,它所操作的真实地址就是**重定位寄存器的100+目标逻辑地址80=180**重定位的复杂过程见CSAPP的相关讲解
分页管理地址转换
可以把进程中所有的页看作一个个连续存放的模块,当页面分配到页框中后,虽然页面彼此之间是分散的,但是每个页面之内却是连续的。因此如果知道一个模块(一个页)的起始地址,并且知道某逻辑地址相对于该起始地址的偏移量,那么物理地址自然很容易得到。关键分为以下几步
- 要算出逻辑地址对应的页号
- 要算出某逻辑地址在页面内的偏移量
- 要知道该页号对应页面在内存中的起始地址
- 物理地址=页面的起始地址+偏移量
计算方式
- 页号:逻辑地址除以页面长度,取整数部分
- 如80/50=1
- 页内偏移量:逻辑地址对页面长度取余
- 如80%50=30
- 页面起始位置:系统会用某种数据结构(页表)记录进程各个页面的起始位置
- 若 P ≥ M P \ge M P≥M页号大于页表长度,产生越界中断,否则继续执行
- 页表中页号 P P P对应的页表项地址=页表起始地址 F F F+页号 P P P×页表项长度,取出该页表项内容** b b b,即为内存块号**
- 拆分逻辑地址为页号和页内地址(逻辑地址)
- CPU中的页表基址寄存器指明了页表在主存中的存放位置(以地址为1058为例,就表示当前运行的程序其所对应的页表是从1058后的存储单元开始存储的,由于每个页表项的大小相同,所以只需顺次读取即可)
- 查询页表找到逻辑页面对应的主存块
- 块号+页内地址=物理地址(二进制拼接起来)
- 进行访存操作(Cache未命中时进入主存)
分段
把程序和其相关的数据划分到几个段中。
- 段有一个最大长度限制,但不要求所有程序的所有段的长度都相等。
- 与分页的区别:段的大小是可变的
- 分段与动态分区的区别
- 一个程序可以占据多个分区
- 一个程序占据的多个分区不需要是连续的
- 会产生外部碎片,但跟动态分区比,会很小
逻辑地址和物理地址的转换
-
段表:将逻辑地址映射为物理地址
-
段基地址:包含该段在内存中的开始物理地址段界限:指定该段的长度
-
逻辑地址:段号s+段内偏移d
-
逻辑地址到物理地址的转换
- 段号与段表长度进行比较,若段号超过了段表长度,则越界(非法地址),否则转2
- 根据段表始址和段号计算出该段对应段表项的位置,从中读出该段在内存的起始地址,检查段内地址是否超过该段的段长,若超过则越界(非法地址),否则转3
- 将该段的起始地址与段内位移相加,从而得到要访问的物理地址
-
例子:
表1为段表,表2为逻辑地址表,表三为物理地址表
虚拟内存——硬件
实存储器(实存):内存
虚存储器(虚存):磁盘交换分区,也称交换内存
虚拟内存:作为整体时指通过操作系统虚存管理机制,实现的实存储器和虚存储器的结合体
局部性
指程序在执行过程中的一个较短时间内,所执行的指令地址或操作数地址分别局限于一定的存储区域中。又可细分时间局部性和空间局部性。
- 时间局部性:最近访问过的程序代码和数据很快又被访问。
- 空间局部性:某存储单元被使用之后,其相邻的存储单元也很快被使用。
虚拟内存(virtual memory)
允许进程的执行不必完全在内存中,程序可以比物理内存大。
在许多情况下不需要将整个程序放到内存中:
-
处理异常错误条件的代码(几乎不执行)
-
数组、链表和表通常分配了比实际所需更多的内存
-
程序的某些选项或特点可能很少使用能够执行
只有部分在内存中的程序的好处:
- 程序不再受现有的物理内存空间限制,可以做的很大
- 更多程序可同时执行,CPU利用率相应增加用户程序会运行的更快
虚拟内存符合局部性原理,因为:
- 时间上,不常用或暂时用完了的内容被交换至外存,而频繁访问的内存保持在内存内
- 空间上,只有连续使用的一系列单元会放在一起
虚拟内存特征
有以下三个主要特征
- 多次性:作业运行时无需一次全部装入内存,而是允许分批次调入内存
- 对换性:作业运行时无需一直常驻内存,而是允许在运行时进行换入和换出
- 虚拟性:从逻辑上扩充了内存的容量
虚拟内存控制结构
虚拟内存技术,允许一个作业分多次调入内存。如果采用连续分配方式,会不方便实现。
虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。主要有以下三种
- 请求分页存储管理
- 请求分段存储管理
- 请求段页式存储管理
与传统的非连续存储管理方式相比,区别在于
- 所访问信息不在内存时,由操作系统负责将信息从外存调入内存——请求调页功能
- 如果内存空间不够,由操作系统负责将内存中暂时用不到的信息换出外存——页面置换功能
基于分页的虚拟内存
在CSAPP内的讲解:页表地址翻译
请求分页式的虚拟内存是现代最常见的,Linux和Windows等现代系统都使用分页式虚拟内存
分页式虚存不把作业信息(程序和数据)全部装入主存,仅装入立即使用的页面,在执行过程中访问到不在主存的页面时,产生缺页中断,再从磁盘动态地装入 。
怎样才能发现页面不在主存中呢?怎样处理这种情况呢?采用的办法是:扩充页表的内容,增加驻留标志位和页面辅存的地址等信息。
多级页表
系统为每个进程建一张页目录表,它的每个表项对应一个页表页,而页表页的每个表项给出了页面和页框的对应关系,页目录表是一级页表,页表页是二级页表。
intel目前的虚拟寻址:64 位虚拟地址的格式为:全局页目录项(9位)+ 上层页目录项(9位)+ 中间页目录项(9位)+ 页表项(9位)+ 页内偏移(12位)。共 48 位组成的虚拟内存地址。
倒排页表
普通页表设计页表大小和虚拟地址空间的大小成正相关(因普通页表设计需要每个进程一个页表)倒排页表应运而生。
倒排页表运用散列表技术,虚拟地址的页号部分映射到散列表中,散列表包含指向散列表的指针。故无论有多少进程、有多少虚拟页,页表都只需要实际内存中的一个页表。
快表
TLB: translation lookaside buffer,转换检测缓冲区也称为快表
TLB是一个cache组件(不是内存,是内存的cache),由于在某些程序中(尤其带有循环和函数调用的操作中),以及就空间局部性的需要,可能要反复调用同一个页表项,故TLB用来存放最近访问的若干页表项的副本,以加速地址变化过程
-
CPU给出逻辑地址,由某个硬件(即MMU,内存定位器)算得页号、页内偏移量,将页号与快表中的所有页号进行比较
-
如果找到匹配的页号,说明要访问的页表在快表中有副本,则直接从中取出该页对应的内存块号,再将其与页内偏移量拼接形成物理地址访存;因此如果快表命中,则访问某个逻辑地址仅需一次访存即可
-
如果没有找到匹配的页号,则需要访问内存中的页表,找到对应的页表项,得到页面存放的内存块号,再将其与页内偏移量拼接形成物理地址访存;因此,若快表未命中,则访问某个逻辑地址需要两次访存
-
注意:
- 如果快表未命中,在页表中找到页表项后,基于局部性原理,应同时将其存入快表,以便后面可能再次访问;
- 因为无法把整个页表都放在TLB中,所以,如果TLB已满,则必须按照一定的算法对旧的页表项进行替换(页面置换算法)
计算
快表命中:快表访问一次+内存逻辑地址的一次访问;
快表未有命中:快表访问一次+访问内存中的页表一次+内存寻址一次
假定访问主存时间为100毫微秒,访问快表时间为20毫微秒,相联存储器为32个单元时快表命中率可达90%,按逻辑地址存取的平均时间为:(100+20)×90%+(100+100+20)×(1-90%)=130比两次访问主存的时间100×2=200下降了三成多。
与一般分页寻址的区别
基于分段的虚拟内存
分段式虚拟存储系统把作业的所有分段的副本都存放在辅助存储器中,当作业被调度投入运行时,首先把当前需要的一段或几段装入主存,在执行过程中访问到不在主存的段时再把它们装入。段表也需要增加一些控制位。
基于段页式存储的虚拟内存
分页对程序员是透明的,消除了外部碎片,可以更有效地使用内存。此外,移入或移出的块是大小相等的,易于处理。分段对程序员是可见的,具有处理不断增长的数据结构的能力以及支持共享和保护的能力。把两者结合起来就是段页式存储管理。
- 内存划分成大小小等的页框。
- 用户的地址空间被程序员划分成许多段,
- 每个段一次划分成许多固定大小的页
- 页的长度等于内存中的页框大小。
虚拟内存的操作系统策略
读取策略:确定一个页何时放入内存
- 请求分页
- 预先分页
放置策略:决定一个进程块驻留在内存中什么地方
置换策略
与传统的非连续存储管理方式相比,区别在于
- 所访问信息不在内存时,由操作系统负责将信息从外存调入内存——请求调页功能
- 如果内存空间不够,由操作系统负责将内存中暂时用不到的信息换出外存——页面置换功能
使用页面置换算法可以选择某个页被换出,具体有
- 最佳置换算法(OPT)
- 先进先出置换算法(FIFO)
- 近期最少用置换算法(LRU)
- 时钟置换算法(CLOCK)
- 改进型的时钟置换算法
OPT
最佳置换算法(OPT,Optimal):每次淘汰的页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证最低的缺页率
- 该算法能导致最少的缺页中断,但它要求操作系统必须知道将来的事件,所以不可能实现,可作为一种标准来衡量其它算法的性能。
计算
假设系统为某个进程分配了三个内存块,然后会依次访问某些页面,顺序为:7-0-1-2-0-3-0-4-2-3-0-3-2-1-2-0-1-7-0-1
在OPT算法下的操作如下:
缺页率: 9 / 20 = 45 % 9/20=45\% 9/20=45%
FIFO
先进先出,队列体制,置换驻留在内存中时间最长的页
思想:一个很久以前取入内存的页,到现在可能已经不会再用到了。
问题:经常出现一部分程序或数据在整个程序的生命周期中使用频率都很高的情况。
计算
缺页率: 15 / 20 = 75 % 15/20=75\% 15/20=75%
Belady
FIFO算法实现虽然简单,但是该算法与进程实际运行时的规律不相适应,因为先进入的页面也有可能是需要经常访问的,所以性能差
LRU
分页的复习:
- 状态位:操作系统需要知道每个页面是否已经调入内存,供程序访问时参考
- 访问字段:可以记录最近该页面被访问的频次,供页面置换算法选择页面时进行参考
- 修改位段:标识该页面调入后是否被修改过
- 外存地址:页面在外存的存放位置,通常是物理块号,供调入该页时参考
置换内存中上次使用距当前最远的页。
- 根据程序局部性原理,在较长时间里未被使用的页面,可能不会马上使用到。
- 性能接近于OPT,但比较难以实现
- 法1:为每一页添加一个最后一次访问的时间戳,并且必须在每次访问内存时,都要更新时间戳。
- 法2:维护一个关于访问页的栈。
- 开销很大。
计算
缺页率: 12 / 20 = 60 % 12/20=60\% 12/20=60%
CLOCK
以较小的开销接近LRU的性能。
a)简单时钟策略
- 一个页面首次装入内存,其“引用位”置1 。
- 主存中的任何页面被访问时, “引用位”也置1。
- 当需要置换一页时,扫描以查找“引用位”为0的页框,把遇到的第一个页框置换。
- 查找过程中,把遇到的“引用位”是1的页面的“引用位”清0,跳过这个页面,继续查找。
- 扫描时,如果遇到的所有页面的“引用位”为1,指针就会绕整个循环队列一圈,把碰到的所有页面的“引用位”清0;指针停在起始位置,并置换掉这一页。
b)变种时钟策略
简单时钟置换算法仅仅考虑到了一个页面是否被访问过,但访问不一定修改,而页面一旦遭到修改就需要写回至外存,涉及I/O操作,所以在淘汰页面时不止要考虑是否被访问过而且要考虑是否被修改过
改进型时钟置换算法:为每个页面再设置一个修改位,淘汰页面时优先淘汰没有被修改过的页面
- 如果修改位为0:表示页面没有被修改过
- 如果修改位为1:表示页面被修改过
同时为了方便讨论,可以使用(访问位,修改位)这样二元组的形式表示各页面的状态
- (0,0):一个页面既没有被访问过也没有被修改过
- (0,1):一个页面没有被访问但是被修改过
- (1,0):一个页面被访问过但没有被修改过
- (1,1):一个页面被访问过且被修改过
具体规则:将所有可能被置换的页面排成一个循环队列
- 第一轮: 从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
- 第二轮: 若第一轮失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0
- 第三轮: 若第二轮失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
- 第四轮: 若第三轮失败,则重新扫描,查找第一个(0,1)的帧用于替换
由于第二轮已将所有帧的访问位设置为了0,所以经过第三轮、第四轮扫描一定有一个帧被选中,所以改进型CLOCK算法选择一个淘汰页面至多进行四轮扫描
分配策略
驻留集
对于分页式虚拟内存,在进程准备执行时,操作系统必须决定读取多少页,也即决定给特定的进程分配多少页框。所以给一个进程分配物理页框的集合就是这个进程的驻留集。在采用了虚拟存储技术的系统中,驻留集大小一般小于进程的总大小。驻留集太大和太小都会对进程或系统产生一定影响,所以要选择一个合适的驻留集大小
- 若驻留集太小: 导致缺页频繁,系统需要花费大量时间来处理缺页,实际用于推进进程的时间很少
- 如果一个进程在内存中的页数比较少,尽管有局部性原理,缺页率仍然相对较高。
- 给特定进程分配的内存空间超过一定的大小后,由于局部性原理,该进程的缺页率没有明显的变化
- 若驻留集太大: 导致多道程序并发度下降,资源利用率降低
- 分配给一个进程的内存越少,在任何时候驻留在内存中的进程数就越多,从而增加了操作系统至少找到一个就绪进程的可能性
根据驻留集大小是否可变,可以有如下两种分配方式
- 固定分配: 操作系统为每个进程分配一组固定数目的物理块,进程运行期间不再改变
- 可变分配: 先为每个进程分配一定数目的物理块,在进程运行期间,可以根据情况做适当的增加或者减少
- 缺页率高,可增加分配的页框;
- 缺页率很低,可适当减少分配的页框。
根据置换时置换的范围不同,可以有两种置换方式
- 局部置换:发生缺页时只能选进程自己的物理块进行置换
- 全局置换:可以将操作系统保留的空闲物理块分配给进程;也可以将别的进程持有的物理块置换到外存,然后再分配给缺页进程
工作集策略
进程工作集指“在某一段时间间隔内进程运行所需访问的页面集合”。
W(t,△)
- t是虚拟时间
- △是观察进程的虚拟时间窗口
优点:
- 可用于指导驻留集大小监视每个进程的工作集
- 周期性地从一个进程的驻留集中移去那些不在它的工作集中的页
- 只有当一个进程的工作集在内存中时,才可以执行
缺点
- 根据过去预测将来的不准确性为每个进程真实地测量工作集是不实际的。
- △的最优值是未知的,并且它在任何情况下都会变化。
清除策略
确定在何时将一个被修改过的页写回辅存。
- 请求式清除:只有当一页被选择用于置换时才被写回
- 预约式清除:被修改的多个页在需要用到它们所占据的页框之前成批地写回辅存
加载策略
- 系统并发度
- 进程挂起:如果系统并发度被减小,一个或多个当前驻留进程必须被挂起。
- 最低优先级进程缺
- 页中断进程
- 最后一个被激活的进程
- 驻留集最小的进程
- 最大空间的进程
- 具有最大剩余执行窗口的进程
常见操作系统的内存分配策略
Linux
Linux内存的基本单位是物理页。页面积大小通常为4KB,也可以支持大页例如2MB。
进程的虚拟内存分配
Linux使用三级页表结构,如下图所示。其中Global directory只有一页,每个活动进程对应一个Global Directory,必须存在于内存中。Middle directory和page table都可能是多页。
页面分配,内核维护一系列大小固定的连续页框组,一组可以包含1、2、3、8、16或32个页框,当一页在内存中被分配或者解除分配时,使用slob伙伴算法来分裂或者合并。伙伴算法示意图如下:
页面置换算法,每个页关联一个8位的age和一个修改位。当页被访问时,age++,当周期性扫描全局页池,age–,属于最少使用频率的策略,缺点是周期性扫描页缓存池,增大了CPU的时间。
在2.6.28之后,引入了新的页面置换算法,分割LRU。页面关联两个状态PG_avtive和PG_referenced。并被分为两大类,激活和非激活,使用链表对他们进行管理。非激活链表中的一页,第一次访问PG_referenced设置为有效,再被访问一次,会从非激活链表中移动到激活链表;若两次访问时间间隔太长,PG_referenced会被重置为无效;同样的激活链表在超时之后会被放到非激活链表中去(状态转移如下图所示)。判断超时进行修改的工作由内核驻留进程kswapd在后台周期性执行。在非激活状态的链表中会被LRU的页面置换算法进行置换。
内核内存分配
Linux内核内存管理物理内存页框,主要功能是为特定用途分配和回收页框。页框的可能所有者包括用户空间进程(即页框是某个进程的虚拟内存当前驻留在实存中的一部分)、动态分配的内核数据、静态内核代码以及业缓冲区
加载和链接
加载
加载器把加载的模块放置在内存x开始的位置(满足7.1 中提到的寻址需求)。可以采用三种方式:
-
绝对加载
绝对加载器要求给定加载模块总被加载到内存中的同一个位置。但这个耦合度太高,通常会将绝对的内存地址用符号来代替,上层程序通过符号引用来进行替代。如图(b) Absolute load module。
-
可重定位加载
可分配到内存中任何地方的加载模块。汇编器和编译器不使用绝对地址,而是使用相对某些已知点(加载时确定)的地址。如下图的© Relative load module.
-
动态运行加载
在上述两种方案中,使用的地址都在程序被加载时确认且不能修改,这就不能满足程序被换出后换回到不同内存地址的问题。动态运行加载,在真正被执行时才计算它的绝对地址(通过硬件设备保证执行的效率)
[
Object module 是用户程序指明使用的符号地址
链接
链接器的功能是把一组目标模块作为输入,产生一个包含完整程序和数据模块的加载模块,并传递给加载器。
链接编辑程序(静态),如下图(b)所示,产生可重定位加载模块的链接器。将外部引用替换成了外部模块所在的符号地址。
动态链接器,把某些外部模块的链接推迟到创建加载模块之后,因此加载模块包含到其他程序的未解析的引用,这些引用可以在运行时或加载时解析。
- 加载时的动态链接,如图Fig 7.14中的Loader下的Dynamic Library,优点如下:
- 更容易的并入已改变或已升级的目标模块
- 多应用程序自动代码共享,节省内存空间
- 更容易的扩展功能
- 运行时的动态链接,某些链接工作被推迟到了执行时。当调用的模块不存在时,OS定位给模块,加载他。并把他链接到调用模块中。