MIT6.S081Lab3:page tables

2023-12-05 15:04
文章标签 page mit6 tables s081lab3

本文主要是介绍MIT6.S081Lab3:page tables,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

准备环境

按照官方指南准备环境,即

  $ git fetch$ git checkout pgtbl$ make clean

如果提示之前的修改没有提交的话可以把之前的修改去掉(备份一份)。

1.Print a page table

编写vmprint函数,接收一个pagetable_t作为参数,按照格式打印页表内容,对于这个功能,主要是打印第一个进程的页表。

这个lab并不难,难度也是easy,首先搞懂要打印什么,就是打印页表的pte内容,然后对应的物理地址,物理地址可以通过PTE2PA得到,PTE2PA就是右移十位把标志位去掉,然后左移十二位得到物理地址。然后搞懂freewalk函数怎么写的,我们模仿写就行了。只是要按格式打印,即不同级的页表格式不同,所以要加一个表示级数的参数,不过vmprint函数只能有一个参数,所以额外写一个辅助函数,整体如下:

void vmprint(pagetable_t pagetable) {printf("page table %p\n", pagetable);helpvmprint(pagetable, 1);
}void helpvmprint(pagetable_t pagetable, int deep) {if (deep > 3) {return;}for (int i = 0; i < 512; ++i) {pte_t pte = pagetable[i];if (pte & PTE_V) {uint64 child = PTE2PA(pte);if (deep == 1) {printf("..");} else if (deep == 2) {printf(".. ..");} else if (deep == 3) {printf(".. .. ..");}printf("%d: pte %p pa %p\n", i, pte, child);helpvmprint((pagetable_t)child, deep + 1);}}
}

在kernel/defs.h中加上这两个函数的声明

void            vmprint(pagetable_t);
void            helpvmprint(pagetable_t, int);

启动qemu可以看到

page table 0x0000000087f6e000
..0: pte 0x0000000021fda801 pa 0x0000000087f6a000
.. ..0: pte 0x0000000021fda401 pa 0x0000000087f69000
.. .. ..0: pte 0x0000000021fdac1f pa 0x0000000087f6b000
.. .. ..1: pte 0x0000000021fda00f pa 0x0000000087f68000
.. .. ..2: pte 0x0000000021fd9c1f pa 0x0000000087f67000
..255: pte 0x0000000021fdb401 pa 0x0000000087f6d000
.. ..511: pte 0x0000000021fdb001 pa 0x0000000087f6c000
.. .. ..510: pte 0x0000000021fdd807 pa 0x0000000087f76000
.. .. ..511: pte 0x0000000020001c0b pa 0x0000000080007000

执行命令

sudo ./grade-lab-pgtbl  pte print

得到

make: 'kernel/kernel' is up to date.
== Test pte printout == pte printout: OK (1.0s)

编写成功!

2.A kernel page table per process

内核只有一个页表,现在要让每个线程都有一个内核页表副本,并且在内核态运行的时候使用这个副本进行虚拟地址到内核地址的转换。

总的来说分三步完成

step1

初始化每个线程的内核页表副本,这个副本要和内核页表相同,所以仿照kvminit写一个函数生成一个与内核页表相同的页表,如下:

pagetable_t
kvmprocessinit()
{pagetable_t pagetable = (pagetable_t) kalloc();memset(pagetable, 0, PGSIZE);// uart registerskvmprocessmap(pagetable, UART0, UART0, PGSIZE, PTE_R | PTE_W);// virtio mmio disk interfacekvmprocessmap(pagetable, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);// CLINTkvmprocessmap(pagetable, CLINT, CLINT, 0x10000, PTE_R | PTE_W);// PLICkvmprocessmap(pagetable, PLIC, PLIC, 0x400000, PTE_R | PTE_W);// map kernel text executable and read-only.kvmprocessmap(pagetable, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);// map kernel data and the physical RAM we'll make use of.kvmprocessmap(pagetable, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);// map the trampoline for trap entry/exit to// the highest virtual address in the kernel.kvmprocessmap(pagetable, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);return pagetable;
}

allocproc中调用这个函数,给每个进程一个内核页表副本(主要proc结构体中要加一个元素指向内核页表副本)。

然后是内核栈需要在现在这个副本有对应的映射,在procinit函数里面有对应的内核栈映射代码,我们可以把这个代码迁移到allocproc中,但是我们在virtio_disk_rw函数中翻译内核栈虚拟地址的时候使用的kvmpa这个函数,这个函数用的是内核页表来翻译,所以内核页表还是要有各个进程的内核栈映射,所以在procinit里面的代码不改,我们将内核栈对应的虚拟地址和物理地址之间的映射直接存到每个进程的内核页表副本中,也是在allocproc中完成,所以现在的allocproc为:

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();// Allocate a trapframe page.if((p->trapframe = (struct trapframe *)kalloc()) == 0){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;}// 给每个进程分配内核页表副本p->kernelpage = kvmprocessinit();if (p->kernelpage == 0) {freeproc(p);release(&p->lock);return 0;}// 之前是procinit里面把每个进程的内核栈映射在内核页表里面对应好了,现在每个进程都有自己的内核页表了// 所以需要重新给自己的内核页表映射栈kvmprocessmap(p->kernelpage, p->kstack, kvmpa(p->kstack), PGSIZE, PTE_R | PTE_W);// 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;
}

step2

切换进程的时候也要切换satp寄存器,把要运行的进程对应的内核页表副本物理地址加载到satp中去方便使用,直接在scheduler中更改,要在上下文切换之前切换satp寄存器,否则切换了上下文运行的就是另外的指令了,不用在执行完进程后调用kvminithart函数,因为除了内核栈内核页表副本和内核页表是相同的,scheduler也没有用到进程的内核栈,scheduler如下:

void
scheduler(void)
{struct proc *p;struct cpu *c = mycpu();c->proc = 0;for(;;){// Avoid deadlock by ensuring that devices can interrupt.intr_on();int found = 0;for(p = proc; p < &proc[NPROC]; p++) {acquire(&p->lock);if(p->state == RUNNABLE) {// Switch to chosen process.  It is the process's job// to release its lock and then reacquire it// before jumping back to us.p->state = RUNNING;c->proc = p;// 切换satp寄存器w_satp(MAKE_SATP(p->kernelpage));sfence_vma();swtch(&c->context, &p->context);// Process is done running for now.// It should have changed its p->state before coming back.c->proc = 0;found = 1;}release(&p->lock);}if (found == 0) {kvminithart();}
#if !defined (LAB_FS)if(found == 0) {intr_on();asm volatile("wfi");}
#else;
#endif}
}

step3

最后就是freeproc的时候也要释放内核页表副本所占的页表了,注意是副本所占的页表,而不是副本页表指向的页面,所有副本页表和内核页表指向的页面是相同的,不能因为某一个进程释放那些页面,释放的方式可以参考freewalk,不过我们不需要用uvmunmap先清除映射,直接释放有效的页面即可,参考freewalk编写freeprocesswalk,如下:

void freeprocesswalk(pagetable_t pagetable) {// there are 2^9 = 512 PTEs in a page table.for(int i = 0; i < 512; i++){pte_t pte = pagetable[i];if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0) {uint64 child = PTE2PA(pte);freeprocesswalk((pagetable_t)child);pagetable[i] = 0;}}kfree((void*)pagetable);
}

并且在freeproc中使用它。

在命令行中使用命令

sudo ./grade-lab-pgtbl usertests

验证编写是否正确

出现

make: 'kernel/kernel' is up to date.
== Test usertests == (122.5s) 
== Test   usertests: copyin == usertests: copyin: OK 
== Test   usertests: copyinstr1 == usertests: copyinstr1: OK 
== Test   usertests: copyinstr2 == usertests: copyinstr2: OK 
== Test   usertests: copyinstr3 == usertests: copyinstr3: OK 
== Test   usertests: sbrkmuch == usertests: sbrkmuch: OK 
== Test   usertests: all tests == usertests: all tests: OK 

编写成功!

3.Simplify copyin/copyinstr

首先是要搞清楚在什么地方需要将用户页表的内容复制到内核页表来。因为第一个进程是后面所有进程的祖先,所以我们首先把第一个进程用户页表的内容复制过来,然后再页表内容发生改变和产生新进程的地方重新复制。即四个地方:userinit,fork,sbrk,exec。

首先是复制页表的函数,其实这个函数还是很好编写的,只要会用walk函数,如下:

int
copypages(pagetable_t src, pagetable_t dst, uint64 begin, uint64 end)
{uint64 va;pte_t *srcpte;pte_t *dstpte;begin = PGROUNDDOWN(begin);for (va = begin; va < end; va += PGSIZE) {if ((srcpte = walk(src, va, 0)) == 0) {return -1;}if (!(*srcpte & PTE_V)) {return -1;}if ((dstpte = walk(dst, va, 1)) == 0) {return -1;}*dstpte = (*srcpte) & (~PTE_U);}return 0;
}

然后是userinit

void
userinit(void)
{struct proc *p;p = allocproc();initproc = p;// allocate one user page and copy init's instructions// and data into it.uvminit(p->pagetable, initcode, sizeof(initcode));p->sz = PGSIZE;// prepare for the very first "return" from kernel to user.p->trapframe->epc = 0;      // user program counterp->trapframe->sp = PGSIZE;  // user stack pointersafestrcpy(p->name, "initcode", sizeof(p->name));p->cwd = namei("/");p->state = RUNNABLE;// 添加一行即可copypages(p->pagetable, p->kernelpage, 0, p->sz);release(&p->lock);
}

fork:

int
fork(void)
{int i, pid;struct proc *np;struct proc *p = myproc();// Allocate process.if((np = allocproc()) == 0){return -1;}// Copy user memory from parent to child.if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){freeproc(np);release(&np->lock);return -1;}np->sz = p->sz;np->parent = p;// copy saved user registers.*(np->trapframe) = *(p->trapframe);// Cause fork to return 0 in the child.np->trapframe->a0 = 0;// increment reference counts on open file descriptors.for(i = 0; i < NOFILE; i++)if(p->ofile[i])np->ofile[i] = filedup(p->ofile[i]);np->cwd = idup(p->cwd);safestrcpy(np->name, p->name, sizeof(p->name));pid = np->pid;np->state = RUNNABLE;if (copypages(np->pagetable, np->kernelpage, 0, np->sz) == -1) {return -1;}release(&np->lock);return pid;
}

exec

if(p->pid==1) vmprint(p->pagetable);if (copypages(p->pagetable, p->kernelpage, 0, p->sz) == -1) {goto bad;}return argc; 

最需要注意的就是sbrk,其对应的函数是growproc,在这里面要修改三处地方,一个是增长后的内存地址不能超过PLIC,一个是增长时复制页表,还有一个是减少内存时直接取消内核页表匹配相应的部分,如下

int
growproc(int n)
{uint sz;struct proc *p = myproc();sz = p->sz;if(n > 0){if(sz + n > PLIC || (sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {return -1;}if (copypages(p->pagetable, p->kernelpage, sz - n, sz) == -1) {return -1;}} else if(n < 0){sz = uvmdealloc(p->pagetable, sz, sz + n);uvmunmap(p->kernelpage, PGROUNDUP(sz), (PGROUNDUP(p->sz) - PGROUNDUP(sz)) / PGSIZE, 0);}p->sz = sz;return 0;
}

然后将copyin,copyinstr的内容改为对copyin_new和copyinstr_new的调用

int
copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{return copyin_new(pagetable, dst, srcva, len);
}int
copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
{return copyinstr_new(pagetable, dst, srcva, max);
}

最后用sudo ./grade-lab-pgtbl usertestssudo make grade测试即可。

这篇关于MIT6.S081Lab3:page tables的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/458049

相关文章

vue中路由管理(vue-router,page)使用总结

现在的项目都以模块化的方式去开发,所以在这样的开发模式下,如何更好的去管理路由是开发中所需要考虑的重点,幸运的是当前的开发中已经有了成熟的中间件去管理,我们只需要用就可以了 下面是我在学习vue-router的时候在原来基础上修改出来的demo,也是为了有助于对vue-router的理解 首先理解下vue官网的一个示例demo https://jsfiddle.net/yyx990803/x

【0323】Postgres内核之 hash table sequentially search(seq_scan_tables、num_seq_scans)

0. seq scan tracking 我们在这里跟踪活跃的 hash_seq_search() 扫描。 需要这种机制是因为如果扫描正在进行时发生桶分裂(bucket split),它可能会访问两次相同的条目,甚至完全错过某些条目(如果它正在访问同一个分裂的桶中的条目)。因此,如果正在向表中插入数据,我们希望抑制桶分裂。 在当前的使用中,这种情况非常罕见,因此只需将分裂推迟到下一次插入即可。

编程语言之争:Rust 社区活跃开发者 Ed Page 谈 Rust 与 C++ 的未来

作者 | Annie Xu 采访 | CSDN&Boolan 首席顾问 卢威 责编 | 何苗 出品丨GOSIM 开源创新汇 你最 pick 哪种编程语言?C++、Rust,还是 Python? Ed Page 从事编程行业十几年,见证了不同编程语言的兴衰史。从 C++标准版本 C++98 的诞生,到 Jave、D、Go 等编程语言的崛起与发展,并跃跃欲试想挑战 C++ 的江湖

Page directive: illegal to have multiple occurrences of contentType with different values (x,X)之解

Question: Page directive: illegal to have multiple occurrences of contentType with different values (old: text/html; charset=utf-8, new: text/html;charset=UTF-8) Analysis: 出现这个的原因是这两个jsp的contentT

Kafka【五】Buffer Cache (缓冲区缓存)、Page Cache (页缓存)和零拷贝技术

【1】Buffer Cache (缓冲区缓存) 在Linux操作系统中,Buffer Cache(缓冲区缓存)是内核用来优化对块设备(如磁盘)读写操作的一种机制(故而有一种说法叫做块缓存)。尽管在较新的Linux内核版本中,Buffer Cache和Page Cache已经被整合在一起,但在理解历史背景和功能时,了解Buffer Cache仍然很有帮助。 Buffer Cache 的历史和定义

HarmonyOS开发详解(四)——鸿蒙Page Ability功能及UI界面开发详解

HarmonyOS里面的界面通过Page Ability和Java UI一起来实现;讲述Page Ability就离不开Ability。在HarmonyOS里面把各种具备的能力进行抽象,叫做Ability;Ability是程序重要的组成部分;鸿蒙的各种功能、能力都是通过Ability展现,可以说开发鸿蒙应用就离不开Ability。本文将详细讲解Page Ability相关的应用和案

微信小程序开发(四)注册页面 page.js

注册小程序时我们在app.json配置文件里通过pages注册过各个页面 "pages" :[ "pages/index/index" , "pages/logs/logs" ], 每一个页面对应自己的一系列相关文件如index,包括 index.wxml(view层)、index.js(逻辑层contral)、index.wxss(样式文件)

asp.net中Page.ClientScript.RegisterStartupScript用法小结

来源:http://blog.csdn.net/qiujialongjjj/article/details/6680252 //ASP.NET后台页面跳转 Page.ClientScript.RegisterStartupScript(Page.GetType(), “”, ““); //后台弹出确定框 ClientScript.RegisterStartupScript(GetType(

uniapp微信小程序page-container导致滚动失效/向下偏移,返回上一页/左滑取消返回上一页

项目场景: 提示:这里简述项目相关背景: 前提:  使用uniapp来做的微信小程序 有两级tab页面   要求手机的两边往中间滑时 要求(调用手机的物理返回按钮--有震动感) 返回上一页。具体如下图箭头所示: 问题描述 提示:这里描述项目中遇到的问题: uniapp中有钩子函数 onBackPress(e) {} 可以返回上一页面(代码如下)但是此方法有平台差异,以为微

DBA_TABLES ALL_TABLES USER_TABLES

DBA_TABLES ALL_TABLES USER_TABLES DBA_TABLES >= ALL_TABLES >= USER_TABLES DBA_TABLES意为DBA拥有的或可以访问的所有的关系表。 ALL_TABLES意为某一用户拥有的或可以访问的所有的关系表。 USER_TABLES意为某一用户所拥有的所有的关系表。 由上可知,当某一用户本身就为数据库DBA时,DBA_