虚拟内存相关,说实话之前一直不明白 multi level page table,这次由于调 function 所以也还是没太明白 o.O
一开始纠结了很久,发现有好多都不一样,结果发现是 2021 新增的题目和内容,用的 20 版的就做不了了 o.O

Speed up system calls

通过避免 user space, kernel space 之间的切换,来加速 syscall
通过在初始化 process 时候,在 process(user space) 里面储存 pid 来实现。

实现时,在 proc 里添加指向 usyscall 结构体的指针,里面存 pid。于是在初始化 proc 时候就要多初始化一个 usyscall。并且也要 mappagetable 里面。那 kalloc 之后肯定还要 free ,最后记得在 freeproc 里面一起 free 掉就行。

有一点比较坑是忘记 unmap pagetable 了,报 freewalk panic,不过 grep 一下找到函数就想起来忘记 unmap,就改好了。

总的来说有思路之后实现起来没难度。

# kernel/memlayout.h
#define USYSCALL (TRAPFRAME - PGSIZE)
 
struct usyscall {
  int pid;  // Process ID
};
 
# kernel/proc.h
struct proc {
  # ...
  # HERE!
  struct usyscall *usys;        // data page for system calls
};
 
# kernel/proc.c
static struct proc *allocproc(void) {
	# ... 
 
    // Set up new context to start executing at forkret,
    // which returns to user space.
    memset(&p->context, 0, sizeof(p->context));
    p->context.ra = (uint64)forkret;
    p->context.sp = p->kstack + PGSIZE;
 
	# HERE!
    p->usys->pid = p->pid;
 
    return p;
}
 
static void freeproc(struct proc *p) {
	# ...
    if (p->usys)
        kfree((void *)p->usys);
	# ...
}
 
 
pagetable_t proc_pagetable(struct proc *p) {
	# ...
 
	# HERE!
    // map the USYSCALL below the TRAPFRAME
    if (mappages(pagetable, USYSCALL, PGSIZE, (uint64)(p->usys),
                 PTE_R | PTE_U) < 0) {
        uvmunmap(pagetable, TRAMPOLINE, 1, 0);
        uvmunmap(pagetable, TRAPFRAME, 1, 0);
        uvmfree(pagetable, 0);
        return 0;
    }
 
    return pagetable;
}
 
void proc_freepagetable(pagetable_t pagetable, uint64 sz) {
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmunmap(pagetable, TRAPFRAME, 1, 0);
	# HERE !
    uvmunmap(pagetable, USYSCALL, 1, 0);
    uvmfree(pagetable, sz);
}

Bonus

Which other xv6 system call(s) could be made faster using this shared page? Explain how.

理论上所有需要从 kernel space copyout 的 syscall 都可以通过在 userspace 存数据来加速。
或者需要从 kernel 取不敏感数据的也可以?但是这个不敏感比较难区分,pid 似乎就符合这个条件。

那个图一眼递归。
主要是那个输出格式不太好搞,在格式上花了最多时间

思路是抄 freewalk,用一个没有 break 的 switch, 通过 fall through 来输出格式,如果有下一层就递归下去,抄就完事,非常简单
然后卡了半天 grade,最后发现是输出时候多了一个空格,不知道为什么 diff 不给我提示出来
在嘲笑 JS 垃圾之前先嘲笑 C 连 bool 都没有.jpg

void vmprint_helper(pagetable_t pagetable, int level) {
    if (level == -1) {
        return;
    }
 
    for (int i = 0; i < 512; i++) {
        pte_t pte = pagetable[i];
        int is_valid = (pte & PTE_V);
        if (!is_valid) {
            continue;
        }
        uint64 pa = PTE2PA(pte);
 
        switch (level) {
          // Fall through
          case 0:
            printf(" ..");
          case 1:
            printf(" ..");
          case 2:
            printf(" ..");
            printf("%d: pte %p pa %p\n", i, pte, pa);
        }
 
        if ((pte & (PTE_R | PTE_W | PTE_X)) == 0) {
            // this PTE points to a lower-level page table.
            vmprint_helper((pagetable_t)pa, level - 1);
        } 
    }
}
void vmprint(pagetable_t pagetable) {
    printf("page table %p\n", pagetable);
 
    vmprint_helper(pagetable, 2);
}

Detecting which pages have been accessed

不知道这有啥 hard 的,signature 都给完了,辅助函数 walk 也有了,一开始我还以为是要自己设置 access bit,结果是 hardware 设置,那就简单了,唯一注意一下加缺失的函数到 def.h
倒是这位运算有点 CSAPP Lab1 的感觉了,笑死

首先先传参到 syscall,检查参数,通过真正的 generic void * 丢给 pgaccess,检查 access bit 之后再 copyout,不是很 hard。

# kernel/riscv.h
#define PTE_A (1L << 6) // Access bit
 
# kernel/sysproc.c
int sys_pgaccess(void) {
    // lab pgtbl: your code here.
    // 1. starting VA of the first user page to check
    // 2. number of page to check
    // 3. user address to a buffer to store the results into a bitmask
 
    uint64 va;
	if (argaddr(0, &va) < 0)
		return -1;
    int page_to_check;
	if (argint(1, &page_to_check) < 0)
		return -1;
    if (page_to_check > 64) {
        return -1;
    }
    uint64 user_addr;
	if (argaddr(2, &user_addr) < 0)
		return -1;
    return pgaccess((void *)va, page_to_check, (unsigned int *)user_addr);
}
 
# kernel/proc.c
int pgaccess(void *base, int len, void *mask) {
    struct proc *p = myproc();
    if (p->pagetable == 0)
        return -1;
 
    pagetable_t pagetable = p->pagetable;
    uint64 va = (uint64)base;
    uint64 bitmask = 0;
    for (int i = 0; i < len; i++) {
        pte_t *pte = walk(pagetable, (PGSIZE * i + va), 0);
        if (pte == 0) { // not mapped
            return -1;
        }
        if (*pte & PTE_A) {
            bitmask |= (1 << i);
            *pte &= ~PTE_A; // clear access bit
        }
    }
 
    return copyout(pagetable, (uint64)mask, (char *)&bitmask, sizeof(uint64));
}

grade

fail 是因为缺少答案文件对比,前面的测试都过了
image.png

总结

说实话我觉得这次实验设计的不太好,somehow 提到了 pagetable 却没有提到 TLB(还是在手册提了?忘了),并且由于用于访问的函数都给好了所以对多级 pagetable 的理解并没有很深
而且感觉不如 CSAPP LAB 的实现 malloc,那个是真的难顶