地址重定位
- 进程在运行前还没有加载到物理内存中,所以运行前无法确定进程的物理地址。
- 需要地址重定位这个机制,实现从进程空间的地址到物理空间地址的转换,从而保证cpu 访问正确的内存单元。
虚拟地址(相对地址、逻辑地址)
- 用户程序经过编译后形成的目标代码,通常采用相对地址的形式。
交换技术
解决在较小的内存空间运行较大的进程的问题 —— 覆盖技术、交换技术
覆盖技术:将不会同时执行的程序段共享同一块内存区,程序的不同部分在内存中相互替代
交换技术:将内存中的某些进程暂时移到外存,外存中的某些进程换进内存
两种处理内存超载的通用方法是交换技术与虚拟内存。最简单的策略是交换技术,即把一个进程完整调入内存,使该进程运行一段时间,然后把它存回磁盘。
空闲内存管理
交换使得内存中出现了许多空闲区。
一般而言,有两种方法跟踪内存使用的情况:位图 和 空闲区链表
1.位图:每个分配单元对应位图中的一位,0 - 空闲,1 - 占用
2.空闲块链表:维护一个记录已分配内存段和空闲内存段的链表,链表中的一个节点包含一个进程或者两个进程间的一块空闲区。
空闲区表、已分配区表:表中每一项记录了空闲区(分配区)的起始地址、长度
内存分配算法
- 首次适配:在空闲区表中找到第一个满足进程要求的空闲区
- 下次适配:从上次找到的空闲区接着查找
- 最佳适配:找到能够满足进程要求的最小空闲区
- 最差适配:--最大空闲区
内存回收算法
- 相邻的空闲空间合并,然后修改内存空闲区表
虚拟内存
虚拟内存的基本思想是:每个程序拥有自己的地址空间。这个空间被分割成多个块,每一块称作一页或页面。每一页有连续的地址范围。这些页被映射到物理内存,但并不是所有的页都在内存中才能运行程序。
当程序引用到一部分在物理内存中的空间时,由硬件立刻执行必要的映射。当程序引用到不在物理内存中的页时,由操作系统将缺失的部分装入物理内存并重新执行失败的指令。
从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序成为可能。例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0~64K。该计算机只有 32KB 的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。
如图,虚拟地址空间的部分页面加载入了内存。
分页系统地址映射
大部分虚拟内存系统中都使用一种称为分页的技术,例如,在任何一台计算机上,程序执行指令:
MOV REG,10000
它把地址为1000的内存单元的内容复制到REG中。由程序产生的这些地址称为虚拟地址,它们构成了一个虚拟地址空间。在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上,读写操作使用具有相同地址的物理内存字;在使用虚拟内存的情况下,虚拟地址不是被直接送到内存总线上,而是被送到内存管理单元。
内存管理单元(MMU)管理着虚拟地址空间和物理内存的转换,其中的页表(Page table)存储着页(程序地址空间)和页框(物理内存空间)的映射表。
虚拟地址空间按照固定的大小划分为被称为页面的若干单元。物理内存中对应的单元称为页框。页面和页框的大小通常是一样的。
当程序访问了一个未映射的页面,例如访问虚拟页面8时,MMU注意到该页面没有被映射,于是使CPU陷入到操作系统,这个陷入称为缺页中断或缺页错误。操作系统找到一个很少使用的页框且把它的内容写入磁盘,随后把要需要访问的页面读到刚才回收的页框中,修改映射关系,然后重新启动引起陷入的指令。
实际使用中,一个虚拟地址分成两个部分,前 n 位为 页面号,后面的部分为 偏移量。
下图的页表(存储了页框号与有效位)存放着 16 个页,这 16 个页需要用 4 个比特位来进行索引定位。例如对于虚拟地址8196(0010 000000000100),前 4 位是 页面号 2,读取表项内容为(110, 1),页表项最后一位表示是否存在于内存中,1 表示存在,页框号才有效。后 12 位存储偏移量。这个页对应的页框的地址为 (110 000000000100)。
页表
虚拟地址分成两个部分,前 n 位为 虚拟页号,低位部分为 偏移量。虚拟页号可以作为页表的索引,以找到该虚拟页面对应的页表项。
页面置换算法
在程序运行过程中,如果要访问的页面不在内存中,就发生缺页中断从而将该页调入内存中。此时如果内存已无空闲空间,系统必须从内存中调出一个页面到磁盘对换区中来腾出空间。
页面置换算法和缓存淘汰策略类似,可以将内存看成磁盘的缓存。在缓存系统中,缓存的大小有限,当有新的缓存到达时,需要淘汰一部分已经存在的缓存,这样才有空间存放新的缓存数据。
页面置换算法的主要目标是使页面置换频率最低(也可以说缺页率最低)。
1. 最优页面置换算法
OPT, Optimal replacement algorithm
所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。
是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。
2. 最近最久未使用
LRU, Least Recently Used 性能接近 OPT
虽然无法知道将来要使用的页面情况,但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。
为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的,内存区满时淘汰队尾。
因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。
实现:时间戳 或 维护一个访问页的栈 —> 开销大
3. 最近未使用
NRU, Not Recently Used
每个页面都有两个状态位:R 访问标记位 与 M 修改标记位,当页面被访问时设置页面的 R=1,当页面被修改时设置 M=1。其中 R 位会定时被清零。可以将页面分成以下四类:
- R=0,M=0
- R=0,M=1
- R=1,M=0
- R=1,M=1
当发生缺页中断时,NRU 算法随机地从类编号最小的非空类中挑选一个页面将它换出。
NRU 优先换出已经被修改的脏页面(R=0,M=1),比淘汰一个被频繁使用的干净页面(R=1,M=0)要好。
优点:易于理解且能够有效被实现
4. 先进先出
FIFO, First In First Out
选择换出的页面是最先进入的页面。
该算法会将那些经常被访问的页面也被换出,从而使缺页率升高。
5. 第二次机会算法
FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改:
当页面被访问 (读或写) 时设置该页面的 R 位 为 1。需要替换的时候,检查最老页面的 R 位。如果 R 位是 0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续从链表的头部开始搜索。
6. 时钟
Clock
第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面连接起来,再使用一个指针指向最老的页面。
7.工作集算法
基本思想:为进程提供与活跃页面数相等的物理页面数,则可减少缺页中断次数。
工作集:一个进程当前正在使用的页框的集合。
算法:找出一个不在工作集中的页面并置换它。
实现:首先定义工作集为过去T(假定跨过多个改变标志位的时钟滴答)时间中的内存访问所用到的页面的集合,扫描所有页表项,执行操作
- 如果一个页面的R位是1,则将该页面的最后一次访问时间设为当前时间,将R位清零
- 如果一个页面的R位是0,则检查该页面的上次访问时间是否在“当前时间-T(时间间隔)”之前
如果是,则表明该页面不在工作集中,该页面为被置换的页面;
如果不是,记录当前所有被扫描过页面的最后访问时间里面的最小值。如果扫描完整个页表却没有确定被淘汰的页面,就淘汰R=0的页面集中,生存时间最长的页面。若所有页面的R=1,则随机选择一个干净页面淘汰。
8.工作集时钟算法
与时钟算法一样,所需的数据结构是一个以页框为元素的循环表,每个表项包含上次工作时间和R、M位。结合工作集算法,如果当前指针所指页表项被修改过,则指针继续前进,因为可能存在一个旧的干净的页面可以直接操作。
算法 | 评价 |
OPT | 不可实现,但可作为基准 |
NRU - 最近未使用 | LRU的很粗略的近似 |
FIFO | 可能置换出重要的页面 |
Second Chance | 比FIFO有很大的改善 |
Clock | 现实的 |
LRU - 最近最久未使用 | 很优秀,性能接近最佳,但很难实现 |
NFU | LRU的相对粗略的近似 |
Working set - 工作集 | 实现起来开销较大 |
工作集时钟算法 | 好的有效的算法 |
分页系统中的设计问题
1.全局与局部分配策略
当发生缺页中断时,如果只选择本进程中的页面作为候选进行置换的话,则称为局部页面置换算法,在所有进程的页面中进行选择置换的话称为全局页面置换算法。
局部算法可以有效的为每个进程分配固定的内存片段,全局算法在可运行的进程之间动态的分配页框,因此分配给各个进程的页框数是随时间变化的。全局算法在通常情况下工作得比局部算法好。
2.页面大小
页面设置过大,很容易出现内存碎片,造成浪费。
页面设置过小可以提高利用率,但会造成页表项增多,TLB表项增多造成浪费,内存与磁盘之间的传输一般是一次一页,传输中的大部分时间会花在寻道和旋转延迟上。
一般来说,内核进程分配大页面,用户进程分配小页面。
3.共享页面
多个进程同时运行一段代码时,避免内存中有一个页面的两个副本,共享页面效率更高。
只读的页面可以共享,数据页面则不能共享。
4.内存映射文件
进程可以通过发起一个系统调用,将一个文件映射到其虚拟地址空间的一部分,文件的页面不会全部读入内存,而是在访问页面时才会被每次一页的读入,解除文件映射时被写回磁盘。可以把一个文件当做内存中的大字符数组来访问,而不用通过读写操作来访问这个文件。
如果两个或两个以上的进程同时映射了同一个文件,他们就可以通过共享内存在通信。一个进程在共享内存上完成了写操作,此刻当另一个进程在映射到这个文件的虚拟地址空间上执行读操作时,就可以立刻看到上一个进程写操作的结果。
分段
虚拟内存采用的是分页技术,也就是将地址空间划分成固定大小的页,每一页再与内存进行映射。
下图为一个编译器在编译过程中建立的多个表,有 4 个表是动态增长的,如果使用分页系统的一维地址空间,动态增长的特点会导致覆盖问题的出现。
分段的做法是把每个表分成段,一个段构成一个独立的地址空间。每个段的长度可以不同,并且可以动态增长或减小而不影响其他的段。要在这种分段或二维的存储器中指示一个地址,程序必须提供两部分地址:段号和段内地址。
段页式
程序的地址空间划分成多个拥有独立地址空间的段,每个段上的地址空间划分成大小相同的页。这样既拥有分段系统的共享和保护,又拥有分页系统的虚拟内存功能。
分页与分段的比较
-
对程序员的透明性:分页透明,但是分段需要程序员显示划分每个段。
-
地址空间的维度:分页是一维地址空间,分段是二维的。
-
大小是否可以改变:页的大小不可变,段的大小可以动态改变。
-
出现的原因:分页主要用于实现虚拟内存,从而获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。