首页 > 系统相关 >一个操作系统的设计与实现——第8章 内存管理系统

一个操作系统的设计与实现——第8章 内存管理系统

时间:2023-11-12 09:56:43浏览次数:37  
标签:操作系统 管理系统 PTE 虚拟地址 物理地址 地址 内存 PDE 函数

计算机上的任何程序,包括操作系统自己,都需要使用内存。因此,操作系统需要实现内存管理系统,以进行内存的分配和回收。

在我们的操作系统中,内存管理系统由两部分组成:页分配器与页回收器。本章将实现这两个部分。

8.1 从虚拟地址到物理地址

回顾CPU对内存地址的转换过程:

  1. 使用段寄存器中的段选择子,在GDT中找到一个段描述符,从中取得段基址
  2. 将段基址与偏移地址相加,得到虚拟地址
  3. 取CR3中的页目录表地址,并取虚拟地址的最高10位作为页目录表索引值,从页目录表中取得页表地址
  4. 取虚拟地址的中间10位作为页表索引值,从页表中取得页地址
  5. 取虚拟地址的最低12位,将其与页地址相加,得到物理地址

我们的操作系统使用的是平坦模型,所有的段基址都是0,所以上述第1、2步对地址没有任何影响。

当分配新的虚拟页地址时,上述第3、4步都有可能找不到页表(PDE的P位为0)或页(PTE的P位为0)。当PDE的P位为0时,需要分配一页作为页表,并将其物理地址和属性填入PDE;当PTE的P位为0时(对于新分配的虚拟页地址,这个条件一定成立),需要分配一页,并将其物理地址和属性填入PTE。这样一来,从虚拟地址到物理地址的转换就畅通了。

另一方面,无论是虚拟页地址还是物理页地址,怎么知道哪些地址是可用的呢?这就需要构造两个布尔数组,分别用于记录虚拟页地址与物理页地址的使用情况。布尔数组在实现上可以使用位图优化。

分页模式的特点是:虚拟地址连续而物理地址可以不连续。于是,在分配虚拟地址时,需要找N页连续可用的地址;在分配物理地址时,可以分N次进行,每次找一页可用的地址,然后将虚拟地址与物理地址建立联系。

综上,想要分配N页内存,需要依次进行以下步骤:

  1. 在虚拟地址位图中找到N个连续可用位,将其设定为已使用。将找到的位图索引转换为虚拟页地址
  2. 循环N次,每次在物理地址位图中找到1个可用位,将其设定为已使用。将找到的位图索引转换为物理页地址,然后,将当前的虚拟页地址与物理页地址建立联系

同理,想要回收N页虚拟内存,需要依次进行以下步骤:

  1. 将虚拟页地址转换为位图索引,将虚拟地址位图中的对应位设定为可使用
  2. 循环N次,每次从当前的虚拟页地址得到物理页地址,将其转换为位图索引,将物理地址位图中的对应位设定为可使用。然后,将当前的虚拟页地址对应的PTE清零

8.2 PDE和PTE的虚拟地址

想要在虚拟地址和物理地址之间建立联系,就需要设定虚拟地址对应的PDE和PTE。那么,PDE和PTE的地址是什么呢?

这个问题乍一看很简单:页目录表地址在CR3中,而PDE和PTE的索引值在虚拟地址中,只需要将其取出,手动完成查找PDE和PTE的过程,就可以了。这个方案看似很有道理,但问题是:分页模式下,所有的地址都是虚拟地址,而CR3、PDE、PTE中存放的都是物理地址,就算已知页目录表的地址是0x100000,页表的地址是0x101000,也不能使用这两个物理地址,必须使用虚拟地址。那怎么办呢?

事实上,只需一行非常精妙的代码就能解决这个问题,它位于本章代码8/Mbr.s的第32行:

mov dword [0x100ffc], 0x100003

这行代码看上去很普通,它将页目录表的最后一个PDE指向页目录表自己的物理地址。

这样做有什么用呢?再次回顾从虚拟地址到物理地址的转换过程,其需要且必须经历三个步骤:

  1. 找到一个PDE,取得其中的页表地址
  2. 找到一个PTE,取得其中的页地址
  3. 将页地址与偏移地址相加,得到物理地址

现在需要的是PDE和PTE的地址,即第1步和第2步的结果。但上述三个步骤是不能暂停,也不能去除的。不过,由于最后一个PDE指向的是页目录表自身,所以,如果使用这个PDE,就能在页目录表中空兜一次,从而消耗掉一个步骤。

具体来说,如果虚拟地址的最高10位全为1,那么上述过程就会变成这样:

  1. 空兜
  2. 找到一个PDE,取得其中的页表地址
  3. 将页表地址与偏移地址相加,得到PTE地址

进一步的,如果虚拟地址的最高20位全为1,那么上述过程就会变成这样:

  1. 空兜
  2. 空兜
  3. 将页目录表地址与偏移地址相加,得到PDE地址

综上,从虚拟地址获取其对应的PDE和PTE的方法如下:

  1. 构造这样的地址:0xfffff000 | (虚拟地址 >> 22 << 2),就能访问或修改PDE
  2. 构造这样的地址:0xffc00000 | (虚拟地址 >> 12 << 2),就能访问或修改PTE

8.3 内存管理系统的实现

8.3.1 位图的实现

想要实现内存管理系统,就需要先实现出位图。

请看本章代码8/Bitmap.h

第5~9行,定义了位图结构体。位图结构体由指向位图的指针和位图的长度构成。

第12~16行,声明了位图的各种函数。

接下来,请看本章代码8/Bitmap.hpp

bitmapInit函数用于初始化位图。this->__size的单位是位,所以,初始化位图长度时,如果使用的单位是字节,就需要乘以8。函数中使用的memset函数实现于本章代码8/Memory.hpp中。

bitmapGet函数与bitmapSet函数分别用于读取位和设置位。

bitmapAllocate函数用于分配一段连续的位。实现中,0表示可用而1表示不可用。所以,当找到一段连续的0后,应将这些0转变为1。需要注意的是:第40行使用的是无限循环,这是因为我们的操作系统不考虑分配失败,而是假定位图所管理的资源是无限的。

bitmapDeallocate函数用于回收一段连续的位。

8.3.2 页分配器的实现

请看本章代码8/Memory.h

这个头文件中声明了内存管理系统的各种函数。

接下来,请看本章代码8/Memory.hpp

第7~8行,定义了两个位图。__pMemoryBitmap供物理地址使用,__vMemoryBitmap供虚拟地址使用。

memoryInit函数用于初始化这两个位图。在我们的操作系统中,由于不考虑物理内存的实际大小,故物理内存位图固定使用一页;虚拟地址空间的理论大小为4G,但出于简化考虑,实现中也只使用一页位图。一页位图可以管理0x8000个页,即128M内存。位图的地址从0x7e00~0x9ffff都可用,可以随便选。

memset函数与C语言标准库的同名函数等价。

__allocateAddr函数用于分配地址。其先调用bitmapAllocate函数以分配位,然后将得到的位图索引转换为地址。

__installPage函数用于在虚拟地址与物理地址之间建立联系。

第36~37行,使用上文中的公式取得PDE指针和PTE指针。

第39行,判断PDE的P位,如果为0,说明当前虚拟地址没有对应的页表,此时需要分配一页,并将其物理地址与属性填入PDE。

第41行,分配一页,并将其物理地址与属性填入PDE。这里使用的页属性是0x7,表示存在、可读写、所有特权级均可访问。

第42行,将新分配的页表清零。页表的虚拟地址可由PTE地址去除偏移量得到。

第45行,将页的物理地址与属性填入PTE。这里使用的页属性也是0x7

第47行,执行invlpg (虚拟内存地址)指令。这条指令有什么用呢?原来,CPU内部为虚拟地址到物理地址的转换提供了缓存,以加速这一过程。这个缓存被称为快表(Translation Lookaside Buffer,TLB)。然而,如果页目录表或页表发生了变化,与之相关的TLB缓存就失效了,但CPU作为缓存的使用者,无法感知到此事。因此,CPU要求操作系统在修改页目录表和页表时主动刷新TLB。

刷新TLB的方法有两种:

  1. 加载CR3。这样做将直接清空整个TLB
  2. 使用invlpg (虚拟内存地址)指令。这样做将刷新这个虚拟地址的TLB缓存(如果有)

__allocatePage函数用于分配页,它是__allocateAddr函数与__installPage函数的封装。对于虚拟地址,需要调用一次__allocateAddr函数,得到pageCount页连续的虚拟地址;对于物理地址,需要调用pageCount__allocateAddr函数和__installPage函数,每次分配一页物理地址,并将其与虚拟地址建立联系。

allocateKernelPage函数是__allocatePage函数的封装。其将虚拟地址的分配起点设为0x66600000,这是一个随便写的地址,只要大于等于0x100000即可;物理地址的分配起点设为0x200000,这个地址需要大于等于0x102000,因为0x100000的前两页已经被使用了。

8.3.3 页回收器的实现

请看本章代码8/Memory.hpp

__deallocateAddr函数用于将待回收的页地址转换为位图索引,然后调用bitmapDeallocate函数,将其从位图中删除。

__uninstallPage函数比__installPage函数简单的多,其用于将待回收的虚拟地址所在的PTE清零,并刷新TLB。

__deallocatePage函数用于回收页。它是__deallocateAddr函数与__uninstallPage函数的封装,且操作步骤与__allocatePage函数完全相反。

第90行,将虚拟页地址从位图中删除。

第92行,循环pageCount次,每次循环回收一页物理地址,并断开虚拟地址与物理地址之间的联系。

第94行,使用上文中的公式取得PTE指针,然后读取PTE,并去除低12位上的属性,就得到了页的物理地址。

第96行,将物理页地址从位图中删除。

第97行,断开虚拟地址与物理地址之间的联系。

deallocateKernelPage函数是__deallocatePage函数的封装,其使用的参数与allocateKernelPage函数一致。

8.3.4 杂项

请看本章代码8/Mbr.s

第32行,将页目录表的最后一个PDE指向页目录表自己的物理地址。这样做的意义已经在上文讨论过。

接下来,请看本章代码8/Int.s

在上一章中,intTimer函数包含了一段打印数字的代码,在本章中已经将其删除;与之配套的extern printInt声明也已删除。

接下来,请看本章代码8/Kernel.s

第10行,调用memoryInit函数,完成内存管理系统的初始化。

8.4 测试

本章代码8/Kernel.c测试了allocateKernelPage函数与deallocateKernelPage函数,请读者自行分析输出结果。

标签:操作系统,管理系统,PTE,虚拟地址,物理地址,地址,内存,PDE,函数
From: https://www.cnblogs.com/yingyulou/p/17825520.html

相关文章

  • 一个操作系统的设计与实现——第7章 中断
    7.1什么是中断中断是一种能够随时打断CPU正常工作的机制。这句话看着挺别扭的,CPU工作的好好的,为什么要"随时打断"它?这是因为,CPU需要为诸多外部设备提供服务,以键盘为例,当键盘上的键被按下时,CPU需要对此做出响应和处理,如果不能及时响应,我们会说:"电脑很卡";如果一直都不能响应,我们会......
  • 一个操作系统的设计与实现——第6章 显卡驱动
    进入内核以后,应该做些什么呢?本章将实现一个最容易看到效果的模块:显卡驱动。6.1什么是驱动驱动这个词听起来很高大上,但实际上很简单,就是硬件的接口函数。在软件工程中,可以使用接口封装和简化设计,硬件也是一样。例如:想要读硬盘,需要很多指令设定好几个端口,然后等待硬盘就绪,最后才......
  • 一个操作系统的设计与实现——第12章 任务(三):3特权级任务
    特权级是保护模式的核心概念之一,但我们的操作系统一直没有引入这个概念。这是因为,特权级只有在3特权级任务存在时才有意义。本章将要实现的是3特权级任务的加载与任务切换。12.1特权级12.1.1特权级的功能特权级(PrivilegeLevel),是保护模式中用于限制任务权限的机制。特权级有4......
  • 一个操作系统的设计与实现——第11章 任务(二):0特权级任务
    上一章中,我们的操作系统已经支持内核共享,这为任务的加载和运行做好了准备。本章将要实现的是0特权级任务的加载与任务切换。11.1任务切换的原理11.1.1协同式与抢占式任务切换如果CPU上只运行着Kernel.c的main函数,那么情况非常简单,只需要不断执行下一条指令即可。然而,如果现......
  • 一个操作系统的设计与实现——第10章 任务(一):共享内核
    一直以来,我们的操作系统在启动后,运行的都是Kernel.c中的main函数。只运行这一个函数是不够的,操作系统应当有能力加载并运行其他程序。从本章开始,将使用四章的篇幅讨论操作系统如何加载并运行任务。这里的任务(Task)与进程(Process)是同义词,在操作系统领域中,任务这个词更为常用,请读者......
  • 一个操作系统的设计与实现——第13章 任务(四):任务回收
    在前面的两章中,我们的操作系统均不支持任务回收,所以任务不能退出。本章将要实现的是任务回收功能。13.1任务回收的原理如果一个任务位于任务队列中,其就会被运行。所以,如果一个任务的运行已经结束,它就应该从任务队列中删除。仅仅将任务从任务队列中删除是不够的,这是因为任务还......
  • 部署自己的在线管理系统
    B/S架构的管理系统因其使用和维护的便利性而成为了目前管理系统的主流,但通常都是企业自研或定制开发的系统。那么,对于众多的中小企业或个人,是否能低成本地拥有自己的在线管理系统呢?答案是可以的。目前有一些支持私有化部署的管理系统,可以由用户自行安装部署到自己的云服务器上,这......
  • 哪些资源预分配了内存:e820
    在操作系统开始管理内存之前,首先要获取物理内存的信息,比如共有多少物理地址是可用的?有哪些物理地址是被ACPI(AdvancedConfigurationandPowerInterface)数据使用?这些信息从何而来?e820就是x86架构(包括x86_64)上的操作系统引导程序提供物理内存信息的功能。当请求BIOS中断号15H,......
  • 哪些资源预分配了内存:e820
    在操作系统开始管理内存之前,首先要获取物理内存的信息,比如共有多少物理地址是可用的?有哪些物理地址是被ACPI(AdvancedConfigurationandPowerInterface)数据使用?这些信息从何而来?e820就是x86架构(包括x86_64)上的操作系统引导程序提供物理内存信息的功能。当请求BIOS中断号15H,......
  • 哪些资源预分配了内存:e820
    在操作系统开始管理内存之前,首先要获取物理内存的信息,比如共有多少物理地址是可用的?有哪些物理地址是被ACPI(AdvancedConfigurationandPowerInterface)数据使用?这些信息从何而来?e820就是x86架构(包括x86_64)上的操作系统引导程序提供物理内存信息的功能。当请求BIOS中断号15H,......