lab3 page table
lab3 page table
实验结果

实验
Speed up system calls实验
1:找到ugetpid()这个系统调用。
发现定义了 LAB_PGTBL就可以使用ugetpid()这个系统调用
在./user/user.h中使用这个函数
#ifndef LAB_PGTBL
#define LAB_PGTBL
#endif
更改./user/ulib.c中头文件的包含顺序
#include "user/user.h"
#ifdef LAB_PGTBL
#include "kernel/riscv.h"
#include "kernel/memlayout.h"
#endif
2:在./kernel/proc.c中修改的如下。
// 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;
}
// Allocate a trapframe page.
if((p->usyscall = (struct usyscall *)kalloc()) == 0){
freeproc(p);
release(&p->lock);
return 0;
}
p->usyscall->pid = p->pid;
// 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;
}
// free a proc structure and the data hanging from it,
// including user pages.
// p->lock must be held.
static void
freeproc(struct proc *p)
{
if(p->trapframe)
kfree((void*)p->trapframe);
p->trapframe = 0;
if (p->usyscall)
{
kfree((void*)p->usyscall);
}
p->usyscall = 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;
}
// 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();
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 just below TRAMPOLINE, 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;
}
//lab3 speed up system call
if(mappages(pagetable, USYSCALL, PGSIZE,
(uint64)(p->usyscall), PTE_R | PTE_U ) < 0){
uvmunmap(pagetable, TRAPFRAME, 1, 0);
uvmunmap(pagetable, TRAMPOLINE, 1, 0);
uvmunmap(pagetable, USYSCALL, 1, 0);
uvmfree(pagetable, 0);
return 0;
}
return pagetable;
}
// 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);
uvmunmap(pagetable, USYSCALL, 1, 0);
uvmfree(pagetable, sz);
}
3:修改./kernel/memlayout.h。
//用于lab3 speed up systemcall
struct usyscall
{
int pid;
};
#define USYSCALL (TRAPFRAME - PGSIZE)
问题
Which other xv6 system call(s) could be made faster using this shared page? Explain how.
fstat()函数调用
user可以访问的页面存储着对应fd的信息。
但这样的话,需要修改其他有关文件的系统调用,该fd状态信息变化,该页面存储的副本也要变化。用别的system call的速度这个system call的速读。
Print a page table实验
1在./kernel/vm.c中添加了如下代码.
/**
* @param level:第几级页表
* @return if is correct, return 0;
*/
static void PrintValidPa(pagetable_t pagetable, int level)
{
pte_t pte = 0;
uint64 child = 0;
for (int i = 0; i < 512; i++)
{
pte = pagetable[i];
if ((pte & PTE_V) != 0)
{
for(int j = 0; j < level; j++)
{
if (j != level - 1)
{
printf(".. ");
}
else
{
printf("..%d:", i);
}
}
child = PTE2PA(pte);
//pritf(" pte 0x%p pa 0x%p\n", (void*)(pte), &pagetable[i]);
printf(" pte %p pa %p\n", (void*)(pte), child);
if ((pte & (PTE_R|PTE_W|PTE_X)) == 0)
{
PrintValidPa((pagetable_t)child, level + 1);
}
}
}
return;
}
/**
* @brief lab3 seconde question
*/
int vmprint(pagetable_t pagetable)
{
pte_t pte = 0;
uint64 child = 0;
pte = pagetable[0];
child = PTE2PA(pte);
printf("page table %p\n", child);
PrintValidPa(pagetable, 1);
return 0;
}
1在./kernel/defs.h中添加vmprint的声明.
实验问题
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?
应该是指虚拟地址的page 0,看exec()中的实现代码,page0包含了代码段和数据段。
exec()中代码段的大小以及具体实现我并不清楚,如果实现比较大,那么page 1和page 2可能都是数据段和代码段。
page 1如果是代码段,那么只能是rx,不能write。
如果在代码和数据都在page 0里,那么page 是guard page,如果cpu访问,就会产生错误。guard page后面是栈。
Detecting which pages have been accessed实验
#ifdef LAB_PGTBL
extern pte_t *walk(pagetable_t pagetable, uint64 va, int alloc);
int
sys_pgaccess(void)
{
uint64 PageVa = 0;
int len = 0;
uint64 MaskBuffferAdd =0;
char MaskBufffer[50] = {0};//riscv little endian
uint64 TempBigMaskIndex = 0;//数组第几元素
uint64 TempSmallMaskIndex = 0;//第几位
pte_t* TempPagePteP = 0;
if (argaddr(0, &PageVa) < 0)
{
return -1;
}
if (argint(1, &len) < 0)
{
return -1;
}
if (len > (50*8))//超过最大可查询页面数
{
return -1;
}
if (argaddr(2, &MaskBuffferAdd) < 0)
{
return -1;
}
for(int i = 0; i < len; i++)
{
TempPagePteP = walk(myproc()->pagetable, PageVa, 1);
if (((*TempPagePteP) & PTE_A) != 0)
{
*TempPagePteP &= ~(PTE_A);
TempBigMaskIndex = i/8;
TempSmallMaskIndex = i - 8 * TempBigMaskIndex;
MaskBufffer[TempBigMaskIndex] |= (0x1 << TempSmallMaskIndex);
}
PageVa = PageVa + PGSIZE;
}
copyout(myproc()->pagetable, MaskBuffferAdd, &MaskBufffer[0], sizeof(MaskBufffer));
return 0;
}
#endif
相关知识点
xv6虚拟内存的实现
PTE最后十位的意思

如上图所示,一共有V,R,W,X,U,G,A,D,RSW位
V:Valid,表示该页表项是否有效,如果无效,则该页表项无效,不能被访问。
R:Read,表示该页表项是否可以读。
W:Write,表示该页表项是否可以写。
X:Execute,表示该页表项是否可以执行。
U:User,表示该页表项是否可以访问,如果为0,则该页表项只能被内核访问,如果为1,则该页表项可以被内核和用户访问。
下面的具体不清楚,应该是
G:Global,表示该页表项是否为全局页表项,如果为0,则该页表项为局部页表项,如果为1,则该页表项为全局页表项。(被所有进程访问)
A:示自从上次 A 位被清除以来,Accessed,表示该页表项是否被访问过。(当这个page被读过,且A位为0,则硬件会自动set 1,但set 0是由软件做的)
D:示自从上次 D 位被清除以来,Dirty,表示该页表项是否被修改过。(当这个page被修改过,且D位为0,则硬件会自动set 1,但set 0是由软件做的)
RSW:留个操作系统使用。
在XV6里,如果V为1,RWX位为0,则代表该PTE是指向下一个页面的指针;如果RWX不都为0,则代表该PTE指向一个物理页。
XV6操作系统进程里虚拟地址到物理地址的转换
RISCV有STAP寄存器

如上图所示,MMU开启虚拟模式以后,MMU根据stap寄存器的值,找到一级页表,再根据VA中对应位数的值,确定对应页表项的offerset,从而确定二级页表项。在根据va中对应的offeset,确定对应页表项的offerset,从而确定三级页表项,然后再根据va中第12位到第20位的偏移地址,从而确定物理页号,再根据va的最后12位,确定该虚拟地址的物理地址。
每一级页表项的最后10位都是标志位,硬件配和操作系统,确定该页的权限范围。
带有缓存的page 读写

把SRAMn级缓存,DRAM称为主存,再下一层是磁盘,远程分布式系统不考虑。
如图,DRAM页面只够4page,但该用户程序可以使用7个page。8个page 分布在磁盘里,当要访问pp6时,硬件和操作系统发现pp6不在内存中,会产生page fault。然后通过某种算法,替换掉dram中的一个page。
MMU的作用
1:用户程序和kernel之间,用户程序和用户程序之间都有不同page table。实现用户程序和kernel的隔离,实现了用户程序和用户程序之间的隔离。
2:通过RISCV page table entry的最后十位来确定页面的权限。如xv6 用户代码段和数据段上的page的valid就是0,而改页上是栈,当栈溢出时,访问非法的page,会产生异常,避免了栈溢出对代码和数据的修改。
3:MMU给每个用户程序都提供一致的地址抽象,减少用户用户程序编写所需的心智负担。
4:通过page fault(swap)机制,在DRAM和磁盘中交换页面,提供给操作系统超过主存大小的实际使用空间。(实际情况里,多个process的page 可能会同一时间缓存在DRAM里)
如何做到kernel space和user space的隔离?
kernel space中的代码比user space中的代码拥有更高的权限,两者都拥有属于自己独立的内存空间,寄存器等。
通用上:
1:运行kernel的代码时,kernel space中的代码拥有S模式的中断,user space的代码只拥有U模式的中断,甚至二者的中断向量表函数都不一致,让user拥有更低权限的中断,user发生崩溃时,kernel通过更高权限的中断来处理,让user 崩溃不影响kernel。
2:运行kernel的代码与user space的代码时,二者stap寄存器上的值不同,从而使二者拥有不同的3级页表。而且二者运行时,通用寄存器组,PC寄存器也不同,从抽象来看,让各自内存地址空间形成了隔离。
补充,加上XV6 RISCV的角度:
1:kernel运行时,CPU处于s模式,可以处理来自于S模式的中断。user模式的代码运行时,cpu处于u模式,只能处理u模式的中断。
2:kernel 代码运行时,stap寄存器上的值与user space上的代码运行时的stap寄存器上的值不同。让二者拥有不同的虚拟地址空间。
3:kernel代码运行时,svetc上即中断向量表也与user space上的中断向量表不一致
4:kernel与u的32个通用寄存器组和user space的通用寄存器组也不同,pc也是。
xv6系统如何从user进入kernel,再从kernel退出user
user 进入kernel
从用户的角度,通过system call,user space与kernel space进行交互,然后返回所需要的资源和一些状态值。
每个芯片的具体实现可能都不同,但应该都要做到保存和切换上下文(通用寄存器组,PC寄存器,切换虚拟内存),让kernel拥有更高的权限(更高权限的中断,能执行权限更高的指令,访问更高级别的地方),异常的处理,对各种中断的处理。
user进入kernel的具体步骤:
1:进入汇编代码,将系统调用号保存到a7寸,然后调用ecall中断。
2:由于SVECT的设置,进入tramponline.s中的uservec。保存通用寄存器组,释放保存在trampe中kernel stap,sp,trap()函数,cpuid的值到特定寄存器中。
3:进入到usertrap()函数当中。
4:判断是否来自user mode,不是,发生了一些未知错误。把svetc中断向量表上变成kernel的中断向量表。
5:获取cpu,保存程序计数器。
6:判断中断来源是否是systemcall,是的话,pc寄存器+4,使能中断,调用syscall()函数。里面实现了具体的system call函数。别的中断,不执行。中断号不正常的异常处理。
7:如果是定时器中断(cpu调度的那个),执行调度函数。
8:执行usertrapret();返回user space。
具体代码
//
// handle an interrupt, exception, or system call from user space.
// called from trampoline.S
//
void
usertrap(void)
{
int which_dev = 0;
if((r_sstatus() & SSTATUS_SPP) != 0)
panic("usertrap: not from user mode");
// send interrupts and exceptions to kerneltrap(),
// since we're now in the kernel.
w_stvec((uint64)kernelvec);
struct proc *p = myproc();
// save user program counter.
p->trapframe->epc = r_sepc();
if(r_scause() == 8){
// system call
if(p->killed)
exit(-1);
// sepc points to the ecall instruction,
// but we want to return to the next instruction.
p->trapframe->epc += 4;
// an interrupt will change sstatus &c registers,
// so don't enable until done with those registers.
intr_on();
syscall();
} else if((which_dev = devintr()) != 0){
// ok
} else {
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
p->killed = 1;
}
if(p->killed)
exit(-1);
// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
yield();
usertrapret();
}
kernel退出user
每个芯片的具体实现可能都不同,但应该都要做到保存和切换上下文(通用寄存器组,PC寄存器,虚拟地址),降低权限,让user 崩溃不影响kernel(更低权限的中断,能执行权限更第的指令,访问更少的地方),异常的处理,对各种中断的处理。
具体实现看usertrapret()函数(见目录)
部分函数解析
usertrapret()
作用从kernel space返回用户空间
重点:保存和切换上下文(通用寄存器组,PC寄存器,虚拟地址),降低权限,让user 崩溃不影响kernel(更低权限的中断,能执行权限更第的指令,访问更少的地方),异常的处理,对各种中断的处理。
//
// return to user space
//
void
usertrapret(void)
{
struct proc *p = myproc();
// we're about to switch the destination of traps from
// kerneltrap() to usertrap(), so turn off interrupts until
// we're back in user space, where usertrap() is correct.
intr_off();
// send syscalls, interrupts, and exceptions to trampoline.S
w_stvec(TRAMPOLINE + (uservec - trampoline));
// set up trapframe values that uservec will need when
// the process next re-enters the kernel.
p->trapframe->kernel_satp = r_satp(); // kernel page table
p->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stack
p->trapframe->kernel_trap = (uint64)usertrap;
p->trapframe->kernel_hartid = r_tp(); // hartid for cpuid()
// set up the registers that trampoline.S's sret will use
// to get to user space.
// set S Previous Privilege mode to User.
unsigned long x = r_sstatus();
x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode
x |= SSTATUS_SPIE; // enable interrupts in user mode
w_sstatus(x);
// set S Exception Program Counter to the saved user pc.
w_sepc(p->trapframe->epc);
// tell trampoline.S the user page table to switch to.
uint64 satp = MAKE_SATP(p->pagetable);
// jump to trampoline.S at the top of memory, which
// switches to the user page table, restores user registers,
// and switches to user mode with sret.
uint64 fn = TRAMPOLINE + (userret - trampoline);
((void (*)(uint64,uint64))fn)(TRAPFRAME, satp);
}
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)
{
uint64 a, last;
pte_t *pte;
if(size == 0)
panic("mappages: size");
a = PGROUNDDOWN(va);
last = PGROUNDDOWN(va + size - 1);
for(;;){
if((pte = walk(pagetable, a, 1)) == 0)
return -1;
if(*pte & PTE_V)
panic("mappages: remap");
*pte = PA2PTE(pa) | perm | PTE_V;
if(a == last)
break;
a += PGSIZE;
pa += PGSIZE;
}
return 0;
}
allocproc()
作用:fork时使用,分配出一个基本的进程结构体,初始化它的trapframe,user pagetable,pid,上下文切换的信息。初始时只分配一个页表
注意事项:allocproc只会分配一个页表作为页表项,SV39有3级页表,其他两个可以在walk中分配。
// 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;
}
freeproc()
作用:释放进程结构体,释放trapframe,释放user pagetable,释放上下文切换的信息。proc结构体还未使用时,咋样,就要农村咋样。
static void
freeproc(struct proc *p)
{
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_pagetable()
作用:为该进程分配一个根页表,并把实际的trampoline和tramframe的物理地址映射到以该进程的虚拟地址里
// 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();
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 just below TRAMPOLINE, 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;
}
walk()函数
作用:返回指向该虚拟地址所在页表的3级页表项的PTE值。
#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK)
// one beyond the highest possible virtual address.
// MAXVA is actually one bit less than the max allowed by
// Sv39, to avoid having to sign-extend virtual addresses
// that have the high bit set.
#define MAXVA (1L << (9 + 9 + 9 + 12 - 1))
typedef uint64 pte_t;
typedef uint64 *pagetable_t; // 512 PTEs
// Return the address of the PTE in page table pagetable
// that corresponds to virtual address va. If alloc!=0,
// create any required page-table pages.
//
// The risc-v Sv39 scheme has three levels of page-table
// pages. A page-table page contains 512 64-bit PTEs.
// A 64-bit virtual address is split into five fields:
// 39..63 -- must be zero.
// 30..38 -- 9 bits of level-2 index.
// 21..29 -- 9 bits of level-1 index.
// 12..20 -- 9 bits of level-0 index.
// 0..11 -- 12 bits of byte offset within the page.
// alloc为1代表分配
pte_t *
walk(pagetable_t pagetable, uint64 va, int alloc)
{
if(va >= MAXVA)
panic("walk");
for(int level = 2; level > 0; level--) {
pte_t *pte = &pagetable[PX(level, va)];
if(*pte & PTE_V) {
pagetable = (pagetable_t)PTE2PA(*pte);
} else {
if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)
return 0;
memset(pagetable, 0, PGSIZE);
*pte = PA2PTE(pagetable) | PTE_V;
}
}
return &pagetable[PX(0, va)];
}
#define PA2PTE(pa) ((((uint64)pa) >> 12) << 10)
#define PTE2PA(pte) (((pte) >> 10) << 12)
#define PTE_FLAGS(pte) ((pte) & 0x3FF)
// extract the three 9-bit page table indices from a virtual address.
#define PXMASK 0x1FF // 9 bits
#define PXSHIFT(level) (PGSHIFT+(9*(level)))
#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK)

浙公网安备 33010602011771号