00_切换分支

$ git fetch
$ git checkout pgtbl
$ make clean

01_打印页表内容(Print a page table)

1.题干

为了帮助您了解RISC-V页表,也许为了帮助将来的调试,第一个任务是编写一个打印页表内容的函数。

  • 定义一个名为vmprint()的函数。它应当接收一个pagetable_t作为参数,并以下面描述的格式打印该页表。
  • exec.c中的return argc之前插入if(p->pid==1) vmprint(p->pagetable),以打印第一个进程的页表。
  • 当启动xv6时,应该打印以下内容,描述第一个进程的页表信息,在刚执行完exec()时。
page table 0x0000000087f6e000
..0: pte 0x0000000021fda801 pa 0x0000000087f6a000
.. ..0: pte 0x0000000021fda401 pa 0x0000000087f69000
.. .. ..0: pte 0x0000000021fdac1f pa 0x0000000087f6b000
.. .. ..1: pte 0x0000000021fda00f pa 0x0000000087f68000
.. .. ..2: pte 0x0000000021fd9c1f pa 0x0000000087f67000
..255: pte 0x0000000021fdb401 pa 0x0000000087f6d000
.. ..511: pte 0x0000000021fdb001 pa 0x0000000087f6c000
.. .. ..510: pte 0x0000000021fdd807 pa 0x0000000087f76000
.. .. ..511: pte 0x0000000020001c0b pa 0x0000000080007000
  • 第一行显示vmprint的参数。在那之后每行对应一个PTE,包含树中指向page-table pages的PTE。
  • 每个PTE行由一些".."(表明树的深度)缩进。
  • 每个PTE行显示PTE在page-table page中的索引pte地址pte中的物理页地址。不要打印无效PTE。
  • 在上面的例子中,有顶级page-table page:0、255。entry 0的下一级仅有索引0, 这个索引0的下一级有0、1、2三个PTE。
  • 你的代码可能生成与上面那些代码不同的物理地址。entry和虚拟地址的数量应该一样。

2.具体做法

(1)定义打印实现

kernel/vm.c

static void traversal_pt(pagetable_t pagetable, int level){
    for(int i = 0; i < 512; i++){
        pte_t pte = pagetable[i];
        if(pte & PTE_V){
            uint64 child = PTE2PA(pte);
            if (level == 0){
                printf("..%d: pte %p pa %p\n", i, pte, child);
                traversal_pt((pagetable_t)child, level + 1);
            }else if (level == 1){
                printf("...%d: pte %p pa %p\n", i, pte, child);
                traversal_pt((pagetable_t)child, level + 1);
            }else{
                printf("....%d: pte %p pa %p\n", i, pte, child);
            }
        }
    }
}

void vmprint(pagetable_t pagetable){
    printf("page table %p\n", pagetable);
    traversal_pt(pagetable, 0);
}
  • 代码整体逻辑

    vm.c中已经定义了一个freewalk函数用于回收页表页的内存,这个函数是递归的遍历到所有叶子级别的页表,从而接触和释放映射关系,所以这个题目需要的打印页表内容,其实也就是遍历,所以只需要参照freewalk函数的遍历写法进行就行

  • traversal_pt(pagetable_t pagetable, int level) 函数:

    就是一个树的递归遍历

(2)添加vmprint()声明

kernel/defs.h

void kvminit(void);
void kvminithart(void);
uint64 kvmpa(uint64);
void kvmmap(uint64, uint64, uint64, int);
int mappages(pagetable_t, uint64, uint64, uint64, int);
pagetable_t uvmcreate(void);
void uvmclear(pagetable_t);
void uvmfree(pagetable_t, uint64);
uint64 uvmalloc(pagetable_t, uint64, uint64);
uint64 uvmdealloc(pagetable_t, uint64, uint64);
void vmprint(pagetable_t);

vm.c的函数都是在defs.h中声明的

(3)增加对vmprint()的调用

kernel/exec.c

根据题目要求:在exec.c中的return argc之前插入if(p->pid==1) vmprint(p->pagetable),以打印第一个进程的页表。

// Commit to the user image.
oldpagetable = p->pagetable;
p->pagetable = pagetable;
p->sz = sz;
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)

bad:
  if(pagetable)
    proc_freepagetable(pagetable, sz);
  if(ip){
    iunlockput(ip);
    end_op();
  }

init进程也是用户进程,进程创建的时候就会调用exec系统调用,插入if(p->pid==1) vmprint(p->pagetable),就可以使vmprint函数被调用

3.结果

QQ_1725294435880

02_A kernel page table per process

1.实验要求

在讨论内核执行时的内存管理,xv6操作系统使用一个统一的内核页表,该页表对内核虚拟地址进行直接物理地址映射,即虚拟地址 x 直接对应于物理地址 x。此外,xv6为每个进程单独维护一个用户地址空间的页表,这些页表仅包含各自进程的用户内存映射,并始于虚拟地址0。

由于内核页表不包含用户空间的映射,用户地址在内核模式下无效。因此,当内核在处理系统调用时需要使用用户空间的指针,它必须首先将该用户指针翻译成对应的物理地址。

接下来的实验目的是允许内核直接解析用户指针。为实现这一目标,首个任务是修改内核,使得每个进程在内核模式下执行时都使用其自己的内核页表副本。这需要更改struct proc结构,使每个进程都能维护一个内核页表,并修改scheduler()函数以在进程切换时同时切换内核页表。

为了保证系统的稳定和一致性,每个进程的内核页表应该与已存在的全局内核页表保持完全一致。

本节和下一节的目标是允许内核直接解引用用户指针

[^举例]: 假设一个用户程序要求操作系统创建一个文件,程序将包含文件名的字符串指针传递给一个系统调用,如 create_file(const char* filename)用户程序:执行 create_file("/home/user/data.txt");内核处理:首先,内核检查传入的地址 /home/user/data.txt 是否位于用户进程的有效虚拟内存范围内。然后,内核将这个地址转换为对应的物理地址。内核可能会将用户空间的字符串数据复制到内核缓冲区,然后进行实际的文件创建操作。

2.具体实现

(1)在进程结构体添加内核页表项

kernel/proc.h中的struct proc中新加kernelPageTable

// 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 kernel_pagetable;//kernel page table
  pagetable_t pagetable;       // 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)
};
(2)添加创建内核页表的函数

修改kernel/vm.c,新增一个vmmake()方法可以创建一个内核页表(不包含CLINT的映射)

//添加创建内核页表的函数
pagetable_t kvmmake(){
    //使用kalloc分配一页内存给内核的页表。
    pagetable_t kernel_pagetable = (pagetable_t) kalloc();
    //使用 memset() 初始化这页内存。
    memset(kernel_pagetable, 0, PGSIZE);

    // 将 UART0的物理地址映射到虚拟地址。
    mappages(kernel_pagetable, UART0, PGSIZE, UART0, PTE_R | PTE_W);

    // 将 VIRTIO0(虚拟 I/O 设备)的物理地址映射到虚拟地址。
    mappages(kernel_pagetable, VIRTIO0, PGSIZE, VIRTIO0, PTE_R | PTE_W);

    // CLINT
    // mappages(kernel_pagetable, CLINT, 0x10000, CLINT, PTE_R | PTE_W);

    // 将 VIRTIO0(虚拟 I/O 设备)的物理地址映射到虚拟地址。
    mappages(kernel_pagetable, PLIC, 0x400000, PLIC, PTE_R | PTE_W);

    // - 将内核的可执行文本(代码)映射为只读和可执行。(物理地址和虚拟地址相同是因为xv6里映射是直接映射)
    mappages(kernel_pagetable, KERNBASE, (uint64)etext-KERNBASE, KERNBASE, PTE_R | PTE_X);

    // 将内核的数据段和物理内存映射为可读写。
    mappages(kernel_pagetable, (uint64)etext, PHYSTOP-(uint64)etext, (uint64)etext, PTE_R | PTE_W);

    //将用于处理陷阱(如系统调用和中断)的 trampoline 代码映射到内核虚拟地址的最高地址,设置为可读和可执行。
    mappages(kernel_pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X);

    return kernel_pagetable;
}
(3)单独映射CLINT

在kernel/vm.c,修改vminit()方法,内部由vmmake()实现,此处为全局内核页表创建过程,另外加上CLINT的映射。

注意是修改不是添加

void
kvminit()
{
    kernel_pagetable = kvmmake();
    // CLINT
    //函数将 CLINT(Core Local Interruptor)的物理地址区域映射到内核的虚拟地址空间。CLINT 是 RISC-V 架构中用于处理每个核心的局部中断的组件。
    mappages(kernel_pagetable, CLINT, 0x10000, CLINT, PTE_R | PTE_W);
}

之所以CLINT的映射要放在初始化里面,是因为kvmmake 的特性就是与硬件分离,但是CLINT没办法和硬件分离,如果放kvmmake里面就会导致其丧失这个特性。

(4)取消原来的分配内核栈的方法

在kernel/proc.c,修改procinit()方法,不再于此方法中为每个进程分配内核栈,转移到allocproc函数中进行分配

// initialize the proc table at boot time.
void
procinit(void)
{
  struct proc *p;

  initlock(&pid_lock, "nextpid");
  for(p = proc; p < &proc[NPROC]; p++) {
      initlock(&p->lock, "proc");

      // Allocate a page for the process's kernel stack.
      // Map it high in memory, followed by an invalid
      // guard page.

      /*题解注释
      char *pa = kalloc();
      if(pa == 0)
        panic("kalloc");
      uint64 va = KSTACK((int) (p - proc));
      kvmmap(va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
      p->kstack = va;
      */
  }
  //kvminithart();
}
(5)新的分配内核栈的方法

在kernel/proc.c,修改allocproc(),在此时创建内核页表,并在内核页表上分配一个内核栈

static struct proc*
allocproc(void)
{
  struct proc *p;

  for(p = proc; p < &proc[NPROC]; p++) {
    acquire(&p->lock);
    if(p->state == UNUSED) {
      goto found;
    } else {
      release(&p->lock);
    }
  }
  return 0;

found:
  p->pid = allocpid();

  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }

  //题解:在这个地方首先创建一个内核页表,然后给这个页表分配一个内核栈
  p->kernel_pagetable = kvmmake();//分配虚拟内存页表
  char *pa = kalloc();//分配物理内存内存
  if(pa == 0)
      panic("kalloc");
  uint64 va = TRAMPOLINE - 2*PGSIZE;
  mappages(p->kernel_pagetable, va, PGSIZE, (uint64)pa, PTE_R | PTE_W);//将物理内存和虚拟内存建立映射
  p->kstack = va;//将内存栈虚拟地址存储到进程结构体的元素中

  // An empty user page table.
  p->pagetable = proc_pagetable(p);
  if(p->pagetable == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }

  // 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;

  return p;
}
  //题解:在这个地方首先创建一个内核页表,然后给这个页表分配一个内核栈
  p->kernel_pagetable = kvmmake();//分配虚拟内存页表
  char *pa = kalloc();//分配物理内存内存
  if(pa == 0)
      panic("kalloc");
  uint64 va = TRAMPOLINE - 2*PGSIZE;
  mappages(p->kernel_pagetable, va, PGSIZE, (uint64)pa, PTE_R | PTE_W);//将物理内存和虚拟内存建立映射
  p->kstack = va;//将内存栈虚拟地址存储到进程结构体的元素中

解释见注释

(6)修改调度器的过程

在kernel/proc.c,修改scheduler()

  • scheduler() 函数是什么?

    scheduler() 函数是操作系统内核中负责进程调度的核心部分。在多任务操作系统中,调度器(scheduler)的作用是选择下一个要运行的进程,并将 CPU 的控制权交给该进程。

  • 为什么要修改scheduler() 函数?

    因为swtch进行进程切换是在scheduler() 函数里进行的

  • satp是什么?

    SATP 是 RISC-V 架构中的一个控制寄存器,全称为 "Supervisor Address Translation and Protection"。它用于管理和控制虚拟内存系统的页表映射和内存保护。

    修改satp是为了让进程执行阶段是进程的内核页表,执行完之后切换成全局的(使用 kvminithart函数来完成)

void
scheduler(void)
{
  struct proc *p;
  struct cpu *c = mycpu();

  c->proc = 0;
  for(;;){
    // Avoid deadlock by ensuring that devices can interrupt.
    intr_on();

    int found = 0;
    for(p = proc; p < &proc[NPROC]; p++) {
      acquire(&p->lock);
      if(p->state == RUNNABLE) {
        // Switch to chosen process.  It is the process's job
        // to release its lock and then reacquire it
        // before jumping back to us.
        p->state = RUNNING;
        c->proc = p;
        //题解:修改satp以启动进程内核页表
        w_satp(MAKE_SATP(p->kernel_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;
      }
      release(&p->lock);
    }
#if !defined (LAB_FS)
    if(found == 0) {
      intr_on();
      asm volatile("wfi");
    }
#else
    ;
#endif
  }
}
(7)内核页表取消使用全局为当前进程

在kernel/vm.c,kvmpa()方法会在进程执行期间调用,此时需要修改为获取进程内核页表,而不是全局内核页表

uint64
kvmpa(uint64 va)
{
  uint64 off = va % PGSIZE;
  pte_t *pte;
  uint64 pa;

  //题解:启动当前进程的内核页表
  pte = walk(myproc()->kernel_pagetable, va, 0);
  if(pte == 0)
    panic("kvmpa");
  if((*pte & PTE_V) == 0)
    panic("kvmpa");
  pa = PTE2PA(*pte);
  return pa+off;
}
(8)添加进程内核页表的释放

接下来就是在原有的对进程的释放的函数的基础上添加对进程内核页表的资源释放

即在kernel/proc.c文件下,修改freeproc()函数,添加对进程内核页表的资源释放

// 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->kernel_pagetable){
    proc_free_kernel_pagetable(p->kstack,p->kernel_pagetable);
  }
  p->kernel_pagetable = 0;
  //题解

  if(p->pagetable)
    proc_freepagetable(p->pagetable, p->sz);

  p->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;
}
(9)解除所有映射关系

添加(8)中用到的进程内核页表的资源释放的函数

void 
proc_free_kernel_pagetable(uint64 kstack,pagetable_t pagetable){
  //题解:解除所有的映射关系
  uvmunmap(pagetable, UART0, 1, 0);
  uvmunmap(pagetable, VIRTIO0, 1, 0);
  uvmunmap(pagetable, CLINT, 0x10000/PGSIZE, 0);
  uvmunmap(pagetable, PLIC, 0x400000/PGSIZE, 0);
  uvmunmap(pagetable, KERNBASE, ((uint64)etext - KERNBASE)/PGSIZE, 0);
  uvmunmap(pagetable, (uint64)etext, (PHYSTOP-(uint64)etext)/PGSIZE, 0);
  uvmunmap(pagetable, TRAMPOLINE, 1, 0);

  //释放内核页表上的内核栈
  uvmfree2(pagetable, kstack, 1);
} 
(10)释放内核栈

添加(9)的内核栈释放函数

void 
uvmfree2(pagetable_t pagetable,uint64 va,uint npages){
  if(npages>0){
    uvmunmap(pagetable,va,npages,1);
  }
  freewalk(pagetable);
}

3.结果

QQ_1725380243217

03_简化copyincopyinstr函数

1.实验要求

内核的copyin函数读取用户指针指向的内存。它先将它们翻译为物理地址(内核可以直接用)。通过代码walk进程页表实现翻译。在此实验中需要给每个进程的内核页表添加用户映射,使得copyin可以直接使用用户指针。

2.具体实现

(1)在第一个用户进程中映射用户页表和内核页表

在kernel/proc.c中,修改userinit方法

// Set up first user process.
void
userinit(void)
{
  struct proc *p;

  //题解:声明两个页表条目的指针,用于处理虚拟地址到物理地址的映射。
  pte_t *pte, *kernelPte;

  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;

  //题解:将进程页表的mapping,复制一份到进程的内核页表
  pte = walk(p->pagetable, 0, 0);//查找用户页表中虚拟地址 0 对应的页表条目。
  kernelPte = walk(p->kernel_pagetable, 0, 1);//查找内核页表中虚拟地址 0 对应的页表条目,如果不存在则创建。
  *kernelPte = (*pte) & ~PTE_U;  //用户页表中的页表条目复制到内核页表中,但去掉 PTE_U 标志位,确保内核只能访问这段内存,而用户不能访问。

  // 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);
}
(2)子进程也需要映射用户和内核页表

在kernel/proc.c中,修改fork方法

// 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,j;
  struct proc *np;
  struct proc *p = myproc();
  pte_t *kernelPte,*pte;
  // 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;
  }

  //题解:将进程页表的mapping,复制一份到进程内核页表
  for (j = 0; j < p->sz; j += PGSIZE) {//遍历父进程的所有页,步长为一页大小(通常 4KB)。
      pte = walk(np->pagetable, j, 0);//获取子进程的用户页表条目(PTE)。
      kernelPte = walk(np->kernel_pagetable, j, 1);//获取或创建子进程的内核页表条目。
      *kernelPte = (*pte) & ~PTE_U;//将用户页表中的映射复制到内核页表中,但移除 PTE_U 标志位(用户权限位),确保内核可以访问,而用户不能直接访问该内存。
  }
  np->sz = p->sz;

  np->parent = p;

  // 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;
}
(3)重建进程页表映射

在kernel/exec.c中,修改exec(),在用户进程页表重新生成完后,取消进程内核页表之前的映射,在进程内核页表,建立新进程页表的映射

 // push the array of argv[] pointers.
  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;

  int j;
  pte_t *pte,*kernelPte;
  // 题解:释放进程旧内核页表映射
  uvmunmap(p->kernel_pagetable, 0, PGROUNDUP(oldsz)/PGSIZE, 0);

  // 将进程页表的mapping,复制一份到进程内核页表
  for (j = 0; j < sz; j += PGSIZE) {
    pte = walk(pagetable, j, 0);
    kernelPte = walk(p->kernel_pagetable, j, 1);
    *kernelPte = (*pte) & ~PTE_U;
  }

  // arguments to user main(argc, argv)
  // argc is returned via the system call return
  // value, which goes in a0.
  p->trapframe->a1 = sp;

  // Save program name for debugging.
  for(last=s=path; *s; s++)
    if(*s == '/')
      last = s+1;
  safestrcpy(p->name, last, sizeof(p->name));
  • 添加用户空间地址不能大于PLIC的判断

    // Load program into memory.
    for(i=0, off=elf.phoff; i= PLIC){
        goto bad;
      }
      sz = sz1;
      if(ph.vaddr % PGSIZE != 0)
        goto bad;
      if(loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) < 0)
        goto bad;
    }
(4)内存改变也需重建

sysproc.c中,修改sys_sbrk(),在内存扩张、缩小时,相应更改进程内核页表

uint64
sys_sbrk(void)
{
    int addr;
    int n, j;
    struct proc *p = myproc();
    pte_t *pte, *kernelPte;

    if(argint(0, &n) < 0)
        return -1;
    addr = p->sz;
    if (addr + n >= PLIC)
        return -1;

    if(growproc(n) < 0)
        return -1;

    if (n > 0) {
        // 题解:进程页表的mapping,复制一份到进程内核页表
        for (j = addr; j < addr + n; j += PGSIZE) {
            pte = walk(p->pagetable, j, 0);
            kernelPte = walk(p->kernelPageTable, j, 1);
            *kernelPte = (*pte) & ~PTE_U;
        }
    } else {
        for (j = addr - PGSIZE; j >= addr + n; j -= PGSIZE) {
            uvmunmap(p->kernelPageTable, j, 1, 0);
        }
    }

    return addr;
}
(5)释放函数添加对之前操作的复原

在kernel/proc.c中,修改freeproc和proc_free_kernel_pagetable方法,取消进程内核页表地址映射

  • 修改freeproc方法以取消进程内核页表地址映射

    // 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->kernel_pagetable){
      proc_free_kernel_pagetable(p->kstack,p->kernel_pagetable,p->sz);
    }
    p->kernel_pagetable = 0;
    //题解
    
    if(p->pagetable)
      proc_freepagetable(p->pagetable, p->sz);
    
    p->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;
    }

    在proc_free_kernel_pagetable()函数中添加了一个p->sz的参数(进程所占用的用户空间虚拟内存的大小)

  • 修改proc_free_kernel_pagetable方法以取消进程内核页表地址映射

    void 
    proc_free_kernel_pagetable(uint64 kstack,pagetable_t pagetable,uint64 sz){
    //题解:解除所有的映射关系
    uvmunmap(pagetable, UART0, 1, 0);
    uvmunmap(pagetable, VIRTIO0, 1, 0);
    uvmunmap(pagetable, CLINT, 0x10000/PGSIZE, 0);
    uvmunmap(pagetable, PLIC, 0x400000/PGSIZE, 0);
    uvmunmap(pagetable, KERNBASE, ((uint64)etext - KERNBASE)/PGSIZE, 0);
    uvmunmap(pagetable, (uint64)etext, (PHYSTOP-(uint64)etext)/PGSIZE, 0);
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmunmap(pagetable,0,PGROUNDUP(sz)/PGSIZE,0);
    //释放内核页表上的内核栈
    uvmfree2(pagetable, kstack, 1);
    } 
(6)声明简化的函数

kernel/defs.h中,添加copyin_new()、copyinstr_new()的声明

  • //vmcopyin.c
    int             copyin_new(pagetable_t,char *,uint64,uint64);
    int             copyinstr_new(pagetable_t,char *,uint64,uint64);
(7)将原函数替换成简化后的函数

在kernel/vm.c中,替换copyin()、copyinstr()为copyin_new()、copyinstr_new()

// Copy from user to kernel.
// Copy len bytes to dst from virtual address srcva in a given page table.
// Return 0 on success, -1 on error.
int
copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{
    return copyin_new(pagetable, dst, srcva, len);
}

// Copy a null-terminated string from user to kernel.
// Copy bytes to dst from virtual address srcva in a given page table,
// until a '\0', or max.
// Return 0 on success, -1 on error.
int
copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
{
    return copyinstr_new(pagetable, dst, srcva, max);
}

copyin_newcopyinstr_new的函数实现是在vmcopyin.c中由源码已经实现好了的


踏上取经路,比抵达灵山更重要