Lab2:Page Tables

本 lab 的任务是理解 xv6 页表的实现。

参考文章:
xv6实验课程–页表(2021)
6.S081-2021FALL-Lab3:pgtbl
MIT6.S081-Lab3 Pgtbl: answer-pgybl.txt 的讲解

阅读指路:
xv6 book Chapter3
kern/memlayout.h: 记录内存布局
kern/vm.c: 虚拟内存管理
kernel/kalloc.c: 分配和释放物理内存

Speed up system calls (easy)√

一些操作系统 (例如Linux) 在特定的只读区域共享用户态和内核态数据,用于加速特定的系统调用。这消除了系统调用时的上下文切换。

目标

实现 getpid() 系统调用的优化。

方法

每个进程创建时,在 USYSCALL 处(memlayout.h 中定义的虚拟地址)映射一个只读的物理页, 在该页的起始处保存一个 struct usyscall ,初始化为当前进程的 PID
本次实验中,user/ulib.c 提供了 ugetpid() 函数,它会使用 USYSCALL 映射来获取进程的 PID

memlayout.h 部分代码:

// map the trampoline page to the highest address,
// in both user and kernel space.
#define TRAMPOLINE (MAXVA - PGSIZE)// map kernel stacks beneath the trampoline,
// each surrounded by invalid guard pages.
#define KSTACK(p) (TRAMPOLINE - (p)*2*PGSIZE - 3*PGSIZE)// 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)#ifdef LAB_PGTBL
#define USYSCALL (TRAPFRAME - PGSIZE)
struct usyscall {int pid;  // Process ID
};
#endif

ugetpid() 函数定义:

// [user/ulib.c]
#ifdef LAB_PGTBL
int
ugetpid(void)
{struct usyscall *u = (struct usyscall *)USYSCALL;return u->pid;
}
#endif

Hints:

  • kernel/proc.cproc_pagetable() 完成内存映射(mapping)
  • 用户权限设置为只读
  • kernel/vm.c中的mappages() 是很有用的函数
// Create PTEs for virtual addresses starting at va that refer to
// physical addresses starting at pa. va and size might not
// be page-aligned. Returns 0 on success, -1 if walk() couldn't
// allocate a needed page-table page.
int
mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)
  • allocproc() 中分配和初始化物理页
  • freeproc() 中释放物理页

主要工作:

proc.h 定义的 proc 结构体中添加变量,存储共享页的【物理地址】:

  struct usyscall *usyscall;

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++) {  // 寻找是否有UNUSED状态的进程pacquire(&p->lock);if(p->state == UNUSED) {goto found;} else {release(&p->lock);}}return 0;found:  // 找到UNUSED进程p->pid = allocpid();  // 进程pidp->state = USED;  // 进程状态为USED// Allocate a trapframe page. if((p->trapframe = (struct trapframe *)kalloc()) == 0){freeproc(p);  // 如果出错,释放该进程内存并返回0release(&p->lock);return 0;}/* STRAT MY CODE */// Allocate a usyscall page.if((p->usyscall = (struct usyscall *)kalloc()) == 0){freeproc(p);release(&p->lock);return 0;}memmove(p->usyscall, &p->pid, sizeof(int)); // 初始化为存储当前进程号
/* END MY CODE*/// An empty user page table. 分配用户页表p->pagetable = proc_pagetable(p);    // 【调用proc_pagetable():其中完成usyscall为起始地址的物理页到进程页表的内存映射!】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;
}

kernel/proc.cproc_pagetable() 完成内存映射(mapping)

// Create a user page table for a given process,
// with no user memory, but with trampoline pages.
pagetable_t
proc_pagetable(struct proc *p)
{pagetable_t pagetable;// An empty page table.pagetable = uvmcreate(); // create an empty user page table, returns 0 if out of memory.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){
// mappages:
//Create PTEs. Returns 0 on success, -1 if walk() couldn't allocate a needed page-table page.uvmfree(pagetable, 0);
// uvmfree:
// Free user memory pages,then free page-table pages.return 0;}// map the trapframe just below TRAMPOLINE, for trampoline.S.if(mappages(pagetable, TRAPFRAME, PGSIZE,(uint64)(p->trapframe), PTE_R | PTE_W) < 0){uvmunmap(pagetable, TRAMPOLINE, 1, 0);
// uvmunmap:
// Remove npages of mappings starting from va.uvmfree(pagetable, 0);return 0;}/*START MY CODE*/if(mappages(pagetable, USYSCALL, PGSIZE,(uint64)(p->usyscall), PTE_R | PTE_U) < 0){// 注意设定PTE_U// 如果内存映射失败,恢复以上页面:uvmunmap(pagetable, TRAMPOLINE, 1, 0);uvmunmap(pagetable, TRAPFRAME, 1, 0);uvmfree(pagetable, 0);return 0;}
/*END MY CODE*/return pagetable;
}

freeproc() 函数中释放 p->usyscall 对应的物理页内存:

// free a proc structure and the data hanging from it,
// including user pages.
// p->lock must be held.
static void
freeproc(struct proc *p)
{/* START MY CODE */if(p->usyscall)kfree((void*)p->usyscall);p->usyscall = 0;/* END MY CODE*/if(p->trapframe)kfree((void*)p->trapframe);p->trapframe = 0;if(p->pagetable)proc_freepagetable(p->pagetable, p->sz);p->pagetable = 0;p->sz = 0;p->pid = 0;p->parent = 0;p->name[0] = 0;p->chan = 0;p->killed = 0;p->xstate = 0;p->state = UNUSED;
}

最后需要在 proc_freepagetable()释放页表中对应的页表项

// Free a process's page table, and free the
// physical memory it refers to.
void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{uvmunmap(pagetable, TRAMPOLINE, 1, 0);uvmunmap(pagetable, TRAPFRAME, 1, 0);/* START MY CODE */uvmunmap(pagetable, USYSCALL, 1, 0);/* END MY CODE */uvmfree(pagetable, sz);
}

Question:
还有哪些xv6系统调用可以通过共享页来加速?如何实现?
Answer:
系统调用sysinfo可以通过共享页加速。
系统在创建共享页时将sysinfo所需的信息存入,用户只需要读取即可。

Print a page table (easy)

为了可视化RISC-V页表,或帮助以后进行调试,第二个任务是写一个函数来输出页表的内容。

目标:

定义 vmprint() 函数,有一个pagetable_t类型的参数,按下面描述的格式输出页表:
(在 exec.c 中的 “return argc;” 前面加入 if(p->pid==1) vmprint(p->pagetable) ,输出第一个进程的页表)

当你启动xv6时,在第一个进程完成exec()初始化时,输出以下页表信息:
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
… … …509: pte 0x0000000021fdd813 pa 0x0000000087f76000
… … …510: pte 0x0000000021fddc07 pa 0x0000000087f77000
… … …511: pte 0x0000000020001c0b pa 0x0000000080007000

第一行显示vmprint的参数,之后,每个PTE都有一行,包括引用树中较深的页表页的PTE。
每个PTE行都有一些以“…”的缩进,表示它在树中的深度。
每个PTE行显示了的PTE索引,包括页表页、PTE位以及从PTE提取的物理地址。不输出无效的PTE。(在上面的示例中,第一级页表页具有0和255的映射;对于 entry 0,下一级只映射了索引0,该索引0映射了最后一级的0、1、2)

你的代码输出的物理地址与上面显示的可能不相同。但显示项数和虚拟地址应相同。

Hints:

  • kernel/vm.c里面写 vmprint() 函数 √
  • 使用在 kernel/riscv.h 最后部分定义的宏 √
  • freewalk() 函数可能会有所启发 √
  • kernel/defs.h 中定义 vmprint 的原型,这样在 exec.c 中就可以调用它 √
  • printf 中使用 %p 输出十六进制表示的64位页表项PTE,以及物理地址(如上例)√

准备工作:

exec.c 加入 vmprint() 调用:(和建议的调用语句实现上稍有不同)

int
exec(char *path, char **argv)
{......if(p->pid == 1){printf("page table %p\n", p->pagetable);vmprint(p->pagetable, 1);}return argc; // this ends up in a0, the first argument to main(argc, argv)......
}

defs.hvm.c 部分加入 vmprint 函数原型:

void            vmprint(pagetable_t, int);

主要工作:vmprint 函数的实现

先来看一下 hints 里面提到的 vm.c 中的函数 freewalk()
【启发是用递归的思想:遍历当前一级页表的每一个页表项,如果存储的物理地址指向下一级页表,则递归调用,直到最后一级页表,在递归过程中完成相应功能。】

// Recursively free page-table pages.
// All leaf mappings must already have been removed.
void
freewalk(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){// this PTE points to a lower-level page table.uint64 child = PTE2PA(pte);freewalk((pagetable_t)child);pagetable[i] = 0;} else if(pte & PTE_V){panic("freewalk: leaf");}}kfree((void*)pagetable);
}

有了以上的想法,vmprint 实现只需要遍历页表按格式输出页表信息:

// Recursively print page table information:
void vmprint(pagetable_t pagetable, int level){for(int i = 0; i < 512; i++){pte_t pte = pagetable[i];if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){// this PTE points to a lower-level page table.uint64 child = PTE2PA(pte);for(int t=0;t<level;t++){  // 对应页表的级数level进行输出printf(" ..");}printf("%d: pte %p pa %p\n", i, pte, child);vmprint((pagetable_t)child, level + 1);  // 递归过程}else if(pte & PTE_V){uint64 child = PTE2PA(pte);for(int t=0;t<level;t++){printf(" ..");}printf("%d: pte %p pa %p\n", i, pte, child);}}
}

确实是比较简单的一个任务,按照指导来做很快就能完成,有一点小小的充实感,开心!

Question:

2.Explain the output of vmprint in terms of Fig 3-4 from the text.
What does page 0 contain?
What is in page 2?
When running in user mode, could the process read/write the memory mapped by page 1?
What does the third to last page contain?Now when you start xv6 it should print output like this,
describing the page table of the first process at the point
when it has just finished exec()ing init: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.. .. ..509: pte 0x0000000021fdd813 pa 0x0000000087f76000.. .. ..510: pte 0x0000000021fddc07 pa 0x0000000087f77000.. .. ..511: pte 0x0000000020001c0b pa 0x0000000080007000Answer:
page0: date and text of process
page1: guard page for protect stack by present page0 overflow
page2: stack of process
page3 to last page: heap, trapfram, trampoline
用户态下运行不能读写page1,page1用于保护page2不被用户程序访问。

Detecting which pages have been accessed (hard)

垃圾收集器 garbage collectors(自动内存管理的一种形式)可以从已访问(读或写)页面的信息中获益。在本部分的实验中,你将向xv6添加一个新特性,通过检查RISC-V页表中的访问位来获取信息并向用户空间报告这些信息。 RISC-V硬件页面遍历器在解决TLB miss时在PTE中标记这些位。

目标:

实现 pgaccess() 函数:报告已访问哪些页面的系统调用。
系统调用接受三个参数:
第一个参数是需要检查第一个用户页面的起始虚拟地址。
第二个参数是需要检查页数。
最后一个是参数用户缓冲区的地址,检查结果以位掩码(一种数据结构,每页使用一位,第一页对应于最低有效位)的形式存储在这个缓冲区中。

Hints:

  • 首先在 kernel/sysproc.c 中实现 sys_pgaccess()

  • 用argaddr()和argint()解析参数。

  • 对于输出位掩码,在内核中存储一个临时缓冲区并在填充正确的位后将其复制给用户(通过copyout())更容易。

  • 可以设置可扫描页数的上限。

  • kernel/vm.c中的walk()对于查找正确的PTE非常有用。

  • 你需要在kernel/riscv.h中定义PTE_A,即访问位。请参阅RISC-V手册以确定其值。

  • 如果PTE_A已设置,在检查后务必清除它否则,将无法确定自上次调用pgaccess()以来是否访问了页面(该位将被永久设置)

主要工作:

首先在 kernel/riscv.h 中设定 PTE_A(访问位)

// in [kernel/riscv.h]
#define PTE_V (1L << 0) // valid
#define PTE_R (1L << 1)
#define PTE_W (1L << 2)
#define PTE_X (1L << 3)
#define PTE_U (1L << 4) // 1 -> user can access
#define PTE_A (1L << 6) //【设定访问位】

实现 sys_pgaccess() 系统调用:

// in [kernel/sysproc.c]
int
sys_pgaccess(void)
{// lab pgtbl: your code here.  uint64 va;uint64 mask;int num;  const int maxnum = 64; // 【设定可扫描页数的上限,设定为 64 是因为掩码类型 uint64 最多存储 64 位的值】if(argaddr(0, &va) < 0 || argint(1, &num) < 0 || argaddr(2, &mask) < 0) // 解析参数return -1;if(num > maxnum)num = maxnum;struct proc *p = myproc();if(p == 0)return -1;pagetable_t pagetable = p->pagetable;  // 找到进程的页表uint64 procmask = 0;  // 【设定掩码初值为0】for (int i = 0; i < num; i++){// walk:// Return the address of the PTE in page table pagetable// that corresponds to virtual address va.// 【循环调用walk函数,得到页表中制定虚拟地址对应的页表项,并检查每一页的PTE_A位】 pte_t *pte = walk(pagetable, ((uint64)va) + (uint64)PGSIZE * i, 0);if(pte == 0)continue;if (((*pte) & PTE_A)) {procmask |= (1L << i);  // 记录在第i个有效位*pte = *pte & (~PTE_A); // 记录后清除PTE_A位,保证每一次pgaccess的正确性}}// copyout: Copy from kernel to user.// 调用copyout, 将procmask的值复制到页表pagetable中的虚拟地址mask.return copyout(pagetable, mask, (char *) &procmask, sizeof(uint64));
}

XV6 Lab2:Page Tables相关推荐

  1. xv6---Lab3: page tables

    目录 参考资料 RISC -V页表的简化图如下所示 ​编辑​ 多级页表 xv6内核页表 3.6 Process Address Space 3.7 Code: Sbrk 3.8 Code: Exec ...

  2. 6.S081 Lab3 page tables

    6.S081 Lab3 page tables 未完成 文章目录 6.S081 Lab3 page tables 未完成 1. Print a page table ([easy](https://p ...

  3. linux内核学习笔记【一】临时内核页表 Provisional kernel Page Tables

    最近开始学习linux内核,看了<深入理解linux内核>,开始写点学习收获.内核版本为2.6.11 临时全局目录(provisional page global directory)是在 ...

  4. XV6 lab3:Trap

    Lab3:Traps 参考文章: 6.S081 & 操作系统内核 操作系统MIT6.S081:Lab4->Trap Xv6操作系统的系统调用通过 trampoline 来实现用户空间和内 ...

  5. asp.net夜话之五:Page类和回调技术

    asp.net夜话之五:Page类和回调技术 在今天我主要要介绍的有如下知识点: Page类介绍 Page的生命周期 IsPostBack属性 ClientScriptManager类 回调技术(Ca ...

  6. ASP.NET页面对象模型:Page类介绍

    ASP.NET页面对象模型简介 Microsoft Internet 信息服务 (IIS) 所收到的对某 Microsoft ASP.NET 页面的每个请求都被移交给 ASP.NET HTTP 管线. ...

  7. ICC 图文学习——LAB2:Design Planning 设计规划

      这一步也可以叫floorplan(布局规划),对设计进行布局规划.floorplan的合理性直接关系到芯片的时序收敛.布线畅通.电源稳定以及良品率等.这部分内容非常重要,只有这一步做好了,后面成功 ...

  8. 每日MySQL之024:FLUSH TABLES

    FLUSH TABLES 作用是 flush 表,并根据参数加上相应的锁.默认是写日志的,如果不希望写日志,可以设置加上参数 NO_WRITE_TO_BINLOG.另外, FLUSH TABLES 命 ...

  9. 解决Spring Spring Data JPA 错误: Page 1 of 1 containing UNKNOWN instances

    解决Spring Spring Data JPA 错误: Page 1 of 1 containing UNKNOWN instances SpringBoot 整合 Spring-Data-JPA ...

最新文章

  1. HDU 4873 ZCC Loves Intersection(JAVA、大数、推公式)
  2. [转载]jquery cookie的用法
  3. 程序员容易不能生育?
  4. C++多进程并发框架FFLIB
  5. 集成方法(随机森林)
  6. 机器视觉用c还是python_机器视觉_opencv-python环境搭建
  7. 麒麟810加持,华为nova 5z让你一步从青铜变王者
  8. vue改变标签属性_Vue用v-for给循环标签自身属性添加属性值的方法
  9. android 蓝牙与单片机通信原理图,手机蓝牙与HC-06蓝牙模块控制单片机程序加APP...
  10. RDP Wrapper 大于10.0.19041
  11. 用python模拟clark变换和park变换
  12. java工程师容易秃头吗_当程序员会容易秃头?下面这3种职业一样会秃
  13. RFID扫描APP Android
  14. vue-事件修饰符-详解(.prevent .stop .once .capture .self)
  15. 双击桌面计算机删除,笔记本电脑点击图标自动删除怎么办
  16. 华为消费者业务公布2017上半年智能手机收入暴涨
  17. document的用法
  18. DC学习-前言及第一章
  19. PPI (手机屏幕的PPI 和计算方法)
  20. 01git创建本地仓库及操作入门

热门文章

  1. Akka Reaper Pattern
  2. 多线程基本----学而时习之
  3. Web报表系统葡萄城报表:报表系统
  4. 舆情标注是什么意思?企业舆情标签分类
  5. 最高效率的开发如何有效的提问
  6. 进件(贷前)项目的从0到1
  7. Starday作为增速快的电商潜力市场 ,入驻商户能够轻松实现旺季“躺平”
  8. SAP跨公司销售和SAP的业务单据操作
  9. 使用基于GAN的过采样技术提高非平衡COVID-19死亡率预测的模型准确性
  10. 为什么数据集中的mask是彩色的?