6.s081/6.1810(Fall 2022)Lab3: page tables

文章目录

  • 前言
  • 其他篇章
  • 参考链接
  • 0. 前置环境
  • 1. Speed up system calls (easy)
    • 1.1 简单分析
    • 1.2 映射
    • 1.3 页分配
    • 1.4 页释放
    • 1.5 测试
  • 2. Print a page table (easy)
    • 2.1 简单分析
    • 2.2 实现
    • 2.3 测试
  • 3. Detect which pages have been accessed (hard)
    • 3.1 简单分析
    • 3.2 实现
      • 3.2.1 获取参数
      • 3.2.2 传出参数
      • 3.2.3 定义PTE_A
      • 3.2.4 实现主体逻辑
    • 3.3 测试
  • 测试

前言

这一个Lab是往年叫苦声最大的、最难的一个lab,不过今年显然简化了不少,换掉了Task,其间意义见仁见智吧。

其他篇章

环境搭建
Lab1: Utilities
Lab2: System calls
Lab3: Page tables

参考链接

官网链接
xv6手册链接,这个挺重要的,建议做lab之前最好读一读。
xv6手册中文版,这是几位先辈们的辛勤奉献来的呀!再习惯英文文档阅读我还是更喜欢中文一点,开源无敌!
OSTEP,对OS不熟悉的同学做之前可以看一下这本经典书籍,写得很好,也有中文版实体书。
官方文档

0. 前置环境

如果你和我操作步骤一直一样,那就可以在VS的远程仓库里找到分支base/pgtbl分支,选中
上一个lab里我用的命令行拉,这次就

打开分支管理器(Alt->G->M),右键pgtbl,取消设置上游分支,然后右键推送,显示成功推送到origin,这样就成功了
在这里插入图片描述
然后在wsl里的对应文件夹下,git pullgit checkout pgtbl,整体配置完成:
在这里插入图片描述

1. Speed up system calls (easy)

1.1 简单分析

上一个Lab我们实现了两个系统调用,从中可以认识到系统调用涉及到用户态与内核态的切换,自然也就涉及到了各种参数传来传去的问题。本Lab开篇就介绍了许多操作系统都通过维护一个read-only的共享内存区去实现内核态与用户态资源的共享,免去了某些资源交换的过程,从而提升系统调用的效率。
在这里插入图片描述
介绍完后,本Task要求我们在xv6中为getpid实现这种功能,我们知道操作系统通过页表去管理内存,而它告诉我们每个进程创建时都会映射到一个USYSCALL,这玩意是个VA,也就是Virtual Address,这应该就是我们的共享区域的起始地址,打开他提到的文件看一看:
在这里插入图片描述

可以看到,这个USYSCALL是由TRAPFRAME往前偏移一页算出来的,而TRAPFRAME又是由TRAMPOLINE偏移出来的,TRAMPOLINE页相当于在VA的最后一页上,里面映射了一些内核的指令,用于陷入内核,而TRAPFRAME页则负责保存进程相关的一些数据。此外,可以注意到这个地方有一个条件编译,这个是在Makefile里编译启用的,我们不用手动宏定义,或者看着不爽先宏定义一下后面撤掉也行。结构体里面目前就一个pid,后面看看用不用得上。

1.2 映射

然后看一看Hint:
在这里插入图片描述
这在提示我们怎么去做USYSCALL这个映射,我们首先看一下proc.c
在这里插入图片描述
扫一眼就可以看出,这里做的是进程向trampoline pagetrapframe page的映射,在申请资源后,每次map都需要检查一下是否成功,不成功就得释放之前申请过的资源以及映射过的页。因此我们可以往里面添加这样一些代码:

#ifdef LAB_PGTBL // 模仿着memlayout.h加上条件编译// 映射if (mappages(pagetable, USYSCALL, PGSIZE,// TODO: 还差后面两个参数) < 0) {uvmunmap(pagetable, TRAMPOLINE, 1, 0);uvmunmap(pagetable, TRAPFRAME, 1, 0);uvmfree(pagetable, 0);return 0;}
#endif

看一下倒数第二个参数,我们可以发现这个trapframe就是存在proc里的一个指针而已,因此我们也在proc.h加上usyscall指针的定义:
在这里插入图片描述

#ifdef LAB_PGTBL struct usyscall* usyscall;   
#endif

然后看一下mappages函数的最后一个参数,最后一个参数代表了所谓的PTE的值,标记了分页的一些状态,打开定义位置,我们可以看到这里定义了五个宏:
在这里插入图片描述
关于这些标志位的解释xv6 book里有,我之前放那个中文的链接是基于x86的,和现在的RISC-V在这里有一点不一样,所以我这里就放原文了:
在这里插入图片描述
可以看到,这五个标志位分别标记了是否有效、可读、可写、可执行(将页标记为指令,像之前说的trampoline page,里面就放的一些内核的指令,因此我们看到它被标记上了PTE_X)、用户可用,我们的这个共享页需要可读且用户态与内核态都可以访问,因此我们需要将它设置为PTE_R | PTE_U

据此我们依葫芦画瓢照着映射我们的usyscall page就行:

...
#ifdef LAB_PGTBL // 模仿着memlayout.h加上条件编译// 映射到USYSCALLif (mappages(pagetable, USYSCALL, PGSIZE,(uint64)(p->usyscall), PTE_R | PTE_U) < 0) {uvmunmap(pagetable, TRAMPOLINE, 1, 0);uvmunmap(pagetable, TRAPFRAME, 1, 0);uvmfree(pagetable, 0);return 0;}
#endif
...

在这里插入图片描述

1.3 页分配

没啥好说的,找到allocproc函数照猫画虎就行,只是别忘了给pid赋值
在这里插入图片描述

#ifdef LAB_PGTBL if ((p->usyscall = (struct usyscall*)kalloc()) == 0) {freeproc(p);release(&p->lock);return 0;}p->usyscall->pid = p->pid; // 别忘了给usyscall的pid赋值
#endif

1.4 页释放

freeproc里释放usyscall
在这里插入图片描述

#ifdef LAB_PGTBL
if (p->usyscall)kfree((void*)p->usyscall);p->usyscall = 0;
#endif

记得我们前面初始化的时候映射失败需要调用unmap去取消映射吗?正常运行完毕自然也要去做这个事情,做这个事情的函数就在下面那个proc_freepagetable里,F12打开,加上去:
在这里插入图片描述

#ifdef LAB_PGTBLuvmunmap(pagetable, USYSCALL, 1, 0);
#endif

就此就搞定了

1.5 测试

推送后make qemu,本来是很稀松平常的事情,结果我一直报这个错:

make: *** 没有规则可制作目标“kernel/sysinfo.h”,由“kernel/sysproc.o” 需求。 停止。

在这里插入图片描述

然后我又是回退还原又是各种各样的操作,都依然报这个错,网上也一直找不到别人吐槽这个事情,最后我make clean了一下,成功了,,,这玩意卡我一个多小时你敢信?
在这里插入图片描述
硬生生一个多小时
然后输入pgtbltest,看到ugetpid_test那里显示OK就行:
在这里插入图片描述
跑一下 ./grade-lab-pgtbl ugetpid
在这里插入图片描述

2. Print a page table (easy)

2.1 简单分析

这个task要我们写一个打印页表的函数,也比较简单:
在这里插入图片描述
初步阅读上文可以简单提炼出需求:我们需要在kernel/vm.c中定义一个名为vmprintf()的函数,接受并按格式打印一个pagetable_t 类型的参数,然后在exec.creturn argc插入if(p->pid==1) vmprint(p->pagetable)语句用来打印第一个进程的page table,读到这里,我们顺手给他塞进去:
在这里插入图片描述
然后看看打印格式:
在这里插入图片描述

The first line displays the argument to vmprint. After that there is a line for each PTE, including PTEs that refer to page-table pages deeper in the tree. Each PTE line is indented by a number of " …" that indicates its depth in the tree. Each PTE line shows the PTE index in its page-table page, the pte bits, and the physical address extracted from the PTE. Don’t print PTEs that are not valid. In the above example, the top-level page-table page has mappings for entries 0 and 255. The next level down for entry 0 has only index 0 mapped, and the bottom-level for that index 0 has entries 0, 1, and 2 mapped.

可以看到,第一行打印了vmprint的参数,后面各行展示了页表所属下方的条目,那么问题来了——我们怎么知道页表下面有哪些页面呢?参照The function freewalk may be inspirational. 因此我们可以看一下这个函数:
在这里插入图片描述
打开pagetable_t的定义发现这其实就是个指针型别,看注释这里是用了9位用来表示子页表,因此它遍历了512位,寻址后判定对期望的标志位的页面使用PTE2PA截断了低10位和高2位,然后继续递归进入执行逻辑,可以看出这是个DFS。值得注意的一点是,标志位限定了不可读、不可写、不可执行的页面才进入下一步递归,因为这意味着这是个间接层,不记载内容,只作为多级页表的一级。
在这里插入图片描述

2.2 实现

分析清楚后我们就可以写我们的函数了,由于我们要根据深度打印.,因此我们可以给参数传入一个深度的参数,我们可以为这个递归函数设立一个helper函数,对外接口就只暴露调用helper的vmprint本身,避免污染。

void
vmprint_dfs(pagetable_t pagetable, uint depth)
{static char* prefix[] = {[1] = "..",".. ..",".. .. .."};if (depth > 3) {panic("vmprint_dfs: depth > 3");return;}for (int i = 0; i < 512; i++) {pte_t pte = pagetable[i];if (pte & PTE_V) {pte_t child = PTE2PA(pte);printf("%s%d: pte %p pa %p\n", prefix[depth], i, pte, child);if (child & (PTE_R | PTE_W | PTE_X) == 0) {vmprint_dfs((pagetable_t)child, depth + 1);}}}
}void
vmprint(pagetable_t pagetable)
{printf("page table %p\n", pagetable);vmprint_dfs(pagetable, 1);
}

然后在defs.h中暴露出接口:
在这里插入图片描述
到此就基本搞定了,看一看:
在这里插入图片描述

2.3 测试

运行一下测试脚本./grade-lab-pgtbl pte printout,通过:
在这里插入图片描述

3. Detect which pages have been accessed (hard)

3.1 简单分析

首先lab介绍了一下标记page是否被访问过(accessed)是比较有用的一个信息,比如对GC有用,这个位维护在一些位里,由RISC-V的硬件页遍历器(hardware page walker)去维护这些位。我们要做的就是检查这些页,并返回给用户态。
在这里插入图片描述
具体而言,我们需要实现一个名为pgaccess的系统调用,用于报告哪些页被访问过,它接受三个参数:

  1. 待检查的第一个用户页的起始VA
  2. 待检查页面的数量
  3. 存储结果(被访问了的页面号)用的bitmap
    在这里插入图片描述

第一个Hint还告诉我们可以从user/pgtlbtest.c中的pgaccess_test()看一看pgaccess是怎么用的:
在这里插入图片描述
可以看到,pgaccess应当在失败时返回一个-1,第1、2、30页被访问过了,因此最后结果abits的对应位就被置为了1。

3.2 实现

理清楚这些东西,实现起来就很简单了,上个lab告诉了我们syscall实现的步骤,不过这次我们只用写实现就行了,不用关注那些繁文缛节的事情。

3.2.1 获取参数

依赖之前的经验获取参数,不多说

  uint64 va;             // 待检测页表起始地址int num_pages;         // 待检测页表的页数uint64 access_mask;    // 记录检测结果的掩码// 从用户栈中获取参数argaddr(0, &va);  argint(1, &num_pages);argaddr(2, &access_mask);

3.2.2 传出参数

For the output bitmask, it’s easier to store a temporary buffer in the kernel and copy it to the user (via copyout()) after filling it with the right bits. 提示我们可以用一个中间变量把mask存起来由此可以完善我们的实现:

int
sys_pgaccess(void)
{uint64 va;             // 待检测页表起始地址int num_pages;         // 待检测页表的页数uint64 access_mask;    // 记录检测结果掩码的地址// 从用户栈中获取参数argaddr(0, &va);  argint(1, &num_pages);argaddr(2, &access_mask);if (num_pages <= 0 || num_pages > 512){return -1;}uint mask = 0;// TODOcopyout(myproc()->pagetable, access_mask, (char*)&mask, sizeof(mask));return 0;
}

3.2.3 定义PTE_A

刚才说了,我们实际上是用一个accessed位去记录信息的,这个位同样也保存在PTE中,题中要求我们去在riscv.h中定义一下这个位,那么问题来了,这个位定义成多少呢?
在这里插入图片描述
查阅risc-v手册可以看到,risc-v中将PTE_A放在了第六位,因此我们在riscv.h中加入:

#define PTE_A (1L << 6) // accessed

或者干脆全定义了算了()
在这里插入图片描述

3.2.4 实现主体逻辑

然后就比较简单了,我们遍历页表,利用walk获取pte,然后对PTE_A置位的页复位,并把页码放在mask里:

int
sys_pgaccess(void)
{struct proc* p = myproc();uint64 va;             // 待检测页表起始地址int num_pages;         // 待检测页表的页数uint64 access_mask;    // 记录检测结果掩码的地址// 从用户栈中获取参数argaddr(0, &va);  argint(1, &num_pages);argaddr(2, &access_mask);if (num_pages <= 0 || num_pages > 512){return -1;}uint mask = 0;// 遍历页表for (int i = 0; i < num_pages; i++){pte_t* pte = walk(p->pagetable, va + i * PGSIZE, 0);if (pte && (*pte & PTE_V) && (*pte & PTE_A)){*pte &= ~PTE_A;  // 清除访问位mask |= (1 << i);}}// 将检测结果写入用户栈copyout(p->pagetable, access_mask, (char*)&mask, sizeof(mask));return 0;
}

3.3 测试

make qemupgtbltest,测试成功:
在这里插入图片描述
./grade-lab-pgtbl pgaccess一下:
在这里插入图片描述

测试

最后添加time.txtanswers-pgtbl.txt,跑一下make grade,通过(话说不知道那个Test time为什么卡老半天):
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/24128.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

8.物联网操作系统之事件标志组

。事件标志组定义 FreeRTOS事件标志组介绍 FreeRTOS事件标志组工作原理 一。事件标志组定义 信号量信号量只能实现任务与单个事件或任务间的同步。但是某些任务可能会需要与多个事件或任务进行同步&#xff0c;此时就可以使用事件标志组来解决。事件标志组能够实现某个任务与…

【PostgreSQL】系列之 一 用户创建和授权(三)

&#x1f341; 博主 "开着拖拉机回家"带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——&#x1f390;开着拖拉机回家_Linux,Java基础学习,大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341; 希望本文能够给您带来一定的…

如何在 Android 上恢复已删除的视频|快速找回丢失的记忆

想知道是否有任何成功的方法可以从 Android 手机中检索已删除的视频&#xff1f;好吧&#xff0c;本指南将向您展示分步说明&#xff0c;让您轻松从手机中找回丢失的视频文件&#xff01; 您是否不小心从 Android 智能手机中删除了珍贵的生日视频&#xff1f;难道是无处可寻吗…

宝塔面板Mysql数据库无法启动(已解决)

1、错误排查 Mysql 无法正常启动直接使用官方提供的脚本检查出错 wget -O sql-repair.sh http://download.bt.cn/install/sql-repair.sh && sh sql-repair.shwget -O sql-repair.sh http://download.bt.cn/install/sql-repair.sh && sh sql-repair.sh 打印出…

LoadRunner

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录 LoadRunner 安装LoadRunner 三大组件之间的关系LoadRunner 脚本录制WebTours 系统 脚本加强事务插入插入集合点插入检查点参数…

增量式PID算法及其MATLAB实现

增量式PID算法是一种常用的控制算法,用于控制系统中的反馈控制。它通过对系统的误差进行递推式的计算,实现对系统输出的调节,使得系统的输出逐渐趋向于设定值。 delta u(k)=u(k)-u(k-1)=Kp*(e(k)-e(k-1))+Ki*e(k)+Kd*(e(k)-2*e(k-1)+e(k-2)) PID算法由三个部分组成:比例(…

Cat.1如何成为物联网业务加速器?

随着Cat.1芯片及模组在功耗和成本上的不断优化&#xff0c;在窄带物联网领域&#xff0c;越来越多的终端客户把Cat.1当做与NB-IoT相比较的第二选择。越来越多的表计、烟感、市政等行业终端将Cat.1模组应用于非集中化部署的上报类终端业务中&#xff0c;Cat.1这只“网红猫”仍保…

基于vue医院分时段预约挂号系统java病历管理系统snsj0

伴随着我国社会的发展&#xff0c;人民生活质量日益提高。互联网逐步进入千家万户&#xff0c;改变传统的管理方式&#xff0c;医院病历管理系统以互联网为基础&#xff0c;利用java技术&#xff0c;和mysql数据库开发设计一套医院病历管理系统&#xff0c;提高工作效率的同时&…

[Linux]理解文件系统!动静态库详细制作使用!(缓冲区、inode、软硬链接、动静态库)

hello&#xff0c;大家好&#xff0c;这里是bang___bang_&#xff0c;今天来谈谈的文件系统知识&#xff0c;包含有缓冲区、inode、软硬链接、动静态库。本篇旨在分享记录知识&#xff0c;如有需要&#xff0c;希望能有所帮助。 目录 1️⃣缓冲区 &#x1f359;缓冲区的意义 …

Python(六十九)为什么要将元组设计成不可变序列

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

nginx服务

目录 基本介绍 nginx的主要功能 nginx的主要应用场景 nginx常用命令 nginx另外一种安装方式 nginx常用的信号符&#xff1a; nginx配置文件详解 全局配置 event模块 http模块 server模块 location模块&#xff1a; 模块的划分 基本介绍 nginx&#xff1a;高性能、…

06 Ubuntu22.04上的miniconda3安装、深度学习常用环境配置

下载脚本 我依然是在清华镜像当中寻找的脚本。这里找脚本真的十分方便&#xff0c;我十分推荐。 wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-latest-Linux-x86_64.sh 下载十分快速&#xff0c;10秒解决问题 运行miniconda3安装脚本 赋予执…

python数据容器

目录 数据容器 反向索引 list列表 语法 案例 列表的特点 列表的下表索引 list的常用操作 list列表的遍历 while循环遍历 for循环遍历 tuple元组 前言 元组定义 元组特点 获取元组元素 元组的相关操作 元组的遍历 while循环遍历 for循环遍历 字符串 前言…

LeetCode113. 路径总和 II

113. 路径总和 II 文章目录 [113. 路径总和 II](https://leetcode.cn/problems/path-sum-ii/)一、题目二、题解方法一&#xff1a;递归另一种递归版本方法二&#xff1a;迭代 一、题目 给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶…

java+python企业会议在线办公微信小程序 ia505

一、小程序端功能 该部分内容提供员工注册、员工资料修改、通知公告、部门信息、会议记录等等功能。 二、管理员管理功能 该部分内容包含了首页、个人中心、通知公告管理、员工管理、部门信息管理、职位信息管理、会议记录管理、待办事项管理、工资信息管理、留言板管理、系统管…

C++ 类型兼容规则

类型兼容规则是指在需要基类对象的任何地方&#xff0c;都可以使用公有派生类的对象来替代。 通过公有继承&#xff0c;派生类得到了基类中除构造函数和析构函数之外的所有成员。这样&#xff0c;公有派生类实际就具备了基类的所有功能&#xff0c;凡是基类能解决的问题&#x…

微信支付官方文档怎么看

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

Cilium系列-14-Cilium NetworkPolicy 简介

系列文章 Cilium 系列文章 前言 今天我们进入 Cilium 安全相关主题, 介绍 Kubernetes 网络策略以及 CiliumNetworkPolicies 额外支持的内容。 网络策略(NetworkPolicy)的类型 默认情况下&#xff0c;Kubernetes 集群中的所有 pod 都可被其他 pod 和网络端点访问。 网络策…

Kafka3.0.0版本——Broker(Zookeeper服务端存储的Kafka相关信息)

目录 一、启动zookeeper集群及kafka集群服务启动1.1、先启动三台zookeeper集群服务&#xff0c;再启动三台kafka集群服务1.2、使用PrettyZoo连接zookeeper客户端工具 二、在zookeeper服务端存储的Kafka相关信息 一、启动zookeeper集群及kafka集群服务启动 1.1、先启动三台zook…

计算机成下一个土木了吗?

前些年抓住了互联网行业的红利期&#xff0c;进入大厂的员工&#xff0c;基本可以实现在一线城市买房扎根。 但反观现在&#xff0c;“被毕业、逃离互联网、躺平算了...”却成了这个行业的主旋律&#xff0c;不少人在谈论润到国企和外企去了&#xff0c;也放低了对工资的预期&…