Compare commits
2 Commits
checkpoint
...
mmap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce9cb0d6c0 | ||
|
|
6dd1443e74 |
12
.gitignore
vendored
12
.gitignore
vendored
@ -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
244
Makefile
@ -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
582
]
Normal 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
1
conf/lab.mk
Normal file
@ -0,0 +1 @@
|
||||
LAB=mmap
|
||||
69
grade-lab-mmap
Executable file
69
grade-lab-mmap
Executable 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
628
gradelib.py
Normal 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
|
||||
@ -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) {
|
||||
|
||||
@ -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__);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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];
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -20,3 +20,5 @@
|
||||
#define SYS_link 19
|
||||
#define SYS_mkdir 20
|
||||
#define SYS_close 21
|
||||
#define SYS_mmap 22
|
||||
#define SYS_munmap 23
|
||||
|
||||
183
kernel/sysfile.c
183
kernel/sysfile.c
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
444
user/mmaptest.c
Normal file
444
user/mmaptest.c
Normal 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");
|
||||
}
|
||||
23
user/user.h
23
user/user.h
@ -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__);
|
||||
|
||||
@ -36,3 +36,5 @@ entry("getpid");
|
||||
entry("sbrk");
|
||||
entry("sleep");
|
||||
entry("uptime");
|
||||
entry("mmap");
|
||||
entry("munmap");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user