Yra's blog Yra's blog
Homepage
  • LeetCode周赛
  • Acwing周赛
  • 刷题整理
  • 题解
  • CPP
  • Golang
  • System

    • MIT6.S081 Labs
  • Computer Networking

    • CS144: Computer Networking
  • DataBase

    • MySQL
    • Redis
  • Others

    • ......
  • Paper Reading

    • Petri Net
  • Static Analysis

    • NJU Course Notes
  • Deep Learning

    • Dive into Deep Learning
Casual Records
Archives

Yra

Only a vegetable dog.
Homepage
  • LeetCode周赛
  • Acwing周赛
  • 刷题整理
  • 题解
  • CPP
  • Golang
  • System

    • MIT6.S081 Labs
  • Computer Networking

    • CS144: Computer Networking
  • DataBase

    • MySQL
    • Redis
  • Others

    • ......
  • Paper Reading

    • Petri Net
  • Static Analysis

    • NJU Course Notes
  • Deep Learning

    • Dive into Deep Learning
Casual Records
Archives
  • System

    • MIT6.S081 | 21Fall

      • Lab1: Unix utilities
      • Lab2: System Calls
      • Lab3: Page Tables
        • Lab4: Traps
        • Lab5: Copy-on-Write Fork for xv6
        • Lab6: Multithreading
        • Lab7: Networking
        • Lab8: Locks
        • Lab9: File System
        • Lab10: Mmap
    • Computer Networking

    • DataBase

    • Software Engineering

    • Others

    • Learning Notes
    • System
    • MIT6.S081 | 21Fall
    Yra
    2023-02-08
    目录

    [MIT6.S081]Lab3: Page Tables

    # [MIT6.S081]Lab3: Page Tables

    Lab: page tables (mit.edu) (opens new window)

    # 1、Speed up system calls

    这个 assignment 中我们要为 getpid 这个系操作加快速度,因为系统调用需要我们保存上下文,还要从用户态切换到内核态,因此会损耗很多时间。于是我们为每个进程新分配了一个页,其虚拟地址位于 USYSCALL,就在 TRAPFRAME 的下面,并且在这个页的开头用 struct usyscall 保存了进程的 pid。

    我们先要在 proc.h 中添加一个 struct usyscall 类型的变量

    struct usyscall *usyscall;
    

    接着我们在 proc.c 中进行我们的操作,我们先要在进程初始化的时候,去分配一个新页,并将进程的 pid 赋值给 usyscall,在 allocproc 中加入

    if((p->usyscall = (struct usyscall *)kalloc()) == 0){
      freeproc(p);
      release(&p->lock);
      return 0;
    }
    p->usyscall->pid = p->pid;
    

    之后我们在 proc_pagetable 中添加映射

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

    可以直接模仿着 trapframe 的写法,需要注意的是如果映射失败的话在解除映射时要把 TRAMPLINE 和 TRAPFRAME 也解除了。

    接着在释放 page 的时候我们也要接触和 USYSCALL 的映射

    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);
    }
    

    还有释放进程的时候也要把分配的空间回收了,在 freeproc 中加入

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

    最后还有要注意的一点,也是我找了半天的问题,就是在我们要在调用 proc_pagetable 来为进程创建用户页表之前分配 page 给 usyscall,不然没法正确的添加映射,毕竟 page 都没分配,怎么映射。

    # 2、Print a page table

    这个就比较简单了,要在 exec.c return argc 之前打印 pid = 1 的进程页表。

    我们先在 exec.c return 前添加函数

    if (p->pid == 1) {
    	vmprint(p->pagetable);
    }
    
    return argc; // this ends up in a0, the first argument to main(argc, argv)
    

    将 vmprint 函数进行声明,在 defs.h 中

    void vmprint(pagetable_t);
    

    之后我们在 vm.c 中实现该函数,

    根据 hints,我们可以仿照函数 freewalk 来写,显然那我们需要用到递归。

    我们先打印不需要加入递归的第一行,之后调用另外实现的递归打印函数即可。

    void vmprint(pagetable_t pagetable) {
      printf("page table %p\n", pagetable);
      recprint(pagetable, 1);
    }
    

    recprint 函数传递了两个参数,一个是 pagetable_t,还有一个便是递归的层数,因为我们要通过打印 .. 来表示我们在树中的深度。

    void recprint(pagetable_t pagetable, int cnt) {
      for (int i = 0; i < 512; i++) { // 遍历页表
        pte_t pte = pagetable[i]; // 取出 PTE
        if (pte & PTE_V) { // 如果是合法的,则需要打印,先打印 层数 - 1 个 ".. ",带有空格!而最后一个没有空格。
          for (int j = 0; j < cnt - 1; j++) {
            printf(".. ");
          }
          printf("..%d: pte %p pa %p\n", i, pte, PTE2PA (pte)); // 打印最后的 .. 和相关的信息,调用了 riscv.h 中的 PTE2A 函数
          if ((pte & (PTE_R|PTE_W|PTE_X)) == 0) { // 如果不是叶子,则需要继续递归打印下去,只有叶子才有这些标志位
            recprint((pagetable_t)PTE2PA(pte), cnt + 1);
          }
        }
      }
    }
    

    具体看代码注释就好了。

    # 3、Detecting which pages have been accessed

    这个 assignment 也比较简单,首先我们根据 riscv 的手册,可以知道 PTE_A 位于 1L << 6 处

    image-20230209165946270

    因此我们在 riscv.h 中添加定义

    #define PTE_A (1L << 6) // whether have been accessed
    

    之后我们直接在 sysproc.c 中实现即可

    #ifdef LAB_PGTBL
    int
    sys_pgaccess(void)
    {
      // lab pgtbl: your code here.
      uint64 va;
      int len;
      uint64 des_addr;
      if (argaddr(0, &va) < 0)
        return -1;
      if (argint(1, &len) < 0)
        return -1;
      if (argaddr(2, &des_addr) < 0)
        return -1;
      
      int res = 0;
      for (int i = 0; i < len; i++) {
        if (va >= MAXVA) {
          return -1;
        }
        pte_t *pte = walk(myproc()->pagetable, va, 0);
        if (*pte == 0) return -1;
        if (*pte & (PTE_A)) {
          res |= (1 << i);
          *pte &= (~PTE_A);
        } 
        va += PGSIZE;  
      }
    
      if (copyout(myproc()->pagetable, des_addr, (char *)&res, sizeof(res)) < 0) {
        return -1;
      };
    
      return 0;
    }
    #endif
    

    首先要保证虚拟地址是合法的,也就是 va < MAXVA

    接着我们每次用 walk 取出 va 对应的 PTE,记得要用指针,因为后面要修改 PTE。

    res |= (1 << i); 用来表示保存答案,*pte &= (~PTE_A); 表示将 PTE_A 标志位置为 0。

    最后再用 copyout 将答案传到用户态即可。

    # 实验结果


    image-20230209171347404
    #Learning Notes#System#6.S081
    Last Updated: 3/4/2023, 5:38:14 PM
    Lab2: System Calls
    Lab4: Traps

    ← Lab2: System Calls Lab4: Traps→

    最近更新
    01
    408 计组笔记
    07-19
    02
    Dive into Deep Learning
    01-27
    03
    25 考研随记
    11-27
    更多文章>
    Theme by Vdoing | Copyright © 2022-2025
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式