MIT JOS 6.828 Lab2学习笔记

本文详细介绍了JOS操作系统中内存管理的基本原理和技术实现,包括物理内存管理、虚拟内存管理、页表管理和地址空间布局等内容。重点讲解了如何通过编写特定函数来实现内存的分配、释放及映射等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Lab1

Lab2:Memory management

  • inc/memlayout.h
  • kern/pmap.c
  • kern/pmap.h
  • kern/kclock.h
  • kern/kclock.c

以上文件是本Lab会用到的文件,其中memlayout.h描述了虚拟地址空间的结构,通过修改pmap.c文件来实现这个结构。memlayout.h和pmap.h文件定义了一个PageInfo结构,可以记录有哪些物理页是空闲的。kclock.c和kclock.h操作用电池充电的时钟和CMOS RAM设备,该设备记录着PC机拥有的物理内存的数量。pmap.c中的代码必须读取这个设备中的信息才能弄清楚到底有多少内存。

切换到lab2分支上:

git checkout -b lab2 origin/lab2

Part 1:Physical Page Management

Exercise1

在文件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()函数来测试代码是否正确。

mem_init()函数是首先执行的函数,里面会先调用boot_alloc,来看boot_alloc的注释:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pu57iVHs-1663430534215)(images/54.jpg)]

可以看到这个函数只会在初始化时进行,且并不是一个真实的分配器。

仿照前面的写法,ROUNDUP应该是一个用于对齐的宏(这里不确定),n>=0的写法是一致的,都是让nextfree=ROUNDUP(nextfree+n,PGSIZE);另外记得加上panic的代码,完整代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZM5ZkIPO-1663430534216)(images/55.jpg)]

再来看mem_init函数,执行完了boot_alloc后的代码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RymFWQre-1663430534216)(images/56.jpg)]

需要我们补充的代码是分配一块内存,用来存放一个PageInfo结构体数组,存入到pages里并使用memset初始化。代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0kDFcv5-1663430534216)(images/57.jpg)]

下面的函数是page_init(),来看一下注释:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pBkgIcDq-1663430534216)(images/58.jpg)]

这个函数要初始化pages和page_free_list。标记0号物理页为被使用(即1),[PGSIZE, npages_basemem * PGSIZE)为0,IO hole [IOPHYSMEM, EXTPHYSMEM]为1,在extphysmem区域以外的也置为1。代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VQlfBfX3-1663430534217)(images/59.jpg)]

接下来继续看mem_init的内容,初始化关于所有物理内存页的相关数据结构后,进入check_page_free_list(1)子函数,这个函数检查page_free_list链表的所谓空闲页,是否真的都是合法的,空闲的。之后进入check_page_alloc(),这个函数的功能是检查page_alloc(),page_free()两个子函数是否正确运行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X8i2eCNJ-1663430534217)(images/60.jpg)]

先来看page_alloc:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E7yVcNLv-1663430534217)(images/61.jpg)]

该函数分配一个物理页,并返回这个PageInfo结构体

大致的步骤应该是:

1. 从free_page_list中取出一个空闲页的PageInfo结构体

2. 修改free_page_list相关信息

​ 3.修改取出的空闲页的PageInfo结构体信息,初始化该页的内存

代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TSHRQifm-1663430534217)(images/62.jpg)]

再来看page_free:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S8M3uxPM-1663430534217)(images/63.jpg)]

根据注释得知,这个函数就是把一个页的PageInfo结构体再返回给page_free_list空闲页链表,代表回收了这个页。

大致的步骤应该是:

​ 1. 修改被回收的页的PageInfo结构体的相应信息。

​ 2. 把该结构体插入回page_free_list空闲页链表。

代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZIzJF87V-1663430534218)(images/64.jpg)]

写完之后到mem_init把这一行注释掉:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-md7KuWTC-1663430534218)(images/65.jpg)]

然后make qemu:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j5FDRDXh-1663430534218)(images/66.jpg)]
可以看到check_page_free_list和check_page_alloc都成功了,panic处的page_insert需要在后续的练习中完成。

Part 2:Virtual Memory

Virtual, Linear, and Physical Addresses

**虚拟地址(Virtual Address)**由两部分组成,一个是段选择器(segment selector),另一个是段内偏移(segment offset)。**线性地址(Linear Address)**是通过段地址转换机构将虚拟地址转换得到的。**物理地址(Physical Addresses)**是分页地址转换机构把线性地址进行转换之后得到的真实的内存地址,这个地址将会最终送到内存芯片的地址总线。

在boot/boot.S文件中,引入了一个全局描述符表(GDT),这个表通过把所有的段的基址设置为0,界限设置为0xffffffff的方式,关闭了分段管理的功能。因此虚拟地址中的段选择器的内容已经没有任何意义,线性地址的值总是等于虚拟地址中段内偏移的值。

回顾一下Lab1中的part 3,我们引入了一个简单的页表,使得内核可以在0xf0100000的虚拟地址空间运行,而它所在的真实位置是物理地址0x00100000处。这个页表仅仅映射了4MB的内存空间。在本实验中,我们将从虚拟地址 0xf0000000 开始的前 256MB 物理内存扩展,并映射许多其他区域。

Exercise3

使用Qemu的monitor中的xp指令和gdb的x指令来确保虚拟地址和物理地址中的内容是相同的。

ps:qemu的monitor通过ctrl-a c来打开,如果不行的话,在lab中输入qemu-system-i386 -hda obj/kern/kernel.img -monitor stdio -gdb tcp::26000 -D qemu.log

在一个终端使用make qemu-gdb,另一个使用make gdb,ctrl c后使用x命令来看虚拟内存:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V7eUI2SU-1663430534218)(images/67.jpg)]

重新打开一个终端,打开monitor,xp指令查看物理内存:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hb9EWGnn-1663430534219)(images/68.jpg)]

可以看到两者的数据是相同的。

从 CPU 上执行的代码来看,一旦我们处于保护模式,就无法直接使用线性或物理地址。 所有内存引用都被解释为虚拟地址并由 MMU 翻译,所以C 中的所有指针都是虚拟地址。

JOS内核通常需要将地址作为不透明的值或整数来操作,而不是直接解引用,比如物理内存分配器。有时使用虚拟地址,有时使用物理地址。为了能够帮助我们记录代码,JOS源文件中的地址被区分为两种情况:

uintptr_t – 表示虚拟地址

physaddr_t – 表示物理地址

这两种类型其实都是32位的整型数(uint32_t),所以如果把一个类型的变量的值赋给另一个类型变量,编译器不会报错。但是由于他们都是整型数,所以如果解引用他们,编译器会报错。

JOS内核可以先对uintptr_t类型的值进行强制类型转换,然后再解引用。但是对于physaddr_t的值,我们不能这么做,因为内核是需要MMU来首先对你输入的地址进行转化的,如果对physaddr_t进行强制类型装换再解引用,最终得到的地址,可能不是要找的真实物理地址。

总结如下:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zj8LvtHh-1663430534219)(images/69.jpg)]

Question

假设下面的代码是正确的,那么x应该是什么类型,uintptr_t或physaddr_t?

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

答:uintptr_t类型。

JOS内核有时需要读取或者修改内存,但它只知道物理地址。例如,当我们想要加入一个新的页表项时,我们需要分配一块物理内存来存放页目录项,然后初始化这块内存。但内核不能绕过虚拟地址转换这一步,因此它也不能直接加载或者存储物理地址。JOS在虚拟地址0xf0000000处重新映射物理地址0开始的内存的一个原因是,帮助内核读取和写入只知道物理地址的内存。如何把物理地址转换为虚拟地址,可以采用KADDR(pa)指令来做加法获取。反之,从虚拟地址转化物理地址,使用PADDR(va)来做减法。

Reference counting

在之后的实验中,将会经常遇到一种情况,多个不同的虚拟地址被同时映射到相同的物理页上面。这时我们需要在这个物理页的PageInfo结构体的pp_ref成员变量,记录一下其引用数。当这个值变为0时,物理页才可以被释放。通常来说,任意一个物理页的pp_ref值等于它在所有的页表项中,出现在UTOP之下的次数(UTOP之上的映射大多数在内核启动的时候已经完成了,永远不会被释放,因此不会有引用计数)。

在使用page_alloc时应注意,它所返回的页的引用计数值总是0,因此只要对返回的页面要执行某些操作(例如将其插入页面表),pp_ref应该被马上加一。有时这由其他函数(例如page_insert)处理,有时调用page_alloc的函数必须直接处理。

Page Table Management

现在应该可以着手编写管理页表的程序了:包括插入和删除线性地址到物理地址的映射关系,以及创建页表等操作。

Exercise 4

在kern/pmap.c中,完成下列函数:

pgdir_walk()
boot_map_region()
page_lookup()
page_remove()
page_insert()

mem_init函数中的check_page()会测试上面函数的正确性。

先来看pgdir_walk函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wAu3cwhn-1663430534219)(images/70.jpg)]

该函数的功能是:给定一个页目录表指针 pgdir ,返回线性地址va所对应的页表项指针(pte_t类型)。

步骤如下:

​ 1、首先得出根据va得出页目录项的地址;

​ 2、判断页目录项对应的页表项是否在内存中;

​ 3、如果在的话,计算出页表项的基地址,然后返回va的页表项地址。

​ 4、如果不在,判断create;

​ 5、create为false则直接返回NULL;

​ 6、create为true则分配新页,并将信息更新到页目录项中。

代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nzfpgu7U-1663430534219)(images/71.jpg)]

再来看boot_map_region函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EXzVUOov-1663430534219)(images/72.jpg)]

把虚拟地址空间范围[va, va+size)到物理空间[pa, pa+size)的映射关系加入到页表pgdir中。该函数主要目的是设置UTOP以上的地址,且为静态的。pp_ref域不会改变。(记得使用刚刚写好的pgdir_walk)

函数应是一个循环,从va到va+size,将每一个页都进行映射。

代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nLtE2nYi-1663430534220)(images/73.jpg)]

再来看page_lookup函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jee9niBM-1663430534220)(images/74.jpg)]

返回va虚拟地址的物理页的PageInfo结构体的指针。如果pte_store(应该是页表项的指针的指针)不为0,则把这个页表项的指针存入其中。

代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ks6zNIfL-1663430534220)(images/75.jpg)]

再来看page_remove函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Y2HQf7O-1663430534220)(images/76.jpg)]

删除掉va和其物理地址的映射。

注意几个细节:

​ 1、引用计数要记得减一;

​ 2、如果引用计数已经到达0,则将物理页释放;(这两项在page_decref中已经实现。)

​ 3、该页的页表项置0;

​ 4、如果从页表中删除条目,则TLB必须无效。(tlb_invalidate函数)

代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SbVLK4mM-1663430534221)(images/77.jpg)]

最后看page_insert函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qPPRLc6g-1663430534221)(images/78.jpg)]

该函数将物理页pp与虚拟地址va建立映射关系。

步骤如下:

​ 1、通过pgdir_walk函数求出虚拟地址va所对应的页表项;

2、 pp_ref加1;

3、 查看这个页表项,确定va是否已经被映射,如果被映射,则删除之前的映射,并置TLB为无效。

​ 4、必要时要分配页表内存,并插入到pgdir中。

代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mO1JRJwi-1663430534221)(images/79.jpg)]

​ 完成后make qemu即可(这个panic应该是因为后面的Part没完成)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3vmsfUyl-1663430534221)(images/80.jpg)]

Part 3:Kernel Address Space

JOS把32位线性地址虚拟空间划分成两个部分。分为用户环境(进程运行环境,在lab3中加载运行)和内核。用户环境通常占据低地址的那部分,而内核总是占据高地址的部分,叫内核地址空间。这两部分的分界线由memlayout.h文件中的一个宏 ULIM定义,JOS为内核保留了接近256MB的虚拟地址空间。这样就解释了为什么在Lab1中要给操作系统设计一个高地址的地址空间。否则用户环境的地址空间就不够了。

Permissions and Fault Isolation

由于内核和用户进程只能访问各自的地址空间,所以我们必须在x86页表中使用访问权限位(Permission Bits)来使用户进程的代码只能访问用户地址空间,而不是内核地址空间。否则用户代码中的一些错误可能会覆写内核中的数据,最终导致内核的崩溃。用户代码也可能窃取其他环境的私有数据。需要注意的是,可写权限位(PTE_W)会同时影响用户和内核代码。

用户地址空间中的代码不能访问高于ULIM的地址空间,但内核可以读写。在 [UTOP,ULIM)范围内的地址,内核和用户空间都只能读而不能写,这一个部分的地址空间通常用于把一些只读的内核数据结构暴露给用户地址空间的代码。在UTOP之下的地址范围是给用户进程使用的,用户进程可以访问,修改这部分地址空间的内容。

Initializing the Kernel Address Space

现在来设置一下UTOP之上的地址空间:这也是整个虚拟地址空间中的内核地址空间部分。inc/memlayout.h文件中已经向你展示了这部分地址空间的布局,你将使用刚刚编写的函数来设置适当的线性到物理的映射。

Exercise5

完成mem_init函数中check_page()后面的内容。最终通过check_kern_pgdir()和check_page_installed_pgdir()的测试。

先来看需要我们写些什么:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K8kuY1tb-1663430534221)(images/81.jpg)]

要完善的功能就是把操作系统的一些重要的地址范围映射到现在的新页目录项上kern_pgdir上。这里我们可以利用前面定义过的boot_map_region函数。

第一部分是需要我们将pages数组映射到线性地址UPAGES,大小为一个PTSIZE。

所以添加代码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RlvMUrML-1663430534222)(images/82.jpg)]

第二部分是把由bootstack变量所标记的物理地址范围映射给内核的堆栈。范围为[KSTACKTOP-PTSIZE,KSTACKTOP],但这部分需要划分为两部分:

[KTSTACKTOP-KSTKSIZE, KSTACKTOP]部分需要映射,另一部分不需要映射(相当于保护页的做法,这样当栈溢出的时候会触发错误,而不是覆盖掉更低地址的内存)。

代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PqhXUijA-1663430534222)(images/83.jpg)]

第三部分映射整个操作系统内核,虚拟地址范围是[KERNBASE, 232],物理地址范围是[0,232 - KERNBASE]。

代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sqv8OmX1-1663430534222)(images/84.jpg)]

最后的映射图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBB5Nwge-1663430534222)(images/85.jpg)]

最后编译内核:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bz5j9kHZ-1663430534222)(images/86.jpg)]

questions

2、到目前为止页目录表中已经包含多少有效页目录项?他们都映射到哪里?尽可能填写下表

EntryBase Virtual AddressPoints to (logically):
1023?Page table for top 4MB of phys memory
1022??
.??
.??
.??
20x00800000?
10x00400000?
00x00000000[see next question]

参考https://blog.youkuaiyun.com/bysui/article/details/51471788

3、我们将内核和用户环境放在同一个地址空间。为什么用户程序不能读写内核内存?有哪些具体机制保护内核内存?

用户程序不能去随意修改内核中的代码,数据,否则可能会破坏内核,造成程序崩溃。

页表项中有读写保护位,以及PTE_U区分内核和用户,MMU也会实现这种保护。

4、这个操作系统可以支持的最大物理内存量是多少?为什么?

由于这个操作系统利用一个大小为4MB的空间UPAGES来存放所有的页的PageInfo结构体信息,每个结构体的大小为8B,所以一共可以存放4MB / 8B = 512K个PageInfo结构体,即有512K个物理页,每个物理页大小为4KB,所以总的物理内存为512K * 4KB = 2GB。

5、如果现在的物理内存页达到最大个数,那么管理这些内存所需要的额外空间开销有多少?

存放所有的PageInfo(4MB)和页目录表(4KB),页表(512K*4B = 2MB),共4MB+4KB+2MB。

6、回顾kern/entry.S和kern/entrypgdir.c中页表建立的过程,当分页机制开启时,寄存器EIP的值仍旧是一个小的值(1MB)。从什么时候过渡到高于KERNBASE的虚拟地址空间中的?是什么让我们能够在启用分页和开始在 KERNBASE 之上的 EIP 上运行之间,继续以低 EIP 执行?为什么这个过渡是必要的?

在entry.S文件中有一个指令 jmp *%eax,这个指令要完成跳转,就会重新设置EIP的值,把它设置为寄存器eax中的值,而这个值是大于KERNBASE的,所以就完成了EIP从小的值到大于KERNBASE的值的转换。

在entry_pgdir这个页表中,也把虚拟地址空间[0, 4MB)映射到物理地址空间[0, 4MB)上,所以当访问位于[0, 4MB)之间的虚拟地址时,可以把它们转换为物理地址。

Address Space Layout Alternatives

进程的虚拟地址空间的布局不是只有我们讨论的这种唯一的情况,我们也可以把内核映射到低地址处。但是JOS之所以要这么做,是为了保证x86的向后兼容性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值