• 作者:老汪软件技巧
  • 发表时间:2024-11-05 15:02
  • 浏览量:

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

$ 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仓库,可以按下面步骤操作:

$ 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

$ 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中添加共享页面的定义:

// Per-process state
struct proc {
  ...
  struct usyscall *usyscall;   // 共享 page
  ...
};

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

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

// 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保存在共享页面中

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 在释放进程时,释放共享页面

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

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

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 中定义函数

//vm.c
void            vmprint(pagetable_t);

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

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判断是否打印,页表的读写执行权限位判断是否为叶子结点:

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;jprintf(" ..");
      }
      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个参数:

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

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

#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(),先声明。标记的过程并不复杂,主要是综合使用。

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;ip = 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 验证

== 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


上一条查看详情 +机器学习|深度学习卷积模型
下一条 查看详情 +没有了