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;此时就可以使用事件标志组来解决。事件标志组能够实现某个任务与…

Webpack5 动态导入按需加载

文章目录 一、 什么是动态导入和按需加载&#xff1f;二、 具体用法示例二、 总结 一、 什么是动态导入和按需加载&#xff1f; 传统上&#xff0c;在Webpack中&#xff0c;我们使用import语句可以在代码中静态地导入模块。这意味着所有的模块都会在构建时被打包到bundle中。然…

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

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

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

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

前端监控概述

前端监控埋点概览 前端监控主要分为三类&#xff1a;数据监控、性能监控、异常监控 数据监控 记录上报产品在用户端的使用情况&#xff0c;以数据为导向&#xff0c;帮助团队做决策&#xff0c;数据监控有时也被称为行为监控&#xff0c;常见的包括&#xff1a; PV/UV&…

宝塔面板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这只“网红猫”仍保…

在 spark-sql / spark-shell / hive / beeline 中粘贴 sql、程序脚本时的常见错误

一个很小的问题&#xff0c;简单记录一下。有时候我们会粘贴一段已经成功运行过的SQL或程序脚本&#xff0c;但是在spark-sql / spark-shell / hive / beeline 中执行时可能会报这样的错误&#xff1a; hive> CREATE EXTERNAL TABLE IF NOT EXISTS ORDERS(> Display all…

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

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

点击表格行高亮

css中三元表达式 :class"[activeIndex index ? color : , item]"点击行高亮 <div click"actvied(index)" :class"[activeIndex index ? color : , item]"v-for"(item, index) in tableData" :key"index">{{ item…

[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循环遍历 字符串 前言…

SpringBoot复习:(19)Condition接口和@Conditional注解

Condition接口代码如下&#xff1a; public interface Condition {boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);}它是一个函数式接口&#xff0c;只有一个方法matches用来表示条件是否满足。matches方法中的ConditionContext类对象context可以…

OPENCV C++(五)滤波函数+sobel边缘检测+人脸磨皮mask

滤波函数 中值滤波 medianBlur(frame, detectmat, 5); 平均滤波 blur(frame, detectmat, Size(5, 5)); 高斯滤波&#xff08;最后一个是方差 越大越模糊&#xff09; GaussianBlur(frame, detectmat, Size(5, 5),0); sobel的边缘检测函数 Sobel(gray, dx, CV_16S, 1, 0, 3…

图片静态展示

图片静态展示程序&#xff0c;包含选择文件夹路径&#xff0c;旋转&#xff0c;放大缩小&#xff0c;拖动&#xff0c;幻灯片播放&#xff0c;上一张下一张等&#xff0c;程序使用QT实现。 程序下载地址&#xff1a; 图片静态展示&#xff0c;包含选择文件夹路径&#xff0c;…