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
Print a page table (easy)
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中函数g
、f
和main
的代码。RISC-V的使用手册在参考页上。以下是您应该回答的一些问题(将答案存储在answers-traps.txt文件中):
- 哪些寄存器保存函数的参数?例如,在
main
对printf
的调用中,哪个寄存器保存13? main
的汇编代码中对函数f
的调用在哪里?对g
的调用在哪里(提示:编译器可能会将函数内联)printf
函数位于哪个地址?- 在
main
中printf
的jalr
之后的寄存器ra
中有什么值? - 运行以下代码。
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);
程序的输出是什么?这是将字节映射到字符的ASCII码表。
输出取决于RISC-V小端存储的事实。如果RISC-V是大端存储,为了得到相同的输出,你会把i
设置成什么?是否需要将57616
更改为其他值?
- 在下面的代码中,“
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->trapframe
到p->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;
}