MIT6.s081 2021 Lab Page tables

Speed up system calls

思路

题目要求在每个进程初始化时为它的页表插入一个页表项,内核通过这样预先缓存页表项的操作,来加速特定系统调用的执行速度。

由于前不久刚过完一遍《OSTEP》,因此我认为自己对页表机制还算比较熟悉,应对本 Lab 理应比较轻松,但在真正上手的时候,还是觉得有些无所适从,无奈老老实实地把 xv6 手册的第 3 章对照着代码仔细研读了一番,从中提炼出了几个关键的函数:

  1. kernel/kalloc.c:kalloc
void *kalloc(void);

遍历空闲链表,寻找一个可分配的物理页面。若找到,返回该页面的首(物理)地址;否则,返回 0 (空指针)。

  1. kernel/kalloc.c:kfree
void kfree(void *pa);

释放已分配的首地址为 pa 的物理页面,并更新空闲链表。

  1. kernel/proc.c:allocproc
static struct proc *allocproc(void);

遍历进程数组 proc,寻找未被使用的 struct proc。若找到,则初始化其状态,为创建一个新的页表,并返回指向它的指针;否则,返回 0(空指针)。

  1. kernel/proc.c:freeproc
static void freeproc(struct proc *p);

释放与进程 p 相关的数据的内存空间,并清空 pstruct proc 的所有信息。

  1. kernel/vm.c:mappages
int mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm);

在页表 pagetrable 中创建从起始虚拟地址 va 到起始物理地址 pa 的页表项映射,页表项的 flags 位的访问权限部分设置为 perm,其中大小为 size,将 size 分为若干页,为这些页面创建 va + i * PGSIZE -> pa + i * PGSIZEi 代表页面的编号)的映射。

  1. kernel/vm.c:uvmunmap
void uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free);

pagetable 中移除从虚拟地址 va 开始的 npages 个页表项。可指定 do_free 的值,若不为 0,则在移除页表项的同时,释放页表项映射 va -> papa 指向的内存空间。

分析完几个关键函数之后,思路就比较清晰了:

  1. struct proc 结构体添加 struct usyscall *usc 段。
  2. allocproc() 中为 usc 分配物理内存,并对其赋值:p->usc->pid = p->pid;
if ((p->usc = (struct usyscall *)kalloc()) == 0) {
	freeproc(p);
	release(&p->lock);
	return 0;
}
p->usc->pid = p->pid;
  1. freeproc() 中释放 usc 的物理内存。
if (p->usc)
	kfree((void*)p->usc);
p->usc = 0;
  1. proc_pagetable() 中使用 mappages 插入虚拟地址 USYSCALL 的页表项。
if (mappages(pagetable, USYSCALL, PGSIZE, (uint64)(p->usc), PTE_R | PTE_U) < 0) {  
	uvmunmap(pagetable, TRAMPOLINE, 1, 0);
	uvmunmap(pagetable, TRAPFRAME, 1, 0);
	uvmfree(pagetable, 0);
	return 0;
}
  1. 最后,非常容易忽视的,在 proc_freepagetable() 中删除虚拟地址 USYSCALL 的页表项。
uvmunmap(pagetable, USYSCALL, 1, 0);

问题

接下来讲讲我在本题遇到的几个问题。

问题 1:

在这里插入图片描述

原因:p->usc->pid = p->pid 放在分配物理内存之前,导致空指针解引用。

问题 2:

在这里插入图片描述

**原因:**未在 proc_freepagetable() 中解除 USYSCALL 的页表项映射,也就是上面提到的容易忽视的第 5 点。

代码

diff --git a/kernel/proc.c b/kernel/proc.c
index 22e7ce4..5fc573f 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -127,6 +127,14 @@ found:
     return 0;
   }
 
+  // here
+  if ((p->usc = (struct usyscall *)kalloc()) == 0) {
+	freeproc(p);
+	release(&p->lock);
+	return 0;
+  }
+  p->usc->pid = p->pid;
+
   // An empty user page table.
   p->pagetable = proc_pagetable(p);
   if(p->pagetable == 0){
@@ -153,6 +161,9 @@ freeproc(struct proc *p)
   if(p->trapframe)
     kfree((void*)p->trapframe);
   p->trapframe = 0;
+  if (p->usc) // here
+	kfree((void*)p->usc);
+  p->usc = 0;
   if(p->pagetable)
     proc_freepagetable(p->pagetable, p->sz);
   p->pagetable = 0;
@@ -195,6 +206,14 @@ proc_pagetable(struct proc *p)
     uvmfree(pagetable, 0);
     return 0;
   }
+  
+  // here
+  if (mappages(pagetable, USYSCALL, PGSIZE, (uint64)(p->usc), PTE_R | PTE_U) < 0) {  
+	uvmunmap(pagetable, TRAMPOLINE, 1, 0);
+	uvmunmap(pagetable, TRAPFRAME, 1, 0);
+	uvmfree(pagetable, 0);
+	return 0;
+  }
 
   return pagetable;
 }
@@ -206,6 +225,7 @@ proc_freepagetable(pagetable_t pagetable, uint64 sz)
 {
   uvmunmap(pagetable, TRAMPOLINE, 1, 0);
   uvmunmap(pagetable, TRAPFRAME, 1, 0);
+  uvmunmap(pagetable, USYSCALL, 1, 0); // here
   uvmfree(pagetable, sz);
 }
 
diff --git a/kernel/proc.h b/kernel/proc.h
index f6ca8b7..d25a729 100644
--- a/kernel/proc.h
+++ b/kernel/proc.h
@@ -82,6 +82,8 @@ struct trapframe {
 
 enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };
 
+struct usyscall;
+
 // Per-process state
 struct proc {
   struct spinlock lock;
@@ -105,4 +107,7 @@ struct proc {
   struct file *ofile[NOFILE];  // Open files
   struct inode *cwd;           // Current directory
   char name[16];               // Process name (debugging)
+  
+  // here
+  struct usyscall *usc;
 };

Print a page table

思路

仿照 freewalk 函数的写法,递归查找所有有效的页表项,并根据题干要求打印相关信息。涉及的内容较少,如果认真把上面提到的几个关键函数理清楚,并且理解了多级页表的机制,写起来还是比较轻松的,流程如下:

遍历当前页表中的所有页表项,如果页表项有效(flags 的有效位为 1),则将该页表项转换为物理地址向下递归搜索。需要注意的是在递归查找到第 3 级页表时,就不能继续向下递归了,此时得到的 pa 就是进行虚实地址转换后的物理地址。

代码

diff --git a/kernel/defs.h b/kernel/defs.h
index 3564db4..d169300 100644
--- a/kernel/defs.h
+++ b/kernel/defs.h
@@ -170,6 +170,7 @@ uint64          walkaddr(pagetable_t, uint64);
 int             copyout(pagetable_t, uint64, char *, uint64);
 int             copyin(pagetable_t, char *, uint64, uint64);
 int             copyinstr(pagetable_t, char *, uint64, uint64);
+void            vmprint(pagetable_t); // here
 
 // plic.c
 void            plicinit(void);
diff --git a/kernel/exec.c b/kernel/exec.c
index d62d29d..89f3d74 100644
--- a/kernel/exec.c
+++ b/kernel/exec.c
@@ -115,6 +115,11 @@ exec(char *path, char **argv)
   p->trapframe->epc = elf.entry;  // initial program counter = main
   p->trapframe->sp = sp; // initial stack pointer
   proc_freepagetable(oldpagetable, oldsz);
+  
+  // here
+  if(p->pid == 1) {
+	vmprint(p->pagetable);
+  }
 
   return argc; // this ends up in a0, the first argument to main(argc, argv)
 
diff --git a/kernel/vm.c b/kernel/vm.c
index d5a12a0..23eeec9 100644
--- a/kernel/vm.c
+++ b/kernel/vm.c
@@ -432,3 +432,25 @@ copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
     return -1;
   }
 }
+
+// here
+void vmprint_recur(pagetable_t pagetable, int depth) {
+	for (int i = 0; i < 512; ++i) {
+		pte_t pte = pagetable[i];
+		if (pte & PTE_V) { // pte is valid
+			for (int j = 0; j < depth; ++j) {
+				printf(" ..");
+			}
+			uint64 child = PTE2PA(pte);
+			printf("%d: pte %p pa %p\n", i, pte, child);
+			if (depth < 3) {
+				vmprint_recur((pagetable_t)child, depth + 1);	
+			}
+		}
+	}
+}
+
+void vmprint(pagetable_t pagetable) {
+	printf("page table %p\n", pagetable);
+	vmprint_recur(pagetable, 1);
+}

Detecting which pages have been accessed

思路

题目要求实现一个系统调用 sys_pgaccess(),获取指定虚拟页面的最近被访问信息

算是一个大杂烩的题,把 Lab System calls 的内容和 pagetable 结合起来,不要被 hard 难度标签吓到了,只要前面的 Lab 全都认真完成,再运用一些位运算的技巧,本题其实并不 “hard”。

所有的系统调用需要的声明已经实现添加好了,我们只需要关注 sys_pgaccess() 的实现即可,基本流程如下:

  1. 和 Lab System calls 一样,使用 argint()argaddr() 获取用户空间传递的参数:baselenmask
  2. 函数体内定义一个 kmask,作为 mask 的缓冲区。
  3. 从地址 base 开始遍历连续的 len 的页面,获取该页面的页表项 pte,根据 pte 的访问位对 kmask 进行置位,注意不要忘了每次遍历后将 pte 的访问位置 0。
  4. 遍历完成后,使用 copyout()kmask 的数据存入用户空间 mask 处。

有一个值得注意的问题,根据提示:

It’s okay to set an upper limit on the number of pages that can be scanned.

可以设定一个最大扫描范围,这主要根据 kmask 的数据类型而定,这里我选择使用 long 类型,那么最大扫描范围自然就是 64(long 类型为 8 字节大小,64 bit)。

同时,在对 kmask 操作时,可以运用一些位运算的技巧:

首先可以将 kmask 置为 0(二进制位全为 0),如果页面 i 的访问位为 1,则使用 kmask |= (1 << i),将 kmask 第 i 位置为 1 而不影响其它位(0 | 0 = 0; 1 | 0 = 0)。

要清除 pte 的访问位,可使用 *pte &= ~PTE_A,其中 PTE_A = 1L << 6,即访问位为 1,其它位都为 0,取反后,访问位为 0,其它位都为 1,与其进行按位与运算可将访问位置为 0,而不影响其它位(0 & 1 = 0; 1 & 1 = 1)。

问题

问题 1:

在这里插入图片描述

**原因:**比较坑的一个问题,原因是 kernel/defs.h 中没有 walk 函数声明,需要手动添加。

代码

diff --git a/kernel/defs.h b/kernel/defs.h
index d169300..53f1f88 100644
--- a/kernel/defs.h
+++ b/kernel/defs.h
@@ -171,6 +171,7 @@ int             copyout(pagetable_t, uint64, char *, uint64);
 int             copyin(pagetable_t, char *, uint64, uint64);
 int             copyinstr(pagetable_t, char *, uint64, uint64);
 void            vmprint(pagetable_t); // here
+pte_t 			*walk(pagetable_t, uint64, int);
 
 // plic.c
 void            plicinit(void);
diff --git a/kernel/riscv.h b/kernel/riscv.h
index 1691faf..6b130fe 100644
--- a/kernel/riscv.h
+++ b/kernel/riscv.h
@@ -343,6 +343,7 @@ sfence_vma()
 #define PTE_W (1L << 2)
 #define PTE_X (1L << 3)
 #define PTE_U (1L << 4) // 1 -> user can access
+#define PTE_A (1L << 6) // access bit
 
 // shift a physical address to the right place for a PTE.
 #define PA2PTE(pa) ((((uint64)pa) >> 12) << 10)
diff --git a/kernel/sysproc.c b/kernel/sysproc.c
index 3bd0007..359847c 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -81,6 +81,36 @@ int
 sys_pgaccess(void)
 {
   // lab pgtbl: your code here.
+  struct proc *p = myproc();
+  void *base, *mask;
+  long kmask; // buffer
+  int len;
+  pte_t *pte;
+  
+  if (argaddr(0, (uint64 *)&base) < 0) {
+	return -1;
+  }
+  if (argint(1, &len) < 0 || len > 64) { // page limited to 64
+	return -1;
+  }
+  if (argaddr(2, (uint64 *)&mask) < 0) {
+	return -1;
+  }
+  
+  kmask = 0L; // initialize bitmask to zero
+  for (int i = 0; i < len; ++i) {
+	uint64 va = (uint64)(base + i * PGSIZE);
+	pte = walk(p->pagetable, va, 0);
+	if (*pte & PTE_A) { // pte was accessed recently
+	  kmask |= (1 << i);
+	}
+	*pte &= ~PTE_A; // clear access bit
+  }
+  
+  if (copyout(p->pagetable, (uint64)mask, (char *)&kmask, 8) < 0) {
+	return -1;
+  }
+
   return 0;
 }
 #endif

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Open AI Stream Completion Set Variable Inside Function PHP With Openai-php SDK

题意&#xff1a;使用 OpenAI 的 PHP SDK&#xff08;例如 openai-php&#xff09;来在函数内部设置和完成一个流&#xff08;stream&#xff09;相关的变量 问题背景&#xff1a; How to set variable inside this openai-php sdk function in stream completion ? I am usi…

【笔记】手工部署之linux中开放已安装的mysql与tomcat端口

在需要打包的springboot项目中输入mvn clean package 在target下面获得jar包 进入linux中你想要该jar包存在的位置 将jar包上传至linux中 此时在浏览器中输入linux的ip地址&#xff1a;端口号/mapping路径为404 故&#xff1a; 在linux中另开一个标签页 检查mysql和tomcat已…

JavaFX布局-BorderPane

JavaFX布局-BorderPane 实现方式Java实现FXML实现 综合案例 将容器空间分成五个区域&#xff1a;顶部&#xff08;Top&#xff09;、底部&#xff08;Bottom&#xff09;、左侧&#xff08;Left&#xff09;、右侧&#xff08;Right&#xff09;和中心&#xff08;Center&#…

Java案例找素数(三种方法)

目录 一&#xff1a;问题&#xff1a; 二&#xff1a;思路分析&#xff1a; 三&#xff1a;具体代码&#xff1a; 四&#xff1a;运行结果&#xff1a; 一&#xff1a;问题&#xff1a; 二&#xff1a;思路分析&#xff1a; 三&#xff1a;具体代码&#xff1a; Ⅰ&#xf…

03 _ 类型基础(2):动态类型与静态类型

静态类型语言与动态类型语言 通俗定义 静态类型语言&#xff1a;在编译 阶段确定所有变量的类型 动态类型语言&#xff1a;在执行阶段确定所有变量的类型 Javascript 与 C 对比 静态类型与动态类型对比 其他定义 强类型语言&#xff1a;不允许程序在发生错误后继续执行 语…

【STM32】温湿度采集与OLED显示

一、任务要求 1. 学习I2C总线通信协议&#xff0c;使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集&#xff0c;并将采集的温度-湿度值通过串口输出。 任务要求&#xff1a; 1&#xff09;解释什么是“软件I2C”和“硬件I2C”&#xff1f;&#xff08;阅读野火配…

视频号视频怎么下载保存到手机,视频号视频如何下载到电脑本地

在数字化浪潮的推动下&#xff0c;视频号成为了我们获取信息、分享生活的重要平台。但有时候&#xff0c;我们遇到一些精彩的内容&#xff0c;想要保存下来以便日后观看&#xff0c;却发现视频号并不提供直接的下载功能。下面我就来为大家详细介绍视频号视频下载的方法&#xf…

Datax快速使用之牛刀小试

前言 一次我发现业务他们在用 datax数据同步工具&#xff0c;我尤记得曾经 19 年使用过&#xff0c;并且基于当时的版本还修复了个 BUG并且做了数据同步管道的集成开发。没想到时间过的飞快&#xff0c;业务方基于海豚调度 2.0.6 的版本中有在使用&#xff0c;由于业务方还没有…

大促前夕即高点,综合电商平台的“稀缺”魔法正在消失?

新一期618大促早已结束良久了&#xff0c;但似乎其产生的余韵却仍旧未消散。 从最直观的资本市场走势来看&#xff0c;自这一波618大促陆续开展之后&#xff0c;包括京东、阿里巴巴、拼多多等港美股股价就一改此前的上行态势&#xff0c;持续下滑至今。 事实上&#xff0c;早…

【计算机网络期末复习】例题汇总(一)

重点例题选择填空简答题与传输媒体的接口的特性重点 计算机网络的性能指标计算机网络体系结构例题 选择

【Linux】线程id与互斥(线程三)

上一期我们进行了线程控制的了解与相关操作&#xff0c;但是仍旧有一些问题没有解决 本章第一阶段就是解决tid的问题&#xff0c;第二阶段是进行模拟一个简易线程库&#xff08;为了加深对于C库封装linux原生线程的理解&#xff09;&#xff0c;第三阶段就是互斥。 目录 线程id…

【解锁未来:深入了解机器学习的核心技术与实际应用】

解锁未来&#xff1a;深入了解机器学习的核心技术与实际应用 &#x1f48e;1.引言&#x1f48e;1.1 什么是机器学习&#xff1f; &#x1f48e;2 机器学习的分类&#x1f48e;3 常用的机器学习算法&#x1f48e;3.1 线性回归&#xff08;Linear Regression&#xff09;&#x1…

【PYG】Planetoid中边存储的格式,为什么打印前十条边用edge_index[:, :10]

edge_index 是 PyTorch Geometric 中常用的表示图边的张量。它通常是一个形状为 [2, num_edges] 的二维张量&#xff0c;其中 num_edges 表示图中边的数量。每一列表示一条边&#xff0c;包含两个节点的索引。 实际上这是COO存储格式&#xff0c;官方文档里也有写&#xff0c;…

爬虫逆向实战(41)-某巢登陆(AES、MD5、RSA、滑块验证码)

一、数据接口分析 主页地址&#xff1a;某巢 1、抓包 通过抓包可以发现在登录时&#xff0c;网站首先请求captcha/querySlideImage/来获取滑块验证码的图片&#xff0c;然后请求captcha/checkCode/接口来验证滑块验证码。滑块验证码校验成功后&#xff0c;请求noshiro/getPu…

高性能LDO电路设计,有配套文档

内容&#xff1a; 1、电路文件&#xff08;有仿真状态&#xff09;和PDK&#xff08;TSMC180&#xff09; 2、配套仿真结果文档讲解6页 3、参考资料三篇 指标&#xff1a; LDO 温度系数1.09ppm LDO 环路增益在 64.3dB&#xff0c;相位裕度在 66&#xff0c;系统稳定。 LDO 最大…

抛弃 Neofetch?众多优秀替代方案等你体验!

目录 抛弃 Neofetch&#xff1f;众多优秀替代方案等你体验Neofetch 的替代品FastfetchscreenFetchmacchina 抛弃 Neofetch&#xff1f;众多优秀替代方案等你体验 NeoFetch 是用 Bash 3.2 编写的命令行系统信息工具&#xff0c;该项目的主要开发人员已将 GitHub 存储库存档&…

【漏洞复现】和丰多媒体信息发布系统 QH.aspx 任意文件上传漏洞

0x01 产品简介 和丰多媒体信息发布系统也称数字标牌&#xff08;Digital Signage&#xff09;&#xff0c;是指通过大屏幕终端显示设备&#xff0c;发布商业、财经和娱乐信息的多媒体专业视听系统&#xff0c;常被称为除纸张媒体、电台、电视、互联网之外的“第五媒体”。该系…

民生银行收大额罚单:信用卡中心一同被罚,管理层如何施救?

民生银行的2024年&#xff0c;再添变故。 近日&#xff0c;国家金融监管总局宁波分局公布了数则行政处罚信息公开表。内容显示&#xff0c;民生银行&#xff08;SH:600016、HK:01988&#xff09;宁波分行、民生银行信用卡中心宁波分中心因多项违法违规事实&#xff0c;共计被处…

【Git 学习笔记】Ch1.1 Git 简介 + Ch1.2 Git 对象

还是绪个言吧 今天整理 GitHub 仓库&#xff0c;无意间翻到了几年前自学 Git 的笔记。要论知识的稳定性&#xff0c;Git 应该能挤进前三——只要仓库还在&#xff0c;理论上当时的所有开发细节都可以追溯出来。正好过段时间会用到 Git&#xff0c;现在整理出来就当温故知新了。…