前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >6.S081/6.828: 3 Lab page tables

6.S081/6.828: 3 Lab page tables

原创
作者头像
冰寒火
修改2022-11-26 05:02:30
6300
修改2022-11-26 05:02:30
举报
文章被收录于专栏:软件设计软件设计

本实验需要掌握内核页表、用户页表的布局,切换原理,初始化过程,遍历逻辑等方面,可以参考xv6源码分析--页表

一、Speed up system calls

1 问题分析

加速系统调用,希望能够在用户态直接返回,不需要进入内核态,减少上下文切换。

要想实现这样的效果,我觉得需要满足以下条件:

  1. 系统调用的返回结果比较简单而且固定不变,是创建进程时就能够确定的字段,比如pid。
页表布局
页表布局

用户页表上加一页USYSCALL,并且USYSCALL必须是用户态能够访问的、只读的。

2 代码实现

代码语言:c
复制
//memlayout.h
#ifdef LAB_PGTBL
#define USYSCALL (TRAPFRAME - PGSIZE)

struct usyscall {
  int pid;  // Process ID
};
#endif

//1 在proc结构体中增加一个字段
struct usyscall *usyscall;

//2 进程初始化时分配空间并赋值
static struct proc*
allocproc(void)
{
  struct proc *p;

  //...
    
  //Allocate a shared 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);
    
  //...
  return p;
}
pagetable_t
proc_pagetable(struct proc *p)
{
  pagetable_t pagetable;

  //...
    
  if(mappages(pagetable, USYSCALL, PGSIZE,
              (uint64)p->usyscall, PTE_R | PTE_U) < 0){
    uvmunmap(pagetable, USYSCALL, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }

  return pagetable;
}

//3 释放用户页表时释放空间,跟trapframe一样
static void
freeproc(struct proc *p)
{
  if(p->trapframe)
    kfree((void*)p->trapframe);
  if(p->usyscall)
    kfree((void*)p->usyscall);
  p->trapframe = 0;
  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;
}
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);
}

二、vmprint

1 问题分析

打印pid=1的用户页表,效果如下:

image.png
image.png

参考freewalk实现。

2 代码实现

代码语言:c
复制
void
vmprinthelper(pagetable_t pagetable, int level){
  char *indent;
  if(level==2){
    indent="..";
  }else if(level==1){
    indent=".. ..";
  }else{
    indent=".. .. ..";
  }
  // 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){
      // this PTE points to a lower-level page table.
      uint64 child = PTE2PA(pte);
      printf("%s%d: pte %p pa %p\n",indent,i,pte,PTE2PA(pte));
      if(level>0){
        vmprinthelper((pagetable_t)child,level-1);
      }
    } 
  }
}
//print user page table
void
vmprint(pagetable_t pagetable){
  printf("page table %p\n",pagetable);
  vmprinthelper(pagetable,2);
}

三、Detecting which pages have been accessed

1 问题分析

添加系统调用pgaccess,用于获取从上次pgaccess到现在,该页面是否被访问过。入参是页面起始地址、页面数量、返回结果地址,出参是这些页面的access状态,一个bit表示一个page的access状态。

  1. 通过argint、argaddr获取整数或者地址。
  2. 定义一个PTE_A,左移6位。
  3. 设置上限,并且进行地址越界判断。
  4. 通过copyout向用户页表写入返回结果。

2 copyout

进程从用户态经过trampoline,保存寄存器到tramframe,并将satp设置为内核页表的地址,最后进入内核态。那在内核态如何向用户态写入返回结果呢?进程proc中保存了用户页表最高级目录的物理地址,我们可以将用户页表看成一个数据结构,以软件的形式去访问它,找到对应虚拟地址的物理地址,最后解引用直接将数据写入到物理页。

代码语言:c
复制
//从内核将数据拷贝到用户态,采用的是直接解引用,写入到物理内存中的方式,并没有为每个进程维护一个内核页表
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
  uint64 n, va0, pa0;

  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    pa0 = walkaddr(pagetable, va0);
    if(pa0 == 0)
      return -1;
    n = PGSIZE - (dstva - va0);
    if(n > len)
      n = len;
    memmove((void *)(pa0 + (dstva - va0)), src, n);

    len -= n;
    src += n;
    dstva = va0 + PGSIZE;
  }
  return 0;
}

3 代码实现

代码语言:c
复制
#ifdef LAB_PGTBL
int
sys_pgaccess(void)
{
  uint64 startaddr,outaddr;
  int number;
  if(argaddr(0,&startaddr)<0){
    return -1;
  }
  if(argint(1,&number)<0){
    return -1;
  }
  if(argaddr(2,&outaddr)<0){
    return -1;
  }
  return pgaccess(myproc()->pagetable,startaddr,number,outaddr);
}
#endif


//检测startaddr--startaddr+number*PGSIZE这个范围哪些页面,自上次调用pgaccess以来被访问过,
//outaddr是用户态地址,用于返回结果
int pgaccess(pagetable_t pagetable,uint64 startaddr,int number, uint64 outaddr){
  if(pagetable==0 || number>32 ){
    return -1;
  }
  if(outaddr+4>=MAXVA || startaddr+number*PGSIZE>=MAXVA){
    return -1;
  }
  int accessed;
  for (int i = 0; i < number; i++){
    pte_t *pte=walk(pagetable,startaddr+i*PGSIZE,0);
    if(pte!=0 && ((*pte)&PTE_A)){
      accessed|=1<<i;
      *pte^=PTE_A; //清除PTE_A
    }
  }
  return copyout(pagetable,outaddr,(char*)&accessed,sizeof accessed);
}
测试结果
测试结果

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Speed up system calls
    • 1 问题分析
      • 2 代码实现
      • 二、vmprint
        • 1 问题分析
          • 2 代码实现
          • 三、Detecting which pages have been accessed
            • 1 问题分析
              • 2 copyout
                • 3 代码实现
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                http://www.vxiaotou.com