现代操作系统 第4版 第3章笔记

内存管理

    • 3.1 无存储器抽象
    • 3.2 一种存储器抽象:地址空间
    • 3.3 虚拟内存
      • 3.3.1 分页
      • 3.3.2 页表
      • 3.3.3 加速分页过程
      • 3.3.4 针对大内存的页表
    • 3.4 页面置换算法
    • 3.5 分页系统中的设计问题
    • 3.6 有关实现的问题
    • 3.7 分段

‘‘Programs expand to fill the memory available to hold them.’’ —— Parkinson’sLaw

为什么提出分层存储器体系(memory hierarchy)的概念?
因为任何一个存储器都达不到“高速度,大容量,低价格”的目标,只能采用分层结构,形成存储体系。
现代操作系统 第4版 第3章笔记_第1张图片

操作系统中管理分层存储器体系的部分称为存储管理器(memory manager).
内存管理的概念:操作系统对内存的划分和动态分配。

3.1 无存储器抽象

链接:
抽象:程序员必备的能力

最简单的存储器抽象就是根本没有抽象,如:早期大型计算机(20世纪60年代前)、小型计算机(20世纪70年代前)和个人计算机(20世纪80年代前)。
每一个程序都直接访问物理内存。
因此,存储器模型就是简单的物理内存:从0到某个上限的地址集合,每一个地址对应一个可容纳一定数目二进制位的存储单元,通常是8个。而且要想在内存中同时运行两个程序是不可能的。
现代操作系统 第4版 第3章笔记_第2张图片
按以上的方式组织系统时,通常同一时刻只能有一个进程在运行。

在没有存储器抽象的系统中实现并行的一种方法是使用多线程来编程,因为一个进程中的所有线程间可以共享同一内存映像。但是由于人们通常希望能够在同一时间运行没有关联的程序,而这正是线程抽象所不能提供的;而且一个没有存储器抽象的系统也不大可能具有线程抽象的功能,因此该想法并没有被广泛使用。
如何在不使用存储器抽象的情况下运行多个程序?
1.只要在某一个时间内存中只有一个程序,那么就不会发生冲突。(操作系统将当前内存中所有内容保存到磁盘文件中,然后把下一个程序读入到内存中在运行即可.)
2.将内存划分,并给每个单位分配一个4位的保护键(存储在CPU的特殊寄存器中),一个运行中的进程无法访问保护键与其PSW码不同的内存。
缺陷:两个程序都引用了绝对物理地址,会造成冲突。而我们希望每个程序都使用一套私有的本地地址来进行内存寻址。
解决:静态重定位(当一个程序被装载到地址16384时,将常数16384加到每个程序的地址上。)
问题:不通用;减慢装载速度;难以区分哪些内存字中有(可重定位的)地址(即地址or常数).

3.2 一种存储器抽象:地址空间

物理地址暴露给进程带来的问题:
1.用户程序易破坏操作系统
2.很难同时运行多个程序

要使多个应用程序同时处于内存中并且不互相影响,需要解决两个问题:保护和重定位
这就需要创造一个新的存储器抽象:地址空间(一个进程可用于寻址内存的一套地址集合,e.g.电话号码、以“.com”结尾的网络域名的集合)。每个进程有自己一个地址空间,并且它独立于其他进程的地址空间。

如何给每个进程提供私有的地址空间?
方法:使用基址寄存器和界限寄存器(动态重定位)
程序装载到内存中连续的空闲位置且装载期间无须重定位。当一个进程运行时,程序的起始物理地址装载到基址寄存器,程序长度装到界限寄存器。每次一个进程访问内存时,CPU会在把地址发到内存总线前,自动把基址值加到进程发出的地址值上,并检查访问的地址是否超过界限。
缺点:每次访问内存都要进行加法(慢)和比较运算(快)。

如何处理内存超载?
方法一:交换(swapping)技术
把一个进程完整调入内存中运行一段时间,之后存回磁盘。空闲进程主要存储在磁盘上。
现代操作系统 第4版 第3章笔记_第3张图片
内存紧缩(memory compaction):交换在内存中产生了多个空闲区(空洞,hole),通过把所有的进程尽可能向下移动,合并成一大块。
缺点:要耗费大量的CPU时间。

第一种——如果进程的数据段可以增长,相邻的是一个空闲区,就可把该空闲区分配给进程供其增大;但相邻的是另一个进程,那么要么把需要增长的进程移到内存中一个足够大的区域中去,要么把一个或多个进程交换出去生成一个足够大的空闲区。若一个进程在内存中不能增长,磁盘上的交换区也满了,那该进程只能挂起(或结束)。
第二种——如果大部分进程在运行时都要增长,可以在换入或移动进程时为它分配一些额外内存(来减少因内存区域不够而引起的进程交换和移动所产生的开销)。但当进程被换出到磁盘上时,应只交换进程实际上使用的内存中的内容。
第三种——如果进程有两个可增长的段,如数据段和堆栈段,那么堆栈段向下增长,数据段向上增长,两者之间的内存供两者使用。如果也用完了,同第一种,进程必须移动到足够大的空闲区中(或结束)。
现代操作系统 第4版 第3章笔记_第4张图片
方法二:虚拟内存(virtual memory)
使程序在只有一部分被调入内存的情况下运行。

空闲内存管理
在动态分配内存时,操作系统必须对其进行管理,有两种方法跟踪内存使用情况:位图和空闲区链表。

(1)使用位图的存储管理
内存被划分为分配单元,每个分配单元对应位图中一位,0->空闲,1->占用(或相反)。
分配单元越小,位图越大。
内存的大小和分配单元的大小决定位图的大小。
现代操作系统 第4版 第3章笔记_第5张图片
(2)使用链表的存储管理
链表的结点包括:空闲区(H)(or进程§的指示标志)、起始地址、长度、指向下一结点的指针。
现代操作系统 第4版 第3章笔记_第6张图片
上图中,a)更新链表需要把P替换成H
b)&c)两个结点被合并为一个,链表删除一个结点
d)三个结点被合并为一个,链表删除两个结点
由于进程表中表示终止进程的结点中通常含有指向对应其段链表结点的指针,因此使用双链表可能更方便,便于合并。

动态分配内存算法
首次适配算法(first fit):按地址从小到大为序,分配第一个符合条件的分区。
下次适配算法(next fit):每次找到合适的空闲区后记录当前位置,下次从这里开始找。
最佳适配算法(best fit):按空间从小到大为序,找整个链表,找容纳进程的最小的空闲区。
最差适配算法(worst fit):按空间从小到大为序,找最大的可用空闲区。
快速适配算法(quick fit):单独维护常用大小的空闲区链表。但在一个进程终止或被换出时,寻找它的相邻块并查看是否可以合并的过程比较费时;若不合并,内存会分裂出大量无法利用的小空闲区。

e.g.在一个交换系统中,按内存地址排列的空闲区大小是:10KB、4KB、20KB、18KB、7KB、9KB、12KB和15KB。对于连续的段请求:a)12KB;b) 10KB;c)9KB。使用首次适配算法,将找出哪个空闲区?使用最佳适配、最差适配、下次适配算法呢?
答:
首次适配: 20KB,10KB,18KB;
最佳适配: 12KB,10KB,9KB;
最差适配: 20KB,18KB,15KB;
下次适配: 20KB,18KB,9KB。

3.3 虚拟内存

背景:1.管理软件的膨胀 2.需要运行的程序往往大到内存无法容纳
方法:
交换技术——磁盘传输率低,需要好几秒才能换出或换入一个1GB的程序。
覆盖(overlay,把程序分割成许多片段)——费时而且易于出错。

虚拟内存(virtual memory)
基本思想:a.每个程序拥有自己的地址空间,这个空间被分割成多个块,每一块称作一页or页面(page)。b.每一页有连续的地址范围。c.这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。d1.当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射。d2.当程序引用到一部分不在物理内存中的地址空间时,由OS负责将缺失的部分装入物理内存并重新执行失败的指令。
本质:创造一个新的抽象概念——地址空间,这个概念是对物理内存的抽象,类似于进程是对CPU的抽象。是对基址寄存器和界限寄存器的一种综合。
实现:很适合在多道程序设计系统中使用,将虚拟地址空间分解成页,并将每一页映射到物理内存的某个页框或暂时解除映射。

3.3.1 分页

p.s.用户程序的地址空间被划分为若干固定大小的区域,称为“页”。相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配,由一个页表来维护它们之间的映射关系。

由程序产生的地址称为虚拟地址(virtual address),它们构成了一个虚拟地址空间(virtual address space)。
在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上,读写操作使用具有同样地址的物理内存字;
在使有虚拟内存的计算机上,虚拟地址被送到内存管理单元(MMU)而不是内存总线,接着MMU把虚拟地址映射为物理内存地址。
现代操作系统 第4版 第3章笔记_第7张图片
下图说明了这种映射如何工作。
现代操作系统 第4版 第3章笔记_第8张图片
虚拟地址空间按照固定大小划分成被称为页面(page)的若干单元,在物理内存中对应的单元称为页框(page frame),二者通常是一样的,在上图中都是4KB。

e.g.使用以上页表,给出下面每个虚拟地址对应的物理地址:a) 20 b) 4100 c) 8300
a) 因为虚拟地址20(在虚拟页面0中)被映射到到物理页框2中,所以物理地址是:(20-0)+8192=8212.
b) 因为虚拟地址4100(在虚拟页面1中)被映射到到物理页框1中,所以物理地址是:(4100-4096)+4096=4100.
c) 因为虚拟地址8300(在虚拟页面2中)被映射到到物理页框6中,所以物理地址是:(8300-8192)+24576=24684.

缺页中断or缺页错误(page fault):虚拟页面对应的物理地址不存在(要访问的页不在主存),于是使CPU陷入到OS.
OS找到一个很少使用的页框且把它的内容写入磁盘,随后把需要访问的页面读到刚才回收的页框中,修改映射关系,然后重新启动引起陷进的指令。
MMU内部结构:
现代操作系统 第4版 第3章笔记_第9张图片
虚拟页号可用作页表(page table)的索引,以找到该虚拟页面对应的页表项,由页表项找到页框号(如果存在),然后将页框号复制到输出寄存器的的高位端,再加上输入虚拟地址中的低位偏移量,以此构成物理地址。输出寄存器的内容随即被作为物理地址送到内存总线。
输入的16位虚拟地址被分为4位页号(高位部分)和12位偏移量(低位部分)。4位的页号可以表示16个页面,12位偏移量可以为一页内的全部4096个字节编址。(使用其他数也可以)

3.3.2 页表

目的:把虚拟页面映射为页框
p.s. 页表是一个函数,参数是虚拟页号,结果是物理页框号。
虚拟地址=虚拟页号(高位部分)+偏移量(低位部分)

对于20000这个十进制虚拟地址,分别使用4KB页面和8KB页面计算虚拟页号和偏移量。
答:20000 --> 01001110 00100000
对于4KB页面,由于0~4K为 0~4095 ,2^12=4096;所以 page number = 0100,offset = 1110 00100000,即(page,offset)对是(4, 3616)
对于8KB页面,由于4~8K为 4096~8191 ,2^13=8192;所以 page number = 010,offset = 01110 00100000,即(page,offset)对是(2, 3616)

页表项的结构
现代操作系统 第4版 第3章笔记_第10张图片
不同机器的页表项存储的信息大致相同,不同计算机的页表项大小可能不一样,通常为32位。

  1. “在/不在位”
    1时表示该表项是有效的,可以使用;0表示该表项对应的虚拟页面现在不在内存中,访问该页面会引起一个缺页中断。
  2. 保护位
    指出一个页允许什么类型的访问。最简单的是这个域只有一位,0–>读/写 ,1–>只读;更先进的的是是用三位,读、写、执行。
  3. 修改位(脏位)
    为了记录页面的使用状况
    在写入一页时由硬件自动设置修改位
    该位在OS重新分配页框时非常有用,如果一个页面被修改过(脏),则必须把它写回硬盘;如果未被修改过(干净),则丢弃,因为它在磁盘上的副本仍有效。
  4. 访问位
    为了记录页面的使用状况
    无论读还是写,系统都会在该页面被访问时设置访问位。
    它的值被用来帮助OS在发生缺页中断时选择要被淘汰的页面。
  5. 高速缓存禁止位
    用于禁止该页面被高速缓存
    对那些映射到设备寄存器而不是常规内存的页面而言,这个特性很重要;但具有独立的I/O空间而不是用内存映射I/O的机器不需要这一位。

若某个页面不在内存中,用于保存该页面的磁盘地址不是页表的组成部分。

3.3.3 加速分页过程

两个主要问题
一、虚拟地址到物理地址的映射必须非常快
二、如果虚拟地址空间很大,页表也会很大
解决方案

  1. 转换检测缓冲区/TLB/相联存储器/快表
    为计算机设置一个小型的硬件设备,将虚拟地址直接映射到物理地址,而不必再访问页表。
    通常在MMU中,包含少量的表项,实际中很少超过256个。
    现代操作系统 第4版 第3章笔记_第11张图片
    除了虚拟页号(不是必须放在页表中),其他域与页表中的域一一对应。另外还有一位用来记录这个表项是否有效(即是否在使用)。
    TLB如何工作?
    将一个虚拟地址放入MMU中进行转换时,硬件首先通过将该虚拟页号与TLB中所有表项同时(即并行)进行匹配,判断虚拟页面是否在其中。如果发现了一个有效的匹配并且要进行的访问操作并不违反保护位,则将页框号直接从TLB中取出而不必再访问页表。
    如果虚拟页号确实在TLB中,但指令试图在一个只读页面上进行写操作,则会产生一个保护错误,就像对页表进行非法访问一样。
    如果虚拟页号不在TLB中,如果MMU检测到没有有效的匹配项,就会进行正常的页表查询;接着从TLB中淘汰一个表项,然后用新找到的页表项代替它。如果这一页面很快被再次访问,第二次访问TLB时自然将会命中而非未命中。
    当一个表项被清除出TLB时,将修改位复制到内存中的页表项,除了访问位,其他的值不变;而当页表项从页表中装入TLB中时,所有的值都来自内存。
  2. 软件TLB管理
    背景:对TLB的管理和TLB的失效处理都完全由MMU硬件来实现。
    现在:TLB表项被OS显式地装载;当发生TLB访问失效时,不再是由MMU到页表中查找并取出需要的页表项,而是生成一个TLB失效并将问题交给OS解决。系统必须先找到该页面,然后从TLB中删除一个项,接着装载一个新的项,最后再执行先前出错的指令。由于TLB失效比缺页中断发生得频繁,因此所有这一切都必须在有限的几条指令中完成。
    改善:(1)在减少TLB失效的同时,又要在发生TLB失效时减少处理开销 (判断,并预先为下一步可能会被用到的页面在TLB中装载表项)
    TLB失效:
    (1) 软失效——当一个页面访问在内存中而不在TLB中
    (2) 硬失效——当一个页面本身不在内存中(肯定也不在TLB中)
    (3) 其他实际情况
    硬失效的处理时间是软失效的百万倍
    页表遍历:在页表结构中查找相应的映射
    引入TLB可以加快虚拟地址到物理地址的转换

3.3.4 针对大内存的页表

怎样处理巨大的虚拟地址空间?

  1. 多级页表
    引入原因:避免把全部页表一直保存在内存中,特别是那些从不需要的页表就不应该保留。
    现代操作系统 第4版 第3章笔记_第12张图片
    链接:两级页表

1.一级页表
由(a)可知,offset(偏移量)是12位,所以页面大小=2^12 B=4KB,【因此页内地址要用12位表示,剩余20位表示页号】共有2^20个页面;相应的,在一个进程的页表中,会有2 ^20个页表项。若页表项长度为4B,所以一个页表需要2 ^20 * 4B = 2 ^22B。若一个页框(内存块)大小为4B,所以需要2 ^22/2 ^12 = 2 ^10个页框存储该页表。而页表的是连续存储的,因为根据页号查询页表的方法:K号页对应的页表项的位置 = 页表起始地址 + K * 4B(页表项长度)。
这和之前将进程划分成一个个页面以此不用连续的存放在内存中的目标相悖;而且,根据局部性原理可知,进程在一段时间内只需要访问某几个页面就可以正常运行了,因此也没有必要让整个页面都常驻内存。
2.两级页表
将长长的页表进行分组,使每个页面中刚好可以放下一个分组。
e.g. 假设一个机器有38位虚拟地址和32位的物理地址。若采用二级页表,页面大小为16KB,每个页表项为4B,应该对第一级和第二级页表域分配多少位?
分别分配12位。页面大小=16KB=2^14B,offset=14;因为每个页表项长度为4B,每个页面可容纳2 ^12个页表项。因此每2 ^12个连续的页表项为一组,每组刚好占一个页面,需要12个位来索引一个页面。38-12-14=12 bits

  1. 倒排页表
    实际内存中的每个页框对应一个表项,而不是每个虚拟页面对应一个表项。
    优点:节省了大量的空间
    缺点:从虚拟地址到物理地址的转换会变得很困难
    解决:使用TLB (记录所有频繁使用的页面;TLB失效时,就要用软件搜索整个倒排页表。)

3.4 页面置换算法

当发生缺页中断时,OS必须在内存中选择一个页面将其换出内存,再调入另一个页面。如果要换出的页面在内存驻留期间已经被修改过,就必须把它写回磁盘以更新该页面在磁盘上的副本;如果没有被修改过,直接覆盖即可。

  1. 最优页面置换算法(TheOptimal Page Replacement Algorithm)
    应该置换标记最大的页面;但该算法不可能实现,OS无法预知各个页面下一次将在什么时候被访问。

  2. 最近未使用页面置换算法(NRU)
    系统为每一页面设置了两个状态位:访问位(R位),修改位(M位)
    选择在最近一段时间内未使用过的一页并置换。

  3. 先进先出页面置换算法(FIFO)
    选择在内存中驻留时间最长的页并置换它。
    实现:页面链表法(最早进入的页面放在表头,最新进入的页面放在表尾。)
    该算法还会出现奇特的Belady现象

  4. 第二次机会页面置换算法(SCR)
    按照先进先出算法选择某一页面,检查其访问位R,如果为0,则置换该页;如果为1,则给第二次机会,并将访问位置为0。

  5. 时钟页面置换算法(Clock)
    需要用到一个访问位,当一个页面被访问时,将访问为置为 1。
    首先,将内存中的所有页面保存在一个环形链表中,当缺页中断发生时,检查当前指针所指向页面的R位,如果R位为 0,就将该页面换出;否则将该页的访问位设置为 0,给该页面第二次的机会,移动指针继续检查。
    链接:Clock
    CLOCK

  6. 最近最少使用页面置换算法(LRU)
    选择最后一次访问时间距离当前时间最长的一页并置换,即置换未使用时间最长的一页。
    实现:在内存中维护一个所有页面的链表→费时、开销大

  7. 最不常用算法(NFU)
    将每个页面与一个软件计数器相关联,计数器的初始值为0。每次时钟中断时,由OS扫描内存中的所有页面,将每个页面的R位(0/1)加到它的计数器上。这个计数器大体上跟踪了各个页面被访问的频繁程度。当发生缺页中断时,则置换计数器值最小的页面。
    缺点:从来不忘记任何事情

  8. 老化算法(aging)
    首先,在R位被加进之前先将计数器右移一位;其次,将R位加到计数器最左端的位而不是最右端的位。
    现代操作系统 第4版 第3章笔记_第13张图片

  9. 工作集页面置换算法
    请求调页(demand paging):页面在需要时被调入,而不是预先装入。
    预先调页(prepaging):在进程运行前预先装入其工作集页面。
    当前实际运行时间:进程从它开始执行到当前所实际使用CPU时间总数。
    局部性访问:在进程运行的任何阶段,它都只访问较少的一部分页面。
    颠簸(denning):每执行几条指令程序就发生一次缺页中断。
    工作集:根据程序的局部性原理,一般情况下,进程在一段时间内总是集中访问一些页面,这些页面称为活跃页面;如果分配给一个进程的物理页面数太少了,使该进程所需的活跃页面不能全部装入内存,则进程在运行过程中将频繁发生中断。
    工作集是随着时间变化的
    现代操作系统 第4版 第3章笔记_第14张图片

  10. 工作集时钟页面置换算法
    现代操作系统 第4版 第3章笔记_第15张图片
    链接:操作系统:裁员 ——页面置换算法
    链接:页面置换算法

3.5 分页系统中的设计问题

清除策略
设计分页守护进程,大多数时候它是睡眠,但定期被唤醒以检查内存的状态。当需要使用一个已置换出的页框时,如果该页框还没有被覆盖,将其从空闲页框缓冲池中移出即可恢复该页面。
使用一个双指针时钟实现清除策略。前指针由分页守护进程控制:当它指向一个“脏”页面时,就把该页面写回磁盘,前指针向前移动;当它指向一个干净页面时,仅仅向前移动指针。后指针用于页面置换,与标准时钟算法一样。

3.6 有关实现的问题

缺页中断处理

  1. 硬件陷入内核,在堆栈中保存程序计数器。大多数机器将当前指令的各种状态信息保存在特殊的CPU寄存器中。
  2. 启动一个汇编代码例程保存通用寄存器和其他易失的信息,以免被OS破坏。这个例程将OS作为一个函数来调用。
  3. 当OS发现一个缺页中断时,尝试发现需要哪个虚拟页面。通常一个硬件寄存器包含了这一信息,如果没有的话,OS必须检索程序计数器,取出这条指令,用软件分析这条指令,看看它在缺页中断时正在做什么。
  4. 一旦知道了发生缺页中断的虚拟地址,OS检查这个地址是否有效,并检查存取与保护是否一致。如果不一致,向进程发出一个信号或杀掉该进程。如果地址有效且没有保护错误发生,系统则检查是否有空闲页框。如果没有空闲页框,执行页面置换算法寻找一个页面来淘汰。
  5. 如果选择的页框“脏”了,安排该页写回磁盘,并发生一次上下文切换,挂起产生缺页中断的进程,让其他进程运行直至磁盘传输结束。无论如何,该页框被标记为忙,以免因为其他原因而被其他进程占用。
  6. 一旦页框“干净”后(无论是立刻还是在写回磁盘后),OS查找所需页面在磁盘上的地址,通过磁盘操作将其装入。该页面正在被装入时,产生缺页中断的进程仍然被挂起,并且如果有其他可运行的用户进程,则选择另一个用户进程运行。
  7. 当磁盘中断发生时,表明该页已经被装入,页表已经更新可以反映它的位置,页框也被标记为正常状态。
  8. 恢复发生缺页中断指令以前的状态,程序计数器重新指向这条指令。
  9. 调度引发缺页中断的进程,OS返回调用它的汇编语言例程。
  10. 该例程恢复寄存器和其他状态信息,返回到用户空间继续执行,就好像缺页中断没有发生过一样。

3.7 分段

用户程序地址空间按进程自身的逻辑关系划分为若干段,内存空间被动态的划分为若干个长度不相同的区域(可变分区)。以段为单位分配内存,每一段在内存中占据连续空间,各段之间可以不连续存放。

段页式:
用户程序地址空间:段式;内存空间:页式;分配单位:页

纯分段的实现——分段和分页的实现本质上是不同的:页面是定长的,而段不是。
现代操作系统 第4版 第3章笔记_第16张图片

你可能感兴趣的:(现代操作系统 第4版 第3章笔记)