[mit6.s081] 笔记 Lab3: page tables

目录

  • 前言
  • Speed up system calls (easy)
    • kernel/proc.h
    • kernel/proc.c
    • 验收
  • Print a page table (easy)
    • kernel/vm.c
    • 验收
  • Detect which pages have been accessed (hard)
    • kernel/sysproc.c
    • kernel/vm.c
    • 验收

前言

这个实验,我们可以了解page table的实现机制,理解源码后,做起来还是相对容易的

Speed up system calls (easy)

When each process is created, map one read-only page at USYSCALL (a VA defined in memlayout.h). At the start of this page, store a struct usyscall (also defined in memlayout.h), and initialize it to store the PID of the current process. For this lab, ugetpid() has been provided on the userspace side and will automatically use the USYSCALL mapping. You will receive full credit for this part of the lab if the ugetpid test case passes when running pgtbltest.

这个lab是想让你在用户空间和内核之间共享只读区域中的数据,也就是映射一个页表,用来存储进程的pid,此时当你想要获取pid时,就可以在该映射区域直接获取,而不用去切换到内核态,从而实现加速的功能。

kernel/proc.h

  • 在进程结构体里添加一个 struct usyscall*类型的变量,用来保存页表地址
// Per-process state
struct proc {
  struct spinlock lock;
     ·····················//省略
  // these are private to the process, so p->lock need not be held.
	 ·····················//省略
  char name[16];               // Process name (debugging)
  struct usyscall*usyscall;  //USYSCALL
};

kernel/proc.c

  • usyscall分配页表
// 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)
{
  ················//省略

  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;
  }
	····················//省略
  return p;
}
  • 需要在该函数下的proc_pagetable()中执行映射,完成映射关系
// 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;
	````````````````````//省略
  if (mappages(pagetable, USYSCALL, PGSIZE,
               (uint64)(p->usyscall), PTE_R | PTE_U) < 0)
  {
    uvmunmap(pagetable, USYSCALL, 1, 0);
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }
  return pagetable;
}
  • 在释放进程的过程中要将分配给usyscall的page释放掉,将页表插入空闲页链表中
// 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->usyscall);
  p->usyscall=0;
  ···················//省略
}
  • 解除映射关系
// Free a process's page table, and free the
// physical memory it refers to.
void proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
  uvmunmap(pagetable, USYSCALL, 1, 0);
  uvmunmap(pagetable, TRAMPOLINE, 1, 0);
  uvmunmap(pagetable, TRAPFRAME, 1, 0);
  uvmfree(pagetable, sz);
}

验收

这个任务大致就完成了
启动xv6,执行pgtbltest

$ pgtbltest
ugetpid_test starting
ugetpid_test: OK

成功

Print a page table (easy)

Define a function called vmprint(). It should take a pagetable_t argument, and print that pagetable in the format described below. Insert if(p->pid==1) vmprint(p->pagetable) in exec.c just before the return argc, to print the first process’s page table. You receive full credit for this part of the lab if you pass the pte printout test of make grade.

  • 该任务是想让你实现vmprint()函数,作用时在打印第一个进程的页表
    当你启动xv6时,自动打印出来,效果如下

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

这个实验也不是很难,采用了一个dfs的思想,当你了解了xv6的页表机制后,你会很快将他做出来

kernel/vm.c

void vmprint(pagetable_t pagetable, uint64 lv)
{
  char *vmarr[4];
  vmarr[0] = "..";
  vmarr[1] = ".. ..";
  vmarr[2] = ".. .. ..";
  if (lv > 2)
    return;
  if (lv == 0)
  {
    printf("page table %p\n", pagetable);
  }
  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", vmarr[lv], i, pte, child);
      vmprint((pagetable_t)child, lv + 1);
    }
  }
}

分析:xv6采用的是三级页表机制,所以每个页表我们需要递归两次,一个页表右512个pte

验收

以上就是该实验的全部
输入make grade

== Test pte printout ==
$ make qemu-gdb
pte printout: OK (0.5s)

成功

Detect which pages have been accessed (hard)

Your job is to implement pgaccess(), a system call that reports which pages have been accessed. The system call takes three arguments. First, it takes the starting virtual address of the first user page to check. Second, it takes the number of pages to check. Finally, it takes a user address to a buffer to store the results into a bitmask (a datastructure that uses one bit per page and where the first page corresponds to the least significant bit). You will receive full credit for this part of the lab if the pgaccess test case passes when running pgtbltest.

  • 该实验是hard难度,但其实并没有很难,在做这个实验时,需要仔细阅读walk()函数

kernel/sysproc.c

  • 需要在sysproc.c当中实现 sys_pgaccess()函数
int sys_pgaccess(void)
{
  // lab pgtbl: your code here.
  uint64 addr;
  int len;
  int bitmask;
  //获取当前进程
  struct proc *p = myproc();
  if (argint(1, &len) < 0)
  {
    return -1;
  }
  if (argaddr(0, &addr) < 0)
  {
    return -1;
  }
  if (argint(2, &bitmask) < 0)
  {
    return -1;
  }
  int res = 0;
  for (int i = 0; i < len; i ++)
  {
    int va=addr+i*PGSIZE;
    int abit = vm_pgacess(p->pagetable, va);
    res = res | abit << i; //置在那一位上
  }
  if (copyout(p->pagetable, bitmask, (char *)&res, sizeof(res)) < 0)
    return -1;
  return 0;
}

分析:

  • 因为系统调用无法直接通过参数列表获取参数,所以我们只能通过调用函数
    argint(),argaddr(),函数从用户层获取参数
  • 每一位我们都需要进行判断,判断该页是否被访问,所以我们要采用循环
  • 判断改页是否被访问是调用函数vm_pgacess()函数,该函数是我们自己实现,详解在后面
  • 最后结果我们需要调用函数copyout()返回给用户层

kernel/vm.c

  • 我将vm_pgacess()函数的实现写在了 kernel/vm.c里面(你也可以写在其他文件里面,记得声明就可以了)
int vm_pgacess(pagetable_t pagetable, uint64 va)
{
  pte_t *pte;

  if (va >= MAXVA)
    return 0;
  pte = walk(pagetable, va, 0);
  if (pte == 0)
    return 0;
  if ((*pte & PTE_V) == 0)
    return 0;
  if ((*pte & PTE_A) != 0)
  {
    *pte = *pte & (~PTE_A);//置零
    return 1;
  }
  return 0;
}
  • 注意,检测到PTE_V不为零后,要将他重新置为0,避免永久设置

验收

启动xv6,执行pgtbltest

$ pgtbltest
ugetpid_test starting
ugetpid_test: OK
pgaccess_test starting
pgaccess_test: OK
pgtbltest: all tests succeeded

你可能感兴趣的:(mit6.s081学习笔记,操作系统)