Lab 2: Memory Management

Introduction

在这个实验中,你将为你的操作系统编写内存管理代码。内存管理有两个组件。

第一个组件是内核的物理内存分配器使内核可以分配内存并稍后释放内存。分配器的工作单位是4096字节(2的12次方——4KB),称为页。你的任务是维护数据结构,记录哪些物理内存页是空闲的,哪些物理内存页是已分配的,以及有多少进程正在共享每个已分配的内存页。大家还将编写分配和释放内存页的例程。

内存管理的第二个部分是虚拟内存,它将内核和用户软件使用的虚拟地址映射到物理内存中的地址。x86硬件的内存管理单元(MMU)在指令使用内存时执行映射,查阅一组页表。用户可以修改JOS,根据我们提供的规范建立MMU的页表。

Getting started

在本实验室和未来的实验室中,您将逐步构建您的内核。我们也将为您提供一些额外的来源。要获取该源,请使用Git提交自提交实验1(如果有的话)以来所做的更改,获取课程存储库的最新版本,然后基于我们的lab2分支origin/lab2创建一个名为lab2的本地分支:

athena% cd ~/6.828/lab

athena% add git

athena% git pull

Already up-to-date.

athena% git checkout -b lab2 origin/lab2

分支lab2设置跟踪远程分支refs/remotes/origin/lab2。

切换到新分支“lab2”

athena%

上面显示的git checkout -b命令实际上做了两件事:它首先基于课程工作人员提供的origin/lab2分支创建一个本地分支lab2,其次,它更改您的实验室目录的内容,以反映存储在lab2分支上的文件。Git允许使用Git checkout branch-name在现有分支之间切换,不过在切换到另一个分支之前,你应该在一个分支上提交所有未完成的更改。

您现在需要将您在lab1分支中所做的更改合并到lab2分支中,如下所示:

athena% git merge lab1

Merge made by recursive.

kern/kdebug.c | 11 +++++++++--

kern/monitor.c | 19 +++++++++++++++++++

lib/printfmt.c | 7 +++----

3 files changed, 31 insertions(+), 6 deletions(-)

athena%

在某些情况下,Git可能无法弄清楚如何将您的更改与新的实验任务合并(例如,如果您修改了第二次实验任务中更改的一些代码)。在这种情况下,git merge命令会告诉你哪些文件发生了冲突,你应该首先解决冲突(通过编辑相关文件),然后使用git commit -a提交得到的文件。

实验2包含以下新的源文件,您应该浏览它们:

  • inc/memlayout.h

  • kern/pmap.c

  • kern/pmap.h

  • kern/kclock.h

  • kern/kclock.c

memlayout.h描述了必须通过修改pmap.c来实现的虚拟地址空间的布局。memlayout.h和pmap.h定义了PageInfo结构,用于跟踪物理内存中哪些页是空闲的。kclock.c和kclock.h操作PC的电池支持时钟和CMOS RAM硬件,BIOS在其中记录PC所包含的物理内存的数量,以及其他内容。pmap.c中的代码需要读取该设备硬件,以计算出其中有多少物理内存,但这部分代码已经为你完成了:你不需要知道CMOS硬件如何工作的细节。

要特别注意memlayout.h和pmap.h,因为这个实验要求你使用和理解它们包含的许多定义。你可能还想复习inc/mmu.h,因为它也包含了许多对本实验有用的定义。

在开始实验之前,不要忘记add-f 6.828以获得QEMU的6.828版本。

Lab Requirements

在本实验和后续实验中,完成所有实验中描述的常规练习和至少一个挑战性问题。(当然,有些具有挑战性的问题比其他问题更具挑战性!)此外,简要地回答实验中提出的问题,并简短地(例如,一到两段)描述你为解决所选择的挑战问题所做的工作。如果你实现了多个挑战问题,你只需要在文章中描述其中一个,当然也欢迎你做更多。在提交作业之前,请将这些内容保存在实验室目录的顶层,名为answers-lab2.txt的文件中。

Hand-In Procedure

当你准备提交你的实验代码和文章时,将你的answers-lab2.txt添加到Git仓库,提交你的更改,然后运行make handin。

athena% git add answers-lab2.txt

athena% git commit -am "my answer to lab2"

[lab2 a823de9] my answer to lab2

4 files changed, 87 insertions(+), 10 deletions(-)

athena% make handin

和之前一样,我们会给你的答案打分。你可以在lab目录下运行make grade,用评分程序测试你的内核。为了完成实验,您可以更改任何内核源代码和头文件,但不必说您必须更改或以其他方式破坏评分代码。

Part 1: Physical Page Management

操作系统必须跟踪物理内存的哪些部分是空闲的,哪些是当前正在使用的。JOS以粒度管理PC的物理内存,因此它可以使用MMU来映射和保护分配的每一块内存

现在我们来编写物理内存页分配器。它使用struct PageInfo对象的链表跟踪哪些页是空闲的(与xv6不同,这些对象并不嵌入到空闲页本身中),每个对象对应一个物理内存页。在编写其余的虚拟内存实现之前,需要先编写物理页分配器,因为管理页表的代码需要分配存储页表的物理内存。

练习1

在文件kern/pmap.c中,必须实现下列函数的代码(可能是按照给出的顺序)。
boot_alloc()
mem_init() (只有在调用check_page_free_list(1)之前)
page_init()
page_alloc()
page_free()
Check_page_free_list()和check_page_alloc()测试物理页面分配器。您应该启动JOS并查看check_page_alloc()是否报告成功。修复你的代码,让它通过。你可能会发现添加自己的assert()很有帮助,以验证你的假设是否正确。

这个实验室和所有6.828实验室都需要您做一些侦探工作,以确定您需要做什么。这个赋值并没有描述必须添加到JOS中的代码的所有细节。在JOS源代码中需要修改的部分中查找注释;这些评论通常包含规范和提示。您还需要查看JOS的相关部分、Intel手册,也许还需要查看您的6.004或6.033笔记。

Part 2: Virtual Memory

在做其他事情之前,先熟悉一下x86的保护模式内存管理架构:即分段和页转换。

练习2

如果你还没有阅读 Intel 80386 Reference Manual,的第5章和第6章,请阅读。仔细阅读有关页翻译和基于页的保护的章节(5.2和6.4)。我们建议你也浏览一下关于细分的部分;虽然JOS将分页硬件用于虚拟内存和保护,但在x86上不能禁用段转换和基于段的保护,因此您需要对它有一个基本的了解。

Virtual, Linear, and Physical Addresses

(虚拟、线性、物理地址)

在x86术语中,虚拟地址由段选择器和段内的偏移量组成线性地址是在段翻译之后、页翻译之前得到的地址。物理地址是经过段和页转换后最终得到的,也是最终通过硬件总线发送到RAM的地址

线性地址:指虚拟地址到物理地址变换的中间层,是处理器可寻址的内存空间(称为线性地址空间)中的地址。程序代码会产生逻辑地址,或者说段中的偏移地址,加上相应段基址就成了一个线性地址。如果启用了分页机制,那么线性地址可以再经过变换产生物理地址。若是没有采用分页机制,那么线性地址就是物理地址。

原文链接:https://blog.csdn.net/qiuchaoxi/article/details/79616220

总结

逻辑(虚拟)地址经过 分段(查询段表)转化为线性地址。线性地址经过 分页(查询页表)转为物理地址。

C指针是虚拟地址的“偏移量”部分。在/ boot/boot.S,我们安装了一个全局描述符表(GDT),通过将所有段的基址设置为0和限制设置为0xffffffff来有效地禁用段转换。因此,“选择器”没有作用,线性地址始终等于虚拟地址的偏移量。在实验3中,我们必须与分割进行更多的交互以设置特权级别,但是对于内存转换,我们可以在整个JOS实验室中忽略分割,而只关注页面转换

回想一下,在实验1的第3部分中,我们安装了一个简单的页表,以便内核可以在其链接地址0xf0100000处运行,即使它实际上是加载在ROM BIOS上方0x00100000处的物理内存中。这个页表只映射了4MB内存。在本实验室将要为JOS设置的虚拟地址空间布局中,我们将对其进行扩展,以映射从虚拟地址0xf0000000开始的前256MB物理内存,并映射虚拟地址空间的许多其他区域。

练习3

虽然GDB只能通过虚拟地址访问QEMU的内存,但在设置虚拟内存时能够检查物理内存通常很有用。查看实验室工具指南中的QEMU监控命令,特别是xp命令,它可以检查物理内存。要访问QEMU监视器,在终端中按Ctrl-a c(相同的绑定返回到串行控制台)。

在QEMU监视器中使用xp命令和在GDB中使用x命令检查对应物理地址和虚拟地址的内存,并确保看到相同的数据。

我们打过补丁的QEMU提供了一个info pg命令,它也可能被证明是有用的:它显示了当前页表的紧凑但详细的表示,包括所有映射的内存范围、权限和标志。普通QEMU还提供了info mem命令,该命令概述了映射的虚拟地址范围和权限。

从CPU上执行的代码来看,一旦我们进入保护模式(在boot/boot. s中首先进入的是保护模式),就无法直接使用线性地址或物理地址。所有的内存引用都解释为虚拟地址,并由MMU转换,这意味着C语言中所有的指针都是虚拟地址。

JOS内核经常需要将地址操作为不透明值或整数,而不能对它们进行解引用,例如在物理内存分配器中。这些地址有时是虚拟地址,有时是物理地址。为了帮助记录代码,JOS源代码区分了两种情况:类型uintptr_t表示不透明的虚拟地址,而physaddr_t表示物理地址。这两种类型实际上都是32位整数(uint32_t)的同义词,所以编译器不会阻止你将一种类型分配给另一种类型!由于它们是整数类型(而不是指针),如果试图解引用它们,编译器将发出警告。

JOS内核可以通过将uintptr_t转换为指针类型来解除对它的引用。相比之下,由于MMU会转换所有的内存引用,因此内核无法明智地解除物理地址的引用。如果将physaddr_t转换为指针并解引用它,则可能能够加载和存储到结果地址(硬件将其解释为虚拟地址),但可能无法获得预期的内存位置。

问题1

假设下面的JOS内核代码是正确的,变量x应该是哪种类型,uintptr_t还是physaddr_t

    mystery_t x;
    char* value = return_a_pointer();
    *value = 10;
    x = (mystery_t) value;

JOS内核有时需要读取或修改只知道物理地址的内存。例如,添加到页表的映射可能需要分配物理内存来存储页目录,然后初始化该内存。但内核无法绕过虚拟地址转换,因而无法直接装载和存储到物理地址。JOS在虚拟地址0xf0000000重新映射从物理地址0开始的所有物理内存的一个原因是帮助内核读写它只知道物理地址的内存。为了将物理地址转换为内核可以实际读写的虚拟地址,内核必须在物理地址上加上0xf0000000,以便在重新映射的区域中找到其对应的虚拟地址。你应该使用KADDR(pa)来完成这个加法。

根据存储内核数据结构的内存的虚拟地址,JOS内核有时还需要能够找到物理地址。由boot_alloc()分配的内核全局变量和内存位于内核加载的区域中,从0xf0000000开始,这正是我们映射所有物理内存的区域。因此,要将该区域中的虚拟地址转换为物理地址,内核只需减去0xf0000000。你应该使用PADDR(va)来做减法。

Reference counting

在未来的实验室中,您经常将同一个物理内存页同时映射到多个虚拟地址(或多个环境的地址空间)。用户可以在对应物理内存页的struct PageInfo的pp_ref字段中保存对每个物理内存页的引用计数。对于物理内存页,当该计数变为0时,该页将被释放,因为该页不再使用。一般来说,该计数应该等于物理内存页在所有页表中出现在UTOP以下的次数(UTOP以上的映射通常在启动时由内核建立,不应该释放,因此不需要对其进行引用计数)。我们还将使用它来跟踪指向页目录页的指针数目,以及页目录对表页的引用次数。

在使用page_alloc时要小心。它返回的页的引用计数总是0,因此只要对返回的页做了一些操作(比如将其插入到页表中),就应该增加pp_ref。有时这由其他函数处理(例如,page_insert),有时调用page_alloc的函数必须直接完成。

Page Table Management

现在我们将编写一组例程来管理页表:插入和删除线性到物理的映射,以及在需要时创建页表页。

练习4

In the file kern/pmap.c, you must implement code for the following functions.

pgdir_walk()

boot_map_region()

page_lookup()

page_remove()

page_insert()

check_page(), called from mem_init(), tests your page table management routines. You should make sure it reports success before proceeding.

Part 3: Kernel Address Space

JOS将处理器的32位线性地址空间分成两部分。我们将在实验3中开始加载和运行的用户环境(进程)将控制下层的布局和内容,而内核始终保持对上层的完全控制。该分界线由inc/memlayout.h中的ULIM符号随意定义,为内核分配了大约256MB的虚拟地址空间。这解释了为什么我们在实验1中需要向内核提供如此高的链接地址:否则内核的虚拟地址空间中就没有足够的空间同时映射到下面的用户环境中。

参考inc/memlayout.h中的JOS内存布局图对本部分和后续实验都很有帮助。

Permissions and Fault Isolation

由于内核内存和用户内存都存在于每个环境的地址空间中,我们必须在x86页表中使用权限位,以允许用户代码只访问地址空间的用户部分。否则,用户代码中的bug可能会覆盖内核数据,导致崩溃或更微妙的故障。用户代码也可能窃取其他环境的私有数据。请注意,可写权限位(PTE_W)同时影响用户代码和内核代码!

用户环境对ULIM之上的任何内存都没有权限,而内核可以读写这些内存。对于地址范围[UTOP, ULIM),内核和用户环境具有相同的权限:它们可以读取该地址范围,但不能写入。该地址范围用于向用户环境公开某些只读的内核数据结构。最后,UTOP下面的地址空间供用户环境使用。用户环境将设置访问此内存的权限。

Initializing the Kernel Address Space

现在我们将在top之上建立地址空间:地址空间的内核部分。Inc /memlayout.h显示了你应该使用的布局。我们将使用刚才编写的函数建立适当的线性到物理映射

练习5

Fill in the missing code in mem_init() after the call to check_page().

Your code should now pass the check_kern_pgdir() and check_page_installed_pgdir() checks.

Address Space Layout Alternatives

我们在JOS中使用的地址空间布局并不是唯一可行的。操作系统可能在低线性地址映射内核,而将线性地址空间的上部留给用户进程。但X86内核通常不采用这种方法,因为X86的一种向后兼容模式(称为虚拟8086模式)在处理器中是“硬连接”的,用于使用线性地址空间的底部,因此如果内核映射到那里,则根本无法使用。

甚至可以将内核设计成不必为自身分配处理器线性地址空间或虚拟地址空间的任何固定部分,而是有效地允许用户级进程不受限制地使用整个4GB虚拟地址空间,同时仍然完全保护内核不受这些进程的影响,也保护不同进程彼此之间不受影响,尽管这要困难得多。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值