lab pagetable

一 虚拟内存与页表

        操作系统为每一个用户进程分配了虚拟的地址空间,每个进程根据自己的地址空间进行寻址,从而确保了进程与进程之间相互隔离。

        寻址的过程通过页表来完成,页表的任务是实现虚拟地址到物理地址的映射,由硬件实现。页表分为单级页表与多级页表,单级页表非常庞大,占用内存空间。因此提出了多级页表的概念,多级页表将虚拟地址空间进一步划分,每一级页表的PPN指向了下一级页表目录的物理地址,最后一级页表则是指向了目标的物理地址,这样做可以减少不必要的页表目录在内存中的驻留,从而提高了内存空间的利用率。

      通过多级页表进行寻址,需要多次访问内存空间,寻址的效率比较低下。TLB(translation look-aside buffer)的提出可以提高寻址的效率,TLB是一个记录PTE的缓存,记录了最近使用的虚拟地址到物理地址的映射,对于TLB中存在的PTE,不再需要访问页表来获得物理地址,从而提高了寻址的效率。

        RISC-V使用的是三级页表,64位的地址空间,主要的性质如下:

  • 每一个page directory的大小是一个page的大小,4KB
  • 一个PTE的大小是64位,一个page directory有512个PTE
  • The root page directory 的物理地址存放在寄存器satp中,每次CPU切换进程,会将进程的页表首地址存放到satp中

二 实验部分

2.1 print a page table

       该实验部分要实现一个页表打印的功能,传入一个页表,打印出页表中有效的PTE对应的PTE值和PA值。递归遍历页表的PTE即可,代码如下。

void
vmprint(pagetable_t pagetable){
  static int level=1;
  pte_t pte;
  if(level==1)
    printf("page table %p\n",pagetable);
  for(int i=0;i<512;i++){
    if(pagetable[i]&PTE_V){
      for(int j=0;j<level;j++){
        if(j!=0) printf(" ");
	    printf("..");
      }
      pte=PTE2PA(pagetable[i]);
      printf("%d: pte %p pa %p\n",i,pagetable[i],pte);
      if(level!=3){
        level++;
	    vmprint((pagetable_t)pte);
	    level--;
      }
    }
  }
}

2.2 kernel pagetable per process

       为每一个进程分配一个kernel pagetable,目的是为了当传入一个user address,kernel可以直接解引用user address。kernel地址空间示意图如下。

  • kernel address space 是一种direct-map,物理地址和虚拟地址是一样的。
  • trampoline和kstack映射在内核地址空间高位,并在每一个page下设置一个invalid的Guard page,确保不会overflow。
  • 从0到0x0C000000(PLIC)是user address的mapping,这个映射由下一题实现。

  1 在struct proc中添加kernel_pagetable 的变量

  ... ...
  struct context context;      // swtch() here to run process
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
  pagetable_t kernel_pagetable;
  

 2 创建一个kernel_pagetable

//create kernel pagetable
pagetable_t
ukvminit()
{
  pagetable_t pagetable = (pagetable_t) kalloc();
  memset(pagetable, 0, PGSIZE);

  // uart registers
  ukvmmap(pagetable,UART0, UART0, PGSIZE, PTE_R | PTE_W);

  // virtio mmio disk interface
  ukvmmap(pagetable,VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);

  // CLINT
  ukvmmap(pagetable,CLINT, CLINT, 0x10000, PTE_R | PTE_W);

  // PLIC
  ukvmmap(pagetable,PLIC, PLIC, 0x400000, PTE_R | PTE_W);

  // map kernel text executable and read-only.
  ukvmmap(pagetable,KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);

  // map kernel data and the physical RAM we'll make use of.
  ukvmmap(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.
  ukvmmap(pagetable,TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);
  return pagetable;
}

//实现虚拟地址到物理地址在pagetable的映射
void
ukvmmap(pagetable_t pagetable,uint64 va,uint64 pa,uint64 sz,int perm)
{
  if(mappages(pagetable,va,sz,pa,perm)!=0)
    panic("ukvmmap");
}

 3 在allocproc中为进程创建kernel pagetable 并分配kernel stack

  p->kernel_pagetable=ukvminit();
  // Allocate a page for the process's kernel stack.
  // Map it high in memory, followed by an invalid
  // guard page.
  char *pa = kalloc();
  if(pa == 0)
    panic("kalloc");
  uint64 va = KSTACK((int) (p - proc));
  ukvmmap(p->kernel_pagetable,va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
  p->kstack = va;

4 进程调度时,将其kernel pagetable放到satp寄存器中,没有进程在cpu运行时,satp存放的是global kernel pagetable

    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;
	    w_satp(MAKE_SATP(p->kernel_pagetable));
	    sfence_vma();
        swtch(&c->context, &p->context);
        
	    kvminithart();
        // Process is done running for now.
        // It should have changed its p->state before coming back.
        c->proc = 0;

        found = 1;
      }
      release(&p->lock);
    }

5 释放进程的kernel pagetable

//释放为进程分配的kernel stack的物理地址
  if(p->kstack){
    pte_t* pte=walk(p->kernel_pagetable,p->kstack,0);
    kfree((void*)PTE2PA(*pte));
  }
  p->kstack=0;
//释放进程的kernel pagetable,但不释放页表指向的物理地址
  if(p->kernel_pagetable)
    freeprockvm(p->kernel_pagetable);
  p->kernel_pagetable=0;

void
freeprockvm(pagetable_t pagetable){
  for(int i=0;i<512;i++){
    pte_t pte=pagetable[i];
    if(pte&PTE_V){
      pagetable[i]=0;
      if((pte&(PTE_R|PTE_W|PTE_X))==0){
         uint64 child=PTE2PA(pte);
	  freeprockvm((pagetable_t)child);
      }
    }
  }
  kfree((void*)pagetable);
}

2.3 Simplify copyin/copyinstr

        接上一题,将user address映射到进程的kernel pagetable中,实现kernel直接解引用user address的目的。

1 user address 分配地址时,对应的kernel pagetable 也要添加相应的映射

uint64
kvmalloc(pagetable_t user,pagetable_t kernel, uint64 oldsz, uint64 newsz)
{
  uint64 a;
  if(newsz < oldsz)
    return oldsz;
  if(newsz>PLIC)
    return -1;
  oldsz = PGROUNDUP(oldsz);
  for(a = oldsz; a < newsz; a += PGSIZE){
    pte_t *pte1=walk(user,a,0);
    pte_t *pte2=walk(kernel,a,1);
    *pte2=(*pte1&(~PTE_U));
  } 
  return newsz;
}

2 user address释放地址,对应的kernel pagetable 释放对应的映射

uint64
kvmdealloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz)
{
  if(newsz >= oldsz)
    return oldsz;

  if(PGROUNDUP(newsz) < PGROUNDUP(oldsz)){
    int npages = (PGROUNDUP(oldsz) - PGROUNDUP(newsz)) / PGSIZE;
    uvmunmap(pagetable, PGROUNDUP(newsz), npages, 0);
  }

  return newsz;
}

3 fork创建了一个与父进程相同的子进程,将子进程的user address映射到其kernel pagetable中

kvmalloc(np->pagetable,np->kernel_pagetable,0,np->sz);

4 sbrk()调用growproc(),为user address分配和释放地址空间

int
growproc(int n)
{
  uint sz;
  struct proc *p = myproc();

  sz = p->sz;
  if(n > 0){
    if(n+sz>PLIC)
      return -1;
    if((sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {
      return -1;
    }
    kvmalloc(p->pagetable,p->kernel_pagetable,p->sz,p->sz+n);

  } else if(n < 0){
    sz = uvmdealloc(p->pagetable, sz, sz + n);
    kvmdealloc(p->kernel_pagetable,p->sz,p->sz+n);
  }
  p->sz = sz;
  return 0;
}

5 exec将原来的user address替换成新的,因此需要对进程原来的mapping进行释放,再添加新的mapping

  //unmap previous user mapping in kernel pagetable
  uvmunmap(p->kernel_pagetable,0,PGROUNDUP(p->sz)/PGSIZE,0);


  //add user mapping into kernel pagetable
  if(kvmalloc(pagetable,p->kernel_pagetable,0,sz)==-1)
    goto bad;

6 userinit中初始化第一个用户进程,添加mapping


  //add user mapping into kernel pagetable
  kvmalloc(p->pagetable,p->kernel_pagetable,0,PGSIZE);

7 simplify copyin and copyinstr

// Copy from user to kernel.
// Copy len bytes to dst from virtual address srcva in a given page table.
// Return 0 on success, -1 on error.
int
copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{ 	
  return copyin_new(pagetable,dst,srcva,len);
}

// Copy a null-terminated string from user to kernel.
// Copy bytes to dst from virtual address srcva in a given page table,
// until a '\0', or max.
// Return 0 on success, -1 on error.
int
copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
{
  return copyinstr_new(pagetable,dst,srcva,max);
}

2.4 实验结果

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值