Compare commits

...

2 Commits

Author SHA1 Message Date
whatever
ce9cb0d6c0 all done 2024-12-19 15:19:42 +08:00
Frans Kaashoek
6dd1443e74 mmap lab 2024-11-11 08:55:18 -05:00
21 changed files with 2326 additions and 72 deletions

12
.gitignore vendored
View File

@ -11,7 +11,17 @@ entryother
initcode
initcode.out
kernelmemfs
mkfs
mkfs/mkfs
kernel/kernel
user/usys.S
.gdbinit
*.zip
xv6.out*
.vagrant/
submissions/
ph
barrier
/lab-*.json
.DS_Store
*.dSYM
*.pcap

244
Makefile
View File

@ -1,14 +1,16 @@
# To compile and run with a lab solution, set the lab name in conf/lab.mk
# (e.g., LAB=util). Run make grade to test solution with the lab's
# grade script (e.g., grade-lab-util).
-include conf/lab.mk
K=kernel
U=user
OBJS = \
$K/entry.o \
$K/start.o \
$K/console.o \
$K/printf.o \
$K/uart.o \
$K/kalloc.o \
$K/spinlock.o \
$K/string.o \
$K/main.o \
$K/vm.o \
@ -30,9 +32,36 @@ OBJS = \
$K/plic.o \
$K/virtio_disk.o
OBJS_KCSAN = \
$K/start.o \
$K/console.o \
$K/printf.o \
$K/uart.o \
$K/spinlock.o
ifdef KCSAN
OBJS_KCSAN += \
$K/kcsan.o
endif
ifeq ($(LAB),lock)
OBJS += \
$K/stats.o\
$K/sprintf.o
endif
ifeq ($(LAB),net)
OBJS += \
$K/e1000.o \
$K/net.o \
$K/pci.o
endif
# riscv64-unknown-elf- or riscv64-linux-gnu-
# perhaps in /opt/riscv/bin
#TOOLPREFIX =
TOOLPREFIX = /opt/riscv/bin/riscv64-unknown-elf-
# Try to infer the correct TOOLPREFIX if not set
ifndef TOOLPREFIX
@ -57,6 +86,13 @@ OBJCOPY = $(TOOLPREFIX)objcopy
OBJDUMP = $(TOOLPREFIX)objdump
CFLAGS = -Wall -Werror -O -fno-omit-frame-pointer -ggdb -gdwarf-2
ifdef LAB
LABUPPER = $(shell echo $(LAB) | tr a-z A-Z)
XCFLAGS += -DSOL_$(LABUPPER) -DLAB_$(LABUPPER)
endif
CFLAGS += $(XCFLAGS)
CFLAGS += -MD
CFLAGS += -mcmodel=medany
# CFLAGS += -ffreestanding -fno-common -nostdlib -mno-relax
@ -70,6 +106,15 @@ CFLAGS += -fno-builtin-printf -fno-builtin-fprintf -fno-builtin-vprintf
CFLAGS += -I.
CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)
ifeq ($(LAB),net)
CFLAGS += -DNET_TESTS_PORT=$(SERVERPORT)
endif
ifdef KCSAN
CFLAGS += -DKCSAN
KCSANFLAG = -fsanitize=thread -fno-inline
endif
# Disable PIE when possible (for Ubuntu 16.10 toolchain)
ifneq ($(shell $(CC) -dumpspecs 2>/dev/null | grep -e '[^f]no-pie'),)
CFLAGS += -fno-pie -no-pie
@ -80,11 +125,17 @@ endif
LDFLAGS = -z max-page-size=4096
$K/kernel: $(OBJS) $K/kernel.ld $U/initcode
$(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS)
$K/kernel: $(OBJS) $(OBJS_KCSAN) $K/kernel.ld $U/initcode
$(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS) $(OBJS_KCSAN)
$(OBJDUMP) -S $K/kernel > $K/kernel.asm
$(OBJDUMP) -t $K/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $K/kernel.sym
$(OBJS): EXTRAFLAG := $(KCSANFLAG)
$K/%.o: $K/%.c
$(CC) $(CFLAGS) $(EXTRAFLAG) -c -o $@ $<
$U/initcode: $U/initcode.S
$(CC) $(CFLAGS) -march=rv64g -nostdinc -I. -Ikernel -c $U/initcode.S -o $U/initcode.o
$(LD) $(LDFLAGS) -N -e start -Ttext 0 -o $U/initcode.out $U/initcode.o
@ -96,6 +147,10 @@ tags: $(OBJS) _init
ULIB = $U/ulib.o $U/usys.o $U/printf.o $U/umalloc.o
ifeq ($(LAB),lock)
ULIB += $U/statistics.o
endif
_%: %.o $(ULIB)
$(LD) $(LDFLAGS) -T $U/user.ld -o $@ $^
$(OBJDUMP) -S $@ > $*.asm
@ -114,7 +169,7 @@ $U/_forktest: $U/forktest.o $(ULIB)
$(OBJDUMP) -S $U/_forktest > $U/forktest.asm
mkfs/mkfs: mkfs/mkfs.c $K/fs.h $K/param.h
gcc -Werror -Wall -I. -o mkfs/mkfs mkfs/mkfs.c
gcc $(XCFLAGS) -Werror -Wall -I. -o mkfs/mkfs mkfs/mkfs.c
# Prevent deletion of intermediate files, e.g. cat.o, after first build, so
# that disk image changes after first build are persistent until clean. More
@ -140,18 +195,102 @@ UPROGS=\
$U/_wc\
$U/_zombie\
fs.img: mkfs/mkfs README $(UPROGS)
mkfs/mkfs fs.img README $(UPROGS)
ifeq ($(LAB),syscall)
UPROGS += \
$U/_attack\
$U/_attacktest\
$U/_secret
endif
ifeq ($(LAB),lock)
UPROGS += \
$U/_stats
endif
ifeq ($(LAB),traps)
UPROGS += \
$U/_call\
$U/_bttest
endif
ifeq ($(LAB),lazy)
UPROGS += \
$U/_lazytests
endif
ifeq ($(LAB),cow)
UPROGS += \
$U/_cowtest
endif
ifeq ($(LAB),thread)
UPROGS += \
$U/_uthread
$U/uthread_switch.o : $U/uthread_switch.S
$(CC) $(CFLAGS) -c -o $U/uthread_switch.o $U/uthread_switch.S
$U/_uthread: $U/uthread.o $U/uthread_switch.o $(ULIB)
$(LD) $(LDFLAGS) -N -e main -Ttext 0 -o $U/_uthread $U/uthread.o $U/uthread_switch.o $(ULIB)
$(OBJDUMP) -S $U/_uthread > $U/uthread.asm
ph: notxv6/ph.c
gcc -o ph -g -O2 $(XCFLAGS) notxv6/ph.c -pthread
barrier: notxv6/barrier.c
gcc -o barrier -g -O2 $(XCFLAGS) notxv6/barrier.c -pthread
endif
ifeq ($(LAB),pgtbl)
UPROGS += \
$U/_pgtbltest
endif
ifeq ($(LAB),lock)
UPROGS += \
$U/_kalloctest\
$U/_bcachetest
endif
ifeq ($(LAB),fs)
UPROGS += \
$U/_bigfile
endif
ifeq ($(LAB),mmap)
UPROGS += \
$U/_mmaptest
endif
ifeq ($(LAB),net)
UPROGS += \
$U/_nettest
endif
UEXTRA=
ifeq ($(LAB),util)
UEXTRA += user/xargstest.sh
endif
fs.img: mkfs/mkfs README $(UEXTRA) $(UPROGS)
mkfs/mkfs fs.img README $(UEXTRA) $(UPROGS)
newfs.img:
-mv -f fs.img fs.img.bk
-include kernel/*.d user/*.d
clean:
rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg \
clean:
rm -rf *.tex *.dvi *.idx *.aux *.log *.ind *.ilg *.dSYM *.zip *.pcap \
*/*.o */*.d */*.asm */*.sym \
$U/initcode $U/initcode.out $K/kernel fs.img \
mkfs/mkfs .gdbinit \
$U/usys.S \
$(UPROGS)
$U/initcode $U/initcode.out $U/usys.S $U/_* \
$K/kernel \
mkfs/mkfs fs.img fs.img.bk .gdbinit __pycache__ xv6.out* \
ph barrier
# try to generate a unique GDB port
GDBPORT = $(shell expr `id -u` % 5000 + 25000)
@ -162,13 +301,29 @@ QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \
ifndef CPUS
CPUS := 3
endif
ifeq ($(LAB),fs)
CPUS := 1
endif
FWDPORT1 = $(shell expr `id -u` % 5000 + 25999)
FWDPORT2 = $(shell expr `id -u` % 5000 + 30999)
QEMUOPTS = -machine virt -bios none -kernel $K/kernel -m 128M -smp $(CPUS) -nographic
QEMUOPTS += -global virtio-mmio.force-legacy=false
QEMUOPTS += -drive file=fs.img,if=none,format=raw,id=x0
QEMUOPTS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
qemu: $K/kernel fs.img
ifeq ($(LAB),net)
QEMUOPTS += -netdev user,id=net0,hostfwd=udp::$(FWDPORT1)-:2000,hostfwd=udp::$(FWDPORT2)-:2001 -object filter-dump,id=net0,netdev=net0,file=packets.pcap
QEMUOPTS += -device e1000,netdev=net0,bus=pcie.0
endif
# makes a new fs.img
qemu: newfs.img $K/kernel fs.img
$(QEMU) $(QEMUOPTS)
# runs with existing fs.img, if present
qemu-fs: $K/kernel fs.img
$(QEMU) $(QEMUOPTS)
.gdbinit: .gdbinit.tmpl-riscv
@ -178,3 +333,56 @@ qemu-gdb: $K/kernel .gdbinit fs.img
@echo "*** Now run 'gdb' in another window." 1>&2
$(QEMU) $(QEMUOPTS) -S $(QEMUGDB)
ifeq ($(LAB),net)
# try to generate a unique port for the echo server
SERVERPORT = $(shell expr `id -u` % 5000 + 25099)
endif
##
## FOR testing lab grading script
##
ifneq ($(V),@)
GRADEFLAGS += -v
endif
print-gdbport:
@echo $(GDBPORT)
grade:
@echo $(MAKE) clean
@$(MAKE) clean || \
(echo "'make clean' failed. HINT: Do you have another running instance of xv6?" && exit 1)
./grade-lab-$(LAB) $(GRADEFLAGS)
##
## FOR submissions
##
submit-check:
@if ! test -d .git; then \
echo No .git directory, is this a git repository?; \
false; \
fi
@if test "$$(git symbolic-ref HEAD)" != refs/heads/$(LAB); then \
git branch; \
read -p "You are not on the $(LAB) branch. Hand-in the current branch? [y/N] " r; \
test "$$r" = y; \
fi
@if ! git diff-files --quiet || ! git diff-index --quiet --cached HEAD; then \
git status -s; \
echo; \
echo "You have uncomitted changes. Please commit or stash them."; \
false; \
fi
@if test -n "`git status -s`"; then \
git status -s; \
read -p "Untracked files will not be handed in. Continue? [y/N] " r; \
test "$$r" = y; \
fi
zipball: clean submit-check
git archive --verbose --format zip --output lab.zip HEAD
.PHONY: zipball clean grade submit-check

582
] Normal file
View File

@ -0,0 +1,582 @@
//
// File-system system calls.
// Mostly argument checking, since we don't trust
// user code, and calls into file.c and fs.c.
//
#include "types.h"
#include "riscv.h"
#include "defs.h"
#include "param.h"
#include "stat.h"
#include "spinlock.h"
#include "proc.h"
#include "fs.h"
#include "sleeplock.h"
#include "file.h"
#include "fcntl.h"
// Fetch the nth word-sized system call argument as a file descriptor
// and return both the descriptor and the corresponding struct file.
static int
argfd(int n, int *pfd, struct file **pf)
{
int fd;
struct file *f;
argint(n, &fd);
if(fd < 0 || fd >= NOFILE || (f=myproc()->ofile[fd]) == 0)
return -1;
if(pfd)
*pfd = fd;
if(pf)
*pf = f;
return 0;
}
// Allocate a file descriptor for the given file.
// Takes over file reference from caller on success.
static int
fdalloc(struct file *f)
{
int fd;
struct proc *p = myproc();
for(fd = 0; fd < NOFILE; fd++){
if(p->ofile[fd] == 0){
p->ofile[fd] = f;
return fd;
}
}
return -1;
}
uint64
sys_dup(void)
{
struct file *f;
int fd;
if(argfd(0, 0, &f) < 0)
return -1;
if((fd=fdalloc(f)) < 0)
return -1;
filedup(f);
return fd;
}
uint64
sys_read(void)
{
struct file *f;
int n;
uint64 p;
argaddr(1, &p);
argint(2, &n);
if(argfd(0, 0, &f) < 0)
return -1;
return fileread(f, p, n);
}
uint64
sys_write(void)
{
struct file *f;
int n;
uint64 p;
argaddr(1, &p);
argint(2, &n);
if(argfd(0, 0, &f) < 0)
return -1;
return filewrite(f, p, n);
}
uint64
sys_close(void)
{
int fd;
struct file *f;
if(argfd(0, &fd, &f) < 0)
return -1;
myproc()->ofile[fd] = 0;
fileclose(f);
return 0;
}
uint64
sys_fstat(void)
{
struct file *f;
uint64 st; // user pointer to struct stat
argaddr(1, &st);
if(argfd(0, 0, &f) < 0)
return -1;
return filestat(f, st);
}
// Create the path new as a link to the same inode as old.
uint64
sys_link(void)
{
char name[DIRSIZ], new[MAXPATH], old[MAXPATH];
struct inode *dp, *ip;
if(argstr(0, old, MAXPATH) < 0 || argstr(1, new, MAXPATH) < 0)
return -1;
begin_op();
if((ip = namei(old)) == 0){
end_op();
return -1;
}
ilock(ip);
if(ip->type == T_DIR){
iunlockput(ip);
end_op();
return -1;
}
ip->nlink++;
iupdate(ip);
iunlock(ip);
if((dp = nameiparent(new, name)) == 0)
goto bad;
ilock(dp);
if(dp->dev != ip->dev || dirlink(dp, name, ip->inum) < 0){
iunlockput(dp);
goto bad;
}
iunlockput(dp);
iput(ip);
end_op();
return 0;
bad:
ilock(ip);
ip->nlink--;
iupdate(ip);
iunlockput(ip);
end_op();
return -1;
}
// Is the directory dp empty except for "." and ".." ?
static int
isdirempty(struct inode *dp)
{
int off;
struct dirent de;
for(off=2*sizeof(de); off<dp->size; off+=sizeof(de)){
if(readi(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de))
panic("isdirempty: readi");
if(de.inum != 0)
return 0;
}
return 1;
}
uint64
sys_unlink(void)
{
struct inode *ip, *dp;
struct dirent de;
char name[DIRSIZ], path[MAXPATH];
uint off;
if(argstr(0, path, MAXPATH) < 0)
return -1;
begin_op();
if((dp = nameiparent(path, name)) == 0){
end_op();
return -1;
}
ilock(dp);
// Cannot unlink "." or "..".
if(namecmp(name, ".") == 0 || namecmp(name, "..") == 0)
goto bad;
if((ip = dirlookup(dp, name, &off)) == 0)
goto bad;
ilock(ip);
if(ip->nlink < 1)
panic("unlink: nlink < 1");
if(ip->type == T_DIR && !isdirempty(ip)){
iunlockput(ip);
goto bad;
}
memset(&de, 0, sizeof(de));
if(writei(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de))
panic("unlink: writei");
if(ip->type == T_DIR){
dp->nlink--;
iupdate(dp);
}
iunlockput(dp);
ip->nlink--;
iupdate(ip);
iunlockput(ip);
end_op();
return 0;
bad:
iunlockput(dp);
end_op();
return -1;
}
static struct inode*
create(char *path, short type, short major, short minor)
{
struct inode *ip, *dp;
char name[DIRSIZ];
if((dp = nameiparent(path, name)) == 0)
return 0;
ilock(dp);
if((ip = dirlookup(dp, name, 0)) != 0){
iunlockput(dp);
ilock(ip);
if(type == T_FILE && (ip->type == T_FILE || ip->type == T_DEVICE))
return ip;
iunlockput(ip);
return 0;
}
if((ip = ialloc(dp->dev, type)) == 0){
iunlockput(dp);
return 0;
}
ilock(ip);
ip->major = major;
ip->minor = minor;
ip->nlink = 1;
iupdate(ip);
if(type == T_DIR){ // Create . and .. entries.
// No ip->nlink++ for ".": avoid cyclic ref count.
if(dirlink(ip, ".", ip->inum) < 0 || dirlink(ip, "..", dp->inum) < 0)
goto fail;
}
if(dirlink(dp, name, ip->inum) < 0)
goto fail;
if(type == T_DIR){
// now that success is guaranteed:
dp->nlink++; // for ".."
iupdate(dp);
}
iunlockput(dp);
return ip;
fail:
// something went wrong. de-allocate ip.
ip->nlink = 0;
iupdate(ip);
iunlockput(ip);
iunlockput(dp);
return 0;
}
uint64
sys_open(void)
{
char path[MAXPATH];
int fd, omode;
struct file *f;
struct inode *ip;
int n;
argint(1, &omode);
if((n = argstr(0, path, MAXPATH)) < 0)
return -1;
begin_op();
if(omode & O_CREATE){
ip = create(path, T_FILE, 0, 0);
if(ip == 0){
end_op();
return -1;
}
} else {
if((ip = namei(path)) == 0){
end_op();
return -1;
}
ilock(ip);
if(ip->type == T_DIR && omode != O_RDONLY){
iunlockput(ip);
end_op();
return -1;
}
}
if(ip->type == T_DEVICE && (ip->major < 0 || ip->major >= NDEV)){
iunlockput(ip);
end_op();
return -1;
}
if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){
if(f)
fileclose(f);
iunlockput(ip);
end_op();
return -1;
}
if(ip->type == T_DEVICE){
f->type = FD_DEVICE;
f->major = ip->major;
} else {
f->type = FD_INODE;
f->off = 0;
}
f->ip = ip;
f->readable = !(omode & O_WRONLY);
f->writable = (omode & O_WRONLY) || (omode & O_RDWR);
if((omode & O_TRUNC) && ip->type == T_FILE){
itrunc(ip);
}
iunlock(ip);
end_op();
return fd;
}
uint64
sys_mkdir(void)
{
char path[MAXPATH];
struct inode *ip;
begin_op();
if(argstr(0, path, MAXPATH) < 0 || (ip = create(path, T_DIR, 0, 0)) == 0){
end_op();
return -1;
}
iunlockput(ip);
end_op();
return 0;
}
uint64
sys_mknod(void)
{
struct inode *ip;
char path[MAXPATH];
int major, minor;
begin_op();
argint(1, &major);
argint(2, &minor);
if((argstr(0, path, MAXPATH)) < 0 ||
(ip = create(path, T_DEVICE, major, minor)) == 0){
end_op();
return -1;
}
iunlockput(ip);
end_op();
return 0;
}
uint64
sys_chdir(void)
{
char path[MAXPATH];
struct inode *ip;
struct proc *p = myproc();
begin_op();
if(argstr(0, path, MAXPATH) < 0 || (ip = namei(path)) == 0){
end_op();
return -1;
}
ilock(ip);
if(ip->type != T_DIR){
iunlockput(ip);
end_op();
return -1;
}
iunlock(ip);
iput(p->cwd);
end_op();
p->cwd = ip;
return 0;
}
uint64
sys_exec(void)
{
char path[MAXPATH], *argv[MAXARG];
int i;
uint64 uargv, uarg;
argaddr(1, &uargv);
if(argstr(0, path, MAXPATH) < 0) {
return -1;
}
memset(argv, 0, sizeof(argv));
for(i=0;; i++){
if(i >= NELEM(argv)){
goto bad;
}
if(fetchaddr(uargv+sizeof(uint64)*i, (uint64*)&uarg) < 0){
goto bad;
}
if(uarg == 0){
argv[i] = 0;
break;
}
argv[i] = kalloc();
if(argv[i] == 0)
goto bad;
if(fetchstr(uarg, argv[i], PGSIZE) < 0)
goto bad;
}
int ret = exec(path, argv);
for(i = 0; i < NELEM(argv) && argv[i] != 0; i++)
kfree(argv[i]);
return ret;
bad:
for(i = 0; i < NELEM(argv) && argv[i] != 0; i++)
kfree(argv[i]);
return -1;
}
uint64
sys_pipe(void)
{
uint64 fdarray; // user pointer to array of two integers
struct file *rf, *wf;
int fd0, fd1;
struct proc *p = myproc();
argaddr(0, &fdarray);
if(pipealloc(&rf, &wf) < 0)
return -1;
fd0 = -1;
if((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0){
if(fd0 >= 0)
p->ofile[fd0] = 0;
fileclose(rf);
fileclose(wf);
return -1;
}
if(copyout(p->pagetable, fdarray, (char*)&fd0, sizeof(fd0)) < 0 ||
copyout(p->pagetable, fdarray+sizeof(fd0), (char *)&fd1, sizeof(fd1)) < 0){
p->ofile[fd0] = 0;
p->ofile[fd1] = 0;
fileclose(rf);
fileclose(wf);
return -1;
}
return 0;
}
const struct vma Null= {.addr=0,.len=0,.prot=0,.flags=0,.file=(struct file*)0,.offset=0};
uint64 sys_mmap(void) {
uint64 addr,len;
int prot, flags, fd, offset;
argaddr(0, &addr);
argaddr(1, &len), argint(2, &prot), argint(3, &flags), argint(4, &fd), argint(5, &offset);
struct proc *p = myproc();
struct vma *vma = p->vma;
uint64 ret = p->sz;
for (int i = 0; i < NVMA; ++i) // don't process when it's full
if (vma[i].len == Null.len) { // getting a mem size of 0 seems crazy
vma[i].addr=ret;//addr is always 0, and ret is the position
vma[i].len=len;
vma[i].prot=prot;
vma[i].flags=flags;// which is MAP flags
vma[i].file=p->ofile[fd];
filedup(vma[i].file);//increased ref here
vma[i].offset=offset;
break;
}
p->sz += len;
return ret;
}
uint64 sys_munmap(void) {
uint64 addr,len;
argaddr(0,&addr),argaddr(1,&len);
// well it's possible that the passed addr and len does not match a munmap
return 0;
}
int lazymap(){
struct proc *p=myproc();
uint64 va=PGROUNDDOWN(r_stval());
if(va>=p->sz)
goto err1;
for(int i=0;i<NVMA;++i) {
const uint64 addr=p->vma[i].addr;
const int vma_flag=p->vma[i].prot;
const int offset=p->vma[i].offset;
struct file *file=p->vma[i].file;
struct inode *ip=file->ip;
const int len=p->vma[i].len;
// each addr and len won't intersect with each other, it's defined by sys_mmap process
if(PGROUNDDOWN(addr) + len>= va){// it's considered that addr is page-aligned
char *mem=kalloc();
memset(mem,0,PGSIZE);
if(mem==0)
goto err1;
int pte_flag=0;
if(vma_flag & PROT_READ)
pte_flag|=PTE_R;
if(vma_flag & PROT_WRITE)
pte_flag|=PTE_W;
if(vma_flag & PROT_EXEC)
pte_flag|=PTE_X;
pte_flag|=PTE_U;
if(mappages(p->pagetable,va,PGSIZE,(uint64)mem,pte_flag)<0){
kfree(mem);
goto err1;
}
ilock(ip);
// readi won't increase inode ref
if(!readi(ip,0,(uint64)mem,offset + (va-addr),PGSIZE))
goto err2;
DEBUG();
// file pointer handles ref to inode, so don't increase inode ref there
iunlock(ip);
return 0;
err2:
iunlock(ip);
goto err1;
}
}
err1:
return -1;
}

1
conf/lab.mk Normal file
View File

@ -0,0 +1 @@
LAB=mmap

69
grade-lab-mmap Executable file
View File

@ -0,0 +1,69 @@
#!/usr/bin/env python3
import re
from gradelib import *
r = Runner(save("xv6.out"))
@test(0, "running mmaptest")
def test_mmaptest():
r.run_qemu(shell_script([
'mmaptest'
]), timeout=180)
@test(20, "mmaptest: mmap basic", parent=test_mmaptest)
def test_mmaptest_mmap_basic():
r.match('^test basic mmap: OK$')
@test(10, "mmaptest: mmap private", parent=test_mmaptest)
def test_mmaptest_mmap_private():
r.match('^test mmap private: OK$')
@test(10, "mmaptest: mmap read-only", parent=test_mmaptest)
def test_mmaptest_mmap_readonly():
r.match('^test mmap read-only: OK$')
@test(10, "mmaptest: mmap read/write", parent=test_mmaptest)
def test_mmaptest_mmap_readwrite():
r.match('^test mmap read/write: OK$')
@test(10, "mmaptest: mmap dirty", parent=test_mmaptest)
def test_mmaptest_mmap_dirty():
r.match('^test mmap dirty: OK$')
@test(10, "mmaptest: not-mapped unmap", parent=test_mmaptest)
def test_mmaptest_mmap_unmap():
r.match('^test not-mapped unmap: OK$')
@test(10, "mmaptest: lazy access", parent=test_mmaptest)
def test_mmaptest_mmap_unmap():
r.match('^test lazy access: OK$')
@test(10, "mmaptest: two files", parent=test_mmaptest)
def test_mmaptest_mmap_two():
r.match('^test mmap two files: OK$')
@test(40, "mmaptest: fork_test", parent=test_mmaptest)
def test_mmaptest_fork_test():
r.match('^test fork: OK$')
@test(10, "mmaptest: munmap_noaccess", parent=test_mmaptest)
def test_mmaptest_munmap_noaccess():
r.match('^test munmap prevents access: OK$')
@test(10, "mmaptest: read_only_write", parent=test_mmaptest)
def test_mmaptest_read_only_write():
r.match('^test writes to read-only mapped memory: OK$')
@test(19, "usertests")
def test_usertests():
r.run_qemu(shell_script([
'usertests -q'
]), timeout=300)
r.match('^ALL TESTS PASSED$')
@test(1, "time")
def test_time():
check_time()
run_tests()

628
gradelib.py Normal file
View File

@ -0,0 +1,628 @@
from __future__ import print_function
import sys, os, re, time, socket, select, subprocess, errno, shutil, random, string, json
from subprocess import check_call, Popen
from optparse import OptionParser
__all__ = []
##################################################################
# Test structure
#
__all__ += ["test", "end_part", "run_tests", "get_current_test"]
TESTS = []
TOTAL = POSSIBLE = 0
PART_TOTAL = PART_POSSIBLE = 0
CURRENT_TEST = None
GRADES = {}
def test(points, title=None, parent=None):
"""Decorator for declaring test functions. If title is None, the
title of the test will be derived from the function name by
stripping the leading "test_" and replacing underscores with
spaces."""
def register_test(fn, title=title):
if not title:
assert fn.__name__.startswith("test_")
title = fn.__name__[5:].replace("_", " ")
if parent:
title = " " + title
def run_test():
global TOTAL, POSSIBLE, CURRENT_TEST, GRADES
# Handle test dependencies
if run_test.complete:
return run_test.ok
run_test.complete = True
parent_failed = False
if parent:
parent_failed = not parent()
# Run the test
fail = None
start = time.time()
CURRENT_TEST = run_test
sys.stdout.write("== Test %s == " % title)
if parent:
sys.stdout.write("\n")
sys.stdout.flush()
try:
if parent_failed:
raise AssertionError('Parent failed: %s' % parent.__name__)
fn()
except AssertionError as e:
fail = str(e)
# Display and handle test result
POSSIBLE += points
if points:
print("%s: %s" % (title, \
(color("red", "FAIL") if fail else color("green", "OK"))), end=' ')
if time.time() - start > 0.1:
print("(%.1fs)" % (time.time() - start), end=' ')
print()
if fail:
print(" %s" % fail.replace("\n", "\n "))
else:
TOTAL += points
if points:
GRADES[title] = 0 if fail else points
for callback in run_test.on_finish:
callback(fail)
CURRENT_TEST = None
run_test.ok = not fail
return run_test.ok
# Record test metadata on the test wrapper function
run_test.__name__ = fn.__name__
run_test.title = title
run_test.complete = False
run_test.ok = False
run_test.on_finish = []
TESTS.append(run_test)
return run_test
return register_test
def end_part(name):
def show_part():
global PART_TOTAL, PART_POSSIBLE
print("Part %s score: %d/%d" % \
(name, TOTAL - PART_TOTAL, POSSIBLE - PART_POSSIBLE))
print()
PART_TOTAL, PART_POSSIBLE = TOTAL, POSSIBLE
show_part.title = ""
TESTS.append(show_part)
def write_results():
global options
if not options.results:
return
try:
with open(options.results, "w") as f:
f.write(json.dumps(GRADES))
except OSError as e:
print("Provided a bad results path. Error:", e)
def run_tests():
"""Set up for testing and run the registered test functions."""
# Handle command line
global options
parser = OptionParser(usage="usage: %prog [-v] [filters...]")
parser.add_option("-v", "--verbose", action="store_true",
help="print commands")
parser.add_option("--color", choices=["never", "always", "auto"],
default="auto", help="never, always, or auto")
parser.add_option("--results", help="results file path")
(options, args) = parser.parse_args()
# Start with a full build to catch build errors
make()
# Clean the file system if there is one
reset_fs()
# Run tests
limit = list(map(str.lower, args))
try:
for test in TESTS:
if not limit or any(l in test.title.lower() for l in limit):
test()
if not limit:
write_results()
print("Score: %d/%d" % (TOTAL, POSSIBLE))
except KeyboardInterrupt:
pass
if TOTAL < POSSIBLE:
sys.exit(1)
def get_current_test():
if not CURRENT_TEST:
raise RuntimeError("No test is running")
return CURRENT_TEST
##################################################################
# Assertions
#
__all__ += ["assert_equal", "assert_lines_match"]
def assert_equal(got, expect, msg=""):
if got == expect:
return
if msg:
msg += "\n"
raise AssertionError("%sgot:\n %s\nexpected:\n %s" %
(msg, str(got).replace("\n", "\n "),
str(expect).replace("\n", "\n ")))
def assert_lines_match(text, *regexps, **kw):
"""Assert that all of regexps match some line in text. If a 'no'
keyword argument is given, it must be a list of regexps that must
*not* match any line in text."""
def assert_lines_match_kw(no=[]):
return no
no = assert_lines_match_kw(**kw)
# Check text against regexps
lines = text.splitlines()
good = set()
bad = set()
for i, line in enumerate(lines):
if any(re.match(r, line) for r in regexps):
good.add(i)
regexps = [r for r in regexps if not re.match(r, line)]
if any(re.match(r, line) for r in no):
bad.add(i)
if not regexps and not bad:
return
# We failed; construct an informative failure message
show = set()
for lineno in good.union(bad):
for offset in range(-2, 3):
show.add(lineno + offset)
if regexps:
show.update(n for n in range(len(lines) - 5, len(lines)))
msg = []
last = -1
for lineno in sorted(show):
if 0 <= lineno < len(lines):
if lineno != last + 1:
msg.append("...")
last = lineno
msg.append("%s %s" % (color("red", "BAD ") if lineno in bad else
color("green", "GOOD") if lineno in good
else " ",
lines[lineno]))
if last != len(lines) - 1:
msg.append("...")
if bad:
msg.append("unexpected lines in output")
for r in regexps:
msg.append(color("red", "MISSING") + " '%s'" % r)
raise AssertionError("\n".join(msg))
##################################################################
# Utilities
#
__all__ += ["make", "maybe_unlink", "reset_fs", "color", "random_str", "check_time", "check_answers"]
MAKE_TIMESTAMP = 0
def pre_make():
"""Delay prior to running make to ensure file mtimes change."""
while int(time.time()) == MAKE_TIMESTAMP:
time.sleep(0.1)
def post_make():
"""Record the time after make completes so that the next run of
make can be delayed if needed."""
global MAKE_TIMESTAMP
MAKE_TIMESTAMP = int(time.time())
def make(*target):
pre_make()
if Popen(("make",) + target).wait():
sys.exit(1)
post_make()
def show_command(cmd):
from shlex import quote
print("\n$", " ".join(map(quote, cmd)))
def maybe_unlink(*paths):
for path in paths:
try:
os.unlink(path)
except EnvironmentError as e:
if e.errno != errno.ENOENT:
raise
COLORS = {"default": "\033[0m", "red": "\033[31m", "green": "\033[32m"}
def color(name, text):
if options.color == "always" or (options.color == "auto" and os.isatty(1)):
return COLORS[name] + text + COLORS["default"]
return text
def reset_fs():
if os.path.exists("obj/fs/clean-fs.img"):
shutil.copyfile("obj/fs/clean-fs.img", "obj/fs/fs.img")
def random_str(n=8):
letters = string.ascii_letters + string.digits
return ''.join(random.choice(letters) for _ in range(n))
def check_time():
try:
print("")
with open('time.txt') as f:
d = f.read().strip()
if not re.match(r'^\d+$', d):
raise AssertionError('time.txt does not contain a single integer (number of hours spent on the lab)')
except IOError:
raise AssertionError('Cannot read time.txt')
def check_answers(file, n=10):
try:
print("")
with open(file) as f:
d = f.read().strip()
if len(d) < n:
raise AssertionError('%s does not seem to contain enough text' % file)
except IOError:
raise AssertionError('Cannot read %s' % file)
##################################################################
# Controllers
#
__all__ += ["QEMU", "GDBClient"]
class QEMU(object):
_GDBPORT = None
def __init__(self, *make_args):
# Check that QEMU is not currently running
try:
GDBClient(self.get_gdb_port(), timeout=0).close()
except socket.error:
pass
else:
print("""\
GDB stub found on port %d.
QEMU appears to already be running. Please exit it if possible or use
'killall qemu' or 'killall qemu.real'.""" % self.get_gdb_port(), file=sys.stderr)
sys.exit(1)
if options.verbose:
show_command(("make",) + make_args)
cmd = ("make", "-s", "--no-print-directory") + make_args
self.proc = Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.PIPE)
# Accumulated output as a string
self.output = ""
# Accumulated output as a bytearray
self.outbytes = bytearray()
self.on_output = []
@staticmethod
def get_gdb_port():
if QEMU._GDBPORT is None:
p = Popen(["make", "-s", "--no-print-directory", "print-gdbport"],
stdout=subprocess.PIPE)
(out, _) = p.communicate()
if p.returncode:
raise RuntimeError(
"Failed to get gdbport: make exited with %d" %
p.returncode)
QEMU._GDBPORT = int(out)
return QEMU._GDBPORT
def fileno(self):
if self.proc:
return self.proc.stdout.fileno()
def handle_read(self):
buf = os.read(self.proc.stdout.fileno(), 4096)
self.outbytes.extend(buf)
self.output = self.outbytes.decode("utf-8", "replace")
for callback in self.on_output:
callback(buf)
if buf == b"":
self.wait()
return
def write(self, buf):
if isinstance(buf, str):
buf = buf.encode('utf-8')
self.proc.stdin.write(buf)
self.proc.stdin.flush()
def wait(self):
if self.proc:
self.proc.wait()
self.proc = None
def kill(self):
if self.proc:
self.proc.terminate()
class GDBClient(object):
def __init__(self, port, timeout=15):
start = time.time()
while True:
self.sock = socket.socket()
try:
self.sock.settimeout(1)
self.sock.connect(("localhost", port))
break
except socket.error:
if time.time() >= start + timeout:
raise
self.__buf = ""
def fileno(self):
if self.sock:
return self.sock.fileno()
def handle_read(self):
try:
data = self.sock.recv(4096).decode("ascii", "replace")
except socket.error:
data = ""
if data == "":
self.sock.close()
self.sock = None
return
self.__buf += data
while True:
m = re.search(r"\$([^#]*)#[0-9a-zA-Z]{2}", self.__buf)
if not m:
break
pkt = m.group(1)
self.__buf = self.__buf[m.end():]
if pkt.startswith("T05"):
# Breakpoint
raise TerminateTest
def __send(self, cmd):
packet = "$%s#%02x" % (cmd, sum(map(ord, cmd)) % 256)
self.sock.sendall(packet.encode("ascii"))
def __send_break(self):
self.sock.sendall(b"\x03")
def close(self):
if self.sock:
self.sock.close()
self.sock = None
def cont(self):
self.__send("c")
def breakpoint(self, addr):
self.__send("Z1,%x,1" % addr)
##################################################################
# QEMU test runner
#
__all__ += ["TerminateTest", "Runner"]
class TerminateTest(Exception):
pass
class Runner():
def __init__(self, *default_monitors):
self.__default_monitors = default_monitors
def run_qemu(self, *monitors, **kw):
"""Run a QEMU-based test. monitors should functions that will
be called with this Runner instance once QEMU and GDB are
started. Typically, they should register callbacks that throw
TerminateTest when stop events occur. The target_base
argument gives the make target to run. The make_args argument
should be a list of additional arguments to pass to make. The
timeout argument bounds how long to run before returning."""
def run_qemu_kw(target_base="qemu", make_args=[], timeout=30):
return target_base, make_args, timeout
target_base, make_args, timeout = run_qemu_kw(**kw)
# Start QEMU
pre_make()
self.qemu = QEMU(target_base + "-gdb", *make_args)
self.gdb = None
try:
# Wait for QEMU to start or make to fail. This will set
# self.gdb if QEMU starts.
self.qemu.on_output = [self.__monitor_start]
self.__react([self.qemu], timeout=90)
self.qemu.on_output = []
if self.gdb is None:
print("Failed to connect to QEMU; output:")
print(self.qemu.output)
sys.exit(1)
post_make()
# QEMU and GDB are up
self.reactors = [self.qemu, self.gdb]
# Start monitoring
for m in self.__default_monitors + monitors:
m(self)
# Run and react
self.gdb.cont()
self.__react(self.reactors, timeout)
finally:
# Shutdown QEMU
try:
if self.gdb is None:
sys.exit(1)
self.qemu.kill()
self.__react(self.reactors, 5)
self.gdb.close()
self.qemu.wait()
except:
print("""\
Failed to shutdown QEMU. You might need to 'killall qemu' or
'killall qemu.real'.
""")
raise
def __monitor_start(self, output):
if b"\n" in output:
try:
self.gdb = GDBClient(self.qemu.get_gdb_port(), timeout=2)
raise TerminateTest
except socket.error:
pass
if not len(output):
raise TerminateTest
def __react(self, reactors, timeout):
deadline = time.time() + timeout
try:
while True:
timeleft = deadline - time.time()
if timeleft < 0:
sys.stdout.write("Timeout! ")
sys.stdout.flush()
return
rset = [r for r in reactors if r.fileno() is not None]
if not rset:
return
rset, _, _ = select.select(rset, [], [], timeleft)
for reactor in rset:
reactor.handle_read()
except TerminateTest:
pass
def user_test(self, binary, *monitors, **kw):
"""Run a user test using the specified binary. Monitors and
keyword arguments are as for run_qemu. This runs on a disk
snapshot unless the keyword argument 'snapshot' is False."""
maybe_unlink("obj/kern/init.o", "obj/kern/kernel")
if kw.pop("snapshot", True):
kw.setdefault("make_args", []).append("QEMUEXTRA+=-snapshot")
self.run_qemu(target_base="run-%s" % binary, *monitors, **kw)
def match(self, *args, **kwargs):
"""Shortcut to call assert_lines_match on the most recent QEMU
output."""
assert_lines_match(self.qemu.output, *args, **kwargs)
##################################################################
# Monitors
#
__all__ += ["save", "stop_breakpoint", "call_on_line", "stop_on_line", "shell_script"]
def save(path):
"""Return a monitor that writes QEMU's output to path. If the
test fails, copy the output to path.test-name."""
def setup_save(runner):
f.seek(0)
f.truncate()
runner.qemu.on_output.append(f.write)
get_current_test().on_finish.append(save_on_finish)
def save_on_finish(fail):
f.flush()
save_path = path + "." + get_current_test().__name__[5:]
if fail:
shutil.copyfile(path, save_path)
print(" QEMU output saved to %s" % save_path)
elif os.path.exists(save_path):
os.unlink(save_path)
print(" (Old %s failure log removed)" % save_path)
f = open(path, "wb")
return setup_save
def stop_breakpoint(addr):
"""Returns a monitor that stops when addr is reached. addr may be
a number or the name of a symbol."""
def setup_breakpoint(runner):
if isinstance(addr, str):
addrs = [int(sym[:16], 16) for sym in open("kernel/kernel.sym")
if sym[17:].strip() == addr]
assert len(addrs), "Symbol %s not found" % addr
runner.gdb.breakpoint(addrs[0])
else:
runner.gdb.breakpoint(addr)
return setup_breakpoint
def call_on_line(regexp, callback):
"""Returns a monitor that calls 'callback' when QEMU prints a line
matching 'regexp'."""
def setup_call_on_line(runner):
buf = bytearray()
def handle_output(output):
buf.extend(output)
while b"\n" in buf:
line, buf[:] = buf.split(b"\n", 1)
line = line.decode("utf-8", "replace")
if re.match(regexp, line):
callback(line)
runner.qemu.on_output.append(handle_output)
return setup_call_on_line
def stop_on_line(regexp):
"""Returns a monitor that stops when QEMU prints a line matching
'regexp'."""
def stop(line):
raise TerminateTest
return call_on_line(regexp, stop)
def shell_script(script, terminate_match=None):
"""Returns a monitor that plays the script, and stops when the script is
done executing."""
def setup_call_on_line(runner):
class context:
n = 0
buf = bytearray()
def handle_output(output):
context.buf.extend(output)
if terminate_match is not None:
if re.match(terminate_match, context.buf.decode('utf-8', 'replace')):
raise TerminateTest
if b'$ ' in context.buf:
context.buf = bytearray()
if context.n < len(script):
runner.qemu.write(script[context.n])
runner.qemu.write('\n')
context.n += 1
else:
if terminate_match is None:
raise TerminateTest
runner.qemu.on_output.append(handle_output)
return setup_call_on_line

View File

@ -118,9 +118,7 @@ brelse(struct buf *b)
{
if(!holdingsleep(&b->lock))
panic("brelse");
releasesleep(&b->lock);
acquire(&bcache.lock);
b->refcnt--;
if (b->refcnt == 0) {

View File

@ -1,3 +1,7 @@
#ifdef LAB_MMAP
typedef unsigned long size_t;
typedef long int off_t;
#endif
struct buf;
struct context;
struct file;
@ -117,6 +121,10 @@ void initlock(struct spinlock*, char*);
void release(struct spinlock*);
void push_off(void);
void pop_off(void);
int atomic_read4(int *addr);
#ifdef LAB_LOCK
void freelock(struct spinlock*);
#endif
// sleeplock.c
void acquiresleep(struct sleeplock*);
@ -173,6 +181,12 @@ uint64 walkaddr(pagetable_t, uint64);
int copyout(pagetable_t, uint64, char *, uint64);
int copyin(pagetable_t, char *, uint64, uint64);
int copyinstr(pagetable_t, char *, uint64, uint64);
#if defined(LAB_PGTBL) || defined(SOL_MMAP)
void vmprint(pagetable_t);
#endif
#ifdef LAB_PGTBL
pte_t* pgpte(pagetable_t, uint64);
#endif
// plic.c
void plicinit(void);
@ -187,3 +201,40 @@ void virtio_disk_intr(void);
// number of elements in fixed-size array
#define NELEM(x) (sizeof(x)/sizeof((x)[0]))
#ifdef LAB_PGTBL
// vmcopyin.c
int copyin_new(pagetable_t, char *, uint64, uint64);
int copyinstr_new(pagetable_t, char *, uint64, uint64);
#endif
#ifdef LAB_LOCK
// stats.c
void statsinit(void);
void statsinc(void);
// sprintf.c
int snprintf(char*, unsigned long, const char*, ...);
#endif
#ifdef KCSAN
void kcsaninit();
#endif
#ifdef LAB_NET
// pci.c
void pci_init();
// e1000.c
void e1000_init(uint32 *);
void e1000_intr(void);
int e1000_transmit(char *, int);
// net.c
void netinit(void);
void net_rx(char *buf, int len);
#endif
#define DEBUG() printf("File %s, Line %d, Function %s\n",__FILE__,__LINE__,__func__);

View File

@ -3,3 +3,13 @@
#define O_RDWR 0x002
#define O_CREATE 0x200
#define O_TRUNC 0x400
#ifdef LAB_MMAP
#define PROT_NONE 0x0
#define PROT_READ 0x1
#define PROT_WRITE 0x2
#define PROT_EXEC 0x4
#define MAP_SHARED 0x01
#define MAP_PRIVATE 0x02
#endif

View File

@ -478,7 +478,6 @@ readi(struct inode *ip, int user_dst, uint64 dst, uint off, uint n)
return 0;
if(off + n > ip->size)
n = ip->size - off;
for(tot=0; tot<n; tot+=m, off+=m, dst+=m){
uint addr = bmap(ip, off/BSIZE);
if(addr == 0)

View File

@ -273,7 +273,7 @@ growproc(int n)
p->sz = sz;
return 0;
}
extern int lazymap(uint64);
// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
@ -282,12 +282,35 @@ fork(void)
int i, pid;
struct proc *np;
struct proc *p = myproc();
// Allocate process.
if((np = allocproc()) == 0){
return -1;
}
// not lazy anymore, forcefully alloc all pages in VMA
release(&np->lock);
for(int i=0;i<NVMA;++i){
np->vma[i]=p->vma[i];
if(p->vma[i].len==0)
continue;
const int addr=p->vma[i].addr;
const int len=p->vma[i].len;
const int vis=p->vma[i].vis;
filedup(np->vma[i].file);
for(int j=0;j<len;j+=PGSIZE){
const int cur=addr+j;
const int x=j/PGSIZE;
if((1<<x)&vis)
continue;
pte_t *pte=walk(p->pagetable,cur,0);
if(pte!=0 &&(*pte & PTE_V)!=0)
continue;
if(lazymap(cur)<0){
freeproc(np);
return -1;
}
}
}
acquire(&np->lock);
// Copy user memory from parent to child.
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
freeproc(np);
@ -321,7 +344,6 @@ fork(void)
acquire(&np->lock);
np->state = RUNNABLE;
release(&np->lock);
return pid;
}
@ -343,6 +365,7 @@ reparent(struct proc *p)
// Exit the current process. Does not return.
// An exited process remains in the zombie state
// until its parent calls wait().
extern int munmap(uint64,uint64);
void
exit(int status)
{
@ -364,7 +387,30 @@ exit(int status)
iput(p->cwd);
end_op();
p->cwd = 0;
#ifdef CHECK_IF_VALID_ADDR
int cnt=0;
#endif
for(int i=0;i<NVMA;++i){
int x=0;
for(int j=1;j<NVMA;++j)
if(p->vma[j].addr>p->vma[x].addr)
x=j;
if(p->vma[x].len==0)
break;
#ifdef CHECK_IF_VALID_ADDR
printf("current sz=%lu\n",p->sz);
if(p->vma[x].addr+p->vma[x].len!=p->sz){
printf("addr=%lu,len=%d,cnt=%d\n",p->vma[x].addr,p->vma[x].len,cnt);
panic("invalid addr");
}
++cnt;
#endif
if(p->vma[x].len)
munmap(p->vma[x].addr,p->vma[x].len);
}
#ifdef CHECK_IF_VALID_ADDR
printf("it's still alive, sz=%lu\n",p->sz);
#endif
acquire(&wait_lock);
// Give any children to init.
@ -379,7 +425,6 @@ exit(int status)
p->state = ZOMBIE;
release(&wait_lock);
// Jump into the scheduler, never to return.
sched();
panic("zombie exit");
@ -446,7 +491,6 @@ scheduler(void)
{
struct proc *p;
struct cpu *c = mycpu();
c->proc = 0;
for(;;){
// The most recent process to run may have had interrupts

View File

@ -20,7 +20,7 @@ struct context {
// Per-CPU state.
struct cpu {
struct proc *proc; // The process running on this cpu, or null.
struct proc *proc; // The process running on this cpu, or Null.
struct context context; // swtch() here to enter scheduler().
int noff; // Depth of push_off() nesting.
int intena; // Were interrupts enabled before push_off()?
@ -80,7 +80,14 @@ struct trapframe {
};
enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };
#define NVMA 16
struct vma{
uint64 addr;
int len,prot,flags,offset;
struct file *file;
int vis;// validity bitmask used, bit 1 stands for being unmapped, init be 0
// the 0-th bit stands for addr, then 1-st bit stands for addr+PGSIZE, etc
};
// Per-process state
struct proc {
struct spinlock lock;
@ -104,4 +111,5 @@ struct proc {
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
struct vma vma[NVMA];
};

View File

@ -101,7 +101,8 @@ extern uint64 sys_unlink(void);
extern uint64 sys_link(void);
extern uint64 sys_mkdir(void);
extern uint64 sys_close(void);
extern uint64 sys_mmap(void);
extern uint64 sys_munmap(void);
// An array mapping syscall numbers from syscall.h
// to the function that handles the system call.
static uint64 (*syscalls[])(void) = {
@ -126,6 +127,8 @@ static uint64 (*syscalls[])(void) = {
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
[SYS_mmap] sys_mmap,
[SYS_munmap] sys_munmap
};
void

View File

@ -20,3 +20,5 @@
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
#define SYS_mmap 22
#define SYS_munmap 23

View File

@ -503,3 +503,186 @@ sys_pipe(void)
}
return 0;
}
const struct vma Null= {.addr=0,.len=0,.prot=0,.flags=0,.file=(struct file*)0,.offset=0,.vis=0};
uint64 sys_mmap(void) {
uint64 addr,len;
int prot, flags, fd, offset;
argaddr(0, &addr);
argaddr(1, &len), argint(2, &prot), argint(3, &flags), argint(4, &fd), argint(5, &offset);
struct proc *p = myproc();
struct vma *vma = p->vma;
if((!p->ofile[fd]->readable) && (prot & PROT_READ))
goto err1;
if((!p->ofile[fd]->writable) && (prot &PROT_WRITE) && (flags & MAP_SHARED))
goto err1;
uint64 ret = p->sz;
for (int i = 0; i < NVMA; ++i) // don't process when it's full
if (vma[i].len == Null.len) { // getting a mem size of 0 seems crazy
vma[i].addr=ret;//addr is always 0, and ret is the position
vma[i].len=len;
vma[i].prot=prot;
vma[i].flags=flags;// which is MAP flags
vma[i].file=p->ofile[fd];
filedup(vma[i].file);//increased ref here
vma[i].offset=offset;
vma[i].vis=0;
p->sz+=len;
return ret;
}
err1:
return -1;
}
void shrink_vma(){
struct proc *p=myproc();
//DEBUG();
while(1){
int found=0;
int i;
for(i=0;i<NVMA;++i)
if(p->vma[i].vis==-1 && p->vma[i].addr+p->vma[i].len==p->sz){
found=1;
break;
}
if(!found)
break;
#ifdef CHECK_IF_EXISTS
printf("current sz=%lu\n",p->sz);
#endif
p->sz-=p->vma[i].len;
p->vma[i]=Null;
}
}
int munmap(uint64 Argaddr,uint64 Arglen){
struct proc *p=myproc();
// well it's possible that the passed addr and len does not match a munmap
// how do I know that all of the pages of an mmap zone has been removed --using vis
// don't do crazy things like unmap two different mmap area at same time --it's fine, no such operation in test
for(int i=0;i<NVMA;++i){// all of which are changable
uint64 addr=p->vma[i].addr;
const int offset=p->vma[i].offset;
struct file *file=p->vma[i].file;
struct inode *ip=file->ip;
const int len=p->vma[i].len;
int *vis=&p->vma[i].vis;
const int map_flag=p->vma[i].flags;
if(*vis!=-1 && len >0 && PGROUNDDOWN(addr) + len >= Argaddr+Arglen && PGROUNDDOWN(addr) <= Argaddr){
// for(int i=0;i<Arglen;i+=PGSIZE){
// const int cur = Argaddr + i;
// const int x = (cur-addr)/PGSIZE;
// delete it, even if being already deleted
//if((1<<x)&(*vis))
// goto err1; //attempting to visit a dellocated page
// }
for(int i=0;i<Arglen;i+=PGSIZE){
const uint64 cur = Argaddr + i;
const int x = (cur-addr)/PGSIZE;
pte_t *pte=walk(p->pagetable,cur,0);
if(pte!=0 && (*pte & PTE_V)!=0){
// this page may not actually allocated
// wait what happens for uvmunmap, if there's a mmap page between normal pages... --I bet this won't happen
if(map_flag & (MAP_SHARED)){// write back
begin_op(); // don't forget begin_op
ilock(ip);
const uint sz=ip->size;
const int pos=offset+(cur-addr);
const uint remain=sz-pos;
if(writei(ip,1,cur,pos,PGSIZE<remain?PGSIZE:remain)<0)
goto err2;
iunlock(ip);
end_op();
}
uvmunmap(p->pagetable,cur,1,1);
}
*vis |= 1<<x;// add dellocated sign
//kfree((char *)cur); it's not kernel memory space
// delete such memory page
}
int all_clear=1;
for(int i=0;i<len/PGSIZE;++i)
all_clear&=(*vis)>>i;
if(all_clear){ // clear this vma
// wait, what happens if this vma is the highest address?
// and, it's responsible for exit, to call munmap in a dereasing order
//if(p->sz == addr+len)
//p->sz-=len;
// release the file
fileclose(file);
// clean this vma
//p->vma[i]=Null;// well, I don't need those pointers at all...
p->vma[i].vis=-1;
shrink_vma();
}
return 0;
err2:
iunlock(ip);
goto err1;
}
}
err1:
// hey! why did a uint64 func return -1??
return -1; //default not found
}
uint64 sys_munmap(void) {
uint64 Argaddr,Arglen;
argaddr(0,&Argaddr),argaddr(1,&Arglen);
return munmap(Argaddr,Arglen);
}
int lazymap(uint64 va){
struct proc *p=myproc();
if(va>=p->sz)
goto err1;
uint64 scause=r_scause();
for(int i=0;i<NVMA;++i) {
const uint64 addr=p->vma[i].addr;
const int vma_flag=p->vma[i].prot;
const int offset=p->vma[i].offset;
struct file *file=p->vma[i].file;
struct inode *ip=file->ip;
const int len=p->vma[i].len;
const int vis=p->vma[i].vis;
// each addr and len won't intersect with each other, it's defined by sys_mmap process
// it's > not >=, however
// we must make sure that this va is within the range
if(vis!=-1 && len > 0 && PGROUNDDOWN(addr) + len>va && PGROUNDDOWN(addr)<=va){// it's considered that addr is page-aligned
const int x=(va-addr)/PGSIZE;
if((1<<x)&vis)
goto err1;// attempting to visit a dellocated page
if(scause == 13 && (vma_flag & PROT_READ)==0)
goto err1;
if(scause ==15 && (vma_flag & PROT_WRITE)==0)
goto err1;
char *mem=kalloc();
memset(mem,0,PGSIZE);
if(mem==0)
goto err1;
int pte_flag=0;
if(vma_flag & PROT_READ)
pte_flag|=PTE_R;
if(vma_flag & PROT_WRITE)
pte_flag|=PTE_W;
if(vma_flag & PROT_EXEC)
pte_flag|=PTE_X;
pte_flag|=PTE_U;
if(mappages(p->pagetable,va,PGSIZE,(uint64)mem,pte_flag)<0){
kfree(mem);
goto err1;
}
// why I need to call readi??
// oh, that's because I can't get file+offset directly...
ilock(ip);
// readi won't increase inode ref
if(readi(ip,0,(uint64)mem,offset + (va-addr),PGSIZE)<=0)
goto err2;
// file pointer handles ref to inode, so don't increase inode ref there
iunlock(ip);
return 0;
err2:
uvmunmap(p->pagetable,va,1,1);
iunlock(ip);
goto err1;
}
}
err1:
return -1;
}

View File

@ -5,62 +5,45 @@
#include "memlayout.h"
#include "spinlock.h"
#include "proc.h"
uint64
sys_exit(void)
{
uint64 sys_exit(void) {
int n;
argint(0, &n);
exit(n);
return 0; // not reached
return 0; // not reached
}
uint64
sys_getpid(void)
{
return myproc()->pid;
}
uint64 sys_getpid(void) { return myproc()->pid; }
uint64
sys_fork(void)
{
return fork();
}
uint64 sys_fork(void) { return fork(); }
uint64
sys_wait(void)
{
uint64 sys_wait(void) {
uint64 p;
argaddr(0, &p);
return wait(p);
}
uint64
sys_sbrk(void)
{
uint64 sys_sbrk(void) {
uint64 addr;
int n;
argint(0, &n);
addr = myproc()->sz;
if(growproc(n) < 0)
if (growproc(n) < 0)
return -1;
return addr;
}
uint64
sys_sleep(void)
{
uint64 sys_sleep(void) {
int n;
uint ticks0;
argint(0, &n);
if(n < 0)
if (n < 0)
n = 0;
acquire(&tickslock);
ticks0 = ticks;
while(ticks - ticks0 < n){
if(killed(myproc())){
while (ticks - ticks0 < n) {
if (killed(myproc())) {
release(&tickslock);
return -1;
}
@ -70,20 +53,17 @@ sys_sleep(void)
return 0;
}
uint64
sys_kill(void)
{
uint64 sys_kill(void) {
int pid;
argint(0, &pid);
return kill(pid);
}
// return how many clock tick interrupts have occurred
// since start.
uint64
sys_uptime(void)
{
uint64 sys_uptime(void) {
uint xticks;
acquire(&tickslock);
@ -91,3 +71,4 @@ sys_uptime(void)
release(&tickslock);
return xticks;
}

View File

@ -15,7 +15,7 @@ extern char trampoline[], uservec[], userret[];
void kernelvec();
extern int devintr();
extern int lazymap(uint64);
void
trapinit(void)
{
@ -65,7 +65,14 @@ usertrap(void)
intr_on();
syscall();
} else if((which_dev = devintr()) != 0){
}else if(r_scause() == 13 || r_scause() == 15){ //on handling page fault
if(killed(p))
exit(-1);// for sure?
if(lazymap(PGROUNDDOWN(r_stval()))<0)
setkilled(p);
// kill will call exit(), and exit() will handle munmap
// do I need to call intr_on()? no
}else if((which_dev = devintr()) != 0){
// ok
} else {
printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid);
@ -77,9 +84,9 @@ usertrap(void)
exit(-1);
// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
if(which_dev == 2){
yield();
}
usertrapret();
}
@ -151,9 +158,9 @@ kerneltrap()
}
// give up the CPU if this is a timer interrupt.
if(which_dev == 2 && myproc() != 0)
if(which_dev == 2 && myproc() != 0){
yield();
}
// the yield() may have caused some traps to occur,
// so restore trap registers for use by kernelvec.S's sepc instruction.
w_sepc(sepc);

1
time.txt Normal file
View File

@ -0,0 +1 @@
12

444
user/mmaptest.c Normal file
View File

@ -0,0 +1,444 @@
#include "kernel/param.h"
#include "kernel/fcntl.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/riscv.h"
#include "kernel/fs.h"
#include "user/user.h"
void mmap_test();
void fork_test();
void more_test();
char buf[PGSIZE];
#define MAP_FAILED ((char *) -1)
int
main(int argc, char *argv[])
{
mmap_test();
fork_test();
more_test();
printf("mmaptest: all tests succeeded\n");
exit(0);
}
void
err(char *why)
{
printf("mmaptest failure: %s, pid=%d\n", why, getpid());
exit(1);
}
//
// check the content of the two mapped pages.
//
void
_v1(char *p)
{
int i;
for (i = 0; i < PGSIZE*2; i++) {
if (i < PGSIZE + (PGSIZE/2)) {
if (p[i] != 'A') {
printf("mismatch at %d, wanted 'A', got 0x%x\n", i, p[i]);
err("v1 mismatch (1)");
}
} else {
if (p[i] != 0) {
printf("mismatch at %d, wanted zero, got 0x%x\n", i, p[i]);
err("v1 mismatch (2)");
}
}
}
}
//
// create a file to be mapped, containing
// 1.5 pages of 'A' and half a page of zeros.
//
void
makefile(const char *f)
{
int i;
int n = PGSIZE/BSIZE;
unlink(f);
int fd = open(f, O_WRONLY | O_CREATE);
if (fd == -1)
err("open");
memset(buf, 'A', BSIZE);
// write 1.5 page
for (i = 0; i < n + n/2; i++) {
if (write(fd, buf, BSIZE) != BSIZE)
err("write 0 makefile");
}
if (close(fd) == -1)
err("close");
}
void
mmap_test(void)
{
int fd;
int i;
const char * const f = "mmap.dur";
//
// create a file with known content, map it into memory, check that
// the mapped memory has the same bytes as originally written to the
// file.
//
makefile(f);
if ((fd = open(f, O_RDONLY)) == -1)
err("open (1)");
printf("test basic mmap\n");
//
// this call to mmap() asks the kernel to map the content
// of open file fd into the address space. the first
// 0 argument indicates that the kernel should choose the
// virtual address. the second argument indicates how many
// bytes to map. the third argument indicates that the
// mapped memory should be read-only. the fourth argument
// indicates that, if the process modifies the mapped memory,
// that the modifications should not be written back to
// the file nor shared with other processes mapping the
// same file (of course in this case updates are prohibited
// due to PROT_READ). the fifth argument is the file descriptor
// of the file to be mapped. the last argument is the starting
// offset in the file.
//
char *p = mmap(0, PGSIZE*2, PROT_READ, MAP_PRIVATE, fd, 0);
if (p == MAP_FAILED)
err("mmap (1)");
_v1(p);
if (munmap(p, PGSIZE*2) == -1)
err("munmap (1)");
printf("test basic mmap: OK\n");
printf("test mmap private\n");
// should be able to map file opened read-only with private writable
// mapping
p = mmap(0, PGSIZE*2, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (p == MAP_FAILED)
err("mmap (2)");
if (close(fd) == -1)
err("close (1)");
_v1(p);
for (i = 0; i < PGSIZE*2; i++)
p[i] = 'Z';
if (munmap(p, PGSIZE*2) == -1)
err("munmap (2)");
close(fd);
// file should not have been modified.
if((fd = open(f, O_RDONLY)) < 0) err("open");
if(read(fd, buf, PGSIZE) != PGSIZE) err("read");
if(buf[0] != 'A')
err("write to MAP_PRIVATE was written to file");
if(read(fd, buf, PGSIZE) != PGSIZE/2) err("read");
if(buf[0] != 'A')
err("write to MAP_PRIVATE was written to file");
close(fd);
printf("test mmap private: OK\n");
printf("test mmap read-only\n");
// check that mmap doesn't allow read/write mapping of a
// file opened read-only.
if ((fd = open(f, O_RDONLY)) == -1)
err("open (2)");
p = mmap(0, PGSIZE*2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p != MAP_FAILED)
err("mmap (3)");
if (close(fd) == -1)
err("close (2)");
printf("test mmap read-only: OK\n");
printf("test mmap read/write\n");
// check that mmap does allow read/write mapping of a
// file opened read/write.
if ((fd = open(f, O_RDWR)) == -1)
err("open (3)");
p = mmap(0, PGSIZE*3, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
err("mmap (4)");
if (close(fd) == -1)
err("close (3)");
// check that the mapping still works after close(fd).
_v1(p);
// write the mapped memory.
for (i = 0; i < PGSIZE; i++)
p[i] = 'B';
for (i = PGSIZE; i < PGSIZE*2; i++)
p[i] = 'C';
// unmap just the first two of three pages of mapped memory.
if (munmap(p, PGSIZE*2) == -1)
err("munmap (3)");
printf("test mmap read/write: OK\n");
printf("test mmap dirty\n");
// check that the writes to the mapped memory were
// written to the file.
if ((fd = open(f, O_RDONLY)) == -1)
err("open (4)");
if(read(fd, buf, PGSIZE) != PGSIZE)
err("dirty read #1");
for (i = 0; i < PGSIZE; i++){
if (buf[i] != 'B')
err("file page 0 does not contain modifications");
}
if(read(fd, buf, PGSIZE) != PGSIZE/2)
err("dirty read #2");
for (i = 0; i < PGSIZE/2; i++){
if (buf[i] != 'C')
err("file page 1 does not contain modifications");
}
if (close(fd) == -1)
err("close (4)");
printf("test mmap dirty: OK\n");
printf("test not-mapped unmap\n");
// unmap the rest of the mapped memory.
if (munmap(p+PGSIZE*2, PGSIZE) == -1)
err("munmap (4)");
printf("test not-mapped unmap: OK\n");
printf("test lazy access\n");
if(unlink(f) != 0) err("unlink");
makefile(f);
if ((fd = open(f, O_RDWR)) == -1)
err("open");
p = mmap(0, PGSIZE*2, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
err("mmap");
close(fd);
// mmap() should not have read the file at this point,
// so that the file modification we're about to make
// ought to be visible to a subsequent read of the
// mapped memory.
if((fd = open(f, O_RDWR)) == -1)
err("open");
if(write(fd, "m", 1) != 1)
err("write");
close(fd);
if(*p != 'm')
err("read was not lazy");
if(munmap(p, PGSIZE*2) == -1)
err("munmap");
printf("test lazy access: OK\n");
printf("test mmap two files\n");
//
// mmap two different files at the same time.
//
int fd1;
if((fd1 = open("mmap1", O_RDWR|O_CREATE)) < 0)
err("open (5)");
if(write(fd1, "12345", 5) != 5)
err("write (1)");
char *p1 = mmap(0, PGSIZE, PROT_READ, MAP_PRIVATE, fd1, 0);
if(p1 == MAP_FAILED)
err("mmap (5)");
if (close(fd1) == -1)
err("close (5)");
if (unlink("mmap1") == -1)
err("unlink (1)");
int fd2;
if((fd2 = open("mmap2", O_RDWR|O_CREATE)) < 0)
err("open (6)");
if(write(fd2, "67890", 5) != 5)
err("write (2)");
char *p2 = mmap(0, PGSIZE, PROT_READ, MAP_PRIVATE, fd2, 0);
if(p2 == MAP_FAILED)
err("mmap (6)");
if (close(fd2) == -1)
err("close (6)");
if (unlink("mmap2") == -1)
err("unlink (2)");
if(memcmp(p1, "12345", 5) != 0)
err("mmap1 mismatch");
if(memcmp(p2, "67890", 5) != 0)
err("mmap2 mismatch");
if (munmap(p1, PGSIZE) == -1)
err("munmap (5)");
if(memcmp(p2, "67890", 5) != 0)
err("mmap2 mismatch (2)");
if (munmap(p2, PGSIZE) == -1)
err("munmap (6)");
printf("test mmap two files: OK\n");
}
//
// mmap a file, then fork.
// check that the child sees the mapped file.
//
void
fork_test(void)
{
int fd;
int pid;
const char * const f = "mmap.dur";
printf("test fork\n");
// mmap the file twice.
makefile(f);
if ((fd = open(f, O_RDONLY)) == -1)
err("open (7)");
if (unlink(f) == -1)
err("unlink (3)");
char *p1 = mmap(0, PGSIZE*2, PROT_READ, MAP_SHARED, fd, 0);
if (p1 == MAP_FAILED)
err("mmap (7)");
char *p2 = mmap(0, PGSIZE*2, PROT_READ, MAP_SHARED, fd, 0);
if (p2 == MAP_FAILED)
err("mmap (8)");
// read just 2nd page.
if(*(p1+PGSIZE) != 'A')
err("fork mismatch (1)");
if((pid = fork()) < 0)
err("fork");
if (pid == 0) {
_v1(p1);
if (munmap(p1, PGSIZE) == -1) // just the first page
err("munmap (7)");
exit(0); // tell the parent that the mapping looks OK.
}
int status = -1;
wait(&status);
if(status != 0){
printf("fork_test failed\n");
exit(1);
}
// check that the parent's mappings are still there.
_v1(p1);
_v1(p2);
printf("test fork: OK\n");
}
void
more_test()
{
int fd, pid;
char *p;
const char * const f = "mmap.dur";
printf("test munmap prevents access\n");
makefile(f);
if ((fd = open(f, O_RDWR)) == -1)
err("open");
p = mmap(0, PGSIZE*2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
err("mmap");
close(fd);
*p = 'X';
*(p+PGSIZE) = 'Y';
pid = fork();
if(pid < 0) err("fork");
if(pid == 0){
*p = 'a';
*(p+PGSIZE) = 'b';
if(munmap(p+PGSIZE, PGSIZE) == -1)
err("munmap");
// this should cause a fatal fault
printf("*(p+PGSIZE) = %x\n", *(p+PGSIZE));
exit(0);
}
int st = 0;
wait(&st);
if(st != -1)
err("child #1 read unmapped memory");
pid = fork();
if(pid < 0) err("fork");
if(pid == 0){
*p = 'c';
*(p+PGSIZE) = 'd';
if(munmap(p, PGSIZE) == -1)
err("munmap");
// this should cause a fatal fault
printf("*p = %x\n", *p);
exit(0);
}
st = 0;
wait(&st);
if(st != -1)
err("child #2 read unmapped memory");
// parent should still be able to access the memory.
*p = 'P';
*(p+PGSIZE) = 'Q';
if(munmap(p, PGSIZE) == -1)
err("munmap");
*(p+PGSIZE) = 'R';
if(munmap(p+PGSIZE, PGSIZE) == -1)
err("munmap");
// read the file, check that the first page starts
// with P and the second page with R.
fd = open(f, O_RDONLY);
if(fd < 0) err("open");
if(read(fd, buf, PGSIZE) != PGSIZE) err("read");
if(buf[0] != 'P') err("first byte of file is wrong");
if(read(fd, buf, PGSIZE) != PGSIZE/2) err("read");
if(buf[0] != 'R') err("first byte of 2nd page of file is wrong");
close(fd);
printf("test munmap prevents access: OK\n");
printf("test writes to read-only mapped memory\n");
makefile(f);
pid = fork();
if(pid < 0) err("fork");
if(pid == 0){
if ((fd = open(f, O_RDWR)) == -1)
err("open");
p = mmap(0, PGSIZE*2, PROT_READ, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
err("mmap");
// this should cause a fatal fault
*p = 0;
exit(*p);
}
st = 0;
wait(&st);
if(st != -1)
err("child wrote read-only mapping");
printf("test writes to read-only mapped memory: OK\n");
}

View File

@ -1,3 +1,10 @@
#ifdef LAB_MMAP
typedef unsigned long size_t;
typedef long int off_t;
void *mmap(void*,size_t,int,int,int,off_t);
int munmap(void*,size_t);
#endif
struct stat;
// system calls
@ -22,6 +29,17 @@ int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);
#ifdef LAB_NET
int bind(uint16);
int unbind(uint16);
int send(uint16, uint32, uint16, char *, uint32);
int recv(uint16, uint32*, uint16*, char *, uint32);
#endif
#ifdef LAB_PGTBL
int ugetpid(void);
uint64 pgpte(void*);
void kpgtbl(void);
#endif
// ulib.c
int stat(const char*, struct stat*);
@ -37,7 +55,12 @@ void* memset(void*, int, uint);
int atoi(const char*);
int memcmp(const void *, const void *, uint);
void *memcpy(void *, const void *, uint);
#ifdef LAB_LOCK
int statistics(void*, int);
#endif
// umalloc.c
void* malloc(uint);
void free(void*);
#define DEBUG() printf("File %s, Line %d, Function %s\n",__FILE__,__LINE__,__func__);

View File

@ -36,3 +36,5 @@ entry("getpid");
entry("sbrk");
entry("sleep");
entry("uptime");
entry("mmap");
entry("munmap");