练习零:填写已有实验

   本实验依赖实验1。请把你做的实验1的代码填入本实验中代码中有“LAB1”的注释相应部分。提示:可采用diff和patch工具进行半自动的合并(merge),也可用一些图形化的比较/merge工具来手动合并,比如meld,eclipse中的diff/merge工具,understand中的diff/merge工具等。

其实 lab1 中只有 kern/debug/kdebug.c kern/init/init.c kern/trap/trap.c 被改过了
所以只用把这三个文件复制到lab2中就可以了。

练习一:实现 first-fit 连续物理内存分配算法(需要编程)

   在实现first fit 内存分配算法的回收函数时,要考虑地址连续的空闲块之间的合并操作。提示:在建立空闲页块链表时,需要按照空闲页块起始地址来排序,形成一个有序的链表。可能会修改default_pmm.c中的default_init,default_init_memmap,default_alloc_pages, default_free_pages等相关函数。请仔细查看和理解default_pmm.c中的注释。

  查看注释后,发现代码已经写了,于是make qemu 了一发,发现错了,断言失败。仔细看了后,代码是不完整的。

  断言失败之前的检查都是针对页分配成功、失败的测试,从失败的断言这里开始是对分配的页的位置进行的检查。

  实际上,最初的实现 default_*_pages 只是简单的插入到链表,根本没有关心被插入到了哪里,所以在多次 alloc/free 之后页块在链表中的位置乱得一塌糊涂。如果需要保序,需要对每个改动链表的地方注意一下位置。

struct Page {
// 页帧的 引用计数
int ref;
// 页帧的状态 Reserve 表示是否被内核保留 另一个是 表示是否 可分配
uint32_t flags;
// 记录连续空闲页块的数量 只在第一块进行设置
unsigned int property;
// 用于将所有的页帧串在一个双向链表中 这个地方很有趣 直接将 Page 这个结构体加入链表中会有点浪费空间 因此在 Page 中设置一个链表的结点 将其结点加入到链表中 还原的方法是将 链表中的 page_link 的地址 减去它所在的结构体中的偏移 就得到了 Page 的起始地址
list_entry_t page_link;
}; // 初始化空闲页块链表
static void default_init(void) {
list_init(&free_list);
nr_free = 0; // 空闲页块一开始是0个
}
// 初始化n个空闲页块
static void default_init_memmap(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(PageReserved(p)); // 看看这个页是不是被内核保留的
p->flags = p->property = 0;
set_page_ref(p, 0);
}
base->property = n; // 头一个空闲页块 要设置数量
SetPageProperty(base);
nr_free += n;
// 初始化玩每个空闲页后 将其要插入到链表每次都插入到节点前面 因为是按地址排序
list_add_before(&free_list, &(base->page_link));
}
// 分配n个页块
static struct Page * default_alloc_pages(size_t n) {
assert(n > 0);
if (n > nr_free) {
return NULL;
}
struct Page *page = NULL;
list_entry_t *le = &free_list;
// 查找 n 个或以上 空闲页块 若找到 则判断是否大过 n 则将其拆分 并将拆分后的剩下的空闲页块加回到链表中
while ((le = list_next(le)) != &free_list) {
// 此处 le2page 就是将 le 的地址 - page_link 在 Page 的偏移 从而找到 Page 的地址
struct Page *p = le2page(le, page_link);
if (p->property >= n) {
page = p;
break;
}
}
if (page != NULL) {
if (page->property > n) {
struct Page *p = page + n;
p->property = page->property - n;
SetPageProperty(p);
// 将多出来的插入到 被分配掉的页块 后面
list_add(&(page->page_link), &(p->page_link));
}
// 最后在空闲页链表中删除掉原来的空闲页
list_del(&(page->page_link));
nr_free -= n;
ClearPageProperty(page);
}
return page;
}
// 释放掉 n 个 页块
static void default_free_pages(struct Page *base, size_t n) {
assert(n > 0);
struct Page *p = base;
for (; p != base + n; p ++) {
assert(!PageReserved(p) && !PageProperty(p));
p->flags = 0;
set_page_ref(p, 0);
}
base->property = n;
SetPageProperty(base);
list_entry_t *le = list_next(&free_list);
// 合并到合适的页块中
while (le != &free_list) {
p = le2page(le, page_link);
le = list_next(le);
if (base + base->property == p) {
base->property += p->property;
ClearPageProperty(p);
list_del(&(p->page_link));
}
else if (p + p->property == base) {
p->property += base->property;
ClearPageProperty(base);
base = p;
list_del(&(p->page_link));
}
}
nr_free += n;
le = list_next(&free_list);
// 将合并好的合适的页块添加回空闲页块链表
while (le != &free_list) {
p = le2page(le, page_link);
if (base + base->property <= p) {
break;
}
le = list_next(le);
}
list_add_before(le, &(base->page_link));
}

成功后会提示:



然后会在另一个地方断言挂掉,那是接下来的实验了。

Question 1

你的first fit算法是否有进一步的改进空间

当然啦,这种东西(对于一个区间中某些数字的存在性维护)用线段树可以实现 alloc 和 free 达到 O(log n) 级别的时间复杂度,不过空间(复杂度虽然还是 O(n) 不变)要增加一倍。

练习二:实现寻找虚拟地址对应的页表项(需要编程)

  通过设置页表和对应的页表项,可建立虚拟内存地址和物理内存地址的对应关系。其中的get_pte函数是设置页表项环节中的一个重要步骤。此函数找到一个虚地址对应的二级页表项的内核虚地址,如果此二级页表项不存在,则分配一个包含此项的二级页表。本练习需要补全get_pte函数 in kern/mm/pmm.c,实现其功能。请仔细查看和理解get_pte函数中的注释。get_pte函数的调用关系图如下所示:

  具体是在 check_pgdir 函数中一个断言 get_pte 返回值失败的,看到 get_pte 函数,就是第二个练习需要编写修改的地方。

get_pte ,是给出 Page Directory 基址和物理地址,求对应的 Page Table Entry 地址。

pte_t *get_pte(pde_t *pgdir, uintptr_t la, bool create) {
pde_t *pdep = &pgdir[PDX(la)]; // 找到 PDE 这里的 pgdir 可以看做是 页目录表的基址
if (!(*pdep & PTE_P)) { // 看看 PDE 指向的页表 是否存在
struct Page* page = alloc_page(); // 不存在就申请一页物理页
/* 通过 default_alloc_pages() 分配的页 的地址 并不是真正的页分配的地址
实际上只是 Page 这个结构体所在的地址而已 故而需要 通过使用 page2pa() 将 Page 这个结构体
的地址 转换成真正的物理页地址的线性地址 然后需要注意的是 无论是 * 或是 memset 都是对虚拟地址进行操作的
所以需要将 真正的物理页地址再转换成 内核虚拟地址
*/
if (!create || page == NULL) {
return NULL;
}
set_page_ref(page, 1);
uintptr_t pa = page2pa(page);
memset(KADDR(pa), 0, PGSIZE); // 将这一页清空 此时将 线性地址转换为内核虚拟地址
*pdep = pa | PTE_U | PTE_W | PTE_P; // 设置 PDE 权限
}
return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];
}

  首先注释中的 pdep ( Page Directory Entry Pointer ),就是 PD 基质加上 PDE 编号,编号就是 LA 的高十位( x86 的约定),可以通过 PDX 宏获取。

  然后检查是否设置了 Present 位,也就是 PDE_P 位。不过实际上并没有 PDE_P 这个宏,使用注释里告诉我们的等价的 PTE_P 来检查(注释里告诉我们这个位是 PDE 和 PTE 通用的,虽然这三个位的确是通用的,不过毕竟 PD 和 PT 的保留位还是有所不同的)。

  如果没有设置而且 bool create 设置为需要申请,就需要按注释 (3) ~ (7) 中提示一般的 alloc 一个页、设置引用计数、获得线性地址、使用 memset 清空页内容、设置 PDE 项中权限位。

最后再从 la 中获取一下中间十位,也就是 PTE 编号,从 PD 获取一下基址,相加就是 PTE 的线性地址了,用 KADDR 处理一下即可。

请回答如下问题:

  请描述页目录项(Pag Director Entry)和页表(Page Table Entry)中每个组成部分的含义和以及对ucore而言的潜在用处。

PDE 详解

从低到高,分别是:



P (Present) 位:表示该页保存在物理内存中。

R (Read/Write) 位:表示该页可读可写。

U (User) 位:表示该页可以被任何权限用户访问。

W (Write Through) 位:表示 CPU 可以直写回内存。

D (Cache Disable) 位:表示不需要被 CPU 缓存。

A (Access) 位:表示该页被写过。

S (Size) 位:表示一个页 4MB 。

9-11 位保留给 OS 使用。

12-31 位指明 PTE 基质地址。

PTE 详解



从低到高,分别是:

0-3 位同 PDE。

C (Cache Disable) 位:同 PDE D 位。

A (Access) 位:同 PDE 。

D (Dirty) 位:表示该页被写过。

G (Global) 位:表示在 CR3 寄存器更新时无需刷新 TLB 中关于该页的地址。

9-11 位保留给 OS 使用。

12-31 位指明物理页基址。

  如果ucore执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?

  进行换页操作 首先 CPU 将产生页访问异常的线性地址 放到 cr2 寄存器中 ;然后就是和普通的中断一样 保护现场 将寄存器的值压入栈中 ;然后压入 error_code 中断服务例程将外存的数据换到内存中来 ;最后 退出中断回到进入中断前的状态。

练习3:释放某虚地址所在的页并取消对应二级页表项的映射

  当释放一个包含某虚地址的物理内存页时,需要让对应此物理内存页的管理数据结构Page做相关的清除处理,使得此物理内存页成为空闲;另外还需把表示虚地址与物理地址对应关系的二级页表项清除。请仔细查看和理解page_remove_pte函数中的注释。为此,需要补全在 kern/mm/pmm.c中的page_remove_pte函数。page_remove_pte函数的调用关系图如下所示:

  之前断言失败是因为在 page_insert 中调用了 page_remove_pte ,而 page_remove_pte 并没有修改引用计数所以导致了对于引用计数的断言失败。

static inline void page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
if ((*ptep & PTE_P)) {
struct Page *page = pte2page(*ptep);
if (page_ref_dec(page) == 0) { // 若引用计数减一后为0 则释放该物理页
free_page(page);
}
*ptep = 0; // 清空 PTE
tlb_invalidate(pgdir, la); // 刷新 tlb
}
}

首先判断一下 ptep 是不是合法——检查一下 Present 位就是了;然后通过注释中所说的,通过 pte2page(*ptep) 获取相应页,减少引用计数并决定是否释放页;最后把 TLB 中该页的缓存刷掉就可以了。

  数据结构Page的全局变量(其实是一个数组)的每一项与页表中的页目录项和页表项有无对应关系?如果有,其对应关系是啥?

  可以参照 kern/mm/pmm.h 中的转换函数。其实就是 Page 全局数组中以 Page Directory Index 和 Page Table Index 的组合 PPN (Physical Page Number) 为索引的那一项。

剩下的不会;

参考链接:

[1].https://yuerer.com/操作系统-uCore-Lab-2/

[2].https://xr1s.me/2018/05/27/ucore-lab2-report/

OS课程 ucore_lab2实验报告的更多相关文章

  1. 第四周课程总结&实验报告(二)

    Java实验报告(二) 实验二 Java简单类与对象 一. 实验目的 (1) 掌握类的定义,熟悉属性.构造函数.方法的作用,掌握用类作为类型声明变量和方法返回值: (2) 理解类和对象的区别,掌握构造 ...

  2. 第六周课程总结&实验报告(四)

    实验报告(四) 一.实验目的 1.掌握类的继承 2.变量的继承和覆盖,方法的继承,重载和覆盖的实现 二.实验的内容 1.根据下面的要求实现圆类Circle. 圆类Circle的成员变量:radius表 ...

  3. 第十四周课程总结 & 实验报告

    一.JDBC JDBC概述 JDBC提供了一种与平台无关的用于执行SQL语句的标准JavaAPI,可以方便的实现多种关系型数据库的统一操作,它由一组用Java语言编写的类和接口组成 JDBC的主要操作 ...

  4. 第九周课程总结 & 实验报告(七)

    第九周课程总结 一.多线程 1.线程的状态 2.线程操作的相关方法 二.Java IO 1.操作文件的类---File ()基本介绍 ()使用File类操作文件 .RandomAccessFile类 ...

  5. 第八周课程总结 & 实验报告(六)

    第八周课程总结 一.包装类 介绍 装箱与拆箱 应用 二.异常 基本概念 基本格式 异常类的继承结构 throws关键字 throw关键字 Exception类和RuntimeException类 自定 ...

  6. 第七周课程总结 & 实验报告(五)

    第七周课程总结 一.抽象类与接口的应用 1.实例化 2.实际应用 ---模板设计(抽象类) ---制定标准(接口) 3.设计模式 ---工厂设计 ---代理设计 ---适配器设计 二.抽象类与接口之间 ...

  7. 第三周课程总结&实验报告

    课程总结 在这周对Java进行了更深层次的学习,Java的学习也变得越来越困难而有趣,加入了一些新的构造新的方法,还学习了一些简化代码的方式. 面向对象的基本概念 对于面向对象的程序设计有三个主要特征 ...

  8. 第四周课程总结&实验报告

    实验报告 1.写一个名为Rectangle的类表示矩形. 其属性包括宽width.高height和颜色color,width和height都是double型的,而color则是String类型的.要求 ...

  9. 第五周课程总结&实验报告(三)

    实验三 String类的应用 实验目的: (1)掌握类String类的使用: (2)学会使用JDK帮助文档: 实验内容: 1.已知字符串:"this is a test of java&qu ...

随机推荐

  1. 【一起学源码-微服务】Nexflix Eureka 源码五:EurekaClient启动要经历哪些艰难险阻?

    前言 在源码分析三.四都有提及到EurekaClient启动的一些过程.因为EurekaServer在集群模式下 自己本身就是一个client,所以之前初始化eurekaServerContext就有 ...

  2. 【接口测试】使用httpClient获取cookies+携带获取的cookies访问get接口

    数据准备 在本机或者远端机器安装部署moco-runner(参考:https://blog.csdn.net/qq_32706349/article/details/80472445) 这里我们只需要 ...

  3. 开箱即用!使用Rancher 2.3 启用Istio初体验

    本文来自Rancher Labs Rancher的理念是Run Kubernetes Everywhere,Rancher 2.3中许多重大更新,让这一理念的实现又向前一步. 其中,最重要的两个特性是 ...

  4. curl使用post方式访问Spring Cloud gateway报time out错误

    公司老的项目使用是php,要进行重构.其他团队使用php curl函数使用post方式调用Spring Cloud gateway 报time out错误. 但是使用postman测试是没有任何问题, ...

  5. 最强常用开发库总结 - JSON库详解

    最强常用开发库总结 - JSON库详解 JSON应用非常广泛,对于Java常用的JSON库要完全掌握.@pdai JSON简介 JSON是什么 JSON 指的是 JavaScript 对象表示法(Ja ...

  6. 下载并部署 ArcGIS API for JavaScript 4.10

    学习ArcGIS API for JavaScript 4.10 的第一步就是下载并部署该文件. 有的读者由于之间没接触过,不知道怎么下载和部署文件.这些读者要求作者详细的写一篇关于下载和部署的文章( ...

  7. iOS多线程编程原理及实践

    摘要:iOS开发中,开发者不仅要做好iOS的内存管理,而且如果你的iOS涉及多线程,那你也必须了解iOS编程中对多线程的限制,iOS主线程的堆栈大小为1M,其它线程均为512KB,且这个限制开发者是无 ...

  8. 更好用的 Python 任务自动化工具:nox 官方教程

    英文| nox tutorial 出处| nox 官方文档 译者| 豌豆花下猫@Python猫 Github地址:https://github.com/chinesehuazhou/nox_doc_c ...

  9. ASP.Net MVC 引用动态 js 脚本

    希望可以动态生成 js  发送给客户端使用. layout页引用: <script type="text/javascript" src="@Url.Action( ...

  10. LoopBox 用于包装循环的盒子

    /******************************************************* * * 作者:朱皖苏 * 创建日期:20180608 * 说明:此文件只包含一个类,具 ...