实验 2:内存管理 您所在的位置:网站首页 2mb是多少mp 实验 2:内存管理

实验 2:内存管理

#实验 2:内存管理 | 来源: 网络整理| 查看: 265

lab通关记录

MIT-6.828实验通关记录 介绍

在本实验中,将为操作系统编写内存管理代码。内存管理有两个组成部分。

第一部分是内核的物理内存分配器,以便内核可以分配内存并稍后释放它。分配器将以 4096 字节为一页运行。我们将维护数据结构,记录哪些物理页面是空闲的,哪些已分配,以及有多少进程正在共享每个分配的页面。并且还将编写分配和释放内存页的代码。

第二部分是虚拟内存,它将内核和用户软件使用的虚拟地址映射到物理内存中的地址。x86 硬件的内存管理单元 (MMU) 在指令使用内存时执行映射,查询一组页表。我们将编写页表填写的代码。

进入新的实验代码

实验采取了git来管理代码,因此我们对于远程的labx进行切换,合并lab(x-1)的代码,这样就能在原有代码基础上引入新实验必要的代码框架,进行进一步的修改和提交,以此类推一直到最后的lab6.

12$git checkout lab2$git merge lab1

实验 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,因为它还包含许多对本实验有用的定义。

测试程序1%./grade-lab2

会给你一个评分和错误显示。

首先你必须明白一个道理:当你写代码的时候,代码里的给定地址操作中地址都会被mmu进行转换。因此即便是内核代码:假如你编写页表时候,你根据基址和偏移取页表项这个动作,也应该给出虚拟基址而不是物理地址,否则你直接变量名。为什么用变量名可以呢?前面的lab中已经知道了ELF规定里内核的代码也按照虚拟内存布局来存放。因此当你创建一个数组a的时候,数组自然存放在物理内存中,但是如果你把a当作地址,那么很不幸你只能得到虚拟地址,除非你做更多的转换动作。

那么内核的地址到底怎么在页表创立之前进行高低转换呢?前面的lab给出了答案:位移

如果没有明白以上的内容,你将完全看不懂本实验。

第 1 部分:物理页面管理

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

您现在将编写物理页面分配器。它通过一个struct PageInfo对象链接列表来跟踪哪些页面是空闲的(与 xv6 不同的是,它们没有嵌入到空闲页面本身中),每个页面对应一个物理页面。您需要先编写物理页分配器,然后才能编写其余的虚拟内存实现,因为页表管理代码需要分配物理内存来存储页表。

Exercise1

在文件kern/pmap.c 中,您必须为以下函数实现代码(可以按照给定的顺序)。

12345boot_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() 以验证您的假设是否正确很有帮助。

第 2 部分:虚拟内存

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

Exercise2

查看 Intel 80386 参考手册的第 5 章和第 6 章 ,如果您还没有这样做的话。仔细阅读有关页面转换和基于页面的保护的部分(5.2 和 6.4)。我们建议您还浏览有关细分的部分;虽然 JOS 使用分页硬件进行虚拟内存和保护,但不能在 x86 上禁用段转换和基于段的保护,因此您需要对其有基本的了解。

虚拟、线性和物理地址

jos和linux一样,放弃了段的管理,只是用来描述符和选择子的权限检查。因此内存管理主要是针对分页管理。

123456789 Selector +--------------+ +-----------+ ---------->| | | | | Segmentation | | Paging |Software | |-------->| |----------> RAM Offset | Mechanism | | Mechanism | ---------->| | | | +--------------+ +-----------+ Virtual Linear Physical

我们通常把指针是当作虚拟地址的“偏移量”。在boot/boot.S 中,我们安装了一个全局描述符表 (GDT),它通过将所有段基地址设置为 0 并将限制设置为 来有效地禁用段转换0xffffffff。因此“选择子”不起作用,线性地址总是等于虚拟地址的偏移量。在实验 3 中,我们将不得不与分段进行更多交互以设置权限级别,但是对于地址映射,我们可以在整个 JOS 实验中忽略分段,而只关注页表映射。

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

Exercise3

虽然 GDB 只能通过虚拟地址访问 QEMU 的内存,但在设置虚拟内存时能够检查物理内存通常很有用。查看实验室工具指南中的 QEMU监视器命令。

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

JOS 内核通常需要将地址作为不透明值或整数进行操作,而不是取消引用它们,例如在物理内存分配器中。有时这些是虚拟地址,有时它们是物理地址。为了帮助记录代码,JOS 源区分了两种情况:类型uintptr_t表示虚拟地址,和physaddr_t表示物理地址。这两种类型实际上只是 32 位整数 ( uint32_t) 的同义词。所以你应该自己做好识别。

JOS 内核可以uintptr_t通过首先将其转换为指针类型来取消引用 a 。相比之下,内核无法合理地取消引用物理地址,因为 MMU 会转换所有内存引用。如果您将 aphysaddr_t转换为指针并取消引用它,您可能能够加载并存储到结果地址(硬件会将其解释为虚拟地址),但您可能无法获得您想要的内存位置。

总结一下:

类型声明 地址类型 T* 虚拟 uintptr_t 虚拟 physaddr_t 物理

问题

假设下面的 JOS 内核代码是正确的,变量x应该是什么类型: unitptr_t,还是physaddr_?

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

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

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

引用计数

在未来的实验中,您通常会将相同的物理页面同时映射到多个虚拟地址(或多个环境的地址空间中)。您将在物理页面对应的pp_ref字段中记录对每个物理页面的引用次数struct PageInfo。当一个物理页面的这个计数变为零时,该页面可以被释放,因为它不再被使用。一般来说,这个计数应该等于物理页在所有页表中出现*在下面 UTOP*的次数(上面的映射 UTOP大部分在启动时由内核设置,永远不应该被释放,所以没有必要对它们进行引用计数)。我们还将使用它来跟踪我们保留的指向页表的指针的数量,进而跟踪页目录对页表页的引用数量。

使用page_alloc时要小心。它返回的页面将始终具有 0 的引用计数,因此,只要您对返回的页面进行了某些操作(例如将其插入到页表中),就应该增加pp_ref。有时这由其他函数处理(例如,page_insert),有时调用page_alloc的函数必须直接执行此操作。

页表管理

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

Exercise4

在文件kern/pmap.c 中,您必须实现以下函数的代码。

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

check_page()调用 mem_init(),测试您的页表管理。在继续之前,您应该确保它报告成功。

第 3 部分:内核地址空间

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

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

权限和故障隔离

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

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

初始化内核地址空间

现在您将设置上面的地址空间UTOP:地址空间的内核部分。 inc/memlayout.h显示您应该使用的布局。您将使用刚刚编写的函数来设置适当的线性到物理映射。

Exercise5

在mem_init()调用 之后填写缺少的代码check_page()。

您的代码现在应该通过check_kern_pgdir() 和check_page_installed_pgdir()检查。

问题

页面目录中的哪些条目(行)此时已被填充?它们映射哪些地址以及指向何处?换句话说,尽可能多地填写这张表:

入口 基本虚拟地址 指向(逻辑上): 1023 ? 前 4MB 物理内存的页表 1022 ? ? . ? ? . ? ? . ? ? 2 0x00800000 ? 1 0x00400000 ? 0 0x00000000 [见下一个问题]

我们已经将内核和用户环境放在相同的地址空间中。为什么用户程序不能读写内核内存?什么具体机制保护内核内存?

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

如果我们实际上拥有最大数量的物理内存,那么管理内存有多少空间开销?这个开销是如何分解的?

重新访问kern/entry.S和 kern/entrypgdir.c 中的页表设置。在我们打开分页后,EIP 仍然是一个很小的数字(略高于 1MB)。我们在什么时候过渡到在 KERNBASE 之上的 EIP 上运行?是什么让我们可以在启用分页和开始在高于 KERNBASE 的 EIP 上运行之间继续以低 EIP 执行?为什么需要这种转变?

回答问题汇总Exercise1

boot_alloc:分配虚拟地址,实际分配为page_alloc,(因为只是增加指针而已,页表项,PageInfo等都没有设置)

12345678910111213141516171819202122static void *boot_alloc(uint32_t n){ static char *nextfree; // 下一个空页虚拟地址,static非常重要,接下来有用 char *result; // 初始化nextfree :第一次被boot_alloc时候 // 'end' 指向.bss节最高处: // 从此代码和数据分配完成,接下来自由分配 if (!nextfree) { extern char end[]; nextfree = ROUNDUP((char *) end, PGSIZE); } //你应该1.分配足够大的内存,2.并更新nextfree,确保了nextfree(地址)以PGSIZE倍数对齐,如果不明白对齐含义,你应该看《深入理解操作系统》中虚拟内存里堆malloc地址对齐的讲解 // 你的代码 result = nextfree; nextfree = ROUNDUP(result + n, PGSIZE); return result;//分配页的va}

mem_init:创建二级页表,填充页表项,建立映射

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586voidmem_init(void){ uint32_t cr0; size_t n; // 找到这个机器以页为单位到底有多大 (npages & npages_basemem). i386_detect_memory(); // 当你准备好测试的时候,把这一行注释:因为是报错用 //panic("mem_init: This function is not finished\n"); ////////////////////////////////////////////////////////////////////// // 创建初始页目录表 kern_pgdir = (pde_t *) boot_alloc(PGSIZE); memset(kern_pgdir, 0, PGSIZE); ////////////////////////////////////////////////////////////////////// // 插入 PD 作为页表映射: // 目前而言,下面这行,不用太多的理解,即: // 权限: 内核 R, 用户 R kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P; ////////////////////////////////////////////////////////////////////// // 你应该分配一个PageInfo的数组,你应该知道这个结构体定义,什么用:所有能分配和访问的物理页 // 内核通过这个数组追踪页, 'npages' 代表了数组的长度:页面总数。 你应该使用 memset,来完成初始化页面为0。同时虚拟页表映射部分pages数组 // 你的代码:模仿上面的页目录表设置即可 pages = (struct PageInfo *) boot_alloc(sizeof(struct PageInfo) * npages); memset(pages, 0, sizeof(struct PageInfo) * npages); ////////////////////////////////////////////////////////////////////// // 完成了内核的页目录表,页表,空页分配,初始化后,以后的操作将会通过 page_* functions. 完成。其中的操作,指映射内存:boot_map_region或者page_insert page_init(); check_page_free_list(1); check_page_alloc(); check_page(); ////////////////////////////////////////////////////////////////////// // 现在我们解决以下虚拟内存sd:flags ////////////////////////////////////////////////////////////////////// // 对于线性地址 UPAGES (用户只读)进行页面映射 // 权限设定: // - UPAGES 的新快照--权限 内核 读,用户 读 // (ie. perm = PTE_U | PTE_P) // - 页面本身权限 -- 内核 RW, 用户无权限 // 你的代码: boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U); ////////////////////////////////////////////////////////////////////// // 使用 'bootstack' 引用的物理内存作为内核栈. 内核栈从虚拟地址 KSTACKTOP 开始向下增长. // 从 [KSTACKTOP-PTSIZE, KSTACKTOP) // 作为内核栈, 但是划分为两块: // * [KSTACKTOP-KSTKSIZE, KSTACKTOP) -- 有物理内存作为映射 // * [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) -- 没有物理内存对应; 所以如果内核栈溢出, 会出错而不是覆写溢出对应的内存,比如guard page // 权限: 内核 可读可写, 用户 无权限 // 你的代码: boot_map_region(kern_pgdir, KSTACKTOP-KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W); ////////////////////////////////////////////////////////////////////// // 映射所有 KERNBASE 开始的物理内存. // Ie. 虚拟地址范围 [KERNBASE, 2^32) 应该映射 // 到实际物理地址范围 [0, 2^32 - KERNBASE) // 我们可能实际没有这么大: 2^32 - KERNBASE 物理内存的字节, but // 但是你应该还是要设置这个映射. // 权限: 内核 可读可写, 用户 无权限 // 你的代码:boot_map_region(kern_pgdir, KERNBASE, 0xffffffff - KERNBASE, 0, PTE_W); // 检查初始化的页目录表是否正确设置 check_kern_pgdir(); // 从小页表转换到 我们创建的全kern_pgdir // 页目录表. 我们的pc现在应该指向 // 在 KERNBASE and KERNBASE+4MB , 那么两个页表都会有这样的地址映射 // 如果机器这时候重启了, 你应该就是设置kern_pgdir页目录表的时候错了. lcr3(PADDR(kern_pgdir)); check_page_free_list(0); // entry.S 设置了cr0中的 flags (包括了小页表建立). 我们这里设置一些我们需要的flags cr0 = rcr0(); cr0 |= CR0_PE|CR0_PG|CR0_AM|CR0_WP|CR0_NE|CR0_MP; cr0 &= ~(CR0_TS|CR0_EM); lcr0(cr0); // 一些更多的设置,只有当kern_pgdir页目录表初始化后才能起效果 check_page_installed_pgdir();}

page_init:对pages数组初始化

12345678910111213141516171819202122232425262728293031323334353637voidpage_init(void){ size_t i; for (i = 0; i < npages; i++) { if(i == 0) { pages[i].pp_ref = 1; pages[i].pp_link = NULL; } else if(i>=1 && i=IOPHYSMEM/PGSIZE && i< EXTPHYSMEM/PGSIZE ) { pages[i].pp_ref = 1; pages[i].pp_link = NULL; } else if( i >= EXTPHYSMEM / PGSIZE && i < ( (int)(boot_alloc(0)) - KERNBASE)/PGSIZE)//KERNBASE注意要加,pages里i就是物理地址从0开始的页数 { pages[i].pp_ref = 1; pages[i].pp_link =NULL; } else { pages[i].pp_ref = 0; pages[i].pp_link = page_free_list; page_free_list = &pages[i]; } } }

page_alloc:从空闲表中分配一页

1234567891011121314151617struct PageInfo *page_alloc(int alloc_flags){ struct PageInfo *res = page_free_list; if (res == NULL) { cprintf("page_alloc: out of free memory\n"); return NULL; } page_free_list = res->pp_link; res->pp_link = NULL; if (alloc_flags & ALLOC_ZERO) { memset(page2kva(res), 0, PGSIZE); } return res;}

page_free释放页到空闲表中

123456789101112voidpage_free(struct PageInfo *pp){ // 你的代码 // Hint: 你应该调用panic如果 pp->pp_ref 非0(有被引用) 或pp->pp_link 非空(找不到空闲表就无法找到要做空的页面). if (pp->pp_ref != 0 || pp->pp_link != NULL) { panic("page_free: pp->pp_ref is nonzero or pp->pp_link is not NULL\n"); } pp->pp_link = page_free_list; page_free_list = pp;//放入空闲表头部 } Exercise3

虚拟地址:我们的代码都会被mmu进行转换,因此指针只能作为虚拟地址(或者偏移量)

Exercise4

page_walk从页目录开始找到va point to PTE

1234567891011121314151617181920pte_t *pgdir_walk(pde_t *pgdir, const void *va, int create){ // Fill this function in pde_t* ppde = pgdir + PDX(va); if (!(*ppde & PTE_P)) { if (create) { struct PageInfo *pp = page_alloc(1); if (pp == NULL) { return NULL; } pp->pp_ref++; *ppde = (page2pa(pp)) | PTE_P | PTE_U | PTE_W; } else { return NULL; } } return (pte_t *)KADDR(PTE_ADDR(*ppde)) + PTX(va); //注意返回的是页表项虚址}

boot_map_region给定范围进行映射

123456789101112131415161718static voidboot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm){ // Fill this function in size_t pgs = size / PGSIZE; if (size % PGSIZE != 0) {//多一页 pgs++; } for (int i = 0; i < pgs; i++) {//对页表进行映射:填充页表项 pte_t *pte = pgdir_walk(pgdir, (void *)va, 1); if (pte == NULL) { panic("boot_map_region(): out of memory\n"); } *pte = pa | PTE_P | perm;//填充页表项 pa += PGSIZE; va += PGSIZE; }}

page_insert插入一页:页表项的创建或者替换

123456789101112131415161718intpage_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm){ // Fill this function in pte_t *pte = pgdir_walk(pgdir, va, 1);//我们可能新建了一个页表项 if (pte == NULL) { return -E_NO_MEM; } pp->pp_ref++; if ((*pte) & PTE_P) { //替换这个页表项 page_remove(pgdir, va); } physaddr_t pa = page2pa(pp); *pte = pa | perm | PTE_P; pgdir[PDX(va)] |= perm;//页目录也要同步权限 return 0;}

page_lookup根据虚拟地址找到PageInfo

1234567891011121314151617181920struct PageInfo *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store){ // Fill this function in struct PageInfo *pp; pte_t *pte = pgdir_walk(pgdir, va, 0); //不允许创建页表项 if (pte == NULL) { return NULL; } if (!(*pte) & PTE_P) { return NULL; } physaddr_t pa = PTE_ADDR(*pte); pp = pa2page(pa); if (pte_store != NULL) { *pte_store = pte; } return pp;}

page_remove删除一页

12345678910111213voidpage_remove(pde_t *pgdir, void *va){ // Fill this function in pte_t *pte_store; struct PageInfo *pp = page_lookup(pgdir, va, &pte_store); if (pp == NULL) { return; } page_decref(pp); *pte_store = 0; tlb_invalidate(pgdir, va);}

happy!代码在这:Source Code

Exercise5 我们的虚拟内存包含了内核和用户空间映射. 为什么用户程序不能访问内核空间?有什么机制?

A:页表的PTE_U做权限保护。

操作系统最大能支持多大的物理空间?为什么?

A:总共有4KB * 1K = 4MB的大小,用来存放pages数组,PageInfo有8B,也就是总共可以存4MB/8B=0.5M个页面,页大小4KB,那么内存支持为0.5M*4KB=2GB,所以最多提供2GB。之所以不是4GB是因为这里最终使用pages(UPAGES)来管理内存并且只有4MB,所以二级页表好像有点拉垮?总之如果pages扩展到8MB就会有4GB

如果物理内存尽可能用,那么我们能有多大的地方来管理内存?

A:pages数组4MB,总页表对应页表项4B*0.5M=2MB,对应只用了内核页目录2KB,所以6MB+2KB。

是什么时候EIP从低地址到KERNBASE上开始运行?是什么导致了这种转变?为什么?

A:是页表设置完毕之后。mmu会将每个地址访问(数据/代码)通过cr3来进行转换。原先给定的偏移只能映射4MB,但是代码会给出高于这个偏移能力的地址。因此只能通过划分地址,访问编写的多级页表,根据所给的填写物理地址的页表/页目录项来完成映射。

关键文件代码注释

本实验中有用的东西快速回忆

123456789101112131415161718192021222324252627282930宏 PADDR(kva)kDDR(pa)UVPTUPAGESKERNBASEIOPHYSMEMPGSIZEPTSIZEPTE_ADDRPDX(la)PTX(la)npagesnpages_basememnpages_extmem函数page2pa(struct PageInfo *pp)pa2page(pa)page2kva(struct PageInfo *pp)定义unit32_t pte_tunit32_t pde_tstruct PageInfo{struct PageInfo *pp_link;int pp_ref;}struct PageInfo *pages;pde_t *kern_pgdir inc/memlayout.h123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185#ifndef JOS_INC_MEMLAYOUT_H#define JOS_INC_MEMLAYOUT_H#ifndef __ASSEMBLER__#include #include #endif /* not __ASSEMBLER__ *//* * 这个文件定义了实验os中的虚拟空间布局, * 包括了内核空间和用户空间布局. */// 全局描述符的存放偏移#define GD_KT 0x08 // 内核代码#define GD_KD 0x10 // 内核数据#define GD_UT 0x18 // 用户代码#define GD_UD 0x20 // 用户数据#define GD_TSS0 0x28 // 对于CPU0的段选择子/* * 虚拟空间 : 权限 * kernel/user * * 4 G --------> +------------------------------+ * | | RW/-- * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * : . : * : . : * : . : * |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| RW/-- * | | RW/-- * | 内核空间:映射lab1放的物理地址 | RW/-- * | | RW/-- * 内核空间起点, ----> +------------------------------+ 0xf0000000 * | CPU0's Kernel Stack | RW/-- KSTKSIZE | * | - - - - - - - - - - - - - - -| | * | Invalid Memory (*) | --/-- KSTKGAP | * +------------------------------+ | * | CPU1's Kernel Stack | RW/-- KSTKSIZE | * | - - - - - - - - - - - - - - -| PTSIZE * | Invalid Memory (*) | --/-- KSTKGAP | * +------------------------------+ | * : . : | * : . : | * MMIOLIM ------> +------------------------------+ 0xefc00000 --+ * | Memory-mapped I/O | RW/-- PTSIZE * ULIM, MMIOBASE --> +------------------------------+ 0xef800000 * | Cur. 页表存放:(用户只读) | R-/R- PTSIZE * UVPT ----> +------------------------------+ 0xef400000 * | RO PAGES | R-/R- PTSIZE * UPAGES ----> +------------------------------+ 0xef000000 * | RO ENVS | R-/R- PTSIZE * UTOP,UENVS ------> +------------------------------+ 0xeec00000 * UXSTACKTOP -/ | User Exception Stack | RW/RW PGSIZE * +------------------------------+ 0xeebff000 * | Empty Memory (*) | --/-- PGSIZE * 用户栈顶 ---> +------------------------------+ 0xeebfe000 * | 用户栈 | RW/RW PGSIZE * +------------------------------+ 0xeebfd000 * | | * | | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * . . * . . * . . * |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| * | 用户代码,数据 & 堆 | * UTEXT --------> +------------------------------+ 0x00800000 * PFTEMP -------> | Empty Memory (*) | PTSIZE * | | * UTEMP --------> +------------------------------+ 0x00400000 --+ * | Empty Memory (*) | | * | - - - - - - - - - - - - - - -| | * | User 符号表 Data (可选) | PTSIZE * 用户数据起始 ----> +------------------------------+ 0x00200000 | * | Empty Memory (*) | | * 0 ------------> +------------------------------+ --+ * * (*) Note: 内核保证invalid memory永远不会被映射. * "Empty Memory" 可以用于需要情况下的映射 * 如果有必要,用户程序可以暂时占用虚拟内存内存UTEMP. */// 页目录结构从这个地址开始#define KERNBASE 0xF0000000// At IOPHYSMEM (640K) 有个 384K 空洞留给 I/O. From the kernel,// IOPHYSMEM 可以指定为 KERNBASE + IOPHYSMEM. 空洞// 在物理地址 EXTPHYSMEM 截止#define IOPHYSMEM 0x0A0000#define EXTPHYSMEM 0x100000// 内核栈#define KSTACKTOP KERNBASE#define KSTKSIZE (8*PGSIZE) // 内核栈大小#define KSTKGAP (8*PGSIZE) // guard内核栈大小:防止访问越界// 内存映射IO虚址.#define MMIOLIM (KSTACKTOP - PTSIZE)#define MMIOBASE (MMIOLIM - PTSIZE)#define ULIM (MMIOBASE)/* * 只读的用户映射! 从这里往下直到UTOP都是用户只读 * 在env分配时候,这是全局页表的映射地址 */// 用户只读的页表基虚址 (see 'uvpt' below)#define UVPT (ULIM - PTSIZE)// 只读的页表复制#define UPAGES (UVPT - PTSIZE)// 只读的全局env页表虚址#define UENVS (UPAGES - PTSIZE)/* * 用户虚址顶部,用户可以从 UTOP-1 开始向下访问! */// 用户访问虚址顶#define UTOP UENVS// 用户的异常栈顶部#define UXSTACKTOP UTOP// Next page left invalid to guard against exception stack overflow; then:// Top of normal user stack#define USTACKTOP (UTOP - 2*PGSIZE)// Where user programs generally begin#define UTEXT (2*PTSIZE)// Used for temporary page mappings. Typed 'void*' for convenience#define UTEMP ((void*) PTSIZE)// Used for temporary page mappings for the user page-fault handler// (should not conflict with other temporary page mappings)#define PFTEMP (UTEMP + PTSIZE - PGSIZE)// The location of the user-level STABS data structure#define USTABDATA (PTSIZE / 2)#ifndef __ASSEMBLER__typedef uint32_t pte_t;typedef uint32_t pde_t;#if JOS_USER/* * 首先根据虚拟地址进入页目录(lib/entry.S里看设定pte_t/pde_t结构(uvpd和uvpt)),从 * [UVPT, UVPT + PTSIZE) 范围,找到页表物理地址。因此 * 页目录表同时也可以认为是页表:实际上就这么干了,具体代码看下面 * * 简单来说,进程自有的页表映射由于内存问题被分为两部分:页目录表和页表,我们根据虚拟地址就可以进入自有页表,根据uvpt[N](得到uvpt[N]方法: (UVPT + (UVPT >> PGSHIFT)))得到”页“pa,完成物理映射 * 不过还有问题:1.从虚拟地址访问的页目录表,那么他的物理地址在哪?2.页表直接访问页的地址吗? */ //1.进行位移来映射:PADDR,KADDR来完成 //2.页表访问了pages数组,用于管理页的申请,引用,释放 //说句题外话,xv6设计为内核/用户的页表切换,但是linux不进行切换页表,因为内核/用户切换开销大。并且对于内核页表,有两种方式:1.共享2.复制。linux选择了后者,并且统一内核/用户的页表。并且访问得到”物理地址“后,并没有直接访问页。还需要一个空闲表(哈希队列)来完成页的管理,所以很麻烦。 extern volatile pte_t uvpt[]; // 页表extern volatile pde_t uvpd[]; // 页目录表#endif/* * 在UPAGES里存储的PageInfo,可以认为是个描述符,实际上就是个链表: * Read/write 内核, read-only 用户. * * 我们知道每个页表项唯一映射一个虚页和物理页的地址 * 但是这里的PageInfo没有存储物理页,只是有个指针,具体物理地址去kern/pmap.h里的page2pa()找 */ struct PageInfo { // 物理内存空闲(未被进程使用:让我们考虑生产者-消费者问题)表的第一个页指针:注意这两种PageInfo的不同使用情景:页表项存放PageInfo->空闲表的头->空闲表访问 struct PageInfo *pp_link; // pp_ref是指向空闲表里空闲页面的引用计数:考虑多个paddr映射同一个vaddr,如果为0,我们就可以认为这个物理页可以被重新分配给任何进程使用 // 通过 page_alloc来分配内存,还有映射 // 在boot时候就会使用pamp.c来分配内存映射,做一个小页表 // boot_alloc 并没有有效的引用计数,所以可以认为是个”小“页表,第一个实验就要实现这个 uint16_t pp_ref;};#endif /* !__ASSEMBLER__ */#endif /* !JOS_INC_MEMLAYOUT_H */ kern/pmap.h12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485/* See COPYRIGHT for copyright information. *///pmap一般用来分配内存,建立/删除映射,所以相当于虚拟空间和物理空间之间的接口:接受/返回 虚拟地址/物理地址,从而进行内存交互。#ifndef JOS_KERN_PMAP_H#define JOS_KERN_PMAP_H#ifndef JOS_KERNEL# error "This is a JOS kernel header; user programs should not #include it"#endif#include #include extern char bootstacktop[], bootstack[];extern struct PageInfo *pages;extern size_t npages;extern pde_t *kern_pgdir;/* 这个宏 接受虚拟的内核地址--当然指向KERNEBASE上, 返回相应的物理地址. 如果传递非内核地址,报错。其中,他最高能够达到256MB物理地址的映射 */#define PADDR(kva) _paddr(__FILE__, __LINE__, kva)static inline physaddr_t_paddr(const char *file, int line, void *kva){ if ((uint32_t)kva < KERNBASE) _panic(file, line, "PADDR called with invalid kva %08lx", kva); return (physaddr_t)kva - KERNBASE;}/* 这个宏 接受物理地址,返回虚拟地址. 如果物理地址是错误的,就报错#define KADDR(pa) _kaddr(__FILE__, __LINE__, pa)static inline void*_kaddr(const char *file, int line, physaddr_t pa){ if (PGNUM(pa) >= npages) _panic(file, line, "KADDR called with invalid pa %08lx", pa); return (void *)(pa + KERNBASE);}enum { // 对于分配内存页, 我们对所有的内存进行置0.(当然很多内存分配不置0,比如malloc这样的,也有置0的,比如linux中,mmap加载ELF只分配虚拟地址,访问.bss节时,没有页表项,中断分配内存时就并不访问磁盘,而是找个物理内存页,置0) ALLOC_ZERO = 1pp_ref引用计数为0,代表页面应该释放,于是应该从空闲表拿出,放入头部,方便下次当作空页取出使用)//voidpage_free(struct PageInfo *pp){ // 你的代码 // Hint: 你应该调用panic如果 pp->pp_ref 非0(有被引用) 或pp->pp_link 非空(找不到空闲表就无法找到要做空的页面). }// 减少引用计数,当为0时候,free掉它voidpage_decref(struct PageInfo* pp){ if (--pp->pp_ref == 0) page_free(pp);}// 给定pgdir作为指向页目录表的指针, pgdir_walk 返回指向页表的虚拟地址'va'(pte).// 这需要走两级“页表”:进程的”页表“来找到页目录表,页目录表来找到页表//// 寻找页表过程中相关的页表项可能并不存在.我们可能就应该创建页表,来开始新的映射建立// 如果创造新页表失败, 那么 create == false, 那么pgdir_walk 返回NULL.// 否则, pgdir_walk 通过调用page_alloc分配一个新页作为新页表.// - 如果分配失败, pgdir_walk 返回 NULL.// - 否则, 增加新页的引用计数,清除页内容// 然后 pgdir_walk 返回一个新的页表页的指针.//// Hint 1: 你可以把PageInfo * 通过page2pa(在kern/pmap.h里)转换得到页物理地址 //// Hint 2: x86 MMU 检查权限位:无论是页目录表还是页表,所以你或许应该放宽你的权限:走个形式//// Hint 3: 看 inc/mmu.h 里有对多级页表(页目录表和页表)的宏定义,有些帮助//pte_t *pgdir_walk(pde_t *pgdir, const void *va, int create){ // Fill this function in return NULL;}// 给定页目录表的根外设的页表映射 // 在pgdir中映射虚拟地址 [va, va+size) 到物理地址[pa, pa+size)// 大小是 PGSIZE, 并且va和pa都要地址页对齐// 使用权限位 perm|PTE_P 来进行入口时mmu检查.//// 这个函数仅仅为了建立从UTOP上的静态映射(RO ENVS),比如,我们不应该增加pp_ref引用计数。//// Hint: 通过使用 pgdir_walk解决寻找到页表项填充映射问题static voidboot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm){ // 你的代码}// 分配页表中:映射物理页 'pp' 到虚拟页 'va'.// 页表PTE权限 (低 12 bits) // 应该被设置为'perm|PTE_P'.//// // - 如果已经有虚拟页映射 'va', 应该调用page_remove().// - 如果有必要, on demand, 一个页表应该被分配,然后快照插入到pgdir页目录表// - 如果插入成功,那么pp->pp_ref 应该增加:进程引用+1// - 如果已经有页表项占用了va,那么我们的页目录表项就应该无效//// 极端情况处理hint: 你应该考虑在同一个页目录表中,同样的物理页地址重复映射同一个虚拟地址,应该怎么处理。然而,不需要在本函数代码中区分,因为会导致无法避免的bug,你应该在多层调用中分散处理。//// 返回值:// 0 成功插入// -E_NO_MEM, 如果页表不能被分配//// Hint: 解决办法是调用 pgdir_walk, page_remove,page2pa//intpage_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm){ // 你的代码 return 0;}//// 返回物理页面的虚拟地址// 如果页表项 pte_store 不为0, 那么我们就应该认为找到了这个页表项,.它会将被page_remove删除,并且会在系统调用参数的时候验证权限,防止用户直接调用。//// 如果没有页面对应va,那么返回 NULL//// Hint: 通过调用 pgdir_walk 和 pa2page来写代码//struct PageInfo *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store){ // 你的代码 return NULL;}// 删除页表项// 给定页虚拟地址 'va',来取消映射// 如果没有物理页被映射,那么就应该什么都不干//// 细节:// - 引用计数应该-1// - 如果引用计数是0,那么应该做空页面// - 页表项存在的话,那么应该被设置为 0.// - 同时对于页表项的缓存也应该无效//// Hint: 你赢该通过调用page_lookup,tlb_invalidate, page_decref来帮助代码书写//voidpage_remove(pde_t *pgdir, void *va){ // 你的代码}//// 仅仅当页表项已经被修改之后,才对页表项缓存无效化//voidtlb_invalidate(pde_t *pgdir, void *va){ // 仅仅当我们修改这个地址空间,才刷新这个页表项缓存 // 刷新之后,这只有一个地址空间,所以是无效的???没懂 invlpg(va);}// --------------------------------------------------------------// 检查函数// --------------------------------------------------------------//// Check that the pages on the page_free_list are reasonable.//static voidcheck_page_free_list(bool only_low_memory){ struct PageInfo *pp; unsigned pdx_limit = only_low_memory ? 1 : NPDENTRIES; int nfree_basemem = 0, nfree_extmem = 0; char *first_free_page; if (!page_free_list) panic("'page_free_list' is a null pointer!"); if (only_low_memory) { // Move pages with lower addresses first in the free // list, since entry_pgdir does not map all pages. struct PageInfo *pp1, *pp2; struct PageInfo **tp[2] = { &pp1, &pp2 }; for (pp = page_free_list; pp; pp = pp->pp_link) { int pagetype = PDX(page2pa(pp)) >= pdx_limit; *tp[pagetype] = pp; tp[pagetype] = &pp->pp_link; } *tp[1] = 0; *tp[0] = pp2; page_free_list = pp1; } // if there's a page that shouldn't be on the free list, // try to make sure it eventually causes trouble. for (pp = page_free_list; pp; pp = pp->pp_link) if (PDX(page2pa(pp)) < pdx_limit) memset(page2kva(pp), 0x97, 128); first_free_page = (char *) boot_alloc(0); for (pp = page_free_list; pp; pp = pp->pp_link) { // check that we didn't corrupt the free list itself assert(pp >= pages); assert(pp < pages + npages); assert(((char *) pp - (char *) pages) % sizeof(*pp) == 0); // check a few pages that shouldn't be on the free list assert(page2pa(pp) != 0); assert(page2pa(pp) != IOPHYSMEM); assert(page2pa(pp) != EXTPHYSMEM - PGSIZE); assert(page2pa(pp) != EXTPHYSMEM); assert(page2pa(pp) < EXTPHYSMEM || (char *) page2kva(pp) >= first_free_page); if (page2pa(pp) < EXTPHYSMEM) ++nfree_basemem; else ++nfree_extmem; } assert(nfree_basemem > 0); assert(nfree_extmem > 0); cprintf("check_page_free_list() succeeded!\n");}//// Check the physical page allocator (page_alloc(), page_free(),// and page_init()).//static voidcheck_page_alloc(void){ struct PageInfo *pp, *pp0, *pp1, *pp2; int nfree; struct PageInfo *fl; char *c; int i; if (!pages) panic("'pages' is a null pointer!"); // check number of free pages for (pp = page_free_list, nfree = 0; pp; pp = pp->pp_link) ++nfree; // should be able to allocate three pages pp0 = pp1 = pp2 = 0; assert((pp0 = page_alloc(0))); assert((pp1 = page_alloc(0))); assert((pp2 = page_alloc(0))); assert(pp0); assert(pp1 && pp1 != pp0); assert(pp2 && pp2 != pp1 && pp2 != pp0); assert(page2pa(pp0) < npages*PGSIZE); assert(page2pa(pp1) < npages*PGSIZE); assert(page2pa(pp2) < npages*PGSIZE); // temporarily steal the rest of the free pages fl = page_free_list; page_free_list = 0; // should be no free memory assert(!page_alloc(0)); // free and re-allocate? page_free(pp0); page_free(pp1); page_free(pp2); pp0 = pp1 = pp2 = 0; assert((pp0 = page_alloc(0))); assert((pp1 = page_alloc(0))); assert((pp2 = page_alloc(0))); assert(pp0); assert(pp1 && pp1 != pp0); assert(pp2 && pp2 != pp1 && pp2 != pp0); assert(!page_alloc(0)); // test flags memset(page2kva(pp0), 1, PGSIZE); page_free(pp0); assert((pp = page_alloc(ALLOC_ZERO))); assert(pp && pp0 == pp); c = page2kva(pp); for (i = 0; i < PGSIZE; i++) assert(c[i] == 0); // give free list back page_free_list = fl; // free the pages we took page_free(pp0); page_free(pp1); page_free(pp2); // number of free pages should be the same for (pp = page_free_list; pp; pp = pp->pp_link) --nfree; assert(nfree == 0); cprintf("check_page_alloc() succeeded!\n");}//// Checks that the kernel part of virtual address space// has been set up roughly correctly (by mem_init()).//// This function doesn't test every corner case,// but it is a pretty good sanity check.//static voidcheck_kern_pgdir(void){ uint32_t i, n; pde_t *pgdir; pgdir = kern_pgdir; // check pages array n = ROUNDUP(npages*sizeof(struct PageInfo), PGSIZE); for (i = 0; i < n; i += PGSIZE) assert(check_va2pa(pgdir, UPAGES + i) == PADDR(pages) + i); // check phys mem for (i = 0; i < npages * PGSIZE; i += PGSIZE) assert(check_va2pa(pgdir, KERNBASE + i) == i); // check kernel stack for (i = 0; i < KSTKSIZE; i += PGSIZE) assert(check_va2pa(pgdir, KSTACKTOP - KSTKSIZE + i) == PADDR(bootstack) + i); assert(check_va2pa(pgdir, KSTACKTOP - PTSIZE) == ~0); // check PDE permissions for (i = 0; i < NPDENTRIES; i++) { switch (i) { case PDX(UVPT): case PDX(KSTACKTOP-1): case PDX(UPAGES): assert(pgdir[i] & PTE_P); break; default: if (i >= PDX(KERNBASE)) { assert(pgdir[i] & PTE_P); assert(pgdir[i] & PTE_W); } else assert(pgdir[i] == 0); break; } } cprintf("check_kern_pgdir() succeeded!\n");}// This function returns the physical address of the page containing 'va',// defined by the page directory 'pgdir'. The hardware normally performs// this functionality for us! We define our own version to help check// the check_kern_pgdir() function; it shouldn't be used elsewhere.static physaddr_tcheck_va2pa(pde_t *pgdir, uintptr_t va){ pte_t *p; pgdir = &pgdir[PDX(va)]; if (!(*pgdir & PTE_P)) return ~0; p = (pte_t*) KADDR(PTE_ADDR(*pgdir)); if (!(p[PTX(va)] & PTE_P)) return ~0; return PTE_ADDR(p[PTX(va)]);}// check page_insert, page_remove, &cstatic voidcheck_page(void){ struct PageInfo *pp, *pp0, *pp1, *pp2; struct PageInfo *fl; pte_t *ptep, *ptep1; void *va; int i; extern pde_t entry_pgdir[]; // should be able to allocate three pages pp0 = pp1 = pp2 = 0; assert((pp0 = page_alloc(0))); assert((pp1 = page_alloc(0))); assert((pp2 = page_alloc(0))); assert(pp0); assert(pp1 && pp1 != pp0); assert(pp2 && pp2 != pp1 && pp2 != pp0); // temporarily steal the rest of the free pages fl = page_free_list; page_free_list = 0; // should be no free memory assert(!page_alloc(0)); // there is no page allocated at address 0 assert(page_lookup(kern_pgdir, (void *) 0x0, &ptep) == NULL); // there is no free memory, so we can't allocate a page table assert(page_insert(kern_pgdir, pp1, 0x0, PTE_W) < 0); // free pp0 and try again: pp0 should be used for page table page_free(pp0); assert(page_insert(kern_pgdir, pp1, 0x0, PTE_W) == 0); assert(PTE_ADDR(kern_pgdir[0]) == page2pa(pp0)); assert(check_va2pa(kern_pgdir, 0x0) == page2pa(pp1)); assert(pp1->pp_ref == 1); assert(pp0->pp_ref == 1); // should be able to map pp2 at PGSIZE because pp0 is already allocated for page table assert(page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W) == 0); assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp2)); assert(pp2->pp_ref == 1); // should be no free memory assert(!page_alloc(0)); // should be able to map pp2 at PGSIZE because it's already there assert(page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W) == 0); assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp2)); assert(pp2->pp_ref == 1); // pp2 should NOT be on the free list // could happen in ref counts are handled sloppily in page_insert assert(!page_alloc(0)); // check that pgdir_walk returns a pointer to the pte ptep = (pte_t *) KADDR(PTE_ADDR(kern_pgdir[PDX(PGSIZE)])); assert(pgdir_walk(kern_pgdir, (void*)PGSIZE, 0) == ptep+PTX(PGSIZE)); // should be able to change permissions too. assert(page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W|PTE_U) == 0); assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp2)); assert(pp2->pp_ref == 1); assert(*pgdir_walk(kern_pgdir, (void*) PGSIZE, 0) & PTE_U); assert(kern_pgdir[0] & PTE_U); // should be able to remap with fewer permissions assert(page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W) == 0); assert(*pgdir_walk(kern_pgdir, (void*) PGSIZE, 0) & PTE_W); assert(!(*pgdir_walk(kern_pgdir, (void*) PGSIZE, 0) & PTE_U)); // should not be able to map at PTSIZE because need free page for page table assert(page_insert(kern_pgdir, pp0, (void*) PTSIZE, PTE_W) < 0); // insert pp1 at PGSIZE (replacing pp2) assert(page_insert(kern_pgdir, pp1, (void*) PGSIZE, PTE_W) == 0); assert(!(*pgdir_walk(kern_pgdir, (void*) PGSIZE, 0) & PTE_U)); // should have pp1 at both 0 and PGSIZE, pp2 nowhere, ... assert(check_va2pa(kern_pgdir, 0) == page2pa(pp1)); assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp1)); // ... and ref counts should reflect this assert(pp1->pp_ref == 2); assert(pp2->pp_ref == 0); // pp2 should be returned by page_alloc assert((pp = page_alloc(0)) && pp == pp2); // unmapping pp1 at 0 should keep pp1 at PGSIZE page_remove(kern_pgdir, 0x0); assert(check_va2pa(kern_pgdir, 0x0) == ~0); assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp1)); assert(pp1->pp_ref == 1); assert(pp2->pp_ref == 0); // test re-inserting pp1 at PGSIZE assert(page_insert(kern_pgdir, pp1, (void*) PGSIZE, 0) == 0); assert(pp1->pp_ref); assert(pp1->pp_link == NULL); // unmapping pp1 at PGSIZE should free it page_remove(kern_pgdir, (void*) PGSIZE); assert(check_va2pa(kern_pgdir, 0x0) == ~0); assert(check_va2pa(kern_pgdir, PGSIZE) == ~0); assert(pp1->pp_ref == 0); assert(pp2->pp_ref == 0); // 被 page_alloc 返回 assert((pp = page_alloc(0)) && pp == pp1); // 无可分配空页 assert(!page_alloc(0)); // 强制 带回 pp0 ???? assert(PTE_ADDR(kern_pgdir[0]) == page2pa(pp0)); kern_pgdir[0] = 0; assert(pp0->pp_ref == 1); pp0->pp_ref = 0; // 计算检查 pgdir_walk page_free(pp0); va = (void*)(PGSIZE * NPDENTRIES + PGSIZE); ptep = pgdir_walk(kern_pgdir, va, 1); ptep1 = (pte_t *) KADDR(PTE_ADDR(kern_pgdir[PDX(va)])); assert(ptep == ptep1 + PTX(va)); kern_pgdir[PDX(va)] = 0; pp0->pp_ref = 0; // 检查新分配的页表是否被清除干净(否则映射会错误,超级难debug ) memset(page2kva(pp0), 0xFF, PGSIZE); page_free(pp0); pgdir_walk(kern_pgdir, 0x0, 1); ptep = (pte_t *) page2kva(pp0); for(i=0; ipp_ref = 0; // 返回一个新的空闲链表 page_free_list = fl; // 释放页面:引用计数为0 page_free(pp0); page_free(pp1); page_free(pp2); cprintf("check_page() succeeded!\n");}// 通过一个已经初始化的 kern_pgdir,检查 page_insert, page_remove, &c, static voidcheck_page_installed_pgdir(void){ struct PageInfo *pp, *pp0, *pp1, *pp2; struct PageInfo *fl; pte_t *ptep, *ptep1; uintptr_t va; int i; // 检查:对于已经初始化的页表,我们应该可以R/W pp1 = pp2 = 0; assert((pp0 = page_alloc(0))); assert((pp1 = page_alloc(0))); assert((pp2 = page_alloc(0))); page_free(pp0); memset(page2kva(pp1), 1, PGSIZE); memset(page2kva(pp2), 2, PGSIZE); page_insert(kern_pgdir, pp1, (void*) PGSIZE, PTE_W); assert(pp1->pp_ref == 1); assert(*(uint32_t *)PGSIZE == 0x01010101U); page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W); assert(*(uint32_t *)PGSIZE == 0x02020202U); assert(pp2->pp_ref == 1); assert(pp1->pp_ref == 0); *(uint32_t *)PGSIZE = 0x03030303U; assert(*(uint32_t *)page2kva(pp2) == 0x03030303U); page_remove(kern_pgdir, (void*) PGSIZE); assert(pp2->pp_ref == 0); // 强制带回 pp0 assert(PTE_ADDR(kern_pgdir[0]) == page2pa(pp0)); kern_pgdir[0] = 0; assert(pp0->pp_ref == 1); pp0->pp_ref = 0; // 释放我们带回的页 page_free(pp0); cprintf("check_page_installed_pgdir() succeeded!\n");} kern/kclock.h1234567891011121314151617181920212223242526272829/* See COPYRIGHT for copyright information. */#ifndef JOS_KERN_KCLOCK_H#define JOS_KERN_KCLOCK_H#ifndef JOS_KERNEL# error "This is a JOS kernel header; user programs should not #include it"#endif#define IO_RTC 0x070 /* RTC port */#define MC_NVRAM_START 0xe /* start of NVRAM: offset 14 */#define MC_NVRAM_SIZE 50 /* 50 bytes of NVRAM *//* NVRAM bytes 7 & 8: base memory size */#define NVRAM_BASELO (MC_NVRAM_START + 7) /* low byte; RTC off. 0x15 */#define NVRAM_BASEHI (MC_NVRAM_START + 8) /* high byte; RTC off. 0x16 *//* NVRAM bytes 9 & 10: extended memory size (between 1MB and 16MB) */#define NVRAM_EXTLO (MC_NVRAM_START + 9) /* low byte; RTC off. 0x17 */#define NVRAM_EXTHI (MC_NVRAM_START + 10) /* high byte; RTC off. 0x18 *//* NVRAM bytes 38 and 39: extended memory size (between 16MB and 4G) */#define NVRAM_EXT16LO (MC_NVRAM_START + 38) /* low byte; RTC off. 0x34 */#define NVRAM_EXT16HI (MC_NVRAM_START + 39) /* high byte; RTC off. 0x35 */unsigned mc146818_read(unsigned reg);void mc146818_write(unsigned reg, unsigned datum);#endif // !JOS_KERN_KCLOCK_H kern/kclock.c12345678910111213141516171819202122/* See COPYRIGHT for copyright information. *//* 为了读取现实时间用的硬件交互代码. */#include #include unsignedmc146818_read(unsigned reg){ outb(IO_RTC, reg); return inb(IO_RTC+1);}voidmc146818_write(unsigned reg, unsigned datum){ outb(IO_RTC, reg); outb(IO_RTC+1, datum);} inc/mmu.h123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318#ifndef JOS_INC_MMU_H#define JOS_INC_MMU_H/* * 这个文件定义了x86下的内存管理单元:内存(访问,切换)处理逻辑,内存分配在pmap.c里,sd * 包括了使用页表映射,和段相关的数据结构和算法, * 还有%cr0, %cr4, and %eflags寄存器,还有中断. * 代码书写分为:Part1/Part2/Part3 *//* * * Part 1. 给定虚址,在虚拟页表,PageInfo,空闲表中,映射处理的所需要的数据结构和算法. * */// 虚拟地址'la'根据实际情况划分成三个部分://// +--------10------+-------10-------+---------12----------+// | 页目录偏移 | 页表偏移 | 页内偏移 |// | | | |// +----------------+----------------+---------------------+// \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/// \---------- PGNUM(la) ----------///// PDX, PTX, PGOFF, and PGNUM macros decompose linear addresses as shown.// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la),// use PGADDR(PDX(la), PTX(la), PGOFF(la)).// page number field of address#define PGNUM(la) (((uintptr_t) (la)) >> PTXSHIFT)// page directory index#define PDX(la) ((((uintptr_t) (la)) >> PDXSHIFT) & 0x3FF)// page table index#define PTX(la) ((((uintptr_t) (la)) >> PTXSHIFT) & 0x3FF)// offset in page#define PGOFF(la) (((uintptr_t) (la)) & 0xFFF)// construct linear address from indexes and offset#define PGADDR(d, t, o) ((void*) ((d)


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有