Xv6 实验

Lab1: Xv6 and Unix utilities

实验任务

启动xv6(难度:Easy)

1. 获取实验室的xv6源代码并切换到util分支

git clone git://g.csail.mit.edu/xv6-labs-2021
cd xv6-labs-2021
git checkout util

2. 构建并运行xv6

make qemu

退出qemu:先按ctrl+a,后按x

sleep(难度:Easy)

1. 新建文件sleep.c

vim user/sleep.c

2. 编写程序(快捷键i: 插入模式。退出:先按Esc,后加“:wq”保存文件退出)

#include "kernel/types.h"
#include "user/user.h"

int main(int argc, char const *argv[])
{       
        if(argc != 2){
                fprintf(2, "usage: sleep <time>\n");
                exit(1);
        }
        sleep(atoi(argv[1]));
        exit(0);
}

3. 在Makefile文件里添加配置,在 UPROGS 项中最后一行添加 $U/_sleep\,保存退出。

vim Makefile

4. 运行程序

# make qemu
$ sleep 10
$ sleep 10 1

5. 成绩测试(先退出qemu,进入xv6-labs-2021)

# ./grade-lab-util sleep

pingpong(难度:Easy)

1. 新建文件pingpong.c

vim user/pingpong.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

#define READ 0
#define WRITE 1

int main(int argc, char *argv[])
{
        if(argc != 1)
                printf("don't input arguments\n");
        int pipeParToChr[2];
        int pipeChrToPar[2];
        char buf[8];
        pipe(pipeParToChr);
        pipe(pipeChrToPar);
        int pid = fork();
        if(pid == 0){  // child process
                close(pipeParToChr[WRITE]);
                read(pipeParToChr[READ], buf, sizeof(buf));
                close(pipeParToChr[READ]);
                close(pipeChrToPar[READ]);
                write(pipeChrToPar[WRITE], "pong\n", 5);
                close(pipeChrToPar[WRITE]);
                printf("%d: received %s", getpid(), buf);
                exit(0);
        }else{  // parent process
                close(pipeParToChr[READ]);
                write(pipeParToChr[WRITE], "ping\n", 5);
                close(pipeParToChr[WRITE]);
                close(pipeChrToPar[WRITE]);
                read(pipeChrToPar[READ], buf, sizeof(buf));
                close(pipeChrToPar[READ]);
                wait((int*)0);
                printf("%d: received %s", getpid(), buf);
               exit(0);
        }
}

2. 在Makefile文件里添加配置,在 UPROGS 项中最后一行添加 $U/_pingpong\,保存退出。

vim Makefile
 $U/_pingpong\

3. 运行程序

make qemu

4. 成绩测试

./grade-lab-util pingpong

Primes(素数,难度:Moderate/Hard)

1. 新建文件primes.c

vim user/primes.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

#define READ 0
#define WRITE 1

void primes(int num[], int size)
{
        int pipe1[2];
        pipe(pipe1);
        int pid = fork();
        if(pid > 0){
                close(pipe1[READ]);
                for(int i = 0; i < size; i++){
                        write(pipe1[WRITE], &num[i], sizeof(num[i]));
                }
                close(pipe1[WRITE]);
                wait((int*)0);
        }else{
                close(pipe1[WRITE]);
                int numchr[34], indnx = 0;
                int tmp, min;
                while(read(pipe1[READ], &tmp, sizeof(tmp))){
                        if(indnx == 0)
                        {
                                min = tmp;
                                printf("prime %d\n", min);
                                indnx++;
                        }
                        if(tmp % min != 0){
                                numchr[indnx - 1] = tmp;
                                indnx++;
                        }
                }
                close(pipe1[READ]);
                primes(numchr, indnx - 1);
                exit(0);
        }
}

int main(int argc, char *argv[])
{
        int num[34];
        int indnx = 0;
        for(int i = 2; i <= 35; i++){
                num[indnx] = i;
                indnx++;
        }
        primes(num, 34);
        exit(0);
}

2. 在Makefile文件里添加配置,在 UPROGS 项中最后一行添加 $U/_primes\,保存退出。

vim Makefile
$U/_primes\

3. 运行程序

make qemu

4. 成绩测试

./grade-lab-util primes

find(难度:Moderate)

1. 添加文件find.c

vim user/find.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

void find(char *path, char *target)
{
        char buf[512], *p;
        int fd;
        struct dirent de;
        struct stat st;

        if((fd = open(path, 0)) < 0){
                fprintf(2, "find: cannot open %s\n", path);
                return;
        }
        if(fstat(fd, &st) < 0){
                fprintf(2, "find: cannot stat %s\n", path);
                close(fd);
                return;
        }
        switch(st.type)
        {
            case T_FILE:
                        if(strcmp(path + strlen(path) - strlen(target), target) == 0){
                                printf("%s\n", path);
                        }
                        break;
                case T_DIR:
                        if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
                                printf("find: path too long\n");
                                break;
                        }
                        strcpy(buf, path);
                        p = buf + strlen(buf);
                        *p++ = '/';
                        while(read(fd, &de, sizeof(de)) == sizeof(de)){
                                if(de.inum == 0)
                                        continue;
                                memmove(p, de.name, DIRSIZ);
                                p[DIRSIZ] = 0;
                                if(stat(buf, &st) < 0){
                                        printf("find: cannot stat %s\n", buf);
                                        continue;
                                }
                                if(strcmp(".", de.name) != 0 && strcmp("..", de.name) != 0){
                                        find(buf, target);
                                }
                        }
                        break;
                }
        close(fd);
}

int main(int argc, char *argv[]){
        if(argc != 3)
        {
                printf("input arguments: find <path> <filename>\n");
                exit(1);
        }
        find(argv[1], argv[2]);
        exit(0);
}

          

 2. 在Makefile文件里添加配置,在 UPROGS 项中最后一行添加 $U/_find\,保存退出。

vim Makefile
$U/_find\

3. 运行程序

make qemu

4. 成绩测试

./grade-lab-util find

xargs(难度:Moderate)

1. 新建文件xargs.c

vim user/xargs.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"

int main(int argc, char *argv[])
{
        char *new_argv[MAXARG];
        int cur_argv = 1;
        for(cur_argv = 1; cur_argv <= argc - 1; cur_argv++){
                new_argv[cur_argv - 1] = argv[cur_argv];
        }
        char ch;
        char buf[128];
        char *cur_buf = buf;
        new_argv[cur_argv - 1] = buf;
        while(read(0, &ch, sizeof(char))){
                if(ch == ' '){
                        *cur_buf ='\0';
                        cur_buf++;
                        new_argv[cur_argv] = cur_buf;
                        cur_argv++;
                }else if(ch == '\n'){
                        *cur_buf = '\0';
                        new_argv[cur_argv] = 0;
                        int pid = fork();
                        if(pid == 0){
                                exec(new_argv[0], new_argv);
                                exit(0);
                        }else{
                                wait((int*)0);
                                cur_buf = buf;
                                cur_argv = argc;
                        }
                }else{
                        *cur_buf = ch;
                        cur_buf++;
                }
        }
        exit(0);
}

  2. 在Makefile文件里添加配置,在 UPROGS 项中最后一行添加 $U/_xargs\,保存退出。

vim Makefile
$U/_xargs\

3. 运行程序

make qemu

4. 成绩测试

./grade-lab-util xargs

Lab 1 所有实验测试

1. 新建文件time.txt,写入你做此次实验所花的时间。

vim time.txt

2. make grade

make grade

提交实验

1. git add 所有修改或添加的文件

git add time.txt user/find.c user/pingpong.c user/primes.c user/sleep.c user/xargs.c

git commit -am "ready to submit my lab1"

2. 将最终更改提交到实验后,键入make handin以提交实验。打开网址按要求填写信息获取API key。

make handin

lab2: syscall

system call tracing

准备工作:将代码切换到syscall分支

git fetch
git checkout syscall
make clean

1. 在MakeFile文件中找到UPROGS并添加$U/_trace\。

vim Makefile
$U/_trace\

2. 在user/user.h添加函数原型

int uptime(void);
int trace(int);  // New add

3. 在user/usys.pl中添加trace的入口

entry("uptime");
entry("trace");  // New add

4. 在kernel/syscall.h中添加宏定义

#define SYS_close  21
#define SYS_trace  22  // New add

5. 修改kernel/proc.h添加变量存储跟踪号

struct proc {
  ......
  char name[16];               // Process name (debugging)
  char mask[23];               // New add
};

6. 打开kernel/sysproc.c添加一个trace函数的具体实现

// New add
uint64 sys_trace(void){
        int n;
        if(argint(0, &n) < 0){
                return -1;
        }
        struct proc *p = myproc();
        char *mask = p->mask;
        int i = 0;
        while(i < 23 && n > 0){
                if(n % 2){
                        mask[i++] = '1';
                }else{
                        mask[i++] = '0';
                }
                n >>= 1;
        }
        return 0;
}

7. 打开文件kernel/proc.c修改fork()函数在函数中添加一句 safestrcpy(np->mask, p->mask, sizeof(p->mask));

int
fork(void)
{
    ......
    np->sz = p->sz;
    safestrcpy(np->mask, p->mask, sizeof(p->mask));  // New add

    // copy saved user registers.
    *(np->trapframe) = *(p->trapframe);
    ......
}

8. 为了将trace打印出来,修改kernel/syscall.c中的syscall()函数

extern uint64 sys_trace(void);  // New add
static uint64 (*syscalls[])(void) = {
......
[SYS_close]   sys_close,
[SYS_trace]   sys_trace,  // New add
};
static char *syscall_names[23] = {"","fork","exit","wait","pipe","read","kill",
                                "exec","fstat","chdir","dup","getpid","sbrk","sleep",
                                "uptime","open","write","mknod","unlink","link","mkdir",
                                "close","trace"};  // New add

void
syscall(void)
{
  int num;
  struct proc *p = myproc();
  //char* syscall_name; 

  num = p->trapframe->a7;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    p->trapframe->a0 = syscalls[num]();
    if (strlen(p->mask) > 0 && p->mask[num] == '1'){            // NEW ADD
        printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num],p->trapframe->a0);
    }
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

9. 运行程序

make qemu

trace 32 grep hello README
trace 2147483647 grep hello README

10. 成绩测试

./grade-lab-syscall trace

sysinfo

1. $U/_sysinfotest添加到 Makefile 中的 UPROGS

vim Makefile
$U/_sysinfotest\

2. 将系统调用的原型添加到user/user.h.

struct stat;
struct rtcdate;
struct sysinfo;  // New add

// system calls
......
int trace(int);  
int sysinfo(struct sysinfo *);  // New add

3. 存根添加到user/usys.pl

entry("trace");
entry("sysinfo");  // New add

4. 将系统调用编号添加到kernel/syscall.h

#define SYS_trace  22  
#define SYS_sysinfo 23  // New add

5. 在kernel/sysproc.c中添加一个sys_info()函数,并添加#include "sysinfo.h"到文件开头

#include "sysinfo.h"
// New add
uint64 sys_info(void){
        return 0;
}

6. 修改kernel/syscall.c中的syscall()函数

......
extern uint64 sys_trace(void);  
extern uint64 sys_info(void);  // new add
static uint64 (*syscalls[])(void) = {
......
[SYS_trace]   sys_trace,  // New add
[SYS_sysinfo] sys_info,  // New add
};

static char *syscall_names[] = {"","fork","exit","wait","pipe","read","kill",
                                "exec","fstat","chdir","dup","getpid","sbrk","sleep",
                                "uptime","open","write","mknod","unlink","link","mkdir",
                                "close","trace", "sys_info"};  // New add
void
syscall(void)
{
  int num;
  struct proc *p = myproc();
 // char* syscall_name;  // New add

  num = p->trapframe->a7;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    p->trapframe->a0 = syscalls[num]();
    if (strlen(p->mask) > 0 && p->mask[num] == '1'){            // NEW ADD
        printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num],p->trapframe->a0);
    }
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

7. 要获取空闲内存量,请在kernel/kalloc.c中添加一个函数freebytes()

// New add
// get free memory bytes by count freelist
uint64 freebytes(void){
        struct run *r;
        uint64 count = 0;
        acquire(&kmem.lock);
        r = kmem.freelist;
        while(r){
                r = r->next;
                count++;
        }
        release(&kmem.lock);
        return count * PGSIZE;
}

8. 要获取进程数,请在kernel/proc.c中添加一个函数proc_num()

// New add
// get the number of process whose state is not UNUSED
uint64 proc_num(){
        struct proc *p;
        uint64 count = 0;
        for(p = proc; p < &proc[NPROC]; p++){
                acquire(&p->lock);
                if(p->state != UNUSED){
                        count++;
                }
                release(&p->lock);
        }
        return count;
}

9.  在kernel/defs.h文件中添加函数原型

// syscall.c
int             argint(int, int*);
int             argstr(int, char*, int);
int             argaddr(int, uint64 *);
int             fetchstr(uint64, char*, int);
int             fetchaddr(uint64, uint64*);
void            syscall();
uint64          proc_num(void);  // New add
uint64          freebytes(void);  // New add

10. 修改第5步在kernel/sysproc.c中添加的sys_info()函数

// New add
uint64 sys_info(void){
        struct sysinfo info;
        uint64 addr;
        info.freemem = freebytes();
        info.nproc = proc_num();
        if(argaddr(0, &addr) < 0){
                return -1;
        }
        // copy info(kernel space) to addr(user space)
        if(copyout(myproc()->pagetable, addr, (char *)&info, sizeof(info)) < 0){
                return -1;
        }else{
                return 0;
        }
}
~            

11. 程序运行

make qemu
sysinfotest

12. 成绩测试

./grade-lab-syscall sysinfo

 Lab 2 所有实验测试

1. 新建文件time.txt,写入你做此次实验所花的时间(只能是正整数)。

vim time.txt

2. make grade

make grade

提交实验

1. git commit + make handin

Lab3: page tables

前提,切换到pgtbl分支:

$ git fetch
$ git checkout pgtbl
$ make clean

1. 在kernel/vm.c 添加以下函数

void _vmprint(pagetable_t pagetable, int level){
  // there are 2^9 = 512 PTEs in a page table.
  for(int i = 0; i < 512; i++){
    pte_t pte = pagetable[i];
    // PTE_V is a flag for whether the page table is valid
    if(pte & PTE_V){
      // 按照层级输出.. .. .. 
      for (int j = 0; j < level; j++){
        if (j) printf(" ");
        printf("..");
      }
      // 得到pte指向的物理页起始地址
      uint64 child = PTE2PA(pte);
      // 输出当前页表项的内容
      printf("%d: pte %p pa %p\n", i, pte, child);
      // 是否是叶子层页表项--如果不是的话,就继续递归打印
      if((pte & (PTE_R|PTE_W|PTE_X)) == 0){
        // this PTE points to a lower-level page table.
        _vmprint((pagetable_t)child, level + 1);
      }
    }
  }
}

void vmprint(pagetable_t pagetable){
  //打印根页表起始地址
  printf("page table %p\n", pagetable);
  _vmprint(pagetable, 1);
}

2. 在kernel/defs.h文件中添加函数的声明

// vm.c
//......
int             copyinstr(pagetable_t, char *, uint64, uint64);
void            vmprint(pagetable_t); // New add

3. 在kernel/exec.c添加如下语句

int
exec(char *path, char **argv)
{
    ......
    proc_freepagetable(oldpagetable, oldsz);

    // New add
    if(p->pid == 1){
          vmprint(p->pagetable);
    }

    return argc; // this ends up in a0, the first argument to main(argc, argv)
    ......
}

4. 运行程序

make qemu

5. 成绩测试

./grade-lab-pgtbl pte print

A kernel page table per process (hard)

1. 在kernel/proc.h的proc结构体添加kernelpt属性

struct proc {
  // ......
  pagetable_t pagetable;       // User page table
  pagetable_t kernelpt;  // New add
  // ......
}

2. 在kernel/vm.c添加以下函数

// New add lab3.2
void uvmmap(pagetable_t pagetable, uint64 va, uint64 pa, uint64 sz, int perm)
{
        if(mappages(pagetable, va, sz, pa, perm) != 0)
                panic("uvmmap");
}

// New add lab3.2
// Create a kernel page table for the process
pagetable_t
proc_kpt_init(){
        pagetable_t kernelpt = uvmcreate();
        if(kernelpt == 0)
                return 0;
        uvmmap(kernelpt, UART0, UART0, PGSIZE, PTE_R | PTE_W);
        uvmmap(kernelpt, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);
        uvmmap(kernelpt, CLINT, CLINT, 0x10000, PTE_R | PTE_W);
        uvmmap(kernelpt, PLIC, PLIC, 0x400000, PTE_R | PTE_W);
        uvmmap(kernelpt, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);
        uvmmap(kernelpt, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);
        uvmmap(kernelpt, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);
        return kernelpt;
}


3. 在kernel/defs.h添加函数声明

// vm.c
void            uvmmap(pagetable_t, uint64, uint64, uint64, int);  //New add lab3.2
pagetable_t     proc_kpt_init();  // New add lab3.2

4. 在kernel/proc.c里面的allocproc()调用

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

  // New add begin lab3_2
  p->kernelpt = proc_kpt_init(p);
  if(p->pagetable == 0){
          freeproc(p);
          release(&p->lock);
          return 0;
  }
  // New add end lab3_2

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

5. 承接上一步,在kernel/proc.c里面的alloproc()添加以下,并注释掉procinit()函数里的相关部分

// New add begin lab3_2
  p->kernelpt = proc_kpt_init(p);
  if(p->pagetable == 0){
          freeproc(p);
          release(&p->lock);
          return 0;
  }
  // New add end lab3_2
  //
  // New add begin lab3_2
  // Allocate a page for the process's kernel stack.
  // Map it high in memory, followed by invalid
  // guard page
  char *pa = kalloc();
  if(pa == 0)
          panic("kalloc");
  uint64 va = KSTACK((int)(p - proc));
  uvmmap(p->kernelpt, va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
  p->kstack = va;
  //New add end lab3_2
// initialize the proc table at boot time.
void
procinit(void)
{
  struct proc *p;

  initlock(&pid_lock, "nextpid");
  initlock(&wait_lock, "wait_lock");
  for(p = proc; p < &proc[NPROC]; p++) {
      initlock(&p->lock, "proc");
//      p->kstack = KSTACK((int) (p - proc));  // New modify lab3_2
  }
}

6. 在kernel/vm.c添加proc_inithart()

// New add lab3_2
// Store kernel page table to SATP register
void
proc_inithart(pagetable_t kpt){
  w_satp(MAKE_SATP(kpt));
  sfence_vma();
}

7. 在kernel/defs.h添加函数声明

//vm.c
void            proc_inithart(pagetable_t);  // New add lab3_2

8. 修改kernel/proc.c的scheduler()

void
scheduler(void)
{
    // ......
    p->state = RUNNING;
    c->proc = p;

    proc_inithart(p->kernelpt);  // New add lab3_2  
    swtch(&c->context, &p->context);

    
    // come back to the global kernel page table
    kvminithart();  // New add lab3_2

   // Process is done running for now.
   // It should have changed its p->state before coming back.
   c->proc = 0;
   // ......
}

9. 在kernel/proc.c的freeproc()添加如下

static void
freeproc(struct proc *p)
{
  // New add lab3_2
  // free the kernel stack in the RAM
  uvmunmap(p->kernelpt, p->kstack, 1, 1);
  p->kstack = 0;  // New add end
  if(p->trapframe)
    kfree((void*)p->trapframe);
  p->trapframe = 0;
  if(p->pagetable)
    proc_freepagetable(p->pagetable, p->sz);

  // New add lab3_2
  if(p->kernelpt)
          proc_freekernelpt(p->kernelpt);  // New add lab3_2 end

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

10. 在kernel/proc.c添加proc_freekernelpt()函数

// New add lab3_2
void
proc_freekernelpt(pagetable_t kernelpt)
{
        // similar to the freewalk method
        // there are 2^9 = 512 PTEs in a page table
        for(int i = 0; i < 512; i++){
                pte_t pte = kernelpt[i];
                if(pte & PTE_V){
                        kernelpt[i] = 0;
                        if((pte & (PTE_R | PTE_W | PTE_X)) == 0){
                                uint64 child = PTE2PA(pte);
                                proc_freekernelpt((pagetable_t)child);
                        }
                }
        }
        kfree((void*)kernelpt);
}

11. 在kernel/vm.c做出以下修改

①添加头文件

#include "spinlock.h"
#include "proc.h"

②修改walkaddr(),参照别的博主改的,但是改了会出现“panic: init exiting”的错误信息,试着不修改测试的时候也是会出现一些问题。

uint64
walkaddr(pagetable_t pagetable, uint64 va)
{
  pte_t *pte;
  uint64 pa;

  if(va >= MAXVA)
    return 0;

  //pte = walk(pagetable, va, 0);  // New mmodify lab3_2
  pte = walk(myproc()->kernelpt, va, 0);  // modify,根据个人情况决定要不要改,我的是xv-labs-2021, 可能xv6-labs-2020要改

  if(pte == 0)
    return 0;
  if((*pte & PTE_V) == 0)
    return 0;
  if((*pte & PTE_U) == 0)
    return 0;
  pa = PTE2PA(*pte);
  return pa;
}

12. 运行程序

make qemu
usertests

Simplify copyin/copyinstr(hard)

1. 在kernel/vm.c添加u2kvmcopy()函数,并在文件开头声明函数的原型

pte_t *walk(pagetable_t pagetable, uint64 va, int alloc);
// New add lab3_3
void
u2kvmcopy(pagetable_t pagetable, pagetable_t kernelpt, uint64 oldsz, uint64 newsz){
        pte_t *pte_from, *pte_to;
        oldsz = PGROUNDUP(oldsz);
        for(uint64 i = oldsz; i < newsz; i += PGSIZE){
                if((pte_from = walk(pagetable, i, 0)) == 0)
                        panic("u2kvmcopy: src pte does not exist");
                if((pte_to = walk(kernelpt, i, 1)) == 0)
                        panic("u2kvmcopy: pte walk failed");
                uint64 pa = PTE2PA(*pte_from);
                uint flags = (PTE_FLAGS(*pte_from)) & (~PTE_U);
                *pte_to = PA2PTE(pa) | flags;
        }
}

2. 在kernel/defs.h添加函数声明

// vm.c
void            u2kvmcopy(pagetable_t, pagetable_t, uint64, uint64);  // New add lab3_3

3. exec(),在kernel/exec.c

int
exec(char *path, char **argv)
{

  // ......
  uvmclear(pagetable, sz-2*PGSIZE);
  sp = sz;
  stackbase = sp - PGSIZE;

  // New add lab3_3
  u2kvmcopy(pagetable, p->kernelpt, 0, sz);

  // Push argument strings, prepare rest of stack in ustack.
  for(argc = 0; argv[argc]; argc++) {
  // ......
}

4. fork(),在kernel/proc.c

int
fork(void)
{
  // ......
  // 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;

  // New add lab3_3
  u2kvmcopy(np->pagetable, np->kernelpt, 0, np->sz);

  // copy saved user registers.
  *(np->trapframe) = *(p->trapframe);
  // ......
}

5. sbrk(),在kernel/proc.c

// Return 0 on success, -1 on failure.
int
growproc(int n)
{
  uint sz;
  struct proc *p = myproc();

  sz = p->sz;
  if(n > 0){
    
    // New add lab3_3
    if(PGROUNDUP(sz + n) >= PLIC){
            return -1;
    }

    if((sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {
      return -1;
    }
    // New add lab3_3
    u2kvmcopy(p->pagetable, p->kernelpt, sz - n, sz);

  } else if(n < 0){
    sz = uvmdealloc(p->pagetable, sz, sz + n);
  }
  p->sz = sz;
  return 0;
}

6. 修改kernel/vm.c里的copyin()和copyinstr()

荒谬:我的源码里居然没有kernel/vmcopyin.c这个文件, 导致make qemu时总是出现copyin_new()和copyinstr_new()undefined的错误。

int
copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{
 /**  uint64 n, va0, pa0;

  while(len > 0){
    va0 = PGROUNDDOWN(srcva);
    pa0 = walkaddr(pagetable, va0);
    if(pa0 == 0)
      return -1;
    n = PGSIZE - (srcva - va0);
    if(n > len)
      n = len;
    memmove(dst, (void *)(pa0 + (srcva - va0)), n);

    len -= n;
    dst += n;
    srcva = va0 + PGSIZE;
  }
  return 0; */  // New modify lab3_3
  return copyin_new(pagetable, dst, srcva, len);
}
int
copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
{
  /** uint64 n, va0, pa0;
  int got_null = 0;

  while(got_null == 0 && max > 0){
    va0 = PGROUNDDOWN(srcva);
    pa0 = walkaddr(pagetable, va0);
    if(pa0 == 0)
      return -1;
    n = PGSIZE - (srcva - va0);
    if(n > max)
      n = max;

    char *p = (char *) (pa0 + (srcva - va0));
    while(n > 0){
      if(*p == '\0'){
        *dst = '\0';
        got_null = 1;
        break;
      } else {
        *dst = *p;
      }
      --n;
      --max;
      p++;
      dst++;
    }

    srcva = va0 + PGSIZE;
  }
  if(got_null){
    return 0;
  } else {
    return -1;
  }*/  // New modify lab3_3
  return copyinstr_new(pagetable, dst, srcva, max);
}

7. 在kernel/defs.h添加函数声明

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

8. 运行程序

make qemu
make grade

Lab4: Traps

RISC-V assembly (easy)

理解一点RISC-V汇编是很重要的,你应该在6.004中接触过。xv6仓库中有一个文件user/call.c。执行make fs.img编译它,并在user/call.asm中生成可读的汇编版本。

阅读call.asm中函数gfmain的代码。RISC-V的使用手册在参考页上。以下是您应该回答的一些问题(将答案存储在answers-traps.txt文件中):

  1. 哪些寄存器保存函数的参数?例如,在mainprintf的调用中,哪个寄存器保存13?
  2. main的汇编代码中对函数f的调用在哪里?对g的调用在哪里(提示:编译器可能会将函数内联)
  3. printf函数位于哪个地址?
  4. mainprintfjalr之后的寄存器ra中有什么值?
  5. 运行以下代码。
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);

程序的输出是什么?这是将字节映射到字符的ASCII码表

输出取决于RISC-V小端存储的事实。如果RISC-V是大端存储,为了得到相同的输出,你会把i设置成什么?是否需要将57616更改为其他值?

这里有一个小端和大端存储的描述和一个更异想天开的描述

  1. 在下面的代码中,“y=”之后将打印什么(注:答案不是一个特定的值)?为什么会发生这种情况?
printf("x=%d y=%d", 3);

答:

(1). 在a0-a7中存放参数,13存放在a2中

(2). 在C代码中,main调用f,f调用g。而在生成的汇编中,main函数进行了内联优化处理。

从代码li a1,12可以看出,main直接计算出了结果并储存

(3). 在0x630

(4). auipc(Add Upper Immediate to PC):auipc rd imm,将高位立即数加到PC上,从下面的指令格式可以看出,该指令将20位的立即数左移12位之后(右侧补0)加上PC的值,将结果保存到dest位置,图中为rd寄存器

下面来看jalr (jump and link register):jalr rd, offset(rs1)跳转并链接寄存器。jalr指令会将当前PC+4保存在rd中,然后跳转到指定的偏移地址offset(rs1)。

来看XV6的代码:

  30: 00000097       auipc ra,0x0
  34: 600080e7       jalr  1536(ra) # 630 <printf>
第一行代码:00000097H=00...0 0000 1001 0111B,对比指令格式,可见imm=0,dest=00001,opcode=0010111,对比汇编指令可知,auipc的操作码是0010111,ra寄存器代码是00001。这行代码将0x0左移12位(还是0x0)加到PC(当前为0x30)上并存入ra中,即ra中保存的是0x30

第2行代码:600080e7H=0110 0...0 1000 0000 1110 0111B,可见imm=0110 0000 0000,rs1=00001,funct3=000,rd=00001,opcode=1100111,rs1和rd的知识码都是00001,即都为寄存器ra。这对比jalr的标准格式有所不同,可能是此两处使用寄存器相同时,汇编中可以省略rd部分。

ra中保存的是0x30,加上0x600后为0x630,即printf的地址,执行此行代码后,将跳转到printf函数执行,并将PC+4=0X34+0X4=0X38保存到ra中,供之后返回使用。

(5). 57616=0xE110,0x00646c72小端存储为72-6c-64-00,对照ASCII码表

72:r 6c:l 64:d 00:充当字符串结尾标识

因此输出为:HE110 World

若为大端存储,i应改为0x726c6400,不需改变57616

(6). 原本需要两个参数,却只传入了一个,因此y=后面打印的结果取决于之前a2中保存的数据

Backtrace(moderate)

1. 在kernel/defs.h添加backtrace()函数声明

// printf.c
void            printf(char*, ...);
void            panic(char*) __attribute__((noreturn));
void            printfinit(void);
void            backtrace(void);  // New add lab4_2

2. GCC编译器将当前正在执行的函数的帧指针保存在s0寄存器,将下面的函数添加到kernel/riscv.h

static inline uint64
r_fp()
{
  uint64 x;
  asm volatile("mv %0, s0" : "=r" (x) );
  return x;
}

3. 在kernel/printf.c中实现名为backtrace()的函数

// New add lab4_2
void
backtrace(void){
        printf("backtrace:\n");
        uint64 fp = r_fp();
        while(PGROUNDUP(fp) - PGROUNDDOWN(fp) == PGSIZE){
                uint64 ret_addr = *(uint64*)(fp - 8);
                printf("%p\n", ret_addr);
                fp = *(uint64*)(fp - 16);
        }
}

注:

这段代码是一个用于追踪函数调用栈的函数。它的作用是打印出调用该函数的函数的返回地址,从而可以了解函数的调用关系。

具体来说,该函数使用了汇编指令 r_fp() 来获取当前函数的帧指针(frame pointer)。然后,它通过循环遍历调用栈中的每个帧,直到遇到一个不满足条件 PGROUNDUP(fp) - PGROUNDDOWN(fp) == PGSIZE 的帧(即帧指针不再指向一个完整的页面)为止。

在每个循环迭代中,它从当前帧中读取返回地址(即帧指针减去8字节的位置处的值),然后打印出该地址。接着,它更新帧指针,将其设置为当前帧的上一个帧的地址(即帧指针减去16字节的位置处的值)。

通过这种方式,函数可以逐级打印出调用栈中的每个函数的返回地址,从而可以了解函数调用的顺序和层次关系

4. 在kernel/sysproc.c的sys_sleep()添加对backtrace()的调用

uint64
sys_sleep(void)
{
  int n;
  uint ticks0;

  if(argint(0, &n) < 0)
    return -1;
  acquire(&tickslock);
  ticks0 = ticks;
  while(ticks - ticks0 < n){
    if(myproc()->killed){
      release(&tickslock);
      return -1;
    }
    sleep(&ticks, &tickslock);
  }
  release(&tickslock);
  backtrace();  // New add lab4_2
  return 0;
}

5. 运行测试(Ctrl + D退出)

make qemu
bttest

6. 成绩测试

./grade-lab-traps backtrace

Alarm(Hard)

1. 在Makefile的UPROGS添加$U/_alarmtest\

vim Makefile
UPROGS=\
        $U/_cat\
        $U/_echo\
        $U/_forktest\
        $U/_grep\
        $U/_init\
        $U/_kill\
        $U/_ln\
        $U/_ls\
        $U/_mkdir\
        $U/_rm\
        $U/_sh\
        $U/_stressfs\
        $U/_usertests\
        $U/_grind\
        $U/_wc\
        $U/_zombie\
        $U/_alarmtest\  # New add lab4_3

2. 在user/user.h添加函数声明

int sigalarm(int ticks, void(*handler)());  // New add lab4_3
int sigreturn(void);  // New add lab4_3

3. 更新user/usys.pl添加两个函数入口

# ......
entry("uptime");
entry("sigalarm");  # New add lab4_3
entry("sigreturn");  # New add lab4_3

4. 在kernel/syscall.h添加系统调用编号

#define SYS_close  21
#define SYS_sigalarm 22  // New add lab4_3
#define SYS_sigreturn 23  // New add lab4_3

5. 在 kernel/syscall.c 中添加声明和定义

extern uint64 sys_sigalarm(void);  // New add lab4_3
extern uint64 sys_sigreturn(void);  // New add lab4_3

static uint64 (*syscalls[])(void) = {
// ......
[SYS_sigalarm] sys_sigalarm,  // New add lab4_3
[SYS_sigreturn] sys_sigreturn,  // New add lab4_3
};

6. 在kernel/proc.h的struct proc添加如下

  // New add lab4_3
  int alarm_interval; 
  void (*alarm_handler)();
  int ticks_count;
  // New add lab4_3 end  

7. 在kernel/sysproc.c添加sys_sigalarm()和sys_sigreturn()

// New add lab4_3
uint64
sys_sigreturn(void)
{
        return 0;
}

// New add lab4_3  读取参数
uint64
sys_sigalarm(void) {
  if(argint(0, &myproc()->alarm_interval) < 0 ||
    argaddr(1, (uint64*)&myproc()->alarm_handler) < 0)
    return -1;

  return 0;
}

8. 在kernel/proc.c的allocproc()设置3个字段初始化为0,并在freeproc()进行同样的操作

static struct proc*
allocproc(void)
{
 // ......

found:
  p->pid = allocpid();

  p->ticks_count = 0;  // New add lab4_3 
  p->alarm_interval = 0;
  p->alarm_handler = 0; // New add lab4_3 end


  p->state = USED;

// ......
}
static void
freeproc(struct proc *p)
{
// ......
  p->xstate = 0;
  p->state = UNUSED;
  p->ticks_count = 0;  // New add lab4_3 
  p->alarm_interval = 0;
  p->alarm_handler = 0; // New add lab4_3 end

}

9. 在kernel/trap.c的usertrap()添加如下

void
usertrap(void)
{
 // ......
 
 // give up the CPU if this is a timer interrupt.
 if(which_dev == 2){
       if(++p->ticks_count == p->alarm_interval){
               // 更改陷阱帧中保留的程序计数器
               p->trapframe->epc = (uint64)p->alarm_handler;
               p->ticks_count = 0;
       }
       yield();
 }
 usertrapret();
}

至此test0的工作完成。

10. 在kernel/proc.h的struct proc添加两个字段

 // New add lab4_3
  int alarm_interval;
  void (*alarm_handler)();
  int ticks_count;
  int is_alarming;
  struct trapframe* alarm_trapframe;
  // New add lab4_3 end  

11. 查看user/alarmtest.c的periodic()

volatile static int count;

void
periodic()
{
  count = count + 1;
  printf("alarm!\n");
  sigreturn();
}

12. 在kernel/proc.c的allocproc()和freeproc()中设定好相关分配,回收内存的代码

// allocproc()
found:
  // initialize the alarm filed
  if((p->alarm_trapframe = (struct trapframe*)kalloc()) == 0){ // New add lab4_3
        freeproc(p);
        release(&p->lock);
        return 0;
  }
  p->pid = allocpid();
  p->is_alarming = 0; // New add lab4_3
  p->ticks_count = 0;  // New add lab4_3 
  p->alarm_interval = 0;
  p->alarm_handler = 0; // New add lab4_3 end

  p->state = USED;
static void
freeproc(struct proc *p)
{
// ...... 
 if(p->alarm_trapframe)
          kfree((void*)p->alarm_trapframe);  // New add lab4_3
  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;
  p->alarm_trapframe = 0; // New add lab4_3
  p->is_alarming = 0;  // New add lab4_3
  p->ticks_count = 0;  // New add lab4_3 
  p->alarm_interval = 0;  // New add lab4_3
  p->alarm_handler = 0; // New add lab4_3 end

}

13. 在kernel/trap.c 更改usertrap函数,保存进程陷阱帧p->trapframep->alarm_trapframe

// give up the CPU if this is a timer interrupt.
  if(which_dev == 2){  // New add lab4_3
        if(p->alarm_interval != 0 && ++p->ticks_count == p->alarm_interval && p->is_alarming == 0){
                memmove(p->alarm_trapframe, p->trapframe, sizeof(struct trapframe));
                p->trapframe->epc = (uint64)p->alarm_handler;
                p->ticks_count = 0;
                p->is_alarming = 1;
        }
        yield();
  }
  usertrapret();
}

14. 在kernel/sysproc.c 更改sys_sigreturn(),恢复陷阱帧

// New add lab4_3
uint64
sys_sigreturn(void)
{
        memmove(myproc()->trapframe, myproc()->alarm_trapframe, sizeof(struct trapframe));
        myproc()->is_alarming = 0;
        return 0;
}

15. 运行测试

make qemu
alarmtest

usertests

16. 成绩测试

./grade-lab-traps alarm

 Lab 4 所有实验测试

1. 添加time.txt,写入您的实验耗时(只能是正整数)

vim time.txt

2. make grade

提交实验

1. git commit + make handin。如果有untracked的文件先git add

git commit -am "ready to submit my lab4"
make handin

Lab5: xv6 lazy page allocation

切换分支

git fetch

git checkout -b lazy

Eliminae allocation from sbrk() (easy)

1. 修改kernel/sysproc.c里的sys_sbrk()

uint64

sys_sbrk(void)

{

  int addr;

  int n;



  if(argint(0, &n) < 0)

    return -1;

  addr = myproc()->sz;

  // lazy allocation

  myproc()->sz += n; // New add lab5_1

//  if(growproc(n) < 0)

//    return -1;

  return addr;

}

2. 运行测试

make qemu
echo hi

Lazy allocation(moderate)
 

1. 修改kernel/trap.c里的usertrap()函数

if(cause == 8){
    // system call

    if(p->killed)
      exit(-1);

    // sepc points to the ecall instruction,
    // but we want to return to the next instruction.
    p->trapframe->epc += 4;

    // an interrupt will change sstatus &c registers,
    // so don't enable until done with those registers.
    intr_on();

    syscall();
  } else if((which_dev = devintr()) != 0){
    // ok
  }else if(cause == 13 || cause == 15){  // 判断页面是否错误
          uint64 va = r_stval();
          uint64 ka = (uint64)kalloc();
          if(ka == 0){
                  p->killed = 1;
          }
          else if(isValid(p, va) == 0){
                  kfree((void*)ka);
                  p->killed = 1;
          }
          else{
                  memset((void*)ka, 0, PGSIZE);
                  va = PGROUNDDOWN(va);
                  if(mappages(p->pagetable, va, PGSIZE, ka, PTE_U | PTE_R | PTE_W) != 0){
                          kfree((void*)ka);
                          p->killed = 1;
                  }
          }
   }
   else {
// ......
   }

// New add Lab5_2
// 判断va地址是否合法,如果va大于sz或者当虚拟地址比进程的用户栈还小,则不合法
int isValid(struct proc *p, uint64 va){
        uint64 stackbase = PGROUNDDOWN(p->trapframe->sp);
        if(va >= p->sz || (va < stackbase))
                return 0;
    return 1;
}

2. 修改kernel/vm.c里的uvmunmap()函数

uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
  uint64 a;
  pte_t *pte;

  if((va % PGSIZE) != 0)
    panic("uvmunmap: not aligned");

  for(a = va; a < va + npages*PGSIZE; a += PGSIZE){  // 某页分配不报错,则跳过
    if((pte = walk(pagetable, a, 0)) == 0)
    //  panic("uvmunmap: walk");
      continue;
    if((*pte & PTE_V) == 0)
    //  panic("uvmunmap: not mapped");
        continue;
    if(PTE_FLAGS(*pte) == PTE_V)
      panic("uvmunmap: not a leaf");
    if(do_free){
      uint64 pa = PTE2PA(*pte);
      kfree((void*)pa);
    }
    *pte = 0;
  }
}

3. 运行测试

make qemu
echo hi

Lazytests and Usertests(moderate)

1. 处理sbrk()参数为负的情况,减少相应的内存,就是dealloc相应的内存n,注意n不能大于p->sz

uint64
sys_sbrk(void)
{
  int addr;
  int n;

  if(argint(0, &n) < 0)
    return -1;
  struct proc* p =  myproc();  // New add lab5_3
 // addr = myproc()->sz;
  addr = p->sz;
  uint64 sz = p->sz;
  if(n > 0){
          // lazy allocation
          p->sz += n;
  }else if(sz + n > 0){
          sz = uvmdealloc(p->pagetable, sz, sz + n);
          p->sz = sz;
  }else{
          return -1;
  }
  return addr;
  // lazy allocation
//  myproc()->sz += n; // New add lab5_1
//  if(growproc(n) < 0)
//    return -1;
//  return addr;
}

2. 在fork()中正确处理父到子内存拷贝。修改kernel/vm.c里的uvmcopy()函数。
        在fork时会调用uvmcopy复制一份父进程内存,在lazy allocation中可能0->sz中有部分没有真正分配,在uvmcopy中就会导致panic。累次uvmcopy使得在页面不存在时跳过这一页。

int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
  pte_t *pte;
  uint64 pa, i;
  uint flags;
  char *mem;

  for(i = 0; i < sz; i += PGSIZE){
    if((pte = walk(old, i, 0)) == 0)
    //  panic("uvmcopy: pte should exist");
        continue;
    if((*pte & PTE_V) == 0)
    //  panic("uvmcopy: page not present");
        continue;  // 跳过
    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte);
    if((mem = kalloc()) == 0)
      goto err;
    memmove(mem, (char*)pa, PGSIZE);
    if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
      kfree(mem);
      goto err;
    }
  }
  return 0;

3. 处理这种情形:进程从sbrk()向系统调用(如read或write)传递有效地址,但尚未分配该地址的内存。

函数执行流程:在调用write后系统trap到内核态,执行copyin来把用户程序va处的内容复制到内核空间,此时若va处并未分配内存,walkaddr会返回0导致系统调用失败。因此,我们要在kernel/vm.c里的walkaddr()函数中分配内存。

#include "spinlock.h"  // 引用头文件
#include "proc.h"   // New add lab5_3

uint64
walkaddr(pagetable_t pagetable, uint64 va)
{
  pte_t *pte;
  uint64 pa;
  struct proc *p = myproc();  // New add lab5_3
  if(va >= MAXVA)
    return 0;

  pte = walk(pagetable, va, 0);
  if(pte == 0 || (*pte & PTE_V) == 0){
    uint64 ka = (uint64)kalloc();
    if(ka == 0){
      return 0;
     }
    else if(isValid(p, va) == 0){
      kfree((void*)ka);
      return 0;
    }
    else{
            memset((void*)ka, 0, PGSIZE);
            if(mappages(p->pagetable, PGROUNDDOWN(va), PGSIZE, ka, PTE_U | PTE_R | PTE_W) != ){
                    kfree((void*)ka);  // 防止内存泄漏
                    return 0;
    }
    else{
            memset((void*)ka, 0, PGSIZE);
            if(mappages(p->pagetable, PGROUNDDOWN(va), PGSIZE, ka, PTE_U | PTE_R | PTE_W) != ){
                    kfree((void*)ka);
                    return 0;
            }
            return ka;
    }
  }
  if((*pte & PTE_V) == 0)
    return 0;
  if((*pte & PTE_U) == 0)
    return 0;
  pa = PTE2PA(*pte);
  return pa;
}

4. 运行测试

lazytests
usertests

5. 成绩测试

make grade

Lab6:Copy-on-Write Fork for xv6

Implement copy-on write (hard)

1. 切换分支

git fetch
git checkout cow
make clean

2. 查看user/cowtest.c,包含了三个测试simpletest()、threetest()以及filetest()

#include "kernel/types.h"
#include "kernel/memlayout.h"
#include "user/user.h"

// allocate more than half of physical memory,
// then fork. this will fail in the default
// kernel, which does not support copy-on-write.
void
simpletest()
{
  uint64 phys_size = PHYSTOP - KERNBASE;
  int sz = (phys_size / 3) * 2;

  printf("simple: ");

  char *p = sbrk(sz);  // 使用“sbrk”分配一半以上的物理内存
  if(p == (char*)0xffffffffffffffffL){
    printf("sbrk(%d) failed\n", sz);
    exit(-1);
 }

  for(char *q = p; q < p + sz; q += 4096){  // 使用进程ID填充分配的内存
    *(int*)q = getpid();
  }

  int pid = fork();  // 分叉进程并检查分叉是否成功
  if(pid < 0){
    printf("fork() failed\n");
    exit(-1);
  }

  if(pid == 0)  // 在子进程中,它立即退出,在父进程中,等待子进程退出
    exit(0);

  wait(0);

  if(sbrk(-sz) == (char*)0xffffffffffffffffL){
    printf("sbrk(-%d) failed\n", sz);
    exit(-1);
  }
  printf("ok\n");
}

// three processes all write COW memory.
// this causes more than half of physical memory
// to be allocated, so it also checks whether
// copied pages are freed.
// 类似于"simpletest",但分叉两次
// 子进程写入分配内存的子集,而父进程等待它们退出。等待后,他会检查内存的内容,以确保子进程不会覆盖他。
void
threetest()
{
  uint64 phys_size = PHYSTOP - KERNBASE;
  int sz = phys_size / 4;
  int pid1, pid2;

  printf("three: ");

  char *p = sbrk(sz);
  if(p == (char*)0xffffffffffffffffL){
    printf("sbrk(%d) failed\n", sz);
    exit(-1);
  }

  pid1 = fork();
  if(pid1 < 0){
    printf("fork failed\n");
    exit(-1);
  }
  if(pid1 == 0){
    pid2 = fork();
    if(pid2 < 0){
      printf("fork failed");
      exit(-1);
    }
    if(pid2 == 0){
      for(char *q = p; q < p + (sz/5)*4; q += 4096){
        *(int*)q = getpid();
      }
      for(char *q = p; q < p + (sz/5)*4; q += 4096){
        if(*(int*)q != getpid()){
          printf("wrong content\n");
          exit(-1);
        }
      }
      exit(-1);
    }
    for(char *q = p; q < p + (sz/2); q += 4096){
      *(int*)q = 9999;
    }
    exit(0);
  }

  for(char *q = p; q < p + sz; q += 4096){
    *(int*)q = getpid();
  }

  wait(0);

  sleep(1);

  for(char *q = p; q < p + sz; q += 4096){

    if(*(int*)q != getpid()){
      printf("wrong content\n");
      exit(-1);
    }
  }

  if(sbrk(-sz) == (char*)0xffffffffffffffffL){
    printf("sbrk(-%d) failed\n", sz);
    exit(-1);
  }

  printf("ok\n");
}

char junk1[4096];
int fds[2];
char junk2[4096];
char buf[4096];
char junk3[4096];

// test whether copyout() simulates COW faults.
// 使用“pipe”系统调用创建管道,分叉四个进程,每个进程向管道写入一个整数。每个子进程从管道中读取一个整数并退出。父进程等待所有子进程完成,检查缓冲区是否未被子进程修改。
void
filetest()
{
  printf("file: ");

  buf[0] = 99;

  for(int i = 0; i < 4; i++){
    if(pipe(fds) != 0){
      printf("pipe() failed\n");
      exit(-1);
    }
    int pid = fork();
    if(pid < 0){
      printf("fork failed\n");
      exit(-1);
    }
    if(pid == 0){
      sleep(1);
      if(read(fds[0], buf, sizeof(i)) != sizeof(i)){
        printf("error: read failed\n");
        exit(1);
      }
      sleep(1);
      int j = *(int*)buf;
      if(j != i){
        printf("error: read the wrong value\n");
        exit(1);
      }
      exit(0);
    }
    if(write(fds[1], &i, sizeof(i)) != sizeof(i)){
      printf("error: write failed\n");
      exit(-1);
    }
  }
  int xstatus = 0;
  for(int i = 0; i < 4; i++) {
    wait(&xstatus);
    if(xstatus != 0) {
      exit(1);
    }
  }

  if(buf[0] != 99){
    printf("error: child overwrote parent\n");
    exit(1);
  }

  printf("ok\n");
}


int
main(int argc, char *argv[])
{
  simpletest();

  // check that the first simpletest() freed the physical memory.
  simpletest();

  threetest();
  threetest();
  threetest();

  filetest();

  printf("ALL COW TESTS PASSED\n");

  exit(0);
}

在未修改的xv6上,即使是第一个测试也会失败。

3. 在kernel/riscv.h中选取PTE中的保留位定义标记一个页面是否为COW fork页面的标志位

4. 在kernel/kalloc.c中进行如下修改

(1)查看kernel/vm.c里的walk()

// Return the address of the PTE in page table pagetable
// that corresponds to virtual address va.  If alloc!=0,
// create any required page-table pages.
//
// The risc-v Sv39 scheme has three levels of page-table
// pages. A page-table page contains 512 64-bit PTEs.
// A 64-bit virtual address is split into five fields:
//   39..63 -- must be zero.
//   30..38 -- 9 bits of level-2 index.
//   21..29 -- 9 bits of level-1 index.
//   12..20 -- 9 bits of level-0 index.
//    0..11 -- 12 bits of byte offset within the page.
pte_t *
walk(pagetable_t pagetable, uint64 va, int alloc)
{
  if(va >= MAXVA)
    panic("walk");

  for(int level = 2; level > 0; level--) {
    pte_t *pte = &pagetable[PX(level, va)];
    if(*pte & PTE_V) {
      pagetable = (pagetable_t)PTE2PA(*pte);
    } else {
      if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)
        return 0;
      memset(pagetable, 0, PGSIZE);
      *pte = PA2PTE(pagetable) | PTE_V;
    }
  }
  return &pagetable[PX(0, va)];
}

(2)定义引用计数的全局变量ref,其中包含了一个自旋锁和一个引用计数数组,由于ref是全局变量,会被自动初始化为全0.

使用自旋锁是考虑到这种情况:进程P1和P2公用内存M,M引用计数为2,此时CPU1要执行fork产生P1的子进程,CPU2要终止P2,那么假设两个CPU同时读取引用计数为2,执行完成后CPU1中保存的引用计数为3,CPU2保存的计数为1,那么后复制的语句会覆盖掉先赋值的语句,从而产生错误。

// New add Lab6_1
struct ref_stru{
    struct spinlock lock;
    int cnt[PHYSTOP / PGSIZE];
}ref;

(3)在kinit中初始化ref的自旋锁

void
kinit()
{
  initlock(&kmem.lock, "kmem");
  initlock(&ref.lock, "ref");  // New add Lab6_1
  freerange(end, (void*)PHYSTOP);
}

(4)修改kalloc()和kfree(),在kalloc()中初始化内存引用计数为1,在kfree()中对内存引用计数减1,如果引用计数为0时才真正删除。

void
kfree(void *pa)
{
  struct run *r;
  if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");
  // space is recovered only if the reference count is 0, otherwise the reference count is reduced by 1.
  acquire(&ref.lock);
  if(--ref.cnt[(uint64)pa / PGSIZE] == 0){
      release(&ref.lock);
      r = (struct run*)pa;
      // Fill with junk to catch dangling refs.
      memset(pa, 1, PGSIZE);
     
      acquire(&kmem.lock);
      r->next = kmem.freelist;
      kmem.freelist = r;
      release(&kmem.lock);
  }else{
       release(&ref.lock);
  }
}
void *
kalloc(void)
{
  struct run *r;

  acquire(&kmem.lock);
  r = kmem.freelist;
  if(r){
    kmem.freelist = r->next;
    acquire(&ref.lock);  // New add lab6_1
    ref.cnt[(uint64)r / PGSIZE] = 1;  // initialize the reference count to 1
    release(&ref.lock);  // New add lab6_1 end
  }
  release(&kmem.lock);

  if(r)
    memset((char*)r, 5, PGSIZE); // fill with junk
  return (void*)r;
}

(5)添加如下四个函数:cowpage()、cowalloc()、krefcnt()、kaddrefcnt(),并记得在kernel/defs.h中添加函数声明。
在cowalloc()中,读取内存引用计数,如果为1,说明只有当前进程引用了该物理内存(其他进程此前已经被分配到了其他物理页面),就只需要改变PTE使能PTE_W;否则就分配物理页面,并将原来的引用计数减1.该函数需要返回物理地址,这将在copyout中使用到。

/**
 * @brief cowpage judge whether a page is a COW page
 * param pagetable  specifies the page table for the query
 * param va virtual address
 * return 0 Y -1 N
 */
int cowpage(pagetable_t pagetable, uint64 va){
        if(va >= MAXVA)
                return -1;
        pte_t* pte = walk(pagetable, va, 0);
        if(pte == 0)
                return -1;
        if((*pte & PTE_V) == 0)
                return -1;
        return (*pte & PTE_F ? 0: -1);
}
/**
 * brief cowalloc copy_on-write alloter
 * param pagetable specifies the page table
 * @param va specifies virtual addr, must aline the page
 * @return physical address corresponding to va after assignment and if assignment fails, 0 is returned
 */
void* cowalloc(pagetable_t pagetable, uint64 va){
        if(va % PGSIZE != 0)
                return 0;
        uint64 pa = walkaddr(pagetable, va);  // Get the corresponding physical address
        if(pa == 0)
                return 0;
        pte_t* pte = walk(pagetable, va, 0); // get the corresponding PTE
        if(krefcnt((char*)pa) == 1){
                // only one process has a reference to this physical address
                // modify its PTE
                *pte |= PTE_W;
                *pte &= ~PTE_F;
                return (void*)pa;
        }else{
                // multiple process have references to the physical address
                // allocate new page, and copy the content of old page
                char* mem = kalloc();
                if(mem == 0)
                        return 0;
                // copy the content of old page to new page
                memmove(mem, (char*)pa, PGSIZE);
                // clear PTE_V, otherwise it will be judged as remap in mappagges
                *pte &= ~PTE_V;
                // add implications to the new page
                if(mappages(pagetable, va, PGSIZE, (uint64)mem, (PTE_FLAGS(*pte) | PTE_W) & ~PTE_F) != 0){
                        kfree(mem);
                        *pte |= PTE_V;
                        return 0;
                }
                // reduce the original physical memory reference count by 1
                kfree((char*)PGROUNDDOWN(pa));
                return mem;
        }
}
/**
 * @brief krefcnt get the reference count for the memory
 * @param pa specified memory address
 * @return refrence count
 */
int krefcnt(void* pa){
        return ref.cnt[(uint64)pa / PGSIZE];
}

/**
 * @brief kaddrefcnt: increase the reference count of the memory
 * @param pa specified memory address
 * @return 0: success -1:fail
 */
int kaddrefcnt(void* pa){
        if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
                return -1;
        acquire(&ref.lock);
        ++ref.cnt[(uint64)pa / PGSIZE];
        release(&ref.lock);
        return 0;
}

在kernel/defs.h添加函数声明

(5)修改freeange()

void
freerange(void *pa_start, void *pa_end)
{
  char *p;
  p = (char*)PGROUNDUP((uint64)pa_start);
  for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE){
        // in kfree(),you will substract cnt[] by 1, so make it 1 here, otherwise it becomes negative
        ref.cnt[(uint64)p / PGSIZE] = 1;  // New add lab6_1
        kfree(p);
  }
}

5. 修改kernel/vm.c里的uvmcopy(),不为子进程分配内存,而是使父子进程共享内存,但禁用PTE_W,同时标记PTE_F,记得调用kaddrefcnt()增加引用计数。

int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
  pte_t *pte;
  uint64 pa, i;
  uint flags;
  //char *mem;

  for(i = 0; i < sz; i += PGSIZE){
    if((pte = walk(old, i, 0)) == 0)
      panic("uvmcopy: pte should exist");
    if((*pte & PTE_V) == 0)
      panic("uvmcopy: page not present");
    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte);

    // set COW tags only for writable pages
    if(flags & PTE_W){  // New add lab6_1
            // disable writing and setting the COW Fork tag
            flags = (flags | PTE_F) & ~PTE_W;
            *pte = PA2PTE(pa) | flags;
    }

    if((mem = kalloc()) == 0)
      goto err;
    memmove(mem, (char*)pa, PGSIZE);
    //if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
    if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
      kfree(mem);
      goto err;
    }
    // increase the reference count of the memory
    kaddrefcnt((char*)pa);  // New add lab6_1
  }
  return 0;

 err:
  uvmunmap(new, 0, i / PGSIZE, 1);
  return -1;
}

6. 修改kernel/trap.c里的usertrap(),处理页面错误

void
usertrap(void)
{
  int which_dev = 0;

  if((r_sstatus() & SSTATUS_SPP) != 0)
    panic("usertrap: not from user mode");

  // send interrupts and exceptions to kerneltrap(),
  // since we're now in the kernel.
  w_stvec((uint64)kernelvec);

  struct proc *p = myproc();

  // save user program counter.
  p->trapframe->epc = r_sepc();
  uint64 cause = r_scause();  // New add lab6_1  
  if(cause == 8){
    // system call

    if(p->killed)
      exit(-1);

    // sepc points to the ecall instruction,
    // but we want to return to the next instruction.
    p->trapframe->epc += 4;

    // an interrupt will change sstatus &c registers,
    // so don't enable until done with those registers.
    intr_on();

    syscall();
  } else if((which_dev = devintr()) != 0){
    // ok
  } else if(cause == 13 || cause == 15){  // New add lab6_1
          uint64 fault_va = r_stval();  // get the error virtual address
          if(fault_va >= p->sz || cowpage(p->pagetable, fault_va) != 0 || cowalloc(p->pagetable, PGROUNDDOWN(fault_va)) == 0)
                  p->killed = 1;
  }
  else {
    printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
    printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());
    p->killed = 1;
  }

  if(p->killed)
    exit(-1);

  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2)
    yield();

  usertrapret();
}

7. 在kernel/vm.c里的copyout()中处理相同的情况,如果是COW页面,需要更换pa0指向的物理地址

int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
  uint64 n, va0, pa0;

  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    pa0 = walkaddr(pagetable, va0);
    // deal with the situation of COW page
    if(cowpage(pagetable, va0) == 0){  // New add lab6_1
           // change the goal physical address
           pa0 = (uint64)cowalloc(pagetable, va0);
    }
    if(pa0 == 0)
      return -1;
    n = PGSIZE - (dstva - va0);
    if(n > len)
      n = len;
    memmove((void *)(pa0 + (dstva - va0)), src, n);

    len -= n;
    src += n;
    dstva = va0 + PGSIZE;
  }
  return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值