MIT 6.S081 Lab3

前面我们修改了原有的分支仓库名, origin -> old-origin,目的就是为了将完成后的实验推送到origin处。使用git branch命令观察远程分支可以看到如下输出。

bash 复制代码
$ git branch -r
  old-origin/cow
  old-origin/fs
  old-origin/lock
  old-origin/mmap
  old-origin/net
  old-origin/pgtbl
  old-origin/riscv
  old-origin/syscall
  old-origin/thread
  old-origin/traps
  old-origin/util
  origin/pgtbl
  origin/syscall

现在我们想要在old-origin/pgtbl基础上,新建一个pgtbl的本地分支并推送到origin仓库,可以按下面步骤操作:

sql 复制代码
$ git checkout old-origin/pgtbl 先切换到old-origin/pgtbl分支
...
HEAD is now at 1e6b2de pgtbl lab: initial commit
$ git checkout -b pgtbl 在此分支基础上创建本地分支pgtbl
Switched to a new branch 'pgtbl'
$ git push -u origin pgtbl 推送到远程仓库
...
 * [new branch]      pgtbl -> pgtbl
branch 'pgtbl' set up to track 'origin/pgtbl'.

再次使用git branch命令应该可以看到origin/pgtbl

bash 复制代码
$ git branch -r
  old-origin/cow
  old-origin/fs
  old-origin/lock
  old-origin/mmap
  old-origin/net
  old-origin/pgtbl
  old-origin/riscv
  old-origin/syscall
  old-origin/thread
  old-origin/traps
  old-origin/util
  origin/pgtbl
  origin/syscall
  origin/util

Lab: page tables

这一关的代码量不多,主要是对内核空间、内存管理的理解。

Speed up system calls

为了减少因系统调用引起的上下文切换、内核态转换所带来的开销,这里为用户进程和其内核态分配一个共享page,page内容在内核下设置,在用户进程下可以读取内容。

这一关加速的是getpid()调用,使进程在用户态下可以直接读取当前进程pid。

在kernel/proc.h中添加共享页面的定义:

arduino 复制代码
// Per-process state
struct proc {
  ...
  struct usyscall *usyscall;   // 共享 page
  ...
};

在kernel/proc.c中,进程创建页表时为USYSCALL映射物理页面

mappages:这是一个函数,用于将虚拟地址映射到物理地址。它的参数通常包括:

  • pagetable:要修改的页表。
  • USYSCALL:映射的起始虚拟地址。
  • PGSIZE:映射的页面大小。
  • (uint64) (p->usyscall) :要映射的物理地址,这里从进程结构 p 中获取 usyscall 字段。
  • PTE_R | PTE_U :页表项的属性,这里设置为可读(PTE_R)和用户可访问(PTE_U)。
scss 复制代码
// Create a user page table for a given process,
// with no user memory, but with trampoline pages.
pagetable_t
proc_pagetable(struct proc *p)
{
  ...

  // map the trapframe just below TRAMPOLINE, for trampoline.S.
  if (mappages(pagetable, TRAPFRAME, PGSIZE,
               (uint64)(p->trapframe), PTE_R | PTE_W) < 0)
  {
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }

  // 映射共享页面
  if (mappages(pagetable, USYSCALL, PGSIZE,
               (uint64)(p->usyscall), PTE_R | PTE_U) < 0)
  {
    uvmunmap(pagetable, TRAPFRAME, 1, 0);
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }

  return pagetable;
}

经过上一步我们可以在用户进程中通过访问USYSCALL来获取**p->usyscall**

在kernel/proc.c创建进程时,开辟并初始化共享页面,使其真正指向一片内存区域,并将pid保存在共享页面中

rust 复制代码
static struct proc *
allocproc(void)
{
  ...
  // 共享页面
  if ((p->usyscall = (struct usyscall *)kalloc()) == 0)
  {
    freeproc(p);
    release(&p->lock);
    return 0;
  }
  ...
  // 保存pid
  p->usyscall->pid = p->pid;

  return p;
}

/kernel/proc.c 在释放进程时,释放共享页面

scss 复制代码
static void
freeproc(struct proc *p)
{
  ...
  // 释放共享页面
  if (p->usyscall)
  {
    kfree((void *)p->usyscall);
  }
  p->usyscall = 0;
  ...
}

/kernel/proc.c 在释放进程页表时,移除对应的页面

scss 复制代码
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);
}

Print a page table

要求实现打印页表,并在exec中执行。

/kernel/defs.h 中定义函数

arduino 复制代码
//vm.c
void            vmprint(pagetable_t);

/kernel/exec.c 中,在执行pid为1的进程时打印页表:

arduino 复制代码
int exec(char *path, char **argv)
{
  ...
  // 当pid == 1时 打印页表
  if (p->pid == 1)
  {
    vmprint(p->pagetable);
  }
  return argc; // this ends up in a0, the first argument to main(argc, argv)

bad:
    ...
}

/kernel/vm.c 中,实现打印页表逻辑,就是一个简单的递归实现深度优先遍历,根据层级调整输出格式,并通过页表有效位PTE_V判断是否打印,页表的读写执行权限位判断是否为叶子结点:

scss 复制代码
int pgtblprint(pagetable_t pagetable, int depth) {
  // 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) { // 如果页表项有效
      // 按格式打印页表项
      printf("..");
      for(int j=0;j<depth;j++) {
        printf(" ..");
      }
      printf("%d: pte %p pa %p\n", i, pte, PTE2PA(pte));

      // 如果该节点不是叶节点,递归打印其子节点。
      if((pte & (PTE_R|PTE_W|PTE_X)) == 0){
        // this PTE points to a lower-level page table.
        uint64 child = PTE2PA(pte);
        pgtblprint((pagetable_t)child,depth+1);
      }
    }
  }
  return 0;
}

// 打印页表
void vmprint(pagetable_t root)
{
  printf("page table %p\n", root);
  pgtblprint(root, 0);
}

Detecting which pages have been accessed

要求实现pgaccess()系统调用,用于获取哪些页表已被访问,接收3个参数:

  1. 首先,接收要检查的第一个用户页面的起始虚拟地址。 va
  2. 其次,它接收要检查的页面数量。 pnum
  3. 最后,它接收一个用户地址,指向一个缓冲区,用于将结果存储到位掩码中。 Ua

/kernel/riscv.h中定义访问位PTE_A

arduino 复制代码
#define PTE_V (1L << 0) // valid
#define PTE_R (1L << 1)
#define PTE_W (1L << 2)
#define PTE_X (1L << 3)
#define PTE_U (1L << 4) // 1 -> user can access
#define PTE_A (1L << 6) // 1 -> 页面被访问过

/kernel/sysproc.c中实现sys_pgaccess()

这里要使用walk(),先声明。标记的过程并不复杂,主要是综合使用。

scss 复制代码
extern pte_t *walk(pagetable_t, uint64, int);

#ifdef LAB_PGTBL
int sys_pgaccess(void)
{
  uint64 va, ua;
  int pnum; /* pnum-扫描页面数 */
  // get args
  if (argaddr(0, &va) < 0 ||
      argint(1, &pnum) < 0 ||
      argaddr(2, &ua) < 0)
    return -1;
  // 若扫描的页大于PGSIZE*8 返回-1
  if (pnum > 8*PGSIZE)
    return -1;
  // 开辟缓冲区
  char *buf = kalloc();
  // 初始化缓冲区
  memset(buf, 0, PGSIZE);
  //依次扫描页面
  for(int i=0;i<pnum;i++){
    pte_t *p = walk(myproc()->pagetable, va + i*PGSIZE, 0);
    if(*p & PTE_A){
      // 访问过,标记并重置
      buf[i/8] |= 1<<(i%8);
      *p &= ~PTE_A;
    }
  }
  //结果传递给用户空间
  copyout(myproc()->pagetable, ua, buf, pnum);
  //释放页面
  kfree(buf);
  return 0;
}
#endif

实验完成

make grade 验证

ini 复制代码
== Test pgtbltest == 
$ make qemu-gdb
(5.3s) 
== Test   pgtbltest: ugetpid == 
  pgtbltest: ugetpid: OK 
== Test   pgtbltest: pgaccess == 
  pgtbltest: pgaccess: OK 
== Test pte printout == 
$ make qemu-gdb
pte printout: OK (1.0s) 
== Test answers-pgtbl.txt == answers-pgtbl.txt: FAIL 
    Cannot read answers-pgtbl.txt
== Test usertests == 
$ make qemu-gdb
相关推荐
OpenAnolis小助手3 小时前
龙蜥副理事长张东:加速推进 AI+OS 深度融合,打造最 AI 的服务器操作系统
ai·开源·操作系统·龙蜥社区·服务器操作系统·anolis os
小蜗的房子1 天前
SQL Server 2022安装要求(硬件、软件、操作系统等)
运维·windows·sql·学习·microsoft·sqlserver·操作系统
邂逅岁月3 天前
【多线程奇妙屋】 Java 的 Thread类必会小技巧,教你如何用多种方式快速创建线程,学并发编程必备(实践篇)
java·开发语言·操作系统·线程·进程·并发编程·javaee
CXDNW4 天前
【系统面试篇】进程和线程类(1)(笔记)——区别、通讯方式、同步、互斥、死锁
笔记·操作系统·线程·进程·互斥·死锁
掘了5 天前
持久化内存 | Persistent Memory
c++·架构·操作系统
结衣结衣.6 天前
【Linux】掌握库的艺术:我的动静态库封装之旅
linux·运维·服务器·c语言·操作系统·
直接冲冲冲6 天前
操作系统第二章-(2.1进程与线程)
操作系统
周珂呀10 天前
Linux 命令行学习:数据流控制、文本处理、文件管理与自动化脚本 (第二天)
linux·前端·chrome·操作系统·终端·基础知识
编程老船长11 天前
献给刚入学的大学生-如何使用微PE工具箱或其他工具制作 Windows 11 U 盘启动盘
windows·操作系统