MIT6.S081 Lab3: page tables

本文介绍了RISC-V架构下内核如何管理页表,包括添加`vmprint`函数以打印页表,为每个进程创建内核用户页表,`kvminit`函数用于初始化内核页表,以及在进程创建和执行过程中涉及的页表操作。此外,还改进了`copyin`和`copyinstr`函数,增强了内存管理效率。
摘要由CSDN通过智能技术生成

Print a page table

接收一个pagetable_t并把它指向的页表打印。

  1. kernel/def.h中增加函数声明void vmprint(void)并在kernel/vm.c中定义:

    void vmprint(pagetable_t p) {
        printf("page table %p\n", p);
        _vmprint(p, 0);
    }
    
    // vmprint的递归实现
    void _vmprint(pagetable_t p, int level) {
        uint64 pa;
        
        for(int i = 0; i<512; ++i) {
            if(p[i] & PTE_V) {
            pa = PTE2PA(p[i]);
            switch(level) {
            case 0:
                printf("..%d: pte %p pa %p\n", i, p[i], pa);
                _vmprint((pagetable_t)pa, level+1);
                break;
            case 1:
                printf(".. ..%d: pte %p pa %p\n", i, p[i], pa);
                _vmprint((pagetable_t)pa, level+1);
                break;
            case 2:
                printf(".. .. ..%d: pte %p pa %p\n", i, p[i], pa);
                break;
            }
            }
        }
    }
    
  2. kernel/exec.c函数中增加如下字段:

    p->trapframe->epc = elf.entry;  // initial program counter = main
    p->trapframe->sp = sp; // initial stack pointer
    proc_freepagetable(oldpagetable, oldsz);
    
    // ↓↓↓↓↓↓↓↓
    if(p->pid == 1)
        vmprint(p->pagetable);
    // ↑↑↑↑↑↑↑↑
    
    return argc; // this ends up in a0, the first argument to main(argc, argv)
    

A kernel page table per processor

给进程增加一个内核级用户页表,同时拥有用户内存映射和内核内存映射,便于在用户陷入内核时对用户内存进行操作

  1. 给在kernel/proc.h结构体struct proc增加字段pagetable_t k_pagetable

    // Per-process state
    struct proc {
        struct spinlock lock;
    
        // p->lock must be held when using these:
        enum procstate state;        // Process state
        struct proc *parent;         // Parent process
        void *chan;                  // If non-zero, sleeping on chan
        int killed;                  // If non-zero, have been killed
        int xstate;                  // Exit status to be returned to parent's wait
        int pid;                     // Process ID
    
        // these are private to the process, so p->lock need not be held.
        uint64 kstack;               // Virtual address of kernel stack
        uint64 sz;                   // Size of process memory (bytes)
        pagetable_t pagetable;       // User page table
        pagetable_t k_pagetable;     // Kernel user page table // ←←←←←←←←
        struct trapframe *trapframe; // data page for trampoline.S
        struct context context;      // swtch() here to run process
        struct file *ofile[NOFILE];  // Open files
        struct inode *cwd;           // Current directory
        char name[16];               // Process name (debugging)
    };
    

    kernel/vm.c定义函数pagetable_t _kvminit(void)并在kernel/def.h中增加pagetable_t _kvminit(void)函数声明:

    pagetable_t _kvminit() {
        pagetable_t k_pagetable = (pagetable_t)kalloc();
        memset(k_pagetable, 0, PGSIZE);
        
        if(mappages(k_pagetable, UART0, PGSIZE, UART0, PTE_R|PTE_W))
            panic("mappages");
        if(mappages(k_pagetable, VIRTIO0, PGSIZE, VIRTIO0, PTE_R|PTE_W))
            panic("mappages");
        if(mappages(k_pagetable, PLIC, 0x400000, PLIC, PTE_R|PTE_W))
            panic("mappages");
        if(mappages(k_pagetable, KERNBASE, (uint64)etext-KERNBASE, KERNBASE, PTE_R|PTE_X))
            panic("mappages");
        if(mappages(k_pagetable, (uint64)etext, PHYSTOP-(uint64)etext, (uint64)etext, PTE_R|PTE_W))
            panic("mappages");
        if(mappages(k_pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R|PTE_X))
            panic("mappages");
    
        return k_pagetable;
    }
    

    更改kernel/vm.c中的kvminit函数:

    void
    kvminit()
    {
        kernel_pagetable = _kvminit();
        if(mappages(kernel_pagetable, CLINT, 0x10000, CLINT, PTE_R|PTE_W))
            panic("mappages");
    }
    
  2. 删除kernel/main.c中的procinit()函数调用、kernel/def.h中的void procinit(void)函数声明、kernel/proc.c中的procinit函数定义,在kernel/proc.c中的struct proc* allocproc(void)函数增加以下字段:

    p->pagetable = proc_pagetable(p);
    if(p->pagetable == 0){
        freeproc(p);
        release(&p->lock);
        return 0;
    }
    
    // ↓↓↓↓↓↓↓↓
    // An empty kernel user page table
    p->k_pagetable = _kvminit();
    if(p->k_pagetable == 0) {
        freeproc(p);
        release(&p->lock);
        return 0;
    }
    
    // map user's kernel stack at kernel user page table
    uint64 pa = (uint64)kalloc();
    uint64 va = KSTACK(0);
    if(mappages(p->k_pagetable, va, PGSIZE, pa, PTE_R|PTE_W))
        panic("mappages");
    p->kstack = va;
    // ↑↑↑↑↑↑↑↑
    
    // 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;
    
  3. kernel/proc.cvoid scheduler(void)函数中增加以下字段:

    p->state = RUNNING;
    c->proc = p;
    
    // ↓↓↓↓↓↓↓↓
    w_satp(MAKE_SATP(p->k_pagetable));
    sfence_vma();
    
    swtch(&c->context, &p->context);
    
    kvminithart();
    // ↑↑↑↑↑↑↑↑
    
    // Process is done running for now.
    // It should have changed its p->state before coming back.
    c->proc = 0;
    
    found = 1;
    
  4. kernel/proc.c中定义void proc_freekpagetable(pagetable_t pagetable, uint64 sz)函数,在kernel/def.h中增加void proc_freekpagetable(pagetable_t, uint64)函数声明,并在void freeproc(struct proc* p)函数中增加以下字段:

    void proc_freekpagetable(pagetable_t pagetable, uint64 sz) {
        uvmunmap(pagetable, TRAMPOLINE, 1, 0);
        uvmunmap(pagetable, (uint64)etext, (PHYSTOP-(uint64)etext)/PGSIZE, 0);
        uvmunmap(pagetable, KERNBASE, ((uint64)etext-KERNBASE)/PGSIZE, 0);
        uvmunmap(pagetable, PLIC, 0x400000/PGSIZE, 0);
        uvmunmap(pagetable, VIRTIO0, 1, 0);
        uvmunmap(pagetable, UART0, 1, 0);
        // uvmunmap(pagetable, 0, PGROUNDUP(sz)/PGSIZE, 0);
    
        uvmfree(pagetable, 0);
    }
    
    // free a proc structure and the data hanging from it,
    // including user pages.
    // p->lock must be held.
    static void
    freeproc(struct proc *p)
    {
        if(p->trapframe)
            kfree((void*)p->trapframe);
        p->trapframe = 0;
        // ↓↓↓↓↓↓↓↓
        if(p->kstack)
            uvmunmap(p->k_pagetable, p->kstack, 1, 1);
        p->kstack = 0;
        // ↑↑↑↑↑↑↑
        if(p->pagetable)
            proc_freepagetable(p->pagetable, p->sz);
        p->pagetable = 0;
        // ↓↓↓↓↓↓↓↓
        if(p->k_pagetable)
            proc_freekpagetable(p->k_pagetable, p->sz);
        p->k_pagetable = 0;
        // ↑↑↑↑↑↑↑↑
        p->sz = 0;
        p->pid = 0;
        p->parent = 0;
        p->name[0] = 0;
        p->chan = 0;
        p->killed = 0;
        p->xstate = 0;
        p->state = UNUSED;
    }
    
  5. kernel/vm.c中增加以下字段,并对uint64 kvmpa(uint64 pa)函数做如下改动:

    #include "param.h"
    #include "types.h"
    #include "memlayout.h"
    #include "elf.h"
    #include "riscv.h"
    #include "defs.h"
    #include "fs.h"
    // ↓↓↓↓↓↓↓↓
    #include "spinlock.h" 
    #include "proc.h"
    // ↑↑↑↑↑↑↑↑
    
    uint64
    kvmpa(uint64 va)
    {
        uint64 off = va % PGSIZE;
        pte_t *pte;
        uint64 pa;
        
        pte = walk(myproc()->k_pagetable, va, 0); // ←←←←←←←←
        if(pte == 0)
            panic("kvmpa");
        if((*pte & PTE_V) == 0)
            panic("kvmpa");
        pa = PTE2PA(*pte);
        return pa+off;
    }
    

Simplify copyin/copyinstr

  1. kernel/def.h增加以下字段:

    // vmcopyin.c
    int             copyin_new(pagetable_t, char*, uint64, uint64);
    int             copyinstr_new(pagetable_t, char*, uint64, uint64);
    

    更改kernel/vm.ccopyincopyinstr函数实现:

    int
    copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
    {
        return copyin_new(pagetable, dst, srcva, len);
    }
    
    int
    copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
    {
        return copyinstr_new(pagetable, dst, srcva, max);
    }
    
  2. kernel/vm.c中定义kvmcopymap函数并在kernel/def.h中声明:

    void kvmcopymap(pagetable_t pagetable, pagetable_t k_pagetable, uint64 start, uint64 sz) {
        pte_t* pte, *k_pte;
    
        for(int i = start; i<start+sz; i+= PGSIZE) {
            pte = walk(pagetable, i, 0);
            if(!pte)
            panic("kvmcopymap");
            k_pte = walk(k_pagetable, i, 1);
            *k_pte = (*pte) & ~PTE_U;
        }
    }
    
    void            kvmcopymap(pagetable_t, pagetable_t, uint64, uint64);
    
  3. kernel/proc.cuserinit函数和fork函数中增加以下字段:

    // Set up first user process.
    void
    userinit(void)
    {
        struct proc *p;
    
        p = allocproc();
        initproc = p;
        
        // allocate one user page and copy init's instructions
        // and data into it.
        uvminit(p->pagetable, initcode, sizeof(initcode));
        p->sz = PGSIZE;
    
        kvmcopymap(p->pagetable, p->k_pagetable, 0, PGSIZE); // ←←←←←←←←
    
        // prepare for the very first "return" from kernel to user.
        p->trapframe->epc = 0;      // user program counter
        p->trapframe->sp = PGSIZE;  // user stack pointer
    
        safestrcpy(p->name, "initcode", sizeof(p->name));
        p->cwd = namei("/");
    
        p->state = RUNNABLE;
    
        release(&p->lock);
    }
    
    // Create a new process, copying the parent.
    // Sets up child kernel stack to return as if from fork() system call.
    int
    fork(void)
    {
        int i, pid;
        struct proc *np;
        struct proc *p = myproc();
    
        // Allocate process.
        if((np = allocproc()) == 0){
            return -1;
        }
    
        // Copy user memory from parent to child.
        if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
            freeproc(np);
            release(&np->lock);
            return -1;
        }
        np->sz = p->sz;
    
        np->parent = p;
    
        kvmcopymap(np->pagetable, np->k_pagetable, 0, np->sz); // ←←←←←←←←
    
        // copy saved user registers.
        *(np->trapframe) = *(p->trapframe);
    
        // Cause fork to return 0 in the child.
        np->trapframe->a0 = 0;
    
        // increment reference counts on open file descriptors.
        for(i = 0; i < NOFILE; i++)
            if(p->ofile[i])
            np->ofile[i] = filedup(p->ofile[i]);
        np->cwd = idup(p->cwd);
    
        safestrcpy(np->name, p->name, sizeof(p->name));
    
        pid = np->pid;
    
        np->state = RUNNABLE;
    
        release(&np->lock);
    
        return pid;
    }
    
  4. kernel/exec.cexec函数中增加以下字段:

    sz = PGROUNDUP(sz);
    uint64 sz1;
    if((sz1 = uvmalloc(pagetable, sz, sz + 2*PGSIZE)) == 0)
        goto bad;
    // ↓↓↓↓↓↓↓↓
    if(sz1 >= PLIC)
        goto bad;
    // ↑↑↑↑↑↑↑↑
    sz = sz1;
    uvmclear(pagetable, sz-2*PGSIZE);
    
    sz = PGROUNDUP(sz);
    uint64 sz1;
    if((sz1 = uvmalloc(pagetable, sz, sz + 2*PGSIZE)) == 0)
        goto bad;
    // ↓↓↓↓↓↓↓↓
    if(sz1 >= PLIC)
        goto bad;
    // ↑↑↑↑↑↑↑↑
    sz = sz1;
    uvmclear(pagetable, sz-2*PGSIZE);
    sp = sz;
    stackbase = sp - PGSIZE;
    
    sp -= (argc+1) * sizeof(uint64);
    sp -= sp % 16;
    if(sp < stackbase)
        goto bad;
    if(copyout(pagetable, sp, (char *)ustack, (argc+1)*sizeof(uint64)) < 0)
        goto bad;
    
    // ↓↓↓↓↓↓↓↓
    uvmunmap(p->k_pagetable, 0, PGROUNDUP(oldsz)/PGSIZE, 0);
    kvmcopymap(pagetable, p->k_pagetable, 0, sz);
    // ↑↑↑↑↑↑↑↑
    
    // arguments to user main(argc, argv)
    // argc is returned via the system call return
    // value, which goes in a0.
    p->trapframe->a1 = sp;
    
  5. 更改kernel/sysproc.c中的sys_sbrk函数:

    uint64
    sys_sbrk(void)
    {
        int addr;
        int n;
        struct proc* p = myproc();
    
        if(argint(0, &n) < 0)
            return -1;
        addr = p->sz;
    
        if(addr+n >= PLIC)
            panic("sys_sbrk");
    
        if(growproc(n) < 0)
            return -1;
    
        if(n > 0)
            kvmcopymap(p->pagetable, p->k_pagetable, addr, n);
        else
            if(PGROUNDUP(addr+n) < PGROUNDUP(addr))
            uvmunmap(p->k_pagetable, PGROUNDUP(addr+n), (PGROUNDUP(addr)-PGROUNDUP(addr+n))/ PGSIZE, 0);
        return addr;
    }
    
  6. 修改kernel/proc.c中的freeproc函数:

    uvmunmap(pagetable, 0, PGROUNDUP(sz)/PGSIZE, 0); // 将这一行解注释掉
    

添加answers-pgtbl.txt、time.txt,运行测试脚本,结果如下:在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值