Lab3 Page tables


Lab链接:Lab: page tables

Lab源码:momo/MIT-6S081/pgtbl - Gitee.com

1 Speed up system calls

题目

创建进程时,在虚拟地址USYSCALL映射一个只读页面。在此页面的开头,存储一个结构体usyscall(也在memlayout.h中定义),并以当前进程PID初始化该结构体。

思路

kernel/proc.h 中:

  • 在进程结构体 struct proc 中增加字段 struct usyscall *usyscall;,用于存储指向共享页的指针

kernel/proc.c 中:

  • 创建进程时,需为共享页分配物理页,并将其映射到进程的虚拟地址空间
  • 在销毁进程页表之前,必须取消所有物理页在虚拟内存中的映射,确保资源正确释放。因此,无论是进程正常退出,还是创建进程过程中因映射失败而退出,都应在调用页表销毁函数之前,先执行取消映射的操作

报错:【panic:freewalk:leaf】

定位:只有uvmfree调用了freewalk;多处调用uvmfree之前使用uvunmap unmap页表项

释放页表之前,要先unmap页表的对应关系,即清除pte中的PTE_V为0:

  • 三、二级页表:(pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0
  • 一级页表:pte & PTE_V == 1

freewalk在释放一级页表时,如果一级页表项是有效的(pte & PTE_V == 1),则发生上述错误

所以调用freewalk之前,需要unmap一级页表页表项

源码

kernel/proc.h

// struct proc
struct usyscall *usyscall;

kernel/proc.c

void allocproc() {
  // Allocate a USYSCALL page for kernel to share with
  if((p->usyscallpage = (pagetable_t)kalloc()) == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }
  p->usyscall->pid = p->pid;
}

void proc_pagetable() {
    // map the usyscall just below USYSCALL
    if(mappages(pagetable, USYSCALL, PGSIZE,
              (uint64)(p->usyscall), PTE_R | PTE_U) < 0){
        uvmunmap(pagetable, USYSCALL, 1, 0);
        uvmfree(pagetable, 0);
        return 0;
    }
}

void freeproc() {
  if(p->usyscall)
    kfree((void*)p->usyscall);
  p->usyscall = 0; 
}

void proc_freepagetable(pagetable_t pagetable, uint64 sz) {
  uvmunmap(pagetable, USYSCALL, 1, 0);
  // ...
} 

2 Print a page table​​

题目

打印进程页表中所有页表项的信息

思路

定义两个函数:

  • vmprint(pagetable_t):接收进程页表的地址作为参数
  • vmsprint(pagetable_t, int level):接收任意页表地址和当前页表层级,递归打印该页表及其所有子页表项的信息

vmsprint(pagetable_t, int level) 的具体流程如下:

  1. 若当前为最末级页表(level == 3),说明已是数据页,无需进一步处理,直接返回
  2. 否则,当前页表为中间层级页表,需遍历其所有页表项
  3. 对于每一个有效的页表项(pte & PTE_V ):
    • 打印该页表项对应的虚拟地址范围、物理地址及权限标志
    • 传入下一级页表地址和层级,递归调用 vmsprint

源码

kernel/vm.c

void vmprint(pagetable_t pagetable) {
  printf("page table %p\n", *pagetable);
  vmsprint(pagetable, 0);
}

void vmsprint(pagetable_t pagetable, int depth) {
  if(depth == 3)
    return;
        
  pte_t pte;
  uint64 child;

  for(int i = 0; i < 512; ++i) {
    pte = pagetable[i];
    if(pte & PTE_V) {
      printf("..");
      for(int k = 0; k < depth; ++k) {
        printf(" ..");
      }
      child = PTE2PA(pte);
      printf("%d: pte %p pa %p\n", i, pte, child);
      vmsprint((pagetable_t)child, depth+1);
    }
  }
}

C语言中,使用%p占位符打印指针变量的值,也就是虚拟地址。

int *p;
printf("the address is %p\n", p);

PTE_R & PTE_W & PTE_X // x

3 Detecting which pages have been accessed​

题目

实现pgaccess系统调用,汇报哪些page在上次该系统调用之前被修改或访问过,包含以下三个参数:

  • va:待检查的第一个user page的虚拟地址
  • checknum:检查页数
  • dstva:位于用户空间,用于存储结果的缓冲区的虚拟地址(最低位对应第一页)

思路

  1. 计算虚拟地址 va 对应页的起始地址
  2. 调用 walk() 查找该页对应的页表项
  3. 若页表项中访问位(PTE_A)已被设置,则将该页在掩码(mask)中的对应位置为1,并清除 PTE_A 位,防止干扰后续检查
  4. 将最终得到的掩码从内核空间复制回用户空间

源码

kernel/riscv.h

#define PTE_A (1L << 6) // 必须设置为6

kernel/sysproc.c

直接利用walk版

int sys_pgaccess(void) {
  uint64 va, base_va, mask_addr;
  int len, i;
  unsigned int mask = 0;
  struct proc *p;
  pte_t *pte;

  if(argaddr(0, &base_va) < 0 || argint(1, &len) < 0 || argaddr(2, &mask_addr) < 0)
    return -1;
  
  if(len <= 0 || len >= MAX_PGACCESS_CNT)
    return -1;
    
  if(base_va >= MAXVA)
    return -1;
  
  p = myproc();

  va = PGROUNDDOWN(base_va);
  for(i = 0; i < len; i++) {
    pte = walk(p->pagetable, va, 0);
    if(!pte)
      return -1;
    if(*pte & PTE_A) {
      mask = mask | (1 << i);
      *pte = *pte & (~PTE_A);
    }
    va += PGSIZE;
  }

  if(copyout(p->pagetable, mask_addr, (char*)(&mask), sizeof(mask)) < 0)
    return -1;

  return 0;
}

自行寻找页表项版

uint64 sys_pgaccess(void) {
  uint64 va, dstva, mask = 0L;
  int i, level, checknum;
  struct proc *p;
  pagetable_t pagetable;
  pte_t *pte;

  if(argaddr(0, &va) < 0 || argint(1, &checknum) < 0 || argaddr(2, &dstva) < 0) 
    return -1;

  if(va >= MAXVA || checknum > 64)
    return -1;

  va = PGROUNDDOWN(va);

  p = myproc();

  for(i = 0; i < checknum; ++i) {
    pagetable = p->pagetable;
    for(level = 2; level > 0; level--) {
      pte = &pagetable[PX(level, va)];
      pagetable = (pagetable_t)PTE2PA(*pte);
    }
    pte = &pagetable[PX(0, va)];
    if(*pte & PTE_A) {
      mask |= 1 << i;
      *pte &= (~PTE_A);
    }
    va += PGSIZE;
  }

  if(copyout(p->pagetable, dstva, (char *)&mask, (uint64)(sizeof(mask))) < 0)
    return -1;
  return 0;
}

#define PTE_A (1L << 5) // x


文章作者: AthenaCrafter
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 AthenaCrafter !
  目录