diff options
| -rw-r--r-- | .gitignore | 9 | ||||
| -rw-r--r-- | Makefile | 259 | ||||
| -rw-r--r-- | conf/lab.mk | 1 | ||||
| -rwxr-xr-x | grade-lab-pgtbl | 79 | ||||
| -rw-r--r-- | gradelib.py | 611 | ||||
| -rw-r--r-- | kernel/memlayout.h | 17 | ||||
| -rw-r--r-- | kernel/syscall.c | 15 | ||||
| -rw-r--r-- | kernel/syscall.h | 11 | ||||
| -rw-r--r-- | kernel/sysproc.c | 15 | ||||
| -rw-r--r-- | user/pgtbltest.c | 70 | ||||
| -rw-r--r-- | user/ulib.c | 14 | ||||
| -rw-r--r-- | user/user.h | 9 | ||||
| -rwxr-xr-x | user/usys.pl | 2 | 
13 files changed, 1097 insertions, 15 deletions
| @@ -15,3 +15,12 @@ mkfs  kernel/kernel  user/usys.S  .gdbinit +myapi.key +*-handin.tar.gz +xv6.out* +.vagrant/ +submissions/ +ph +barrier +/lab-*.json +.DS_Store
\ No newline at end of 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,6 +32,34 @@ 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),$(filter $(LAB), lock)) +OBJS += \ +	$K/stats.o\ +	$K/sprintf.o +endif + + +ifeq ($(LAB),net) +OBJS += \ +	$K/e1000.o \ +	$K/net.o \ +	$K/sysnet.o \ +	$K/pci.o +endif + +  # riscv64-unknown-elf- or riscv64-linux-gnu-  # perhaps in /opt/riscv/bin  #TOOLPREFIX =  @@ -57,12 +87,28 @@ 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  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 @@ -73,11 +119,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 @@ -89,6 +141,10 @@ tags: $(OBJS) _init  ULIB = $U/ulib.o $U/usys.o $U/printf.o $U/umalloc.o +ifeq ($(LAB),$(filter $(LAB), lock)) +ULIB += $U/statistics.o +endif +  _%: %.o $(ULIB)  	$(LD) $(LDFLAGS) -T $U/user.ld -o $@ $^  	$(OBJDUMP) -S $@ > $*.asm @@ -107,7 +163,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 @@ -133,8 +189,79 @@ UPROGS=\  	$U/_wc\  	$U/_zombie\ -fs.img: mkfs/mkfs README $(UPROGS) -	mkfs/mkfs fs.img README $(UPROGS) + + + +ifeq ($(LAB),$(filter $(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),net) +UPROGS += \ +	$U/_nettests +endif + +UEXTRA= +ifeq ($(LAB),util) +	UEXTRA += user/xargstest.sh +endif + + +fs.img: mkfs/mkfs README $(UEXTRA) $(UPROGS) +	mkfs/mkfs fs.img README $(UEXTRA) $(UPROGS)  -include kernel/*.d user/*.d @@ -144,7 +271,8 @@ clean:  	$U/initcode $U/initcode.out $K/kernel fs.img \  	mkfs/mkfs .gdbinit \          $U/usys.S \ -	$(UPROGS) +	$(UPROGS) \ +	ph barrier  # try to generate a unique GDB port  GDBPORT = $(shell expr `id -u` % 5000 + 25000) @@ -155,12 +283,22 @@ QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \  ifndef CPUS  CPUS := 3  endif +ifeq ($(LAB),fs) +CPUS := 1 +endif + +FWDPORT = $(shell expr `id -u` % 5000 + 25999)  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 +ifeq ($(LAB),net) +QEMUOPTS += -netdev user,id=net0,hostfwd=udp::$(FWDPORT)-:2000 -object filter-dump,id=net0,netdev=net0,file=packets.pcap +QEMUOPTS += -device e1000,netdev=net0,bus=pcie.0 +endif +  qemu: $K/kernel fs.img  	$(QEMU) $(QEMUOPTS) @@ -171,3 +309,102 @@ 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) + +server: +	python3 server.py $(SERVERPORT) + +ping: +	python3 ping.py $(FWDPORT) +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 web handin +## + + +WEBSUB := https://6828.scripts.mit.edu/2023/handin.py + +handin: tarball-pref myapi.key +	@SUF=$(LAB); \ +	curl -f -F file=@lab-$$SUF-handin.tar.gz -F key=\<myapi.key $(WEBSUB)/upload \ +	    > /dev/null || { \ +		echo ; \ +		echo Submit seems to have failed.; \ +		echo Please go to $(WEBSUB)/ and upload the tarball manually.; } + +handin-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 + +UPSTREAM := $(shell git remote -v | grep -m 1 "xv6-labs-2023" | awk '{split($$0,a," "); print a[1]}') + +tarball: handin-check +	git archive --format=tar HEAD | gzip > lab-$(LAB)-handin.tar.gz + +tarball-pref: handin-check +	@SUF=$(LAB); \ +	git archive --format=tar HEAD > lab-$$SUF-handin.tar; \ +	git diff $(UPSTREAM)/$(LAB) > /tmp/lab-$$SUF-diff.patch; \ +	tar -rf lab-$$SUF-handin.tar /tmp/lab-$$SUF-diff.patch; \ +	gzip -c lab-$$SUF-handin.tar > lab-$$SUF-handin.tar.gz; \ +	rm lab-$$SUF-handin.tar; \ +	rm /tmp/lab-$$SUF-diff.patch; \ + +myapi.key: +	@echo Get an API key for yourself by visiting $(WEBSUB)/ +	@read -p "Please enter your API key: " k; \ +	if test `echo "$$k" |tr -d '\n' |wc -c` = 32 ; then \ +		TF=`mktemp -t tmp.XXXXXX`; \ +		if test "x$$TF" != "x" ; then \ +			echo "$$k" |tr -d '\n' > $$TF; \ +			mv -f $$TF $@; \ +		else \ +			echo mktemp failed; \ +			false; \ +		fi; \ +	else \ +		echo Bad API key: $$k; \ +		echo An API key should be 32 characters long.; \ +		false; \ +	fi; + + +.PHONY: handin tarball tarball-pref clean grade handin-check diff --git a/conf/lab.mk b/conf/lab.mk new file mode 100644 index 0000000..2992d87 --- /dev/null +++ b/conf/lab.mk @@ -0,0 +1 @@ +LAB=pgtbl diff --git a/grade-lab-pgtbl b/grade-lab-pgtbl new file mode 100755 index 0000000..121bb3b --- /dev/null +++ b/grade-lab-pgtbl @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +import re +from gradelib import * + +r = Runner(save("xv6.out")) + +PTE_PRINT = """page table 0x0000000087f6b000 + ..0: pte 0x0000000021fd9c01 pa 0x0000000087f67000 + .. ..0: pte 0x0000000021fd9801 pa 0x0000000087f66000 + .. .. ..0: pte 0x0000000021fda01b pa 0x0000000087f68000 + .. .. ..1: pte 0x0000000021fd9417 pa 0x0000000087f65000 + .. .. ..2: pte 0x0000000021fd9007 pa 0x0000000087f64000 + .. .. ..3: pte 0x0000000021fd8c17 pa 0x0000000087f63000 + ..255: pte 0x0000000021fda801 pa 0x0000000087f6a000 + .. ..511: pte 0x0000000021fda401 pa 0x0000000087f69000 + .. .. ..509: pte 0x0000000021fdcc13 pa 0x0000000087f73000 + .. .. ..510: pte 0x0000000021fdd007 pa 0x0000000087f74000 + .. .. ..511: pte 0x0000000020001c0b pa 0x0000000080007000""" + +VAL_RE = "(0x00000000[0-9a-f]+)" +INDENT_RE = r"\s*\.\.\s*" +INDENT_ESC = "\\\s*\.\.\\\s*" + +@test(0, "pgtbltest") +def test_pgtbltest(): +    r.run_qemu(shell_script([ +        'pgtbltest' +    ]), timeout=300) + +@test(10, "pgtbltest: ugetpid", parent=test_pgtbltest) +def test_nettest_(): +    r.match('^ugetpid_test: OK$') + +@test(10, "pgtbltest: pgaccess", parent=test_pgtbltest) +def test_nettest_(): +    r.match('^pgaccess_test: OK$') + +@test(10, "pte printout") +def test_pteprint(): +    first = True +    r.run_qemu(shell_script([ +        'echo hi' +    ])) +    r.match('^hi') +    p = re.compile(VAL_RE) +    d = re.compile(INDENT_RE) +    for l in PTE_PRINT.splitlines(): +        l = d.sub(INDENT_ESC, l) +        l = p.sub(VAL_RE, l) +        r.match(r'^{}$'.format(l)) +        if first: +            first = False +        else: +            matches = re.findall(r'^{}$'.format(l), r.qemu.output, re.MULTILINE) +            assert_equal(len(matches[0]), 2) +            pa = (int(matches[0][0], 16) >> 10) << 12 +            assert_equal(int(matches[0][1], 16), pa) + +@test(5, "answers-pgtbl.txt") +def test_answers(): +    # just a simple sanity check, will be graded manually +    check_answers("answers-pgtbl.txt") + +@test(0, "usertests") +def test_usertests(): +    r.run_qemu(shell_script([ +        'usertests -q' +    ]), timeout=300) + +@test(10, "usertests: all tests", parent=test_usertests) +def test_usertests(): +    r.match('^ALL TESTS PASSED$') + +@test(1, "time") +def test_time(): +    check_time() + +run_tests() diff --git a/gradelib.py b/gradelib.py new file mode 100644 index 0000000..e8d7814 --- /dev/null +++ b/gradelib.py @@ -0,0 +1,611 @@ +from __future__ import print_function + +import sys, os, re, time, socket, select, subprocess, errno, shutil, random, string +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 + +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 + +            # 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 +            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 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") +    (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: +            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 pipes 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: +        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 diff --git a/kernel/memlayout.h b/kernel/memlayout.h index cac3cb1..74d2fd4 100644 --- a/kernel/memlayout.h +++ b/kernel/memlayout.h @@ -25,6 +25,10 @@  #define VIRTIO0 0x10001000  #define VIRTIO0_IRQ 1 +#ifdef LAB_NET +#define E1000_IRQ 33 +#endif +  // core local interruptor (CLINT), which contains the timer.  #define CLINT 0x2000000L  #define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 8*(hartid)) @@ -34,8 +38,11 @@  #define PLIC 0x0c000000L  #define PLIC_PRIORITY (PLIC + 0x0)  #define PLIC_PENDING (PLIC + 0x1000) +#define PLIC_MENABLE(hart) (PLIC + 0x2000 + (hart)*0x100)  #define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart)*0x100) +#define PLIC_MPRIORITY(hart) (PLIC + 0x200000 + (hart)*0x2000)  #define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart)*0x2000) +#define PLIC_MCLAIM(hart) (PLIC + 0x200004 + (hart)*0x2000)  #define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart)*0x2000)  // the kernel expects there to be RAM @@ -50,7 +57,7 @@  // map kernel stacks beneath the trampoline,  // each surrounded by invalid guard pages. -#define KSTACK(p) (TRAMPOLINE - ((p)+1)* 2*PGSIZE) +#define KSTACK(p) (TRAMPOLINE - (p)*2*PGSIZE - 3*PGSIZE)  // User memory layout.  // Address zero first: @@ -59,6 +66,14 @@  //   fixed-size stack  //   expandable heap  //   ... +//   USYSCALL (shared with kernel)  //   TRAPFRAME (p->trapframe, used by the trampoline)  //   TRAMPOLINE (the same page as in the kernel)  #define TRAPFRAME (TRAMPOLINE - PGSIZE) +#ifdef LAB_PGTBL +#define USYSCALL (TRAPFRAME - PGSIZE) + +struct usyscall { +  int pid;  // Process ID +}; +#endif diff --git a/kernel/syscall.c b/kernel/syscall.c index ed65409..beea0ef 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -102,6 +102,13 @@ extern uint64 sys_link(void);  extern uint64 sys_mkdir(void);  extern uint64 sys_close(void); +#ifdef LAB_NET +extern uint64 sys_connect(void); +#endif +#ifdef LAB_PGTBL +extern uint64 sys_pgaccess(void); +#endif +  // An array mapping syscall numbers from syscall.h  // to the function that handles the system call.  static uint64 (*syscalls[])(void) = { @@ -126,8 +133,16 @@ static uint64 (*syscalls[])(void) = {  [SYS_link]    sys_link,  [SYS_mkdir]   sys_mkdir,  [SYS_close]   sys_close, +#ifdef LAB_NET +[SYS_connect] sys_connect, +#endif +#ifdef LAB_PGTBL +[SYS_pgaccess] sys_pgaccess, +#endif  }; + +  void  syscall(void)  { diff --git a/kernel/syscall.h b/kernel/syscall.h index bc5f356..8da572e 100644 --- a/kernel/syscall.h +++ b/kernel/syscall.h @@ -20,3 +20,14 @@  #define SYS_link   19  #define SYS_mkdir  20  #define SYS_close  21 + +// System calls for labs +#define SYS_trace     22 +#define SYS_sysinfo   23 +#define SYS_sigalarm  24 +#define SYS_sigreturn 25 +#define SYS_symlink   26 +#define SYS_mmap      27 +#define SYS_munmap    28 +#define SYS_connect   29 +#define SYS_pgaccess  30 diff --git a/kernel/sysproc.c b/kernel/sysproc.c index 3b4d5bd..88644b2 100644 --- a/kernel/sysproc.c +++ b/kernel/sysproc.c @@ -1,7 +1,7 @@  #include "types.h"  #include "riscv.h" -#include "defs.h"  #include "param.h" +#include "defs.h"  #include "memlayout.h"  #include "spinlock.h"  #include "proc.h" @@ -54,9 +54,8 @@ sys_sleep(void)    int n;    uint ticks0; +    argint(0, &n); -  if(n < 0) -    n = 0;    acquire(&tickslock);    ticks0 = ticks;    while(ticks - ticks0 < n){ @@ -70,6 +69,16 @@ sys_sleep(void)    return 0;  } + +#ifdef LAB_PGTBL +int +sys_pgaccess(void) +{ +  // lab pgtbl: your code here. +  return 0; +} +#endif +  uint64  sys_kill(void)  { diff --git a/user/pgtbltest.c b/user/pgtbltest.c new file mode 100644 index 0000000..bce158a --- /dev/null +++ b/user/pgtbltest.c @@ -0,0 +1,70 @@ +#include "kernel/param.h" +#include "kernel/fcntl.h" +#include "kernel/types.h" +#include "kernel/riscv.h" +#include "user/user.h" + +void ugetpid_test(); +void pgaccess_test(); + +int +main(int argc, char *argv[]) +{ +  ugetpid_test(); +  pgaccess_test(); +  printf("pgtbltest: all tests succeeded\n"); +  exit(0); +} + +char *testname = "???"; + +void +err(char *why) +{ +  printf("pgtbltest: %s failed: %s, pid=%d\n", testname, why, getpid()); +  exit(1); +} + +void +ugetpid_test() +{ +  int i; + +  printf("ugetpid_test starting\n"); +  testname = "ugetpid_test"; + +  for (i = 0; i < 64; i++) { +    int ret = fork(); +    if (ret != 0) { +      wait(&ret); +      if (ret != 0) +        exit(1); +      continue; +    } +    if (getpid() != ugetpid()) +      err("missmatched PID"); +    exit(0); +  } +  printf("ugetpid_test: OK\n"); +} + +void +pgaccess_test() +{ +  char *buf; +  unsigned int abits; +  printf("pgaccess_test starting\n"); +  testname = "pgaccess_test"; +  buf = malloc(32 * PGSIZE); +  if (pgaccess(buf, 32, &abits) < 0) +    err("pgaccess failed"); +  buf[PGSIZE * 1] += 1; +  buf[PGSIZE * 2] += 1; +  buf[PGSIZE * 30] += 1; +  if (pgaccess(buf, 32, &abits) < 0) +    err("pgaccess failed"); +  if (abits != ((1 << 1) | (1 << 2) | (1 << 30))) +    err("incorrect access bits set"); +  free(buf); +  printf("pgaccess_test: OK\n"); +} diff --git a/user/ulib.c b/user/ulib.c index c7b66c4..871adc9 100644 --- a/user/ulib.c +++ b/user/ulib.c @@ -1,8 +1,13 @@  #include "kernel/types.h"  #include "kernel/stat.h"  #include "kernel/fcntl.h" +#ifdef LAB_PGTBL +#include "kernel/riscv.h" +#include "kernel/memlayout.h" +#endif  #include "user/user.h" +  //  // wrapper so that it's OK if main() does not call exit().  // @@ -145,3 +150,12 @@ memcpy(void *dst, const void *src, uint n)  {    return memmove(dst, src, n);  } + +#ifdef LAB_PGTBL +int +ugetpid(void) +{ +  struct usyscall *u = (struct usyscall *)USYSCALL; +  return u->pid; +} +#endif diff --git a/user/user.h b/user/user.h index 4d398d5..16cf173 100644 --- a/user/user.h +++ b/user/user.h @@ -22,6 +22,14 @@ int getpid(void);  char* sbrk(int);  int sleep(int);  int uptime(void); +#ifdef LAB_NET +int connect(uint32, uint16, uint16); +#endif +#ifdef LAB_PGTBL +int pgaccess(void *base, int len, void *mask); +// usyscall region +int ugetpid(void); +#endif  // ulib.c  int stat(const char*, struct stat*); @@ -39,3 +47,4 @@ void free(void*);  int atoi(const char*);  int memcmp(const void *, const void *, uint);  void *memcpy(void *, const void *, uint); +int statistics(void*, int); diff --git a/user/usys.pl b/user/usys.pl index 01e426e..6453fe9 100755 --- a/user/usys.pl +++ b/user/usys.pl @@ -36,3 +36,5 @@ entry("getpid");  entry("sbrk");  entry("sleep");  entry("uptime"); +entry("connect"); +entry("pgaccess"); | 
