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

news2024/11/15 19:38:54

文章目录

  • 前言
  • 其他篇章
  • 参考链接
  • 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加上条件编译
  // 映射到USYSCALL
  if (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_PGTBL
  uvmunmap(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;

  // TODO

  copyout(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.coloradmin.cn/o/838793.html

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

相关文章

DBSCAN聚类

一、概述 DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的聚类算法&#xff0c;簇集的划定完全由样本的聚集程度决定。聚集程度不足以构成簇落的那些样本视为噪声点&#xff0c;因此DBSCAN聚类的方式也可以用于异常点的检测。 二、算法…

一零六七、JVM梳理

JVM&#xff1f; Java虚拟机&#xff0c;可以理解为Java程序的运行环境&#xff0c;可以执行Java字节码&#xff08;Java bytecode&#xff09;并提供了内存管理、垃圾回收、线程管理等功能 java内存区域划分?每块内存中都对应什么? 方法区&#xff1a;类的结构信息、常量池、…

5个顶级的开源有限元分析软件

每当我参加数值分析课程的教学时&#xff0c;都会回顾有限元方法的基础知识&#xff0c;很自然地就会出现使用哪种软件的问题。 以下讨论基于三个基本考虑&#xff1a; 在实际应用中&#xff0c;很少有人从头开始编写 FEM 代码。商业 FEM 软件通常在某些预定义的情况下非常易于…

生命在于学习——Linux安全加固以及基线检查

一、账号管理 1、口令锁定策略 基线检查&#xff1a; 查看文件more /etc/pam.d/password-auth判定条件&#xff1a;是否存在以下内容 auth required pam_tally2.so deny5 onerrfail unlock_time300 even_deny_root5 root_unlock_time600安全加固&#xff1a; &#xff08;1…

MyBatis的输入映射和输出映射

文章目录 前言案例总结resultMap的使用 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; MyBatis的输入映射和输出映射是将Java对象和数据库表的列进行映射&#xff0c;实现数据的自动转换。 输入映射&#xff08;Parameter Mapping&#xff09;&#x…

从小白到大神之路之学习运维第74天-------Docker网络模型详解

第四阶段 时 间&#xff1a;2023年8月4日 参加人&#xff1a;全班人员 内 容&#xff1a; Docker网络模型详解 目录 一、环境配置 &#xff08;一&#xff09;安装docker-ce&#xff08;Linux安装Docker&#xff09; 二、Docker网络基础 &#xff08;一&#xff09;端…

Linux 创建子进程

文章目录 前言一、进程&#xff0c;线程&#xff0c;程序 区分二、创建子进程三、创建多个进程1. 获取进程号2. 循环创建多个进程 四、进程工具。1. ps 查看当前进程.2. kill 进程终止. 总结 前言 在计算机科学中&#xff0c;进程&#xff08;Process&#xff09;、线程&#…

8.5day06 框架基础--反射+注解

文章目录 反射获取类的各种信息获取类的字节码文件 注解元注解 复习redis两道算法题 摆烂了&#xff0c;不想学啦&#xff01;&#xff01;&#xff01; 反射 反射主要用来做框架; 学习内容 获取类的各种信息 第一步 加载类&#xff0c;获取类的字节码文件 第二步 获取类的…

抖音seo矩阵系统源码搭建开发详解

抖音SEO矩阵系统是一个用于提高抖音视频在搜索引擎排名的工具。如果你想开发自己的抖音SEO矩阵系统&#xff0c;以下是详细的步骤&#xff1a; 开发步骤详解&#xff1a; 确定你需要的功能和算法 抖音SEO矩阵系统包含很多功能&#xff0c;比如关键词研究、内容优化、链接建设、…

【ASP.NET MVC】使用动软(一)(9)

一、解决的问题 前文为解决数据库操作设计的 TestMysql 类&#xff0c;仅简单地封装了一个Query函数&#xff0c;代码如下&#xff1a; public class TestMysql{public static string SqlserverConnectStr "server127.0.0.1;charsetutf8;user idroot;persistsecurityin…

PLC4X踩坑记录

plc4x引起的oom 使用Jprofiler查看dump文件 由上可以看出有大量的NioEventLoop对象没有释放 PlcConnection#close 设备断连重连后导致的oom&#xff0c;看源码close方法主要是channel通道关闭。 修改NettyChannelFactory源码 plc4x设计思想是一个设备一个连接&#xff0c;…

剑指OfferII-58.左旋转字符串

剑指OfferII-58.左旋转字符串 目录 剑指OfferII-58.左旋转字符串题目描述解法一&#xff1a;字符数组解法二&#xff1a;原地反转 题目描述 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。 请定义一个函数实现字符串左旋转操作的功能。 比如&#xff0c…

性能测试浅谈

早期的性能测试更关注后端服务的处理能力。 一个用户去访问一个页面的请求过程&#xff0c;如上图。 数据传输时间 当你从浏览器输入网址&#xff0c;敲下回车&#xff0c;开始... 真实的用户场景请不要忽视数据传输时间&#xff0c;想想你给远方的朋友写信&#xff0c;信件…

visio,word添加缺少字体,仿宋_GB2312、楷体_GB2312、方正小标宋简体等字体下载

一. 内容简介 visio,word添加缺少字体,仿宋_GB2312、楷体_GB2312、方正小标宋简体等字体下载 二. 软件环境 2.1 visio 三.主要流程 3.1 下载字体 http://www.downza.cn/ 微软官方给的链接好多字体没有&#xff0c;其他好多字体网站&#xff0c;就是给你看个样式&#xff…

JMeter(二十四)、使用吞吐量控制器实现不同的用户操纵不同的业务

一、需求 需求&#xff1a;博客系统&#xff0c;模拟用户真实行为&#xff0c;80%的用户阅读文章&#xff0c;20%的用户创建文章&#xff0c;创建文章的用户随机的删除或者修改文章。 二、脚本实现 80%的用户查看文章 20%用户创建文章 根据post_id是否能整除2&#xff0c;决…

在线课堂,视频点播,springboo+vue

springbootvue三端&#xff08;管理后台&#xff0c;教师端&#xff0c;用户端&#xff09;端可提供源码&#xff0c;可远程安装&#xff0c;需要的加微信&#xff1a; 体验地址&#xff1a;http://edu.dgrxs.com/ 用户端&#xff1a; 管理端&#xff1a; 教师端&#xff1a;

IDEA中怎么使用git下载项目到本地,通过URL克隆项目(giteegithub)

点击 新建>来自版本控制的项目 点击后会弹出这样一个窗口 通过URL拉取项目代码 打开你要下载的项目仓库 克隆>复制 gitee github也是一样的 返回IDEA 将刚刚复制的URL粘贴进去选择合适的位置点击克隆 下载完成

三星进军机器人市场?特斯拉首款人形机器人“擎天柱”明年上市

根据报道&#xff0c;三星电子正在积极研究进军机器人市场的战略。此战略由三星电子的Device eXperience&#xff08;DX&#xff09;部门的专业企划小组制定。据可靠消息透露&#xff0c;该企划小组已着手制定相关计划&#xff0c;以推动公司在机器人市场的发展。 根据外媒报道…

危大工程智慧工地源码,微服务+Java+Spring Cloud +UniApp +MySql 物联网、人工智能、视频AI分析

一套智慧工地管理平台源码&#xff0c;PC端移动APP端可视货数据管理端源码 智慧工地可视化系统利用物联网、人工智能、云计算、大数据、移动互联网等新一代信息技术&#xff0c;通过工地中台、三维建模服务、视频AI分析服务等技术支撑&#xff0c;实现智慧工地高精度动态仿真&a…

【docker】docker私有仓库

目录 一、说明二、私有仓库搭建三、上传镜像到私有仓库四、从私有仓库拉取镜像 一、说明 1.docker官方的docker hub(https://hub.docker.com)是一个用于管理公共镜像的仓库&#xff0c;可以从上面拉取镜像到本地&#xff0c;也可以把自己的镜像推送上去 2.若服务器无法访问互联…