当前位置: 首页 > news >正文

(undone) MIT6.S081 2023 学习笔记 (Day4: LAB3 page tables)

LAB 网页:https://pdos.csail.mit.edu/6.S081/2023/labs/pgtbl.html


任务1:Speed up system calls

根据网页,操作系统可以通过把部分数据放入用户空间的页表,来使得部分系统调用不用进入内核空间,从而提高速度。我们的第一个任务是:对 getpid() 系统调用实现这个优化(通过对页表插入映射的方式)。

以下是作业要求:
1.当一个进程被创建的时候,可以在 USYSCALL 位置插入一个只读的页。在页的开头,存入一个 usyscall 结构体,并且使用当前进程的 pid 初始化它。在实验中,ugetpid() 已经在用户空间中被实现,它会使用 USYSCALL 的映射。只要 ugetpid() 能通过测试,这个 lab 就满分。

暗示:
1.Choose permission bits that allow userspace to only read the page.
2.There are a few things that need to be done over the lifecycle of a new page. For inspiration, understand the trapframe handling in kernel/proc.c.

思考题:
Which other xv6 system call(s) could be made faster using this shared page? Explain how.

OK,根据作业要求,我们先来看看这个 ugetpid() 的实现,相关的测试代码位于 user/pgtbltest.c

void
err(char *why)
{printf("pgtbltest: %s failed: %s, pid=%d\n", testname, why, getpid());exit(1);
}void
ugetpid_test()
{int i;printf("ugetpid_test starting\n");testname = "ugetpid_test";// 执行 64 次 fork,判断 getpid() 是否等于 ugetpid()for (i = 0; i < 64; i++) {int ret = fork();if (ret != 0) {wait(&ret);if (ret != 0)exit(1);continue;}if (getpid() != ugetpid())err("missmatched PID");exit(0);}printf("ugetpid_test: OK\n");
}int
main(int argc, char *argv[])
{ugetpid_test();pgaccess_test();printf("pgtbltest: all tests succeeded\n");exit(0);
}

从代码来看,实际上就是重复调用 fork(),并且在子进程中调用 getpid() 和 ugetpid(),判断两者是否相等。

直接运行 make qemu,发现能执行 pgtbltest,阅读 Makefile,发现 pgtbltest 默认就被作为用户程序编译进磁盘。但是 pgtbltest 虽能执行,却会报错,报错如下:
在这里插入图片描述
在这里插入图片描述
根据手册,scause = 0xd 是 “Load page fault”,缺页错误。说明用户程序访问了一块自己并不拥有的内存。

我们仔细读读 ugetpid() 的实现,如下:

// User memory layout.
// Address zero first:
//   text
//   original data and bss
//   fixed-size stack
//   expandable heap
//   ...
//   USYSCALL (shared with kernel)
//   TRAPFRAME (p->trapframe, used by the trampoline)
//   TRAMPOLINE (the same page as in the kernel)#define TRAPFRAME (TRAMPOLINE - PGSIZE)
#define USYSCALL (TRAPFRAME - PGSIZE)struct usyscall {int pid;  // Process ID
};int
ugetpid(void)
{struct usyscall *u = (struct usyscall *)USYSCALL;return u->pid;
}

结构体 usyscall 其实就是一个 pid 整型,USYSCALL 是一个地址。从代码来看,ugetpid() 其实就是访问了一块虚拟内存。

现在思路大致明了了:在内核里,分配进程的时候,把该进程 pid 映射到该进程的页表上。

先来看分配进程的函数:kernel/proc.c : allocproc()

// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
static struct proc*
allocproc(void)
{struct proc *p;for(p = proc; p < &proc[NPROC]; p++) {acquire(&p->lock);if(p->state == UNUSED) {goto found;} else {release(&p->lock);}}return 0;found:p->pid = allocpid();p->state = USED;// Allocate a trapframe page.if((p->trapframe = (struct trapframe *)kalloc()) == 0){freeproc(p);release(&p->lock);return 0;}// An empty user page table.p->pagetable = proc_pagetable(p);if(p->pagetable == 0){freeproc(p);release(&p->lock);return 0;}// Set up new context to start executing at forkret,// which returns to user space.memset(&p->context, 0, sizeof(p->context));p->context.ra = (uint64)forkret;p->context.sp = p->kstack + PGSIZE;return p;
}

可以看到,函数里分配了 pid,以及页表。我们现在进入分配页表的 proc_pagetable() 函数看看

// Create a user page table for a given process, with no user memory,
// but with trampoline and trapframe pages.
pagetable_t
proc_pagetable(struct proc *p)
{pagetable_t pagetable;// An empty page table.pagetable = uvmcreate();if(pagetable == 0)return 0;// map the trampoline code (for system call return)// at the highest user virtual address.// only the supervisor uses it, on the way// to/from user space, so not PTE_U.if(mappages(pagetable, TRAMPOLINE, PGSIZE,(uint64)trampoline, PTE_R | PTE_X) < 0){uvmfree(pagetable, 0);return 0;}// map the trapframe page just below the trampoline page, for// trampoline.S.if(mappages(pagetable, TRAPFRAME, PGSIZE,(uint64)(p->trapframe), PTE_R | PTE_W) < 0){uvmunmap(pagetable, TRAMPOLINE, 1, 0);uvmfree(pagetable, 0);return 0;}return pagetable;
}

可以看到这里使用 mappages 来映射用户页表。我们在这里加上 pid 的映射应该就行了。
我们依葫芦画瓢加上对 pid 的映射,如下:

#ifdef LAB_PGTBL// 把 USYSCALL 映射到用户页表,加速部分系统调用// 若出错,释放 TRAMPOLINE 和 TRAMPFRAME,再释放页表if(mappages(pagetable, USYSCALL, PGSIZE,(uint64)(&(p->pid)), PTE_R) < 0){uvmunmap(pagetable, TRAMPOLINE, 1, 0);uvmunmap(pagetable, TRAPFRAME, 1, 1);uvmfree(pagetable, 0);return 0;}
#endif

启动 make qemu,发现报错,如下:
在这里插入图片描述

这是在 freewalk 函数中 panic。我们启动 qemu-gdb,对 panic,打断点,可以看到函数调用栈如下:
在这里插入图片描述
a
a

TODO: here


TODO: here


http://www.mrgr.cn/news/75341.html

相关文章:

  • C++中的栈(Stack)和堆(Heap)
  • 开源 2 + 1 链动模式、AI 智能名片、S2B2C 商城小程序在用户留存与品牌发展中的应用研究
  • 数据结构Python版
  • 未来汽车新变革,智能表面浮出水面
  • 第三十三篇——用变化的眼光看最大值和最小值
  • 刷错学校了—河工大oj 1051- 1072 做题笔记
  • ASR 项目调试记录
  • 【蓝牙协议栈】【BLE】【IAS】蓝牙立即警报服务
  • 前端基础的讲解-JS(12)
  • 深入理解Flutter生命周期函数
  • SQLI LABS | Less-47 GET-Error Based-String-ORDER BY CLAUSE
  • 2024下半年软考架构师真题 回忆整理
  • 2024华为java面经
  • 2.5 以太网拓扑结构演变
  • SQL 连接(JOIN)的深入解析
  • 姓名改成商标名称,李子柒已成身份证名字!
  • 硬件工程师之电子元器件—二极管(5)之肖特基二极管
  • 英语中常用的短语搭配及规律
  • javassmmsyql医院管理的设计与实现87641-计算机毕业设计项目选题推荐(附源码)
  • Java面试要点19 - Java中设计抽象类的原则
  • 【C++动态规划 最长公共子序列】1035. 不相交的线|1805
  • python 编程 在 Matplotlib 中 默认预定的所有颜色,可以使用多种方法来指定颜色,包括预定义的颜色名称、十六进制颜色代码、
  • 自定义Element Plus主题
  • 2.什么是项目集管理
  • `node-gyp` 无法找到版本为 `10.0.19041.0` 的 Windows SDK
  • MudBlazor:基于Material Design风格开源且强大的Blazor组件库