[6.s081]Lab Page Tables

Overview

This lab teaches us the memory layout of xv6, how the kernel and user page table works and how to allocate pages when creating a new process. https://pdos.csail.mit.edu/6.S081/2021/labs/pgtbl.html

Speed up system calls

We can allocate a dedicated page when creating a process and write some data into it in the kernel mode for speeding up the system calls. This way we don't need to copy in/out the data between kernel and user space when do the system calls. Kind of trade space for time efficiency.

Allocate a Page when Creating the Process

static struct proc*
allocproc(void)
{
    // ... some code 
    // Allocate a usyscall page.
    if((p->usyscall = (uint64)kalloc()) == 0) {
        freeproc(p);
        release(&p->lock);
        return 0;
    }
    u.pid = p->pid;
    *(struct usyscall *)p->usyscall = u;
  // ... some code

}

Free the Memory when Destroying a Process

static void
freeproc(struct proc *p)
{
    // ... some code
    if(p->usyscall) 
        kfree((void*)p->usyscall);
    // ... some code
}

Map the Page when Initializing the Page Table

pagetable_t
proc_pagetable(struct proc *p)
{
    // ... some code
    if(mappages(pagetable, USYSCALL, PGSIZE, p->usyscall, PTE_R | PTE_W | PTE_U) < 0) {
        uvmunmap(pagetable, TRAMPOLINE, 1, 0);
        uvmunmap(pagetable, TRAPFRAME, 1, 0);
        uvmfree(pagetable, 0);
    }
  return pagetable;
}

Traverse the page table like walk() or freewalk() function:

// vm.c
void
walk_print(pagetable_t page, int level)
{
    for(int i = 0; i < 512; ++i) {
        pte_t pte = page[i];
        // child PTE exist and is also a PTE
        if((pte & PTE_V) && (pte & (PTE_R | PTE_W | PTE_X)) == 0) {
            uint64 child = PTE2PA(pte);
            for(int j = 0; j < level; ++j) {
                printf(".. ");
            }
            printf("..");
            printf("%d: pte %p pa %p\n", i, pte, child);
            walk_print((pagetable_t)child, level+1);
        } else if(pte & PTE_V) {
            uint64 child = PTE2PA(pte);
            for(int j = 0; j < level; ++j) {
                printf(".. ");
            }
            printf("..");
            printf("%d: pte %p pa %p\n", i, pte, child);
        } 
    }
}

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

Detecting which pages have been accessed

Iterate all the pages one by one to check the PTE_A bit, and remember to reset it after you get a postive results:

int
sys_pgaccess(void)
{
  // lab pgtbl: your code here.
    uint64 fva;
    int pnum;
    uint64 abits;
    int res = 0;    
    pte_t* pte_addr;
    pte_t pte;
    // read the arguments from the stack
    if(argaddr(0, &fva) < 0) {
        return -1;
    }
    if(argint(1, &pnum) < 0) {
        return -1;
    }
    if(argaddr(2, &abits) < 0) {
        return -1;
    }

    pagetable_t pagetable = myproc()->pagetable;
    for(int i = 0; i < pnum; ++i) {
        pte_addr = walk(pagetable, fva, 0);
        pte = *pte_addr;
        if(pte & PTE_A) {
            (*pte_addr) = pte & ~(PTE_A);
            res |= (1 << i);
        }
        fva += PGSIZE;
    }
    if(copyout(pagetable, abits, (char *)&res, sizeof(res))<0) {
        return -1;
    }   
  return 0;
}