段式内存管理VS页式内存管理

在讲解段式内存管理、页式内存管理之前,需要了解X86体系结构中的实模式和保护模式相关内容。

在 X86 架构诞生之初,其实是没有虚拟内存的概念的。1978 年发行的 8086 芯片是 X86 架构的首款芯片,它在内存管理上使用的是直接访问物理内存的方式,这种工作方式,有一个专门的名称,那就是实模式(Real Mode)。直接访问物理内存的工作方式让程序员必须要关心自己使用的内存会不会与其他进程产生冲突,为程序员带来极大的心智负担。

后来,CPU 上就出现虚拟内存的概念,它可以将每个进程的地址空间都隔离开,极大地减轻了程序员的负担,同时由于页表项中有多种权限保护标志,极大地提高了应用程序的数据安全。所以人们把 CPU 的这种工作模式称为保护模式(Protection Mode)。

从实模式演进到保护模式,X86 体系架构的内存管理发生了重大的变化,最大的不同就体现在段式管理和中断的管理上。我们会围绕这两个重点,让你彻底理解X86 体系架构下的内存管理演进。学会阅读 Linux 内核源码的段管理和中断管理的相关部分,还可以增加调试 coredump 文件的能力。
这里我们就按照时间顺序,从 8086 芯片中的实模式开始讲起。

8086 中的实模式

8086 芯片是 Intel 公司在 1978 年推出的 CPU 芯片,它定义的指令集对计算机的发展历程影响十分巨大,之后的 286、386、486、奔腾处理器等等都是在 8086 的基础上演变而来。这一套指令集也被称为 X86 指令集。

8086 的寄存器只有 16 位,我们也习惯于称 8086 的工作模式是 16 位模式。而且,后面的 CPU 为了保持兼容,在芯片上电了以后,还必须运行在 16 位模式之下,这种模式有个正式的名字,叫做实模式(Real Mode)。在实模式下,程序员是不能通过内存管理单元(Memory Management Unit, MMU)访问地址的,程序必须直接访问物理内存。

那实模式下,我们是怎么访问存储的物理地址的呢?

8086 的寄存器位宽是 16 位,但地址总线却有 20 位,这意味着 8086 的寻址空间是1M。但是在写程序的时候,我们是没有办法把一个地址完整地放到一个寄存器里的,因为它的寄存器相比地址少了 4 位。

为了解决这个问题,8086 就引入了段寄存器,例如 cs、ds、es、gs、ss 等。段寄存器中记录了一个段基地址,通过计算可以得到我们存储的真实地址,也就是物理地址。物理地址可以使用“段寄存器: 段内偏移”这样的格式来表示,计算的公式是:

物理地址 = 段寄存器 << 4 + 段内偏移

不过,在我们写汇编代码的时候,也不一定就要使用段寄存器来表示段基址,也可以使用“段基址: 段内偏移”这样的立即数的写法,比如你可以看下这个节选自 Linux 的bootsect 中的代码:

BOOTSEG = 0x7c0

_start:
    jmpl $BOOTSEG, $start2

start2:
    movw $BOOTSEG, %ax
    movw %ax, %ds
    ...

这块代码里,它跳转的目标地址就是 0x7c0 << 4 + OFFSET(start2)。跳转成功以后,cs段寄存器中的值就是段基址 0x7c0,start2 的偏移值是 8,所以记录当前执行指令地址的 ip 寄存器中的值就是实际地址 0x7c08。

而且,这块代码里也包含了段基址和段内偏移值这种地址形式,这种包含了段基址和段内偏移值的地址形式有一个专门的名字,叫做逻辑地址。你可以看到,虚拟地址是一个整数,而逻辑地址是一对整数。所以说,在 8086芯片中,逻辑地址要经过一步计算才可以得到物理地址

在 8086 中,cs 被用来做为代码段基址寄存器,比如上面示例代码中的 jmp 指令,跳转成功就会把段基址自动存入 cs 寄存器。ds 被用来做为数据段基址寄存器,你可以看看下面,这个代码:

INITSEG = 0x9000
    ...
    movw $INITSEG, %ax
    movw %ax, %ds
    movb $0x03, %ah
    xor %bh,%bh
    int $0x10
    movw %dx, (0)
    movb $0x88, %ah
    int $0x15
    movw %ax,(2)

上述代码的第 7 行执行 0x10 号 BIOS 中断,它的结果存放在 dx 寄存器中,然后第 8行,将结果存入内存 0x90000,9 至 11 行再把 0x15 号 BIOS 中断的结果存到 0x90002处。

在寻址时,我们并没有明确地声明数据段基址存储在段寄存器 ds 中,但是 CPU 在执行时会默认使用 ds 做为数据段寄存器。类似的还有 ss,它是做为栈基址寄存器,当我们在使用 push 指令的时候,要保存的数据会放在 ss:(sp) 的位置。

CPU 没有强制规定代码段和数据段分离,也就意味着,你使用 ds 段寄存器去访问指令,CPU 也是允许的。但在实际编程时,我们还是会把数据和代码分到不同的段里,并且将数据段的起始地址放到 ds 寄存器,把代码段的地址放到 cs 寄存器。这种按功能分段的管理内存方式就是段式管理。关于段式管理和页式管理的对比,我们稍后会加以介绍。

到这里 8086 的实模式,我们已经基本讲完了。8086 是最古老的 X86 芯片,在实模式下,它只能直接操作物理内存,非常不便于编程。接下来,我们把目光转向 X86 体系架构中的保护模式,它是实模式的进一步发展。

i386 中的保护模式

经过十年的发展,X86 CPU 迎来了历史上使用最广泛、影响力最大的 32 位 CPU,这就是i386 芯片。i386 与 8086 的一个很大的不同,就是它采用了全新的保护模式。这个体现在,i386 中的段式管理机制,相比 8086 发生了重大变化;同时,i386 芯片在段式管理的基础上,还引入了页式管理。

i386 在完成各种初始化动作以后,就会开启页表,从此程序员就不必再直接操作物理内存的地址空间了,代替它的是线性地址空间。而且由于段和页都能提供对内存的保护,安全性也得到了提升,所以这种工作模式被称为保护模式(Protection Mode)。i386 的保护模式是一种段式管理和页式管理混合使用的模式。

这里我们就来看一下相比 8086,段式管理在 i386 上有了哪些变化。

变化一:段选择子和全局描述符表

在 i386 上,地址总线是 32 位的,通用寄存器也变成 32 位的,这就意味着因为寄存器位数不够而产生的段基址寄存器已经失去了作用。

但是 i386 没有直接放弃掉段寄存器,而是将它进化成了新的段式内存管理。段寄存器仍然是 16 位寄存器,但是其中存的不再是段基址,而是被称为段选择子的东西。

相比 8086 芯片,i386 中多了一个叫全局描述符表(Global Descriptor Table, GDT)的结构。它本质上是一个数组,其中的每一项都是一个全局描述符,32 位的段基址就存储在这个描述符里。段选择子本质上就是这个数组的下标。具体你可以看看下面这张图:

GDT 的地址也要保存在寄存器里,这个寄存器就是 GDTR,这个做法和CR3 寄存器的做法十分相似。

在上面这张图中,CPU 在处理一个逻辑地址“cs:offset”的时候,就会将 GDTR 中的基址加上 cs 中的下标值来得到一个段描述符,再从这个段描述符中取出段基址,最后将段基址与偏移值相加,这样就可以得到线性地址了。这个线性地址就是我们上节课中所讲的虚拟地址。

得到线性地址以后,剩下的工作我们就非常熟悉了:由 CPU 的 MMU 将线性地址映射为物理地址,然后就可以交给地址总线去进行读写了。

变化二:段寄存器对段的保护能力增强

在 8086 中,段寄存器只起到了段基址的作用,对于段的各种属性并没有加以定义。例如,在实模式下,任何指令都可以对代码段进行随意地更改。

但在 i386 中,对段的保护能力加强了,我们先来看一下 i386 中段描述符(也就是 GDT中的每一项)的结构。

你会看到,描述符中除了记录了段基址之外,还记录了段的长度,以及定义了一些与段相关的属性,其中比较重要的属性有 P 位、DPL、S 位、G 位和 Type。我们接下来一个个来分析。

P 位是一个比特,指示了段在内存中是否存在,1 表示段在内存中存在,0 则表示不存在。

DPL,占据了两个比特,指的是描述符特权级,英文是 Descriptor Privilege Level。Intel规定了 CPU 工作的 4 个特权级,分别是 0、1、2、3,数字越小,权限越高。

以 Linux 为例,Linux 只使用了 0 和 3 两个特权级,并且规定 0 是内核态,3 是用户态。特权级的切换是比较复杂的一种机制,但 Linux 只使用了中断这一种,后面我们会再讲到中断。

接下来我们再看 S 位,S 为 1 代表该描述符是数据段 / 代码段描述符,为 0 则代表系统段/ 门描述符。门是 i386 提供的用于切换特权级的机制,有调用门、陷阱门、中断门、任务门等。在 Linux 系统中,只使用了中断门描述符。

然后是 G 位,它指的是定义段颗粒度(Granularity),它的值为 0 时,段界限的单位是字节,为 1 时段界限以 4KB 为单位,也就是一页。

我们也可以从图中看出定义段长度的“段界限”字段并不是连续的,它一共有 20 位,分散在两个地方。当 G=1 时,段界限的最大值是 2^20 * 4K = 4G,这是 i386 一个段的最大长度。

最后是 Type 属性,它定义了描述符类型,我把比较重要的类型用表列在了下面,你可以看看。

到这里,我们已经解释清楚了,i386 中保护模式相比 8086 实模式在段式管理上的升级。那么在现代的 CPU 和操作系统中,段式管理和页式管理又是怎样的关系呢?要讲清楚这一点就要先对比这两种内存管理方式的优缺点。

段式管理对比页式管理

段式管理会按功能把内存空间分割成不同段,有代码段、数据段、只读数据段、堆栈段,等等,为不同的段赋予了不同的读写权限和特权级。通过段式管理,操作系统可以进一步区分内核数据段、内核代码段、用户态数据段、用户态代码段等,为系统提供了更好的安全性。

但是段的长度往往是不能固定的,例如不同的应用程序中,代码段的长度各不相同。如果以段为单位进行内存的分配和回收的话,数据结构非常难于设计,而且难免会造成各种内存空间的浪费。

页式管理则不按照功能区分,而是按照固定大小将内存分割成很多大小相同的页面,不管是存放数据,还是存放代码,都要先分配一个页,再将内容存进页里。

所以,你可以看到,相比页式管理,段式管理的优点是提供更好的安全性,按照内存的用途进行划分更符合人的直观思维。它的缺点就是由于不定长,难于进行分配、回收调度。

而页式管理的优点是大小固定,分配回收都比较容易。而且段式管理所能提供的安全性,在现代 CPU 上也可以被页表项中的属性替代,所以现在段式管理已经变得越来越不重要了。像 64 位 Linux 系统,它把所有段的基地址都设成了从 0 开始,段长度设置为最大。这样段式管理的重要性就大大下降了。

但是,如果我们以 X86 的历史演进来看,你会发现段式管理其实是最早出现的(8086 芯片),然后才出现了页式管理(i386 芯片)。而且,我们现代的 X86 架构的 CPU,也同时兼容段式管理和页式管理,我们可以认为是一种混合的段页式管理(当然,并不是所有人都认可这种命名方式)。

总的来说,现代的操作系统都是采用段式管理来做基本的权限管理,而对于内存的分配、回收、调度都是依赖页式管理。

到这里,我们就讲清楚了 8086 实模式到 i386 保护模式下段式管理的演进,并且进一步分析了段式管理和页式管理的对比和现状。

保护模式相比实模式,发生重大变化的不止是内存管理,同时还有中断管理。因为管理中断的结构与段式管理的全局描述符表的结构非常相似,所以我们在讲保护模式时也一起讲一下。你可以将中断机制与段管理机制比较着一起学习。

中断描述符表

中断描述符表(Interruption Description Table, IDT),是 i386 中一个非常重要的描述符表,它也是保护模式对比实模式的另一大不同。后面学习 fork、execve 的实现时,涉及到的写保护中断,缺页中断等机制都要依赖它。

CPU 与外设之间的协同工作是以中断机制来进行的。例如,我们敲击键盘的时候,键盘的控制器就会向 CPU 发起一个中断请求。CPU 在接到请求以后,就会停下正在做的工作,把当前的寄存器状态全部保存好,然后去调用中断服务程序。当然,这个过程中有一些是CPU 的工作,有一些是操作系统的工作,但因为我们关注的重点是内存,所以就没必要计较这里面细微的差别了。

中断根据中断来源的不同,又可以细分为 Fault、Trap、Abort 以及普通中断。我们这门课对它们也不加区分,例如执行除法的时候除数为 0 的情况、访问数据时权限不足引发的保护错误、由用户使用 int 指令产生的中断等,虽然中断源不同,它们的类型也不相同,但我们统一称它们为中断。

硬件负责产生中断,CPU 会响应中断,但是中断来了以后要做什么事情是由操作系统定义的。操作系统要通过设置某个中断号的中断描述符,来指定中断到达以后要调用的函数。中断描述符表(IDT)的作用就体现在这了,它的本质就是中断描述符的数组。

IDT 的基地址存储在 idtr 寄存器中,这和 GDTR 的设计如出一辙。每个中断都有一个编号与其对应,我们称之为中断向量号。中断向量号是 CPU 提前分配好的,我也把比较重要的中断向量号放在了下表里,你可以看看。

在这个表里,我们没有看到前边所提到的键盘中断,这是因为键盘中断都是由一个名为8259A 的芯片在管理。

两片级联的 8259A 芯片可以管理 16 个中断,其中包括了时钟中断、键盘中断,还有软盘、硬盘、鼠标的中断等等。这些中断的中断向量号是可以通过对 8259A 编程进行设置的。虽然 8259A 的编程比较繁琐,但好在只需要操作系统开机引导时设置一次。

你也可以看到,Linux 系统把中断向量表的 32 号中断(用户自定义中断的第一位)设置成8259A 的 0 号中断,也就是说 IDT 的 32 号至 47 号都分配给了 8259A 所管理的中断。键盘、软盘、硬盘、鼠标的中断服务程序就设置在这里。

现在,我们可以通过一个例子,体验一下中断的使用。在 Linux 系统上,我们把下面这个代码保存到文件 hello.c 中,并且使用"gcc -o hello hello.c"编译,得到可执行程序hello。再运行它,你就可以看到屏幕上打印出一行"hello"。

void sayHello() 
{
    const char* s = "hello\n";
    __asm__("int $0x80\n\r"
            ::"a"(4), "b"(1), "c"(s), "d"(6):);
}

int main()
{
    sayHello();

    return 0;
}

相比于使用 printf 进行打印,需要引入头文件"stdio.h",我们这段代码里没有使用任何头文件,但理论上一样可以在控制台上进行打印。

这是因为,我们使用了 0x80 号中断进行了 Linux 系统调用。系统调用号在 eax 中,也就是 4,代表 write 这个调用。第一个参数在 ebx 中,其值为 1,代表控制台的标准输出;第二个参数是字符串"hello"的地址,在 rcx 中;第三个参数是字符串的长度,也就是 6,存储在 edx 中。

这样,我们就通过中断,就不必再使用 C 语言的 printf 进行输出,这就绕过了 C 语言的基础库,完成了向控制台打印的功能。如果不能够成功输出,想一想为什么?

小结

我们拆解了 X86 体系架构下的实模式和保护模式,也认识了两个 X86 演进史上非常重要的 CPU。

8086 是 16 位的 CPU,我们称 8086 的工作模式为实模式,它的特点是直接操作物理内存,内存管理容易出错,要十分小心,代码编写和调试都很困难。之后出现的 i386,则采用了和实模式不同的保护模式。相比实模式,i386 中的保护模式,采用了页式管理,但它没有彻底放弃 8086 的段式管理,而是将段寄存器中的值由段基址变成了段选择子。段选择子本质是 GDT 表的下标值,段基址都转移到 GDT 中去了。

段式管理负责将逻辑地址转换为线性地址,或者称为虚拟地址,页式管理负责将线性地址映射到物理地址。i386 的保护模式采用了段页式混合管理的模式,兼具了段式管理和页式管理的优点。

除了段页式内存管理这个不同之外,保护模式和实模式的区别还体现在中断描述符表(IDT)上。IDT 是保护模式的一个重要组成部分,它保存着 i386 中断服务程序的入口地址。

8086 和 i386 对 X86 架构的 CPU 影响巨大。直到今天,X86 架构的 CPU 在上电以后,为了与 8086 保持兼容,还是运行在 16 位实模式下,也就是说所有访存指令访问的都是物理内存地址。在启动操作系统后,才会切换到保护模式下进行工作。

段式管理和页式管理会出现内存碎片吗?

段式管理可以做到段根据实际需求分配空间,所以有多少需求就分配多大的段。那么在段的内部就不会产生空间浪费,也就没有碎片,但是由于每个段的长度不固定,所以多个段未必能够恰好使用所有的内存空间,也就是说段之间还是会产生碎片。

而页则是固定大小的,通常是4K,假设我们要为代码准备空间,即使这一段机器码不足4K,我们也要为它分配4K,因为一个页的大小就是4K,我们最少只能分配一个页,所以页式管理,页与页之间精密排列,但是页内会出现内存浪费,也就是内存碎片。

在操作系统中,页式内存管理是非常重要的内容,页式内存管理,引出了多任务程序设计的基础。

段式内存管理回顾

我们知道计算机上电之后,会实模式跳转到保护模式的。那么这个保护模式,是保护什么的?

这里的保护模式是保护内存段的,在X86处理器中内置的保护模式,一旦保护模式打开,那么某一段内存的访问就是受限的,这里的受限就是指受保护的。保护模式就是来保护一段连续的内存空间,这里的一段连续的内存空间,我们简称为段。

这里的"段"具体指什么?

一段连续的内存空间。

为什么会有段式内存管理

  1. 程序的各个部分相对独立(如:数据段,代码段),代码段在运行的过程中会访问数据段,但是代码段和数据段时独立的。
  2. 早期X86处理器无法通过一个寄存器访问所有的内存单元
  3. 解决早期程序运行时的重定位问题

段式内存管理的应用

  1. 在X86系列处理器中,硬件对段式内存管理进行了直接支持。
  2. 另外,段式内存管理也可以使用纯软件实现。
  3. 核心:段首地址+段内偏移地址 = 内存单元地址

段式内存管理在C语言中的体现

  1. 数组的本质:一片连续的内存(段)
  2. 数组名(array):数组的起始内存地址(段地址)
  3. 数组元素的访问:array[i]  <--> *(array + i)
  4. 第i个元素的地址:array(段地址) + i(偏移地址)

操作系统中只使用段式内存管理是否足够?

软件硬件技术发展

硬件技术

  1. 计算机部件独立化(硬件接口相同,可以任意组装)
  2. 计算机配置差异化(各个部件硬件参数不同,如:内存容量)

软件技术

  1. 应用程序出来的问题越来越复杂(解决实际问题)
  2. 应用程序运行需要的资源越来越多(物理内存可能无法满足,所以就诞生了虚拟内存管理)

问题

应用程序规模越来越大,导致多数时候无法全部加载进内存,如何解决?

可行解决方案:按段加载(局部性原理)

  1. 不管应用程序规模多大,在某个很短的时间范围内存,程序总是在某个局部运行。
  2. 只将当前程序运行需要的段加载进内存。
  3. 当某个段不再需要使用,立即从内存中移除。

按断加载可能带来的问题

  1. 段的大小不确定,某些应用程序很复杂,可能一个段的大小就会大于实际的物理内存。有可能程序中的某一个局部的内存段就会大于实际的物理内存,那么这个局部的内存段就没有办法加载到内存中去,那么这个程序就没有办法运行了。
  2. 段是没有固定长度的,段加载时需要具体的长度信息,导致效率不高。

有更进一步的解决方案是,在段的基础上,进行分页。

更进一步的解决方案:内存分页

  1. 页指的是固定大小的内存片(4KB)
  2. 每一个内存段由多个页组成
  3. 页是进行内存管理的基本单位(加载页,换出页)

应用程序数据段、代码段、堆栈段 是相对独立的。

每个段都是有各个页组成的

代码段在运行过程中,会去加载数据段的内容,也会去加载堆栈段的内容。

生活中“段页式”应用

大多数情况下,书都采用“段页式”的方法进行内容编排。

书是由一章一章组成的,一章一章不固定,然后一章一章是由页组成的。

章的大小不固定,就相当于程序中的段,页的大小固定,相当于程序中的页。

进阶虚拟存储技术

  1. 实模式下所使用的是什么地址空间?物理地址空间
  2. 保护模式下所使用的是什么地址空间?偏移地址(线性地址)空间,保护模式下,都是由段地址 + 段内偏移地址 组成的
  3. 如何分离不同应用程序所使用的内存空间?提前分页规划好
  4. 程序运行需要的内存大于实际物理内存该怎么办?分页,按页加载

内存分页的意义

虚拟内存空间(逻辑地址):程序执行时内部所使用的内存空间(独立于其他程序)

物理内存空间(物理地址):物理机器所配置的实际内存空间(所有程序共享)

虚拟地址(逻辑地址)需要进行转换(内存映射)才能得到对应的物理地址。

这个转换(内存映射)是怎么进行的?

页式内存管理中的地址

地址 = 页号 + 页内偏移

  1. 逻辑地址(虚拟地址) = 逻辑页号(虚拟页号) + 页内偏移
  2. 物理地址 = 物理页号 + 页内偏移
  3. 地址转换时仅仅变更页号即可,页内偏移不变

逻辑地址(虚拟地址)到物理地址的映射(重定位)

0XAABBCC12(逻辑地址)  --> MMU --> 0xDDCC12(物理地址)

                                               MMU会去查询地址映射表(页表)

逻辑页号物理页号属性
.................
0XAABB0XDD0X00
.................

0XAABBCC12(虚拟地址) 到 MMU中去查表,发现逻辑页号 0XAABB 存在页表中,然后对应的物理页号是0XDD,所以把虚拟地址的高位AABB 替换成 DD,低位不变。所以最后的物理地址就是:0XDDCC12

页式内存管理中的关键操作一

页请求

访问一个逻辑地址(虚拟地址)时,对应的页不在内存中

  1. 从外存中将目标页加到内存中
  2. 之后更新页表

页式内存管理中的关键操作二

页交换

页请求时发现物理内存不足,需要将暂时不用的页移除

  1. 首先,决定并选择需要移除的页
  2. 将选中页中的所有数据写入外存
  3. 更新页表,重新进行页请求

小结

  1. 内存分段能够解决一定的问题,但无法保证程序的移植行
  2. 根据程序运行的局部性原理,可进一步对内存进行分页
  3. 页指的是固定大小的内存片(4KB) (1MB)
  4. 页的引入使得程序的逻辑地址与内存的物理地址彻底分离
  5. 操作系统的内存管理是以页为单位完成的

页式内存管理需要注意的问题

  1. 操作系统如何管理实际的物理内存?操作系统会根据一定的策略来管理物理内存,使得当进行页请求、页置换的时候,能够高效的得到一个物理页。
  2. 页表与不同任务(app)有怎么样的关系?每个任务都有自己专属的页表,当任务结束之后,这个页表就摧毁了。
  3. 页表对于任务(app)的意义是什么?
  4. 页交换时如何选择需要替换的内存页?
  5. 页表具体是如何构成的?

操作系统如何管理实际的物理内存?

页框与页面(Frame and Page)

  1. 页框(Frame) :物理内存空间中的页(物理页)
  2. 页面(Page) :逻辑内存空间中的页(逻辑页)
  3. 页框(Frame)用于存储页面(Page)内容,而页面内容来源于逻辑内存(虚拟内存)空间。

操作系统必须知道物理内存的使用情况

  1. 建立结构(数据结构)对物理内存进行管理(Frame Table,页框表)
  2. 结构记录:页框是否可用,被谁使用,等
  3. 为具体的应用程序分配页表

页表对于任务(app)的意义是什么?

  1. 页表机制能够保证任务无法意外的访问或破坏其他任务的内存。
  2. 页表是虚拟内存空间与物理内存空间的“分界线”
  3. 因为页表的存在,各个任务才具备相同独立的内存空间
  4. 页表是虚拟内存通往物理内存的“唯一通道”

任务只能在页表机制下间接访问分配的物理内存,因此无法对其他内存进行访问。

页交换时如何选择需要替换的内存页?

原则:挑选不再使用的内存页进行替换

FIFO页交换算法 :将最先进入内存的页移出

LRU页交换算法:将当前使用最少的页从内存中移出

代码实战

  1. 用代码描述:页表(PageTable)、页框表(FrameTable)、任务结构(PCB)
  2. 用代码实现:页请求、页置换
  3. 模拟任务的页面访问过程(访问虚拟内存)
  4. 模拟操作系统中的任务调度(扩展)

实现关键点一

  1. 页框表是全局唯一的,用于记录内存页的使用情况
  2. 页表是任务私有的,用于记录虚拟页到内存页的映射关系
  3. 每一个任务需要一个结构体进行表示

 实现关键点二

  1. 页框分配操作:int GetFrameItem();
  2. 页请求操作:int RequestPage(int pid, int page);
  3. 页交换操作:int SwapPage();

实现关键点三

如何模拟不同的任务?

        不同的任务的虚拟页访问顺序不同

        因此,可以随机产生页面访问序列

简化版本:

#include <QCoreApplication>
#include <iostream>
#include <QString>

using namespace std;

#define FP_NONE -1

class PCB
{
    int m_pid;
    int* m_pageSerial;
    int m_pageSerialCount;
    int m_next;
public:
    PCB(int pid)
    {
        m_pid = pid;
        m_pageSerialCount = qrand()%5 +5;
        m_pageSerial = new int[m_pageSerialCount];

        for(int i=0;i<m_pageSerialCount;i++)
        {
            m_pageSerial[i] = qrand()%8;
        }

        m_next = 0;
    }


    bool running()
    {
        return m_next < m_pageSerialCount;
    }

    int getNextPage()
    {
        int ret = m_next++;

        cout<<"ret="<< ret <<endl;

        if(ret < m_pageSerialCount)
        {
            ret = m_pageSerial[ret];
        }
        else
        {
            ret = FP_NONE;
        }

        return ret;
    }

    void printPageSerial()
    {
        QString s="";

        for(int i=0;i<m_pageSerialCount;i++)
        {
            s+=QString::number(m_pageSerial[i]) + " ";
        }
        cout<<("Task"+QString::number(m_pid)+" : "+s).toStdString()<<endl;
    }

    ~PCB()
    {
        delete[] m_pageSerial;
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    PCB pcb(1);
    pcb.printPageSerial();

    while(pcb.running())
    {
        cout<<pcb.getNextPage()<<endl;
    }

    return a.exec();
}

#include <iostream>
#include <list>
#include <stdio.h>
#include <stdlib.h>
#include <QString>

using namespace std;

#define PAGE_NUM (0xFF +1)  //定义一个任务虚拟内存空间最多有256页,虚拟内存空间从第0页~255页
#define FRAME_NUM (0x04)   //物理内存空间只有4个页
#define FP_NONE   (-1)

struct FrameItem
{
    int pid;     // the task which use the frame
    int pnum;    // the page which the frame hold

    FrameItem()
    {
        pid = FP_NONE;
        pnum = FP_NONE;
    }
};

//页表,一级页表
class PageTable
{
    int m_pt[PAGE_NUM];  //表示每一个任务最大的虚拟内存页数,从第0页 到第 255页。
    public:
        PageTable()
        {
            for(int i=0;i<PAGE_NUM;i++)
            {
                m_pt[i] = FP_NONE;
            }
        }
        int& operator[] (int i)
        {
            if((0<=i) && (i<length()))
            {
                return m_pt[i];
            }
            else
            {
                exit(-1); //非法访问,直接结束
            }
        }

        int length()
        {
            return PAGE_NUM;
        }
};

class PCB   //表示一个任务结构
{
    int m_pid;               //任务id
    PageTable m_pageTable;   //页表
    int* m_pageSerial;       //simulate the page serial access
    int m_pageSerialCount;   //page access count
    int m_next;              //下一次要访问的页面的页号
public:
    PCB(int pid)
    {
        m_pid = pid;
        m_pageSerialCount = qrand() % 5 +5;
        m_pageSerial = new int[m_pageSerialCount];

        for(int i=0;i<m_pageSerialCount;i++)
        {
            m_pageSerial[i] = qrand() % 8;
        }

        m_next = 0;
    }

    int getPID()
    {
        return m_pid;
    }

    PageTable& getPageTable()
    {
        return m_pageTable;
    }

    int getNextPage()
    {
        int ret = m_next++;

        if(ret< m_pageSerialCount)
        {
            ret = m_pageSerial[ret];
        }
        else
        {
            ret = FP_NONE;
        }

        return ret;
    }

    bool running()
    {
        return (m_next < m_pageSerialCount);
    }

    void printPageSerial()
    {
        QString s ="";

        for(int i=0;i<m_pageSerialCount;i++)
        {
            s += QString::number(m_pageSerial[i]) + " ";
        }

        cout<< ("Task" + QString::number(m_pid) + " : " + s).toStdString() <<endl;
    }

    ~PCB()
    {
        delete[] m_pageSerial;
    }
};

int main()
{
    PCB pcb(1);

    pcb.printPageSerial();

    while(pcb.running())
    {
        cout<<pcb.getNextPage()<<endl;
    }

    return 0;
}

可以看到成功的模拟出不同的任务,以及不同的任务的虚拟内存访问顺序。

以上代表,第一次访问第7页,第二次访问第4页,第三次访问第5页,第四次访问第4页,第五次访问第6页。

用代码实现页请求、页交换

版本一:随机选择一个页进行交换

#include <QtCore/QCoreApplication>
#include <QList>
#include <iostream>
#include <ctime>
#include <QQueue>

using namespace std;

#define FRAME_NUM (0x04)
#define PAGE_NUM (0XFF+1)
#define FP_NONE -1

struct FrameItem
{
    int pid;
    int pnum;
    FrameItem()
    {
        pid = FP_NONE;
        pnum = FP_NONE;
    }
};

class PageTable
{
    int m_pt[PAGE_NUM];
public:
    PageTable()
    {
        for(int i=0;i<PAGE_NUM;i++)
        {
            m_pt[i] = FP_NONE;
        }
    }

    int length()
    {
        return PAGE_NUM;
    }

    int& operator[](int i)
    {
        if((0<=i) && (i<length()))
        {
            return m_pt[i];
        }
        else
        {
            QCoreApplication::exit(-1);
            return m_pt[0];
        }
    }
};

class PCB
{
    int m_pid;
    PageTable m_pageTable;
    int* m_serialPage;
    int m_serialPageCount;
    int m_next;
public:
    PCB(int pid)
    {
        m_pid = pid;
        m_serialPageCount = qrand()%5 + 5;
        m_serialPage = new int[m_serialPageCount];

        for(int i=0;i<m_serialPageCount;i++)
        {
            m_serialPage[i] = qrand()%8;
        }

        m_next = 0;
    }

    PageTable& getPageTable()
    {
        return m_pageTable;
    }

    int getPID()
    {
        return m_pid;
    }

    void printSerialPage()
    {
        QString s = "Task"+QString::number(m_pid)+" : ";

        for(int i=0;i<m_serialPageCount;i++)
        {
            s+=QString::number(m_serialPage[i])+" ";
        }

        cout<<s.toStdString()<<endl;
    }

    bool running()
    {
        return m_next < m_serialPageCount;
    }

    int getNextPage()
    {
        int ret = m_next++;

        if(ret < m_serialPageCount)
        {
            ret = m_serialPage[ret];
        }

        return ret;
    }

    ~PCB()
    {
        delete[] m_serialPage;
    }
};

FrameItem FrameTable[FRAME_NUM];
QList<PCB*> TaskTable;

int GetFrameItem();
void AccessPage(PCB& pcb);
int RequestPage(int pid,int page);
int SwapPage();
int Random();

void PrintLog(QString s);
void PrintPageMap(int pid,int page,int frame);
void PrintFatalError(QString s,int pid,int page);


int GetFrameItem()
{
    int ret = FP_NONE;

    for(int i=0;i<FRAME_NUM;i++)
    {
        if(FrameTable[i].pid == FP_NONE)
        {
            ret = i;
            break;
        }
    }

    return ret;
}

void AccessPage(PCB& pcb)
{
    int pid = pcb.getPID();
    PageTable& pageTable = pcb.getPageTable();
    int page = pcb.getNextPage();

    if(page != FP_NONE)
    {
        PrintLog("Access Task"+QString::number(pid) +" for page"+QString::number(page));
        if(pageTable[page] != FP_NONE)
        {
            PrintLog("Found Target page in page table.");
            PrintPageMap(pid,page,pageTable[page]);
        }
        else
        {
            PrintLog("Not Found Target page in page table");

            pageTable[page] = RequestPage(pid,page);

            if(pageTable[page] != FP_NONE)
            {
                PrintLog("succeed to request a frame"+QString::number(pageTable[page]));
            }
            else
            {
                PrintFatalError("Can not request a frame",pid,page);
            }
        }

    }
    else
    {
        PrintLog("Task"+QString::number(pid)+" is finished!");
    }
}

int RequestPage(int pid,int page)
{
    int frame = GetFrameItem();

    if(frame != FP_NONE)
    {
        PrintLog("succeed request a frame"+QString::number(frame));
    }
    else
    {
        PrintLog("Failed to request a frame,need to SwapPage");

        frame = SwapPage();

        if(frame != FP_NONE)
        {
            PrintLog("Succeed swap page from disk");
        }
        else
        {
            PrintLog("Failed to swap from disk");
        }
    }

    FrameTable[frame].pid = pid;
    FrameTable[frame].pnum = page;

    return frame;
}

int SwapPage()
{
    int obj = qrand() % FRAME_NUM;

    PrintLog("Random select a frame to swap from disk,frame"+QString::number(obj));

    FrameTable[obj].pid = FP_NONE;
    FrameTable[obj].pnum = FP_NONE;

    for(int i=0;i<TaskTable.count();i++)
    {
        PageTable& pt = TaskTable[i]->getPageTable();
        for(int j=0;j<pt.length();j++)
        {
            if(pt[j] == obj)
            {
                pt[j] = FP_NONE;
                break;
            }
        }
    }


    return obj;
}

int Random()
{
    return SwapPage();
}

void PrintLog(QString s)
{
    cout<<s.toStdString()<<endl;
}

void PrintPageMap(int pid,int page,int frame)
{
    QString s ="Task"+QString::number(pid) +" Page"+QString::number(page);

    s+=" ==> Frame"+QString::number(frame);

    cout<<s.toStdString()<<endl;
}

void PrintFatalError(QString s,int pid,int page)
{
    s+=" Task"+QString::number(pid)+" Page"+QString::number(page);

    cout<<s.toStdString()<<endl;

    QCoreApplication::exit(-2);
}


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qsrand(time(NULL));

    int index = 0;
    TaskTable.append(new PCB(1));

    for(int i=0;i<TaskTable.count();i++)
    {
        TaskTable[i]->printSerialPage();
    }

    PrintLog("===== Running ==========");

    while(true)
    {
        if(TaskTable[index]->running())
        {
            AccessPage(*TaskTable[index]);
        }

        index = (index + 1) % TaskTable.count();

        cin.get();
    }


    return a.exec();
}

上述实现可以进一步进行优化,当任务运行结束之后:

1.将任务结构从任务表中移除

2.回收任务所使用的页框

3.释放任务结构所占用的内存

主要是在任务结束之后,添加了如下代码:

 PrintLog("Task"+QString::number(TaskTable[index]->getPID()) + " is finished.");

 PCB* pcb = TaskTable[index];

 TaskTable.removeAt(index);

 ClearFrameItem(*pcb);

 delete pcb;

完整代码如下所示:

#include <QtCore/QCoreApplication>
#include <QList>
#include <iostream>
#include <ctime>
#include <QQueue>

using namespace std;

#define FRAME_NUM (0x04)
#define PAGE_NUM (0XFF+1)
#define FP_NONE -1

struct FrameItem
{
    int pid;
    int pnum;
    FrameItem()
    {
        pid = FP_NONE;
        pnum = FP_NONE;
    }
};

class PageTable
{
    int m_pt[PAGE_NUM];
public:
    PageTable()
    {
        for(int i=0;i<PAGE_NUM;i++)
        {
            m_pt[i] = FP_NONE;
        }
    }

    int length()
    {
        return PAGE_NUM;
    }

    int& operator[](int i)
    {
        if((0<=i) && (i<length()))
        {
            return m_pt[i];
        }
        else
        {
            QCoreApplication::exit(-1);
            return m_pt[0];
        }
    }
};

class PCB
{
    int m_pid;
    PageTable m_pageTable;
    int* m_serialPage;
    int m_serialPageCount;
    int m_next;
public:
    PCB(int pid)
    {
        m_pid = pid;
        m_serialPageCount = qrand()%5 + 5;
        m_serialPage = new int[m_serialPageCount];

        for(int i=0;i<m_serialPageCount;i++)
        {
            m_serialPage[i] = qrand()%8;
        }

        m_next = 0;
    }

    PageTable& getPageTable()
    {
        return m_pageTable;
    }

    int getPID()
    {
        return m_pid;
    }

    void printSerialPage()
    {
        QString s = "Task"+QString::number(m_pid)+" : ";

        for(int i=0;i<m_serialPageCount;i++)
        {
            s+=QString::number(m_serialPage[i])+" ";
        }

        cout<<s.toStdString()<<endl;
    }

    bool running()
    {
        return m_next < m_serialPageCount;
    }

    int getNextPage()
    {
        int ret = m_next++;

        if(ret < m_serialPageCount)
        {
            ret = m_serialPage[ret];
        }

        return ret;
    }

    ~PCB()
    {
        delete[] m_serialPage;
    }
};

FrameItem FrameTable[FRAME_NUM];
QList<PCB*> TaskTable;

int GetFrameItem();
void AccessPage(PCB& pcb);
int RequestPage(int pid,int page);
int SwapPage();
int Random();

void PrintLog(QString s);
void PrintPageMap(int pid,int page,int frame);
void PrintFatalError(QString s,int pid,int page);

void ClearFrameItem(PCB& pcb);

int GetFrameItem()
{
    int ret = FP_NONE;

    for(int i=0;i<FRAME_NUM;i++)
    {
        if(FrameTable[i].pid == FP_NONE)
        {
            ret = i;
            break;
        }
    }

    return ret;
}

void AccessPage(PCB& pcb)
{
    int pid = pcb.getPID();
    PageTable& pageTable = pcb.getPageTable();
    int page = pcb.getNextPage();

    if(page != FP_NONE)
    {
        PrintLog("Access Task"+QString::number(pid) +" for page"+QString::number(page));
        if(pageTable[page] != FP_NONE)
        {
            PrintLog("Found Target page in page table.");
            PrintPageMap(pid,page,pageTable[page]);
        }
        else
        {
            PrintLog("Not Found Target page in page table");

            pageTable[page] = RequestPage(pid,page);

            if(pageTable[page] != FP_NONE)
            {
                PrintLog("succeed to request a frame"+QString::number(pageTable[page]));
            }
            else
            {
                PrintFatalError("Can not request a frame",pid,page);
            }
        }

    }
    else
    {
        PrintLog("Task"+QString::number(pid)+" is finished!");
    }
}

int RequestPage(int pid,int page)
{
    int frame = GetFrameItem();

    if(frame != FP_NONE)
    {
        PrintLog("succeed request a frame"+QString::number(frame));
    }
    else
    {
        PrintLog("Failed to request a frame,need to SwapPage");

        frame = SwapPage();

        if(frame != FP_NONE)
        {
            PrintLog("Succeed swap page from disk");
        }
        else
        {
            PrintLog("Failed to swap from disk");
        }
    }

    FrameTable[frame].pid = pid;
    FrameTable[frame].pnum = page;

    return frame;
}

int SwapPage()
{
    int obj = qrand() % FRAME_NUM;

    PrintLog("Random select a frame to swap from disk,frame"+QString::number(obj));

    FrameTable[obj].pid = FP_NONE;
    FrameTable[obj].pnum = FP_NONE;

    for(int i=0;i<TaskTable.count();i++)
    {
        PageTable& pt = TaskTable[i]->getPageTable();
        for(int j=0;j<pt.length();j++)
        {
            if(pt[j] == obj)
            {
                pt[j] = FP_NONE;
                break;
            }
        }
    }


    return obj;
}

int Random()
{
    return SwapPage();
}

void PrintLog(QString s)
{
    cout<<s.toStdString()<<endl;
}

void PrintPageMap(int pid,int page,int frame)
{
    QString s ="Task"+QString::number(pid) +" Page"+QString::number(page);

    s+=" ==> Frame"+QString::number(frame);

    cout<<s.toStdString()<<endl;
}

void PrintFatalError(QString s,int pid,int page)
{
    s+=" Task"+QString::number(pid)+" Page"+QString::number(page);

    cout<<s.toStdString()<<endl;

    QCoreApplication::exit(-2);
}

void ClearFrameItem(PCB& pcb)
{
    for(int i=0;i<FRAME_NUM;i++)
    {
        if(FrameTable[i].pid == pcb.getPID())
        {
            FrameTable[i].pid = FP_NONE;
            FrameTable[i].pnum = FP_NONE;
        }
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qsrand(time(NULL));

    int index = 0;
    TaskTable.append(new PCB(1));
    TaskTable.append(new PCB(2));

    for(int i=0;i<TaskTable.count();i++)
    {
        TaskTable[i]->printSerialPage();
    }

    PrintLog("===== Running ==========");

    while(true)
    {
        if(TaskTable.count() > 0)
        {
            if(TaskTable[index]->running())
            {
                AccessPage(*TaskTable[index]);
            }
            else
            {
                PrintLog("Task"+QString::number(TaskTable[index]->getPID()) + " is finished.");

                PCB* pcb = TaskTable[index];

                TaskTable.removeAt(index);

                ClearFrameItem(*pcb);

                delete pcb;
            }
        }

        if(TaskTable.count() > 0)
        {
            index = (index + 1) % TaskTable.count();
        }

        cin.get();
    }


    return a.exec();
}

版本二:FIFO

重点实现了以下这两个函数:

void ClearFrameItem(int frame)
{
    FrameTable[frame].pid = FP_NONE;
    FrameTable[frame].pnum = FP_NONE;

    for(int i=0;i<TaskTable.count();i++)
    {
        PageTable& pt = TaskTable[i]->getPageTable();
        for(int j=0;j<pt.length();j++)
        {
            if(pt[j] == frame)
            {
                pt[j] = FP_NONE;
                break;
            }
        }
    }
}

int FIFO()
{
    int frame = MoveOut.dequeue();

    PrintLog("Use FIFO strategy to swap,Frame"+QString::number(frame));


    ClearFrameItem(frame);


    return frame;
}

完整版本如下所示:

#include <QtCore/QCoreApplication>
#include <QList>
#include <iostream>
#include <ctime>
#include <QQueue>

using namespace std;

#define FRAME_NUM (0x04)
#define PAGE_NUM (0XFF+1)
#define FP_NONE -1

struct FrameItem
{
    int pid;
    int pnum;
    FrameItem()
    {
        pid = FP_NONE;
        pnum = FP_NONE;
    }
};

class PageTable
{
    int m_pt[PAGE_NUM];
public:
    PageTable()
    {
        for(int i=0;i<PAGE_NUM;i++)
        {
            m_pt[i] = FP_NONE;
        }
    }

    int length()
    {
        return PAGE_NUM;
    }

    int& operator[](int i)
    {
        if((0<=i) && (i<length()))
        {
            return m_pt[i];
        }
        else
        {
            QCoreApplication::exit(-1);
            return m_pt[0];
        }
    }
};

class PCB
{
    int m_pid;
    PageTable m_pageTable;
    int* m_serialPage;
    int m_serialPageCount;
    int m_next;
public:
    PCB(int pid)
    {
        m_pid = pid;
        m_serialPageCount = qrand()%5 + 5;
        m_serialPage = new int[m_serialPageCount];

        for(int i=0;i<m_serialPageCount;i++)
        {
            m_serialPage[i] = qrand()%8;
        }

        m_next = 0;
    }

    PageTable& getPageTable()
    {
        return m_pageTable;
    }

    int getPID()
    {
        return m_pid;
    }

    void printSerialPage()
    {
        QString s = "Task"+QString::number(m_pid)+" : ";

        for(int i=0;i<m_serialPageCount;i++)
        {
            s+=QString::number(m_serialPage[i])+" ";
        }

        cout<<s.toStdString()<<endl;
    }

    bool running()
    {
        return m_next < m_serialPageCount;
    }

    int getNextPage()
    {
        int ret = m_next++;

        if(ret < m_serialPageCount)
        {
            ret = m_serialPage[ret];
        }

        return ret;
    }

    ~PCB()
    {
        delete[] m_serialPage;
    }
};

FrameItem FrameTable[FRAME_NUM];
QList<PCB*> TaskTable;
QQueue<int> MoveOut;

int GetFrameItem();
void AccessPage(PCB& pcb);
int RequestPage(int pid,int page);
int SwapPage();
int Random();

void PrintLog(QString s);
void PrintPageMap(int pid,int page,int frame);
void PrintFatalError(QString s,int pid,int page);

void ClearFrameItem(PCB& pcb);
void ClearFrameItem(int frame);

int FIFO();


int GetFrameItem()
{
    int ret = FP_NONE;

    for(int i=0;i<FRAME_NUM;i++)
    {
        if(FrameTable[i].pid == FP_NONE)
        {
            ret = i;
            break;
        }
    }

    return ret;
}

void AccessPage(PCB& pcb)
{
    int pid = pcb.getPID();
    PageTable& pageTable = pcb.getPageTable();
    int page = pcb.getNextPage();

    if(page != FP_NONE)
    {
        PrintLog("Access Task"+QString::number(pid) +" for page"+QString::number(page));
        if(pageTable[page] != FP_NONE)
        {
            PrintLog("Found Target page in page table.");
            PrintPageMap(pid,page,pageTable[page]);
        }
        else
        {
            PrintLog("Not Found Target page in page table");

            pageTable[page] = RequestPage(pid,page);

            if(pageTable[page] != FP_NONE)
            {
                PrintLog("succeed to request a frame"+QString::number(pageTable[page]));
            }
            else
            {
                PrintFatalError("Can not request a frame",pid,page);
            }
        }

    }
    else
    {
        PrintLog("Task"+QString::number(pid)+" is finished!");
    }
}

int RequestPage(int pid,int page)
{
    int frame = GetFrameItem();

    if(frame != FP_NONE)
    {
        PrintLog("succeed request a frame"+QString::number(frame));
    }
    else
    {
        PrintLog("Failed to request a frame,need to SwapPage");

        frame = SwapPage();

        if(frame != FP_NONE)
        {
            PrintLog("Succeed swap page from disk");
        }
        else
        {
            PrintLog("Failed to swap from disk");
        }
    }

    FrameTable[frame].pid = pid;
    FrameTable[frame].pnum = page;

    MoveOut.enqueue(frame);

    return frame;
}

int SwapPage()
{
    //return Random();
    return FIFO();
}

int Random()
{
    int obj = qrand() % FRAME_NUM;

    PrintLog("Random select a frame to swap from disk,frame"+QString::number(obj));

    FrameTable[obj].pid = FP_NONE;
    FrameTable[obj].pnum = FP_NONE;

    for(int i=0;i<TaskTable.count();i++)
    {
        PageTable& pt = TaskTable[i]->getPageTable();
        for(int j=0;j<pt.length();j++)
        {
            if(pt[j] == obj)
            {
                pt[j] = FP_NONE;
                break;
            }
        }
    }

    return obj;
}



void PrintLog(QString s)
{
    cout<<s.toStdString()<<endl;
}

void PrintPageMap(int pid,int page,int frame)
{
    QString s ="Task"+QString::number(pid) +" Page"+QString::number(page);

    s+=" ==> Frame"+QString::number(frame);

    cout<<s.toStdString()<<endl;
}

void PrintFatalError(QString s,int pid,int page)
{
    s+=" Task"+QString::number(pid)+" Page"+QString::number(page);

    cout<<s.toStdString()<<endl;

    QCoreApplication::exit(-2);
}

void ClearFrameItem(PCB& pcb)
{
    for(int i=0;i<FRAME_NUM;i++)
    {
        if(FrameTable[i].pid == pcb.getPID())
        {
            FrameTable[i].pid = FP_NONE;
            FrameTable[i].pnum = FP_NONE;
        }
    }
}

void ClearFrameItem(int frame)
{
    FrameTable[frame].pid = FP_NONE;
    FrameTable[frame].pnum = FP_NONE;

    for(int i=0;i<TaskTable.count();i++)
    {
        PageTable& pt = TaskTable[i]->getPageTable();
        for(int j=0;j<pt.length();j++)
        {
            if(pt[j] == frame)
            {
                pt[j] = FP_NONE;
                break;
            }
        }
    }
}

int FIFO()
{
    int frame = MoveOut.dequeue();

    PrintLog("Use FIFO strategy to swap,Frame"+QString::number(frame));


    ClearFrameItem(frame);


    return frame;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qsrand(time(NULL));

    int index = 0;
    TaskTable.append(new PCB(1));
    TaskTable.append(new PCB(2));

    for(int i=0;i<TaskTable.count();i++)
    {
        TaskTable[i]->printSerialPage();
    }

    PrintLog("===== Running ==========");

    while(true)
    {
        if(TaskTable.count() > 0)
        {
            if(TaskTable[index]->running())
            {
                AccessPage(*TaskTable[index]);
            }
            else
            {
                PrintLog("Task"+QString::number(TaskTable[index]->getPID()) + " is finished.");

                PCB* pcb = TaskTable[index];

                TaskTable.removeAt(index);

                ClearFrameItem(*pcb);

                delete pcb;
            }
        }

        if(TaskTable.count() > 0)
        {
            index = (index + 1) % TaskTable.count();
        }

        cin.get();
    }


    return a.exec();
}

版本三:FRU

关键点:在页框 FrameItem 中,增加一个 ticks变量,用来表示访问计数。

struct FrameItem
{
    int pid;
    int pnum;
    int ticks;

    FrameItem()
    {
        pid = FP_NONE;
        pnum = FP_NONE;
        ticks = 0XFF;
    }
};

 关键操作:

int LRU()
{
    int obj = 0;
    int ticks = FrameTable[obj].ticks;
    QString s ="";

    for(int i=0;i<FRAME_NUM;i++)
    {
        s += "Frame" + QString::number(i) +" : ticks:"+QString::number(FrameTable[i].ticks) +"  ";
        if(ticks > FrameTable[i].ticks)
        {
            ticks = FrameTable[i].ticks;
            obj = i;
        }
    }

    PrintLog(s);
    PrintLog("Select the LRU page to swap content out: Frame"+QString::number(obj));
    PrintLog("Wriet the selected page conetent back to disk.");


    return obj;
}

完整代码如下所示:

#include <QtCore/QCoreApplication>
#include <QList>
#include <iostream>
#include <ctime>
#include <QQueue>

using namespace std;

#define FRAME_NUM (0x04)
#define PAGE_NUM (0XFF+1)
#define FP_NONE -1

struct FrameItem
{
    int pid;
    int pnum;
    int ticks;

    FrameItem()
    {
        pid = FP_NONE;
        pnum = FP_NONE;
        ticks = 0XFF;
    }
};

class PageTable
{
    int m_pt[PAGE_NUM];
public:
    PageTable()
    {
        for(int i=0;i<PAGE_NUM;i++)
        {
            m_pt[i] = FP_NONE;
        }
    }

    int length()
    {
        return PAGE_NUM;
    }

    int& operator[](int i)
    {
        if((0<=i) && (i<length()))
        {
            return m_pt[i];
        }
        else
        {
            QCoreApplication::exit(-1);
            return m_pt[0];
        }
    }
};

class PCB
{
    int m_pid;
    PageTable m_pageTable;
    int* m_serialPage;
    int m_serialPageCount;
    int m_next;
public:
    PCB(int pid)
    {
        m_pid = pid;
        m_serialPageCount = qrand()%5 + 5;
        m_serialPage = new int[m_serialPageCount];

        for(int i=0;i<m_serialPageCount;i++)
        {
            m_serialPage[i] = qrand()%8;
        }

        m_next = 0;
    }

    PageTable& getPageTable()
    {
        return m_pageTable;
    }

    int getPID()
    {
        return m_pid;
    }

    void printSerialPage()
    {
        QString s = "Task"+QString::number(m_pid)+" : ";

        for(int i=0;i<m_serialPageCount;i++)
        {
            s+=QString::number(m_serialPage[i])+" ";
        }

        cout<<s.toStdString()<<endl;
    }

    bool running()
    {
        return m_next < m_serialPageCount;
    }

    int getNextPage()
    {
        int ret = m_next++;

        if(ret < m_serialPageCount)
        {
            ret = m_serialPage[ret];
        }

        return ret;
    }

    ~PCB()
    {
        delete[] m_serialPage;
    }
};

FrameItem FrameTable[FRAME_NUM];
QList<PCB*> TaskTable;
QQueue<int> MoveOut;

int GetFrameItem();
void AccessPage(PCB& pcb);
int RequestPage(int pid,int page);
int SwapPage();
int Random();

void PrintLog(QString s);
void PrintPageMap(int pid,int page,int frame);
void PrintFatalError(QString s,int pid,int page);

void ClearFrameItem(PCB& pcb);
void ClearFrameItem(int frame);

int FIFO();
int LRU();

int GetFrameItem()
{
    int ret = FP_NONE;

    for(int i=0;i<FRAME_NUM;i++)
    {
        if(FrameTable[i].pid == FP_NONE)
        {
            ret = i;
            break;
        }
    }

    return ret;
}

void AccessPage(PCB& pcb)
{
    int pid = pcb.getPID();
    PageTable& pageTable = pcb.getPageTable();
    int page = pcb.getNextPage();

    if(page != FP_NONE)
    {
        PrintLog("Access Task"+QString::number(pid) +" for page"+QString::number(page));
        if(pageTable[page] != FP_NONE)
        {
            PrintLog("Found Target page in page table.");
            PrintPageMap(pid,page,pageTable[page]);
        }
        else
        {
            PrintLog("Not Found Target page in page table");

            pageTable[page] = RequestPage(pid,page);

            if(pageTable[page] != FP_NONE)
            {
                PrintLog("succeed to request a frame"+QString::number(pageTable[page]));
            }
            else
            {
                PrintFatalError("Can not request a frame",pid,page);
            }
        }

        FrameTable[pageTable[page]].ticks++;

    }
    else
    {
        PrintLog("Task"+QString::number(pid)+" is finished!");
    }
}

int RequestPage(int pid,int page)
{
    int frame = GetFrameItem();

    if(frame != FP_NONE)
    {
        PrintLog("succeed request a frame"+QString::number(frame));
    }
    else
    {
        PrintLog("Failed to request a frame,need to SwapPage");

        frame = SwapPage();

        if(frame != FP_NONE)
        {
            PrintLog("Succeed swap page from disk");
        }
        else
        {
            PrintLog("Failed to swap from disk");
        }
    }

    FrameTable[frame].pid = pid;
    FrameTable[frame].pnum = page;
    FrameTable[frame].ticks = 0xFF;

    MoveOut.enqueue(frame);

    return frame;
}

int SwapPage()
{
    //return Random();
    //return FIFO();
    return LRU();
}

int Random()
{
    int obj = qrand() % FRAME_NUM;

    PrintLog("Random select a frame to swap from disk,frame"+QString::number(obj));

    FrameTable[obj].pid = FP_NONE;
    FrameTable[obj].pnum = FP_NONE;

    for(int i=0;i<TaskTable.count();i++)
    {
        PageTable& pt = TaskTable[i]->getPageTable();
        for(int j=0;j<pt.length();j++)
        {
            if(pt[j] == obj)
            {
                pt[j] = FP_NONE;
                break;
            }
        }
    }

    return obj;
}



void PrintLog(QString s)
{
    cout<<s.toStdString()<<endl;
}

void PrintPageMap(int pid,int page,int frame)
{
    QString s ="Task"+QString::number(pid) +" Page"+QString::number(page);

    s+=" ==> Frame"+QString::number(frame);

    cout<<s.toStdString()<<endl;
}

void PrintFatalError(QString s,int pid,int page)
{
    s+=" Task"+QString::number(pid)+" Page"+QString::number(page);

    cout<<s.toStdString()<<endl;

    QCoreApplication::exit(-2);
}

void ClearFrameItem(PCB& pcb)
{
    for(int i=0;i<FRAME_NUM;i++)
    {
        if(FrameTable[i].pid == pcb.getPID())
        {
            FrameTable[i].pid = FP_NONE;
            FrameTable[i].pnum = FP_NONE;
        }
    }
}

void ClearFrameItem(int frame)
{
    FrameTable[frame].pid = FP_NONE;
    FrameTable[frame].pnum = FP_NONE;

    for(int i=0;i<TaskTable.count();i++)
    {
        PageTable& pt = TaskTable[i]->getPageTable();
        for(int j=0;j<pt.length();j++)
        {
            if(pt[j] == frame)
            {
                pt[j] = FP_NONE;
                break;
            }
        }
    }
}

int FIFO()
{
    int frame = MoveOut.dequeue();

    PrintLog("Use FIFO strategy to swap,Frame"+QString::number(frame));


    ClearFrameItem(frame);


    return frame;
}

int LRU()
{
    int obj = 0;
    int ticks = FrameTable[obj].ticks;
    QString s ="";

    for(int i=0;i<FRAME_NUM;i++)
    {
        s += "Frame" + QString::number(i) +" : ticks:"+QString::number(FrameTable[i].ticks) +"  ";
        if(ticks > FrameTable[i].ticks)
        {
            ticks = FrameTable[i].ticks;
            obj = i;
        }
    }

    PrintLog(s);
    PrintLog("Select the LRU page to swap content out: Frame"+QString::number(obj));
    PrintLog("Wriet the selected page conetent back to disk.");


    return obj;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qsrand(time(NULL));

    int index = 0;
    TaskTable.append(new PCB(1));
    TaskTable.append(new PCB(2));

    for(int i=0;i<TaskTable.count();i++)
    {
        TaskTable[i]->printSerialPage();
    }

    PrintLog("===== Running ==========");

    while(true)
    {
        /* 在操作系统内部,每隔一定的时间就会触发一个中断,当这个中断触发出来之后,
         * 把所有的物理页面的计数都会减一,操作系统认为所有加载到物理内存中的页面地位都是平等*/

        for(int i=0;i<FRAME_NUM;i++)
        {
            FrameTable[i].ticks--;
        }

        if(TaskTable.count() > 0)
        {
            if(TaskTable[index]->running())
            {
                AccessPage(*TaskTable[index]);
            }
            else
            {
                PrintLog("Task"+QString::number(TaskTable[index]->getPID()) + " is finished.");

                PCB* pcb = TaskTable[index];

                TaskTable.removeAt(index);

                ClearFrameItem(*pcb);

                delete pcb;
            }
        }

        if(TaskTable.count() > 0)
        {
            index = (index + 1) % TaskTable.count();
        }

        cin.get();
    }


    return a.exec();
}

缺点

以上随机算法、FIFO算法、LRU算法都存在一个问题是:采用的是一级页表,任务的页表中出现大量的空项(FP_NONE)占用内存,如何改进?可以采用二级页表

疑问

PageTable类的本质是一维数组,那么,为什么PCB类中的页表成员不直接使用一维数组?

设计准则

将未来可能出现的代码变动封装到局部。==> 页表的表示方式可能出现变化吗?

根据程序运行的局部性原理

        -多数情况下,页表为稀疏状态

        -单级页表会浪费大量的内存资源

二级页表

        把页号分为两段:页目录 + 二级页号

        如果下级页表的所有表项为空,那么页目录为空

具体设计

由于最大页号为0XFF,因此

页目录大小为 (0xF + 1)

子页表大小为(0xF +1 )

页访问时将页号分解为两部分

高位部分访问页目录

低位部分访问下级页表

小结

  1. 在实现上,页框表,页表和任务结构都是互相关联的关系
  2. 页框分配和页框回收时需要斩断页框表和页表的双向关联
  3. 任务结构是任务的内在表示,包含了私有的页表
  4. FIFO和LRU页交换算法都是从概率的角度选择页面移除
  5. 在设计上,将未来可能出现的代码变动封装到局部
  6. 多级页表的设计需要先对页号进行合理分割

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

repinkply

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值