From 18998096701a1d4695b2097006d7afc2a539bd89 Mon Sep 17 00:00:00 2001 From: Robert Morris Date: Fri, 8 Sep 2023 09:24:27 -0400 Subject: 3rd lab --- .gitignore | 9 + Makefile | 259 ++++++++++++++++++++++- conf/lab.mk | 1 + grade-lab-pgtbl | 79 +++++++ gradelib.py | 611 +++++++++++++++++++++++++++++++++++++++++++++++++++++ kernel/memlayout.h | 17 +- kernel/syscall.c | 15 ++ kernel/syscall.h | 11 + kernel/sysproc.c | 15 +- user/pgtbltest.c | 70 ++++++ user/ulib.c | 14 ++ user/user.h | 9 + user/usys.pl | 2 + 13 files changed, 1097 insertions(+), 15 deletions(-) create mode 100644 conf/lab.mk create mode 100755 grade-lab-pgtbl create mode 100644 gradelib.py create mode 100644 user/pgtbltest.c diff --git a/.gitignore b/.gitignore index 07216f3..3d25221 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile b/Makefile index 39a99d7..aabd280 100644 --- a/Makefile +++ b/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,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=\ /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"); -- cgit v1.2.3 From 9f94bb691c858ea89c2cf1e3185d4b5c5e66e6e6 Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Tue, 12 Sep 2023 14:08:48 -0400 Subject: Makefile: new submission system --- Makefile | 51 +++++---------------------------------------------- 1 file changed, 5 insertions(+), 46 deletions(-) diff --git a/Makefile b/Makefile index aabd280..5844ba3 100644 --- a/Makefile +++ b/Makefile @@ -338,21 +338,10 @@ grade: ./grade-lab-$(LAB) $(GRADEFLAGS) ## -## FOR web handin +## FOR submissions ## - -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=\ /dev/null || { \ - echo ; \ - echo Submit seems to have failed.; \ - echo Please go to $(WEBSUB)/ and upload the tarball manually.; } - -handin-check: +submit-check: @if ! test -d .git; then \ echo No .git directory, is this a git repository?; \ false; \ @@ -374,37 +363,7 @@ handin-check: 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; - +zipball: submit-check + git archive --format=zip --output lab.zip HEAD -.PHONY: handin tarball tarball-pref clean grade handin-check +.PHONY: zipball clean grade submit-check -- cgit v1.2.3 From 725a5ff207d66697cd0bfe78936a2174d1601278 Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Tue, 12 Sep 2023 17:21:00 -0400 Subject: remove answers --- grade-lab-pgtbl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/grade-lab-pgtbl b/grade-lab-pgtbl index 121bb3b..19706b3 100755 --- a/grade-lab-pgtbl +++ b/grade-lab-pgtbl @@ -57,11 +57,6 @@ def test_pteprint(): 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([ -- cgit v1.2.3 From e7ad79c0c51a27efcdcc94c4bc848ca0f41937e1 Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Wed, 13 Sep 2023 01:24:07 -0400 Subject: release traps --- .gitignore | 9 + Makefile | 218 +++++++++++++++++++- conf/lab.mk | 1 + grade-lab-traps | 69 +++++++ gradelib.py | 611 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ user/alarmtest.c | 193 ++++++++++++++++++ user/bttest.c | 10 + user/call.c | 17 ++ 8 files changed, 1117 insertions(+), 11 deletions(-) create mode 100644 conf/lab.mk create mode 100755 grade-lab-traps create mode 100644 gradelib.py create mode 100644 user/alarmtest.c create mode 100644 user/bttest.c create mode 100644 user/call.c diff --git a/.gitignore b/.gitignore index 07216f3..3d25221 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile b/Makefile index 39a99d7..5844ba3 100644 --- a/Makefile +++ b/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,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,61 @@ 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 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: submit-check + git archive --format=zip --output lab.zip HEAD + +.PHONY: zipball clean grade submit-check diff --git a/conf/lab.mk b/conf/lab.mk new file mode 100644 index 0000000..2b9988d --- /dev/null +++ b/conf/lab.mk @@ -0,0 +1 @@ +LAB=traps diff --git a/grade-lab-traps b/grade-lab-traps new file mode 100755 index 0000000..afa93b8 --- /dev/null +++ b/grade-lab-traps @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +import os +import re +import subprocess +from gradelib import * + +r = Runner(save("xv6.out")) + +BACKTRACE_RE = r"^(0x000000008[0-9a-f]+)" + +def addr2line(): + for f in ['riscv64-unknown-elf-addr2line', 'riscv64-linux-gnu-addr2line', 'addr2line', ]: + try: + devnull = open(os.devnull) + subprocess.Popen([f], stdout=devnull, stderr=devnull).communicate() + return f + except OSError: + continue + raise AssertionError('Cannot find the addr2line program') + +@test(10, "backtrace test") +def test_backtracetest(): + r.run_qemu(shell_script([ + 'bttest' + ])) + a2l = addr2line() + matches = re.findall(BACKTRACE_RE, r.qemu.output, re.MULTILINE) + assert_equal(len(matches), 3) + files = ['sysproc.c', 'syscall.c', 'trap.c'] + for f, m in zip(files, matches): + result = subprocess.run([a2l, '-e', 'kernel/kernel', m], stdout=subprocess.PIPE) + if not f in result.stdout.decode("utf-8"): + raise AssertionError('Trace is incorrect; no %s' % f) + +@test(0, "running alarmtest") +def test_alarmtest(): + r.run_qemu(shell_script([ + 'alarmtest' + ])) + +@test(20, "alarmtest: test0", parent=test_alarmtest) +def test_alarmtest_test0(): + r.match('^test0 passed$') + +@test(20, "alarmtest: test1", parent=test_alarmtest) +def test_alarmtest_test1(): + r.match('^\\.?test1 passed$') + +@test(10, "alarmtest: test2", parent=test_alarmtest) +def test_alarmtest_test2(): + r.match('^\\.?test2 passed$') + +@test(10, "alarmtest: test3", parent=test_alarmtest) +def test_alarmtest_test3(): + r.match('^test3 passed$') + +@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() 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/user/alarmtest.c b/user/alarmtest.c new file mode 100644 index 0000000..b8d85f7 --- /dev/null +++ b/user/alarmtest.c @@ -0,0 +1,193 @@ +// +// test program for the alarm lab. +// you can modify this file for testing, +// but please make sure your kernel +// modifications pass the original +// versions of these tests. +// + +#include "kernel/param.h" +#include "kernel/types.h" +#include "kernel/stat.h" +#include "kernel/riscv.h" +#include "user/user.h" + +void test0(); +void test1(); +void test2(); +void test3(); +void periodic(); +void slow_handler(); +void dummy_handler(); + +int +main(int argc, char *argv[]) +{ + test0(); + test1(); + test2(); + test3(); + exit(0); +} + +volatile static int count; + +void +periodic() +{ + count = count + 1; + printf("alarm!\n"); + sigreturn(); +} + +// tests whether the kernel calls +// the alarm handler even a single time. +void +test0() +{ + int i; + printf("test0 start\n"); + count = 0; + sigalarm(2, periodic); + for(i = 0; i < 1000*500000; i++){ + if((i % 1000000) == 0) + write(2, ".", 1); + if(count > 0) + break; + } + sigalarm(0, 0); + if(count > 0){ + printf("test0 passed\n"); + } else { + printf("\ntest0 failed: the kernel never called the alarm handler\n"); + } +} + +void __attribute__ ((noinline)) foo(int i, int *j) { + if((i % 2500000) == 0) { + write(2, ".", 1); + } + *j += 1; +} + +// +// tests that the kernel calls the handler multiple times. +// +// tests that, when the handler returns, it returns to +// the point in the program where the timer interrupt +// occurred, with all registers holding the same values they +// held when the interrupt occurred. +// +void +test1() +{ + int i; + int j; + + printf("test1 start\n"); + count = 0; + j = 0; + sigalarm(2, periodic); + for(i = 0; i < 500000000; i++){ + if(count >= 10) + break; + foo(i, &j); + } + if(count < 10){ + printf("\ntest1 failed: too few calls to the handler\n"); + } else if(i != j){ + // the loop should have called foo() i times, and foo() should + // have incremented j once per call, so j should equal i. + // once possible source of errors is that the handler may + // return somewhere other than where the timer interrupt + // occurred; another is that that registers may not be + // restored correctly, causing i or j or the address ofj + // to get an incorrect value. + printf("\ntest1 failed: foo() executed fewer times than it was called\n"); + } else { + printf("test1 passed\n"); + } +} + +// +// tests that kernel does not allow reentrant alarm calls. +void +test2() +{ + int i; + int pid; + int status; + + printf("test2 start\n"); + if ((pid = fork()) < 0) { + printf("test2: fork failed\n"); + } + if (pid == 0) { + count = 0; + sigalarm(2, slow_handler); + for(i = 0; i < 1000*500000; i++){ + if((i % 1000000) == 0) + write(2, ".", 1); + if(count > 0) + break; + } + if (count == 0) { + printf("\ntest2 failed: alarm not called\n"); + exit(1); + } + exit(0); + } + wait(&status); + if (status == 0) { + printf("test2 passed\n"); + } +} + +void +slow_handler() +{ + count++; + printf("alarm!\n"); + if (count > 1) { + printf("test2 failed: alarm handler called more than once\n"); + exit(1); + } + for (int i = 0; i < 1000*500000; i++) { + asm volatile("nop"); // avoid compiler optimizing away loop + } + sigalarm(0, 0); + sigreturn(); +} + +// +// dummy alarm handler; after running immediately uninstall +// itself and finish signal handling +void +dummy_handler() +{ + sigalarm(0, 0); + sigreturn(); +} + +// +// tests that the return from sys_sigreturn() does not +// modify the a0 register +void +test3() +{ + uint64 a0; + + sigalarm(1, dummy_handler); + printf("test3 start\n"); + + asm volatile("lui a5, 0"); + asm volatile("addi a0, a5, 0xac" : : : "a0"); + for(int i = 0; i < 500000000; i++) + ; + asm volatile("mv %0, a0" : "=r" (a0) ); + + if(a0 != 0xac) + printf("test3 failed: register a0 changed\n"); + else + printf("test3 passed\n"); +} diff --git a/user/bttest.c b/user/bttest.c new file mode 100644 index 0000000..05405f9 --- /dev/null +++ b/user/bttest.c @@ -0,0 +1,10 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +int +main(int argc, char *argv[]) +{ + sleep(1); + exit(0); +} diff --git a/user/call.c b/user/call.c new file mode 100644 index 0000000..f725dcb --- /dev/null +++ b/user/call.c @@ -0,0 +1,17 @@ +#include "kernel/param.h" +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +int g(int x) { + return x+3; +} + +int f(int x) { + return g(x); +} + +void main(void) { + printf("%d %d\n", f(8)+1, 13); + exit(0); +} -- cgit v1.2.3 From e3512e581807f0b7d0eeda44fcf002062ddd558a Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Wed, 13 Sep 2023 01:54:57 -0400 Subject: release lab cow --- .gitignore | 9 + Makefile | 218 ++++++++++++++++++-- conf/lab.mk | 1 + grade-lab-cow | 55 ++++++ gradelib.py | 611 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ user/cowtest.c | 197 +++++++++++++++++++ 6 files changed, 1080 insertions(+), 11 deletions(-) create mode 100644 conf/lab.mk create mode 100755 grade-lab-cow create mode 100644 gradelib.py create mode 100644 user/cowtest.c diff --git a/.gitignore b/.gitignore index 07216f3..3d25221 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile b/Makefile index 39a99d7..5844ba3 100644 --- a/Makefile +++ b/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,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,61 @@ 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 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: submit-check + git archive --format=zip --output lab.zip HEAD + +.PHONY: zipball clean grade submit-check diff --git a/conf/lab.mk b/conf/lab.mk new file mode 100644 index 0000000..629f978 --- /dev/null +++ b/conf/lab.mk @@ -0,0 +1 @@ +LAB=cow diff --git a/grade-lab-cow b/grade-lab-cow new file mode 100755 index 0000000..08b6990 --- /dev/null +++ b/grade-lab-cow @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 + +import re +from gradelib import * + +r = Runner(save("xv6.out")) + +@test(0, "running cowtest") +def test_cowtest(): + r.run_qemu(shell_script([ + 'cowtest' + ])) + +@test(30, "simple", parent=test_cowtest) +def test_simple(): + matches = re.findall("^simple: ok$", r.qemu.output, re.M) + assert_equal(len(matches), 2, "Number of appearances of 'simple: ok'") + +@test(30, "three", parent=test_cowtest) +def test_three(): + matches = re.findall("^three: ok$", r.qemu.output, re.M) + assert_equal(len(matches), 3, "Number of appearances of 'three: ok'") + +@test(20, "file", parent=test_cowtest) +def test_file(): + r.match('^file: ok$') + +@test(0, "usertests") +def test_usertests(): + r.run_qemu(shell_script([ + 'usertests -q' + ]), timeout=1000) + r.match('^ALL TESTS PASSED$') + +def usertest_check(testcase, nextcase, output): + if not re.search(r'\ntest {}: [\s\S]*OK\ntest {}'.format(testcase, nextcase), output): + raise AssertionError('Failed ' + testcase) + +@test(5, "usertests: copyin", parent=test_usertests) +def test_sbrkbugs(): + usertest_check("copyin", "copyout", r.qemu.output) + +@test(5, "usertests: copyout", parent=test_usertests) +def test_sbrkbugs(): + usertest_check("copyout", "copyinstr1", r.qemu.output) + +@test(19, "usertests: all tests", parent=test_usertests) +def test_usertests_all(): + 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/user/cowtest.c b/user/cowtest.c new file mode 100644 index 0000000..29b918f --- /dev/null +++ b/user/cowtest.c @@ -0,0 +1,197 @@ +// +// tests for copy-on-write fork() assignment. +// + +#include "kernel/types.h" +#include "kernel/memlayout.h" +#include "user/user.h" + +// allocate more than half of physical memory, +// then fork. this will fail in the default +// kernel, which does not support copy-on-write. +void +simpletest() +{ + uint64 phys_size = PHYSTOP - KERNBASE; + int sz = (phys_size / 3) * 2; + + printf("simple: "); + + char *p = sbrk(sz); + if(p == (char*)0xffffffffffffffffL){ + printf("sbrk(%d) failed\n", sz); + exit(-1); + } + + for(char *q = p; q < p + sz; q += 4096){ + *(int*)q = getpid(); + } + + int pid = fork(); + if(pid < 0){ + printf("fork() failed\n"); + exit(-1); + } + + if(pid == 0) + exit(0); + + wait(0); + + if(sbrk(-sz) == (char*)0xffffffffffffffffL){ + printf("sbrk(-%d) failed\n", sz); + exit(-1); + } + + printf("ok\n"); +} + +// three processes all write COW memory. +// this causes more than half of physical memory +// to be allocated, so it also checks whether +// copied pages are freed. +void +threetest() +{ + uint64 phys_size = PHYSTOP - KERNBASE; + int sz = phys_size / 4; + int pid1, pid2; + + printf("three: "); + + char *p = sbrk(sz); + if(p == (char*)0xffffffffffffffffL){ + printf("sbrk(%d) failed\n", sz); + exit(-1); + } + + pid1 = fork(); + if(pid1 < 0){ + printf("fork failed\n"); + exit(-1); + } + if(pid1 == 0){ + pid2 = fork(); + if(pid2 < 0){ + printf("fork failed"); + exit(-1); + } + if(pid2 == 0){ + for(char *q = p; q < p + (sz/5)*4; q += 4096){ + *(int*)q = getpid(); + } + for(char *q = p; q < p + (sz/5)*4; q += 4096){ + if(*(int*)q != getpid()){ + printf("wrong content\n"); + exit(-1); + } + } + exit(-1); + } + for(char *q = p; q < p + (sz/2); q += 4096){ + *(int*)q = 9999; + } + exit(0); + } + + for(char *q = p; q < p + sz; q += 4096){ + *(int*)q = getpid(); + } + + wait(0); + + sleep(1); + + for(char *q = p; q < p + sz; q += 4096){ + if(*(int*)q != getpid()){ + printf("wrong content\n"); + exit(-1); + } + } + + if(sbrk(-sz) == (char*)0xffffffffffffffffL){ + printf("sbrk(-%d) failed\n", sz); + exit(-1); + } + + printf("ok\n"); +} + +char junk1[4096]; +int fds[2]; +char junk2[4096]; +char buf[4096]; +char junk3[4096]; + +// test whether copyout() simulates COW faults. +void +filetest() +{ + printf("file: "); + + buf[0] = 99; + + for(int i = 0; i < 4; i++){ + if(pipe(fds) != 0){ + printf("pipe() failed\n"); + exit(-1); + } + int pid = fork(); + if(pid < 0){ + printf("fork failed\n"); + exit(-1); + } + if(pid == 0){ + sleep(1); + if(read(fds[0], buf, sizeof(i)) != sizeof(i)){ + printf("error: read failed\n"); + exit(1); + } + sleep(1); + int j = *(int*)buf; + if(j != i){ + printf("error: read the wrong value\n"); + exit(1); + } + exit(0); + } + if(write(fds[1], &i, sizeof(i)) != sizeof(i)){ + printf("error: write failed\n"); + exit(-1); + } + } + + int xstatus = 0; + for(int i = 0; i < 4; i++) { + wait(&xstatus); + if(xstatus != 0) { + exit(1); + } + } + + if(buf[0] != 99){ + printf("error: child overwrote parent\n"); + exit(1); + } + + printf("ok\n"); +} + +int +main(int argc, char *argv[]) +{ + simpletest(); + + // check that the first simpletest() freed the physical memory. + simpletest(); + + threetest(); + threetest(); + threetest(); + + filetest(); + + printf("ALL COW TESTS PASSED\n"); + + exit(0); +} -- cgit v1.2.3 From 0a22d28fdb2e421397cc882e0c6028d93058dc2d Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Wed, 13 Sep 2023 16:13:31 -0400 Subject: revert answers --- grade-lab-pgtbl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/grade-lab-pgtbl b/grade-lab-pgtbl index 19706b3..121bb3b 100755 --- a/grade-lab-pgtbl +++ b/grade-lab-pgtbl @@ -57,6 +57,11 @@ def test_pteprint(): 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([ -- cgit v1.2.3 From 4831426469c2ae78ddab6df14a3c0659b9008b03 Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Wed, 13 Sep 2023 16:15:08 -0400 Subject: revert answers --- grade-lab-traps | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/grade-lab-traps b/grade-lab-traps index afa93b8..10613d3 100755 --- a/grade-lab-traps +++ b/grade-lab-traps @@ -7,6 +7,11 @@ from gradelib import * r = Runner(save("xv6.out")) +@test(5, "answers-traps.txt") +def test_answers(): + # just a simple sanity check, will be graded manually + check_answers("answers-traps.txt") + BACKTRACE_RE = r"^(0x000000008[0-9a-f]+)" def addr2line(): -- cgit v1.2.3 From e3489c3f84431b18748e2e63ef33be5d6caddf3a Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Tue, 19 Sep 2023 14:21:54 -0400 Subject: update grading and zipball rule --- .gitignore | 5 ++--- Makefile | 5 +++-- gradelib.py | 21 +++++++++++++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 3d25221..5c33438 100644 --- a/.gitignore +++ b/.gitignore @@ -15,12 +15,11 @@ mkfs kernel/kernel user/usys.S .gdbinit -myapi.key -*-handin.tar.gz +*.zip xv6.out* .vagrant/ submissions/ ph barrier /lab-*.json -.DS_Store \ No newline at end of file +.DS_Store diff --git a/Makefile b/Makefile index 5844ba3..c60ea76 100644 --- a/Makefile +++ b/Makefile @@ -272,6 +272,7 @@ clean: mkfs/mkfs .gdbinit \ $U/usys.S \ $(UPROGS) \ + lab.zip \ ph barrier # try to generate a unique GDB port @@ -363,7 +364,7 @@ submit-check: test "$$r" = y; \ fi -zipball: submit-check - git archive --format=zip --output lab.zip HEAD +zipball: clean submit-check + git archive --verbose --format zip --output lab.zip HEAD .PHONY: zipball clean grade submit-check diff --git a/gradelib.py b/gradelib.py index e8d7814..f0d4934 100644 --- a/gradelib.py +++ b/gradelib.py @@ -1,6 +1,6 @@ from __future__ import print_function -import sys, os, re, time, socket, select, subprocess, errno, shutil, random, string +import sys, os, re, time, socket, select, subprocess, errno, shutil, random, string, json from subprocess import check_call, Popen from optparse import OptionParser @@ -16,6 +16,7 @@ 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 @@ -31,7 +32,7 @@ def test(points, title=None, parent=None): title = " " + title def run_test(): - global TOTAL, POSSIBLE, CURRENT_TEST + global TOTAL, POSSIBLE, CURRENT_TEST, GRADES # Handle test dependencies if run_test.complete: @@ -68,6 +69,9 @@ def test(points, title=None, parent=None): 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 @@ -95,6 +99,16 @@ def end_part(name): 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.""" @@ -105,6 +119,7 @@ def run_tests(): 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 @@ -120,6 +135,7 @@ def run_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 @@ -260,6 +276,7 @@ def check_time(): def check_answers(file, n=10): try: + print("") with open(file) as f: d = f.read().strip() if len(d) < n: -- cgit v1.2.3 From f048e92d8d22d166f6a806ebe79d6bc6f87ba9c1 Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Tue, 19 Sep 2023 14:22:19 -0400 Subject: update grading and zipball rule --- .gitignore | 5 ++--- Makefile | 5 +++-- gradelib.py | 21 +++++++++++++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 3d25221..5c33438 100644 --- a/.gitignore +++ b/.gitignore @@ -15,12 +15,11 @@ mkfs kernel/kernel user/usys.S .gdbinit -myapi.key -*-handin.tar.gz +*.zip xv6.out* .vagrant/ submissions/ ph barrier /lab-*.json -.DS_Store \ No newline at end of file +.DS_Store diff --git a/Makefile b/Makefile index 5844ba3..c60ea76 100644 --- a/Makefile +++ b/Makefile @@ -272,6 +272,7 @@ clean: mkfs/mkfs .gdbinit \ $U/usys.S \ $(UPROGS) \ + lab.zip \ ph barrier # try to generate a unique GDB port @@ -363,7 +364,7 @@ submit-check: test "$$r" = y; \ fi -zipball: submit-check - git archive --format=zip --output lab.zip HEAD +zipball: clean submit-check + git archive --verbose --format zip --output lab.zip HEAD .PHONY: zipball clean grade submit-check diff --git a/gradelib.py b/gradelib.py index e8d7814..f0d4934 100644 --- a/gradelib.py +++ b/gradelib.py @@ -1,6 +1,6 @@ from __future__ import print_function -import sys, os, re, time, socket, select, subprocess, errno, shutil, random, string +import sys, os, re, time, socket, select, subprocess, errno, shutil, random, string, json from subprocess import check_call, Popen from optparse import OptionParser @@ -16,6 +16,7 @@ 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 @@ -31,7 +32,7 @@ def test(points, title=None, parent=None): title = " " + title def run_test(): - global TOTAL, POSSIBLE, CURRENT_TEST + global TOTAL, POSSIBLE, CURRENT_TEST, GRADES # Handle test dependencies if run_test.complete: @@ -68,6 +69,9 @@ def test(points, title=None, parent=None): 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 @@ -95,6 +99,16 @@ def end_part(name): 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.""" @@ -105,6 +119,7 @@ def run_tests(): 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 @@ -120,6 +135,7 @@ def run_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 @@ -260,6 +276,7 @@ def check_time(): def check_answers(file, n=10): try: + print("") with open(file) as f: d = f.read().strip() if len(d) < n: -- cgit v1.2.3 From fa0bdd7cd97c01bb61872eff0589147ec774ec2e Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Tue, 19 Sep 2023 14:22:44 -0400 Subject: update grading and zipball rule --- .gitignore | 5 ++--- Makefile | 5 +++-- gradelib.py | 21 +++++++++++++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 3d25221..5c33438 100644 --- a/.gitignore +++ b/.gitignore @@ -15,12 +15,11 @@ mkfs kernel/kernel user/usys.S .gdbinit -myapi.key -*-handin.tar.gz +*.zip xv6.out* .vagrant/ submissions/ ph barrier /lab-*.json -.DS_Store \ No newline at end of file +.DS_Store diff --git a/Makefile b/Makefile index 5844ba3..c60ea76 100644 --- a/Makefile +++ b/Makefile @@ -272,6 +272,7 @@ clean: mkfs/mkfs .gdbinit \ $U/usys.S \ $(UPROGS) \ + lab.zip \ ph barrier # try to generate a unique GDB port @@ -363,7 +364,7 @@ submit-check: test "$$r" = y; \ fi -zipball: submit-check - git archive --format=zip --output lab.zip HEAD +zipball: clean submit-check + git archive --verbose --format zip --output lab.zip HEAD .PHONY: zipball clean grade submit-check diff --git a/gradelib.py b/gradelib.py index e8d7814..f0d4934 100644 --- a/gradelib.py +++ b/gradelib.py @@ -1,6 +1,6 @@ from __future__ import print_function -import sys, os, re, time, socket, select, subprocess, errno, shutil, random, string +import sys, os, re, time, socket, select, subprocess, errno, shutil, random, string, json from subprocess import check_call, Popen from optparse import OptionParser @@ -16,6 +16,7 @@ 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 @@ -31,7 +32,7 @@ def test(points, title=None, parent=None): title = " " + title def run_test(): - global TOTAL, POSSIBLE, CURRENT_TEST + global TOTAL, POSSIBLE, CURRENT_TEST, GRADES # Handle test dependencies if run_test.complete: @@ -68,6 +69,9 @@ def test(points, title=None, parent=None): 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 @@ -95,6 +99,16 @@ def end_part(name): 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.""" @@ -105,6 +119,7 @@ def run_tests(): 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 @@ -120,6 +135,7 @@ def run_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 @@ -260,6 +276,7 @@ def check_time(): def check_answers(file, n=10): try: + print("") with open(file) as f: d = f.read().strip() if len(d) < n: -- cgit v1.2.3 From 66e11db2fabe4d0330b09f46bdd70b8ba7564d3f Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Thu, 21 Sep 2023 10:30:04 -0400 Subject: clean all zip files, even old ones --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c60ea76..365c91b 100644 --- a/Makefile +++ b/Makefile @@ -272,7 +272,7 @@ clean: mkfs/mkfs .gdbinit \ $U/usys.S \ $(UPROGS) \ - lab.zip \ + *.zip \ ph barrier # try to generate a unique GDB port -- cgit v1.2.3 From 7568e0697ca49d6bdd16651143948b15ff06a938 Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Thu, 21 Sep 2023 10:30:07 -0400 Subject: clean all zip files, even old ones --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c60ea76..365c91b 100644 --- a/Makefile +++ b/Makefile @@ -272,7 +272,7 @@ clean: mkfs/mkfs .gdbinit \ $U/usys.S \ $(UPROGS) \ - lab.zip \ + *.zip \ ph barrier # try to generate a unique GDB port -- cgit v1.2.3 From 5ce1f630132d8b7b35b9dc46b4a55f860eb85d5c Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Thu, 21 Sep 2023 10:30:09 -0400 Subject: clean all zip files, even old ones --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c60ea76..365c91b 100644 --- a/Makefile +++ b/Makefile @@ -272,7 +272,7 @@ clean: mkfs/mkfs .gdbinit \ $U/usys.S \ $(UPROGS) \ - lab.zip \ + *.zip \ ph barrier # try to generate a unique GDB port -- cgit v1.2.3 From f379c1c4c80947365eb6c4046c3ab165629dc8cd Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Mon, 2 Oct 2023 16:22:42 -0500 Subject: ignore & clean dSYM files --- .gitignore | 1 + Makefile | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 5c33438..29977c9 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ ph barrier /lab-*.json .DS_Store +*.dSYM diff --git a/Makefile b/Makefile index 365c91b..473a471 100644 --- a/Makefile +++ b/Makefile @@ -265,14 +265,12 @@ fs.img: mkfs/mkfs README $(UEXTRA) $(UPROGS) -include kernel/*.d user/*.d -clean: - rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg \ +clean: + rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg *.dSYM *.zip \ */*.o */*.d */*.asm */*.sym \ - $U/initcode $U/initcode.out $K/kernel fs.img \ - mkfs/mkfs .gdbinit \ - $U/usys.S \ + $U/initcode $U/initcode.out $K/kernel $U/usys.S \ + mkfs/mkfs fs.img .gdbinit \ $(UPROGS) \ - *.zip \ ph barrier # try to generate a unique GDB port -- cgit v1.2.3 From 1ed40716eb54e371df9d1814b9129666b3fe4f09 Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Wed, 25 Oct 2023 19:42:59 -0400 Subject: release lab net --- .gitignore | 10 +++ Makefile | 227 +++++++++++++++++++++++++++++++++++++++++++++++++---- kernel/defs.h | 49 ++++++++++++ kernel/file.c | 21 ++++- kernel/file.h | 8 ++ kernel/main.c | 12 ++- kernel/memlayout.h | 17 +++- kernel/plic.c | 12 +++ kernel/spinlock.c | 111 +++++++++++++++++++++++++- kernel/syscall.c | 15 ++++ kernel/syscall.h | 11 +++ kernel/sysfile.c | 26 ++++++ kernel/trap.c | 11 ++- kernel/vm.c | 52 +++++++----- user/user.h | 9 +++ user/usys.pl | 2 + 16 files changed, 552 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index 07216f3..d036a8b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,13 @@ mkfs kernel/kernel user/usys.S .gdbinit +*.zip +xv6.out* +.vagrant/ +submissions/ +ph +barrier +/lab-*.json +.DS_Store +*.dSYM +*.pcap diff --git a/Makefile b/Makefile index 39a99d7..da43ce2 100644 --- a/Makefile +++ b/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,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,18 +189,89 @@ 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 -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 .gdbinit __pycache__ xv6.out* \ + ph barrier # try to generate a unique GDB port GDBPORT = $(shell expr `id -u` % 5000 + 25000) @@ -155,12 +282,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 +308,61 @@ 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 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 diff --git a/kernel/defs.h b/kernel/defs.h index a3c962b..c6fef8c 100644 --- a/kernel/defs.h +++ b/kernel/defs.h @@ -8,6 +8,10 @@ struct spinlock; struct sleeplock; struct stat; struct superblock; +#ifdef LAB_NET +struct mbuf; +struct sock; +#endif // bio.c void binit(void); @@ -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*); @@ -187,3 +195,44 @@ 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 + +// stats.c +void statsinit(void); +void statsinc(void); + +// sprintf.c +int snprintf(char*, int, char*, ...); + +#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(struct mbuf*); + +// net.c +void net_rx(struct mbuf*); +void net_tx_udp(struct mbuf*, uint32, uint16, uint16); + +// sysnet.c +void sockinit(void); +int sockalloc(struct file **, uint32, uint16, uint16); +void sockclose(struct sock *); +int sockread(struct sock *, uint64, int); +int sockwrite(struct sock *, uint64, int); +void sockrecvudp(struct mbuf*, uint32, uint16, uint16); +#endif diff --git a/kernel/file.c b/kernel/file.c index 25fa226..0fba21b 100644 --- a/kernel/file.c +++ b/kernel/file.c @@ -80,6 +80,11 @@ fileclose(struct file *f) iput(ff.ip); end_op(); } +#ifdef LAB_NET + else if(ff.type == FD_SOCK){ + sockclose(ff.sock); + } +#endif } // Get metadata about file f. @@ -122,7 +127,13 @@ fileread(struct file *f, uint64 addr, int n) if((r = readi(f->ip, 1, addr, f->off, n)) > 0) f->off += r; iunlock(f->ip); - } else { + } +#ifdef LAB_NET + else if(f->type == FD_SOCK){ + r = sockread(f->sock, addr, n); + } +#endif + else { panic("fileread"); } @@ -173,7 +184,13 @@ filewrite(struct file *f, uint64 addr, int n) i += r; } ret = (i == n ? n : -1); - } else { + } +#ifdef LAB_NET + else if(f->type == FD_SOCK){ + ret = sockwrite(f->sock, addr, n); + } +#endif + else { panic("filewrite"); } diff --git a/kernel/file.h b/kernel/file.h index b076d1d..1eb5107 100644 --- a/kernel/file.h +++ b/kernel/file.h @@ -1,10 +1,17 @@ struct file { +#ifdef LAB_NET + enum { FD_NONE, FD_PIPE, FD_INODE, FD_DEVICE, FD_SOCK } type; +#else enum { FD_NONE, FD_PIPE, FD_INODE, FD_DEVICE } type; +#endif int ref; // reference count char readable; char writable; struct pipe *pipe; // FD_PIPE struct inode *ip; // FD_INODE and FD_DEVICE +#ifdef LAB_NET + struct sock *sock; // FD_SOCK +#endif uint off; // FD_INODE short major; // FD_DEVICE }; @@ -38,3 +45,4 @@ struct devsw { extern struct devsw devsw[]; #define CONSOLE 1 +#define STATS 2 diff --git a/kernel/main.c b/kernel/main.c index f0d3171..48c9555 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -12,6 +12,9 @@ main() { if(cpuid() == 0){ consoleinit(); +#if defined(LAB_LOCK) + statsinit(); +#endif printfinit(); printf("\n"); printf("xv6 kernel is booting\n"); @@ -28,11 +31,18 @@ main() iinit(); // inode table fileinit(); // file table virtio_disk_init(); // emulated hard disk +#ifdef LAB_NET + pci_init(); + sockinit(); +#endif userinit(); // first user process +#ifdef KCSAN + kcsaninit(); +#endif __sync_synchronize(); started = 1; } else { - while(started == 0) + while(atomic_read4((int *) &started) == 0) ; __sync_synchronize(); printf("hart %d starting\n", cpuid()); 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/plic.c b/kernel/plic.c index 4175db9..5c9d96a 100644 --- a/kernel/plic.c +++ b/kernel/plic.c @@ -14,6 +14,13 @@ plicinit(void) // set desired IRQ priorities non-zero (otherwise disabled). *(uint32*)(PLIC + UART0_IRQ*4) = 1; *(uint32*)(PLIC + VIRTIO0_IRQ*4) = 1; + +#ifdef LAB_NET + // PCIE IRQs are 32 to 35 + for(int irq = 1; irq < 0x35; irq++){ + *(uint32*)(PLIC + irq*4) = 1; + } +#endif } void @@ -25,6 +32,11 @@ plicinithart(void) // for the uart and virtio disk. *(uint32*)PLIC_SENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO0_IRQ); +#ifdef LAB_NET + // hack to get at next 32 IRQs for e1000 + *(uint32*)(PLIC_SENABLE(hart)+4) = 0xffffffff; +#endif + // set this hart's S-mode priority threshold to 0. *(uint32*)PLIC_SPRIORITY(hart) = 0; } diff --git a/kernel/spinlock.c b/kernel/spinlock.c index 9840302..266a698 100644 --- a/kernel/spinlock.c +++ b/kernel/spinlock.c @@ -8,12 +8,52 @@ #include "proc.h" #include "defs.h" +#ifdef LAB_LOCK +#define NLOCK 500 + +static struct spinlock *locks[NLOCK]; +struct spinlock lock_locks; + +void +freelock(struct spinlock *lk) +{ + acquire(&lock_locks); + int i; + for (i = 0; i < NLOCK; i++) { + if(locks[i] == lk) { + locks[i] = 0; + break; + } + } + release(&lock_locks); +} + +static void +findslot(struct spinlock *lk) { + acquire(&lock_locks); + int i; + for (i = 0; i < NLOCK; i++) { + if(locks[i] == 0) { + locks[i] = lk; + release(&lock_locks); + return; + } + } + panic("findslot"); +} +#endif + void initlock(struct spinlock *lk, char *name) { lk->name = name; lk->locked = 0; lk->cpu = 0; +#ifdef LAB_LOCK + lk->nts = 0; + lk->n = 0; + findslot(lk); +#endif } // Acquire the lock. @@ -25,12 +65,21 @@ acquire(struct spinlock *lk) if(holding(lk)) panic("acquire"); +#ifdef LAB_LOCK + __sync_fetch_and_add(&(lk->n), 1); +#endif + // On RISC-V, sync_lock_test_and_set turns into an atomic swap: // a5 = 1 // s1 = &lk->locked // amoswap.w.aq a5, a5, (s1) - while(__sync_lock_test_and_set(&lk->locked, 1) != 0) - ; + while(__sync_lock_test_and_set(&lk->locked, 1) != 0) { +#ifdef LAB_LOCK + __sync_fetch_and_add(&(lk->nts), 1); +#else + ; +#endif + } // Tell the C compiler and the processor to not move loads or stores // past this point, to ensure that the critical section's memory @@ -108,3 +157,61 @@ pop_off(void) if(c->noff == 0 && c->intena) intr_on(); } + +// Read a shared 32-bit value without holding a lock +int +atomic_read4(int *addr) { + uint32 val; + __atomic_load(addr, &val, __ATOMIC_SEQ_CST); + return val; +} + +#ifdef LAB_LOCK +int +snprint_lock(char *buf, int sz, struct spinlock *lk) +{ + int n = 0; + if(lk->n > 0) { + n = snprintf(buf, sz, "lock: %s: #test-and-set %d #acquire() %d\n", + lk->name, lk->nts, lk->n); + } + return n; +} + +int +statslock(char *buf, int sz) { + int n; + int tot = 0; + + acquire(&lock_locks); + n = snprintf(buf, sz, "--- lock kmem/bcache stats\n"); + for(int i = 0; i < NLOCK; i++) { + if(locks[i] == 0) + break; + if(strncmp(locks[i]->name, "bcache", strlen("bcache")) == 0 || + strncmp(locks[i]->name, "kmem", strlen("kmem")) == 0) { + tot += locks[i]->nts; + n += snprint_lock(buf +n, sz-n, locks[i]); + } + } + + n += snprintf(buf+n, sz-n, "--- top 5 contended locks:\n"); + int last = 100000000; + // stupid way to compute top 5 contended locks + for(int t = 0; t < 5; t++) { + int top = 0; + for(int i = 0; i < NLOCK; i++) { + if(locks[i] == 0) + break; + if(locks[i]->nts > locks[top]->nts && locks[i]->nts < last) { + top = i; + } + } + n += snprint_lock(buf+n, sz-n, locks[top]); + last = locks[top]->nts; + } + n += snprintf(buf+n, sz-n, "tot= %d\n", tot); + release(&lock_locks); + return n; +} +#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/sysfile.c b/kernel/sysfile.c index 16b668c..4b2189a 100644 --- a/kernel/sysfile.c +++ b/kernel/sysfile.c @@ -503,3 +503,29 @@ sys_pipe(void) } return 0; } + + +#ifdef LAB_NET +int +sys_connect(void) +{ + struct file *f; + int fd; + uint32 raddr; + uint32 rport; + uint32 lport; + + argint(0, (int*)&raddr); + argint(1, (int*)&lport); + argint(2, (int*)&rport); + + if(sockalloc(&f, raddr, lport, rport) < 0) + return -1; + if((fd=fdalloc(f)) < 0){ + fileclose(f); + return -1; + } + + return fd; +} +#endif diff --git a/kernel/trap.c b/kernel/trap.c index 512c850..5332dda 100644 --- a/kernel/trap.c +++ b/kernel/trap.c @@ -68,6 +68,8 @@ usertrap(void) } else if((which_dev = devintr()) != 0){ // ok } else { + + printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid); printf(" sepc=%p stval=%p\n", r_sepc(), r_stval()); setkilled(p); @@ -75,6 +77,7 @@ usertrap(void) if(killed(p)) exit(-1); + // give up the CPU if this is a timer interrupt. if(which_dev == 2) @@ -190,7 +193,13 @@ devintr() uartintr(); } else if(irq == VIRTIO0_IRQ){ virtio_disk_intr(); - } else if(irq){ + } +#ifdef LAB_NET + else if(irq == E1000_IRQ){ + e1000_intr(); + } +#endif + else if(irq){ printf("unexpected interrupt irq=%d\n", irq); } diff --git a/kernel/vm.c b/kernel/vm.c index 5c31e87..7119242 100644 --- a/kernel/vm.c +++ b/kernel/vm.c @@ -4,6 +4,8 @@ #include "elf.h" #include "riscv.h" #include "defs.h" +#include "spinlock.h" +#include "proc.h" #include "fs.h" /* @@ -30,6 +32,14 @@ kvmmake(void) // virtio mmio disk interface kvmmap(kpgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W); +#ifdef LAB_NET + // PCI-E ECAM (configuration space), for pci.c + kvmmap(kpgtbl, 0x30000000L, 0x30000000L, 0x10000000, PTE_R | PTE_W); + + // pci.c maps the e1000's registers here. + kvmmap(kpgtbl, 0x40000000L, 0x40000000L, 0x20000, PTE_R | PTE_W); +#endif + // PLIC kvmmap(kpgtbl, PLIC, PLIC, 0x400000, PTE_R | PTE_W); @@ -136,9 +146,8 @@ kvmmap(pagetable_t kpgtbl, uint64 va, uint64 pa, uint64 sz, int perm) } // Create PTEs for virtual addresses starting at va that refer to -// physical addresses starting at pa. -// va and size MUST be page-aligned. -// Returns 0 on success, -1 if walk() couldn't +// physical addresses starting at pa. va and size might not +// be page-aligned. Returns 0 on success, -1 if walk() couldn't // allocate a needed page-table page. int mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm) @@ -146,17 +155,11 @@ mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm) uint64 a, last; pte_t *pte; - if((va % PGSIZE) != 0) - panic("mappages: va not aligned"); - - if((size % PGSIZE) != 0) - panic("mappages: size not aligned"); - if(size == 0) panic("mappages: size"); - a = va; - last = va + size - PGSIZE; + a = PGROUNDDOWN(va); + last = PGROUNDDOWN(va + size - 1); for(;;){ if((pte = walk(pagetable, a, 1)) == 0) return -1; @@ -186,8 +189,10 @@ uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free) for(a = va; a < va + npages*PGSIZE; a += PGSIZE){ if((pte = walk(pagetable, a, 0)) == 0) panic("uvmunmap: walk"); - if((*pte & PTE_V) == 0) + if((*pte & PTE_V) == 0) { + printf("va=%p pte=%p\n", a, *pte); panic("uvmunmap: not mapped"); + } if(PTE_FLAGS(*pte) == PTE_V) panic("uvmunmap: not a leaf"); if(do_free){ @@ -363,13 +368,21 @@ copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) while(len > 0){ va0 = PGROUNDDOWN(dstva); - if(va0 >= MAXVA) + if (va0 >= MAXVA) return -1; - pte = walk(pagetable, va0, 0); - if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0 || - (*pte & PTE_W) == 0) + if((pte = walk(pagetable, va0, 0)) == 0) { + // printf("copyout: pte should exist 0x%x %d\n", dstva, len); + return -1; + } + + + // forbid copyout over read-only user text pages. + if((*pte & PTE_W) == 0) + return -1; + + pa0 = walkaddr(pagetable, va0); + if(pa0 == 0) return -1; - pa0 = PTE2PA(*pte); n = PGSIZE - (dstva - va0); if(n > len) n = len; @@ -389,7 +402,7 @@ int copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len) { uint64 n, va0, pa0; - + while(len > 0){ va0 = PGROUNDDOWN(srcva); pa0 = walkaddr(pagetable, va0); @@ -449,3 +462,6 @@ copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max) return -1; } } + + + 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"); -- cgit v1.2.3 From cfae93475dfb4cb5cfe264f4c029136e1447c262 Mon Sep 17 00:00:00 2001 From: Sanjit Bhat Date: Thu, 26 Oct 2023 06:44:48 -0400 Subject: net add missing files --- conf/lab.mk | 1 + grade-lab-net | 43 ++++ gradelib.py | 628 +++++++++++++++++++++++++++++++++++++++++++++++++++++ kernel/e1000.c | 129 +++++++++++ kernel/e1000_dev.h | 125 +++++++++++ kernel/net.c | 374 +++++++++++++++++++++++++++++++ kernel/net.h | 173 +++++++++++++++ kernel/pci.c | 61 ++++++ kernel/sysnet.c | 185 ++++++++++++++++ ping.py | 12 + server.py | 13 ++ user/nettests.c | 297 +++++++++++++++++++++++++ user/pingpong.c | 52 +++++ 13 files changed, 2093 insertions(+) create mode 100644 conf/lab.mk create mode 100755 grade-lab-net create mode 100644 gradelib.py create mode 100644 kernel/e1000.c create mode 100644 kernel/e1000_dev.h create mode 100644 kernel/net.c create mode 100644 kernel/net.h create mode 100644 kernel/pci.c create mode 100644 kernel/sysnet.c create mode 100644 ping.py create mode 100644 server.py create mode 100644 user/nettests.c create mode 100644 user/pingpong.c diff --git a/conf/lab.mk b/conf/lab.mk new file mode 100644 index 0000000..26dcd75 --- /dev/null +++ b/conf/lab.mk @@ -0,0 +1 @@ +LAB=net diff --git a/grade-lab-net b/grade-lab-net new file mode 100755 index 0000000..dd193e6 --- /dev/null +++ b/grade-lab-net @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +import re +import subprocess +from gradelib import * + +r = Runner(save("xv6.out")) + +@test(0, "running nettests") +def test_nettest(): + server = subprocess.Popen(["make", "server"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + r.run_qemu(shell_script([ + 'nettests' + ]), timeout=30) + server.terminate() + server.communicate() + +@test(40, "nettest: ping", parent=test_nettest) +def test_nettest_(): + r.match('^testing ping: OK$') + +@test(20, "nettest: single process", parent=test_nettest) +def test_nettest_(): + r.match('^testing single-process pings: OK$') + +@test(20, "nettest: multi-process", parent=test_nettest) +def test_nettest_fork_test(): + r.match('^testing multi-process pings: OK$') + +@test(19, "nettest: DNS", parent=test_nettest) +def test_nettest_dns_test(): + r.match('^DNS OK$') + +#@test(10, "answers-net.txt") +#def test_answers(): +# # just a simple sanity check, will be graded manually +# check_answers("answers-net.txt") + +@test(1, "time") +def test_time(): + check_time() + +run_tests() diff --git a/gradelib.py b/gradelib.py new file mode 100644 index 0000000..f0d4934 --- /dev/null +++ b/gradelib.py @@ -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 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: + 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 diff --git a/kernel/e1000.c b/kernel/e1000.c new file mode 100644 index 0000000..70a2adf --- /dev/null +++ b/kernel/e1000.c @@ -0,0 +1,129 @@ +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" +#include "e1000_dev.h" +#include "net.h" + +#define TX_RING_SIZE 16 +static struct tx_desc tx_ring[TX_RING_SIZE] __attribute__((aligned(16))); +static struct mbuf *tx_mbufs[TX_RING_SIZE]; + +#define RX_RING_SIZE 16 +static struct rx_desc rx_ring[RX_RING_SIZE] __attribute__((aligned(16))); +static struct mbuf *rx_mbufs[RX_RING_SIZE]; + +// remember where the e1000's registers live. +static volatile uint32 *regs; + +struct spinlock e1000_lock; + +// called by pci_init(). +// xregs is the memory address at which the +// e1000's registers are mapped. +void +e1000_init(uint32 *xregs) +{ + int i; + + initlock(&e1000_lock, "e1000"); + + regs = xregs; + + // Reset the device + regs[E1000_IMS] = 0; // disable interrupts + regs[E1000_CTL] |= E1000_CTL_RST; + regs[E1000_IMS] = 0; // redisable interrupts + __sync_synchronize(); + + // [E1000 14.5] Transmit initialization + memset(tx_ring, 0, sizeof(tx_ring)); + for (i = 0; i < TX_RING_SIZE; i++) { + tx_ring[i].status = E1000_TXD_STAT_DD; + tx_mbufs[i] = 0; + } + regs[E1000_TDBAL] = (uint64) tx_ring; + if(sizeof(tx_ring) % 128 != 0) + panic("e1000"); + regs[E1000_TDLEN] = sizeof(tx_ring); + regs[E1000_TDH] = regs[E1000_TDT] = 0; + + // [E1000 14.4] Receive initialization + memset(rx_ring, 0, sizeof(rx_ring)); + for (i = 0; i < RX_RING_SIZE; i++) { + rx_mbufs[i] = mbufalloc(0); + if (!rx_mbufs[i]) + panic("e1000"); + rx_ring[i].addr = (uint64) rx_mbufs[i]->head; + } + regs[E1000_RDBAL] = (uint64) rx_ring; + if(sizeof(rx_ring) % 128 != 0) + panic("e1000"); + regs[E1000_RDH] = 0; + regs[E1000_RDT] = RX_RING_SIZE - 1; + regs[E1000_RDLEN] = sizeof(rx_ring); + + // filter by qemu's MAC address, 52:54:00:12:34:56 + regs[E1000_RA] = 0x12005452; + regs[E1000_RA+1] = 0x5634 | (1<<31); + // multicast table + for (int i = 0; i < 4096/32; i++) + regs[E1000_MTA + i] = 0; + + // transmitter control bits. + regs[E1000_TCTL] = E1000_TCTL_EN | // enable + E1000_TCTL_PSP | // pad short packets + (0x10 << E1000_TCTL_CT_SHIFT) | // collision stuff + (0x40 << E1000_TCTL_COLD_SHIFT); + regs[E1000_TIPG] = 10 | (8<<10) | (6<<20); // inter-pkt gap + + // receiver control bits. + regs[E1000_RCTL] = E1000_RCTL_EN | // enable receiver + E1000_RCTL_BAM | // enable broadcast + E1000_RCTL_SZ_2048 | // 2048-byte rx buffers + E1000_RCTL_SECRC; // strip CRC + + // ask e1000 for receive interrupts. + regs[E1000_RDTR] = 0; // interrupt after every received packet (no timer) + regs[E1000_RADV] = 0; // interrupt after every packet (no timer) + regs[E1000_IMS] = (1 << 7); // RXDW -- Receiver Descriptor Write Back +} + +int +e1000_transmit(struct mbuf *m) +{ + // + // Your code here. + // + // the mbuf contains an ethernet frame; program it into + // the TX descriptor ring so that the e1000 sends it. Stash + // a pointer so that it can be freed after sending. + // + + return 0; +} + +static void +e1000_recv(void) +{ + // + // Your code here. + // + // Check for packets that have arrived from the e1000 + // Create and deliver an mbuf for each packet (using net_rx()). + // +} + +void +e1000_intr(void) +{ + // tell the e1000 we've seen this interrupt; + // without this the e1000 won't raise any + // further interrupts. + regs[E1000_ICR] = 0xffffffff; + + e1000_recv(); +} diff --git a/kernel/e1000_dev.h b/kernel/e1000_dev.h new file mode 100644 index 0000000..9b462df --- /dev/null +++ b/kernel/e1000_dev.h @@ -0,0 +1,125 @@ +// +// E1000 hardware definitions: registers and DMA ring format. +// from the Intel 82540EP/EM &c manual. +// + +/* Registers */ +#define E1000_CTL (0x00000/4) /* Device Control Register - RW */ +#define E1000_ICR (0x000C0/4) /* Interrupt Cause Read - R */ +#define E1000_IMS (0x000D0/4) /* Interrupt Mask Set - RW */ +#define E1000_RCTL (0x00100/4) /* RX Control - RW */ +#define E1000_TCTL (0x00400/4) /* TX Control - RW */ +#define E1000_TIPG (0x00410/4) /* TX Inter-packet gap -RW */ +#define E1000_RDBAL (0x02800/4) /* RX Descriptor Base Address Low - RW */ +#define E1000_RDTR (0x02820/4) /* RX Delay Timer */ +#define E1000_RADV (0x0282C/4) /* RX Interrupt Absolute Delay Timer */ +#define E1000_RDH (0x02810/4) /* RX Descriptor Head - RW */ +#define E1000_RDT (0x02818/4) /* RX Descriptor Tail - RW */ +#define E1000_RDLEN (0x02808/4) /* RX Descriptor Length - RW */ +#define E1000_RSRPD (0x02C00/4) /* RX Small Packet Detect Interrupt */ +#define E1000_TDBAL (0x03800/4) /* TX Descriptor Base Address Low - RW */ +#define E1000_TDLEN (0x03808/4) /* TX Descriptor Length - RW */ +#define E1000_TDH (0x03810/4) /* TX Descriptor Head - RW */ +#define E1000_TDT (0x03818/4) /* TX Descripotr Tail - RW */ +#define E1000_MTA (0x05200/4) /* Multicast Table Array - RW Array */ +#define E1000_RA (0x05400/4) /* Receive Address - RW Array */ + +/* Device Control */ +#define E1000_CTL_SLU 0x00000040 /* set link up */ +#define E1000_CTL_FRCSPD 0x00000800 /* force speed */ +#define E1000_CTL_FRCDPLX 0x00001000 /* force duplex */ +#define E1000_CTL_RST 0x00400000 /* full reset */ + +/* Transmit Control */ +#define E1000_TCTL_RST 0x00000001 /* software reset */ +#define E1000_TCTL_EN 0x00000002 /* enable tx */ +#define E1000_TCTL_BCE 0x00000004 /* busy check enable */ +#define E1000_TCTL_PSP 0x00000008 /* pad short packets */ +#define E1000_TCTL_CT 0x00000ff0 /* collision threshold */ +#define E1000_TCTL_CT_SHIFT 4 +#define E1000_TCTL_COLD 0x003ff000 /* collision distance */ +#define E1000_TCTL_COLD_SHIFT 12 +#define E1000_TCTL_SWXOFF 0x00400000 /* SW Xoff transmission */ +#define E1000_TCTL_PBE 0x00800000 /* Packet Burst Enable */ +#define E1000_TCTL_RTLC 0x01000000 /* Re-transmit on late collision */ +#define E1000_TCTL_NRTU 0x02000000 /* No Re-transmit on underrun */ +#define E1000_TCTL_MULR 0x10000000 /* Multiple request support */ + +/* Receive Control */ +#define E1000_RCTL_RST 0x00000001 /* Software reset */ +#define E1000_RCTL_EN 0x00000002 /* enable */ +#define E1000_RCTL_SBP 0x00000004 /* store bad packet */ +#define E1000_RCTL_UPE 0x00000008 /* unicast promiscuous enable */ +#define E1000_RCTL_MPE 0x00000010 /* multicast promiscuous enab */ +#define E1000_RCTL_LPE 0x00000020 /* long packet enable */ +#define E1000_RCTL_LBM_NO 0x00000000 /* no loopback mode */ +#define E1000_RCTL_LBM_MAC 0x00000040 /* MAC loopback mode */ +#define E1000_RCTL_LBM_SLP 0x00000080 /* serial link loopback mode */ +#define E1000_RCTL_LBM_TCVR 0x000000C0 /* tcvr loopback mode */ +#define E1000_RCTL_DTYP_MASK 0x00000C00 /* Descriptor type mask */ +#define E1000_RCTL_DTYP_PS 0x00000400 /* Packet Split descriptor */ +#define E1000_RCTL_RDMTS_HALF 0x00000000 /* rx desc min threshold size */ +#define E1000_RCTL_RDMTS_QUAT 0x00000100 /* rx desc min threshold size */ +#define E1000_RCTL_RDMTS_EIGTH 0x00000200 /* rx desc min threshold size */ +#define E1000_RCTL_MO_SHIFT 12 /* multicast offset shift */ +#define E1000_RCTL_MO_0 0x00000000 /* multicast offset 11:0 */ +#define E1000_RCTL_MO_1 0x00001000 /* multicast offset 12:1 */ +#define E1000_RCTL_MO_2 0x00002000 /* multicast offset 13:2 */ +#define E1000_RCTL_MO_3 0x00003000 /* multicast offset 15:4 */ +#define E1000_RCTL_MDR 0x00004000 /* multicast desc ring 0 */ +#define E1000_RCTL_BAM 0x00008000 /* broadcast enable */ +/* these buffer sizes are valid if E1000_RCTL_BSEX is 0 */ +#define E1000_RCTL_SZ_2048 0x00000000 /* rx buffer size 2048 */ +#define E1000_RCTL_SZ_1024 0x00010000 /* rx buffer size 1024 */ +#define E1000_RCTL_SZ_512 0x00020000 /* rx buffer size 512 */ +#define E1000_RCTL_SZ_256 0x00030000 /* rx buffer size 256 */ +/* these buffer sizes are valid if E1000_RCTL_BSEX is 1 */ +#define E1000_RCTL_SZ_16384 0x00010000 /* rx buffer size 16384 */ +#define E1000_RCTL_SZ_8192 0x00020000 /* rx buffer size 8192 */ +#define E1000_RCTL_SZ_4096 0x00030000 /* rx buffer size 4096 */ +#define E1000_RCTL_VFE 0x00040000 /* vlan filter enable */ +#define E1000_RCTL_CFIEN 0x00080000 /* canonical form enable */ +#define E1000_RCTL_CFI 0x00100000 /* canonical form indicator */ +#define E1000_RCTL_DPF 0x00400000 /* discard pause frames */ +#define E1000_RCTL_PMCF 0x00800000 /* pass MAC control frames */ +#define E1000_RCTL_BSEX 0x02000000 /* Buffer size extension */ +#define E1000_RCTL_SECRC 0x04000000 /* Strip Ethernet CRC */ +#define E1000_RCTL_FLXBUF_MASK 0x78000000 /* Flexible buffer size */ +#define E1000_RCTL_FLXBUF_SHIFT 27 /* Flexible buffer shift */ + +#define DATA_MAX 1518 + +/* Transmit Descriptor command definitions [E1000 3.3.3.1] */ +#define E1000_TXD_CMD_EOP 0x01 /* End of Packet */ +#define E1000_TXD_CMD_RS 0x08 /* Report Status */ + +/* Transmit Descriptor status definitions [E1000 3.3.3.2] */ +#define E1000_TXD_STAT_DD 0x00000001 /* Descriptor Done */ + +// [E1000 3.3.3] +struct tx_desc +{ + uint64 addr; + uint16 length; + uint8 cso; + uint8 cmd; + uint8 status; + uint8 css; + uint16 special; +}; + +/* Receive Descriptor bit definitions [E1000 3.2.3.1] */ +#define E1000_RXD_STAT_DD 0x01 /* Descriptor Done */ +#define E1000_RXD_STAT_EOP 0x02 /* End of Packet */ + +// [E1000 3.2.3] +struct rx_desc +{ + uint64 addr; /* Address of the descriptor's data buffer */ + uint16 length; /* Length of data DMAed into data buffer */ + uint16 csum; /* Packet checksum */ + uint8 status; /* Descriptor status */ + uint8 errors; /* Descriptor Errors */ + uint16 special; +}; + diff --git a/kernel/net.c b/kernel/net.c new file mode 100644 index 0000000..137ea2b --- /dev/null +++ b/kernel/net.c @@ -0,0 +1,374 @@ +// +// networking protocol support (IP, UDP, ARP, etc.). +// + +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "net.h" +#include "defs.h" + +static uint32 local_ip = MAKE_IP_ADDR(10, 0, 2, 15); // qemu's idea of the guest IP +static uint8 local_mac[ETHADDR_LEN] = { 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 }; +static uint8 broadcast_mac[ETHADDR_LEN] = { 0xFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF }; + +// Strips data from the start of the buffer and returns a pointer to it. +// Returns 0 if less than the full requested length is available. +char * +mbufpull(struct mbuf *m, unsigned int len) +{ + char *tmp = m->head; + if (m->len < len) + return 0; + m->len -= len; + m->head += len; + return tmp; +} + +// Prepends data to the beginning of the buffer and returns a pointer to it. +char * +mbufpush(struct mbuf *m, unsigned int len) +{ + m->head -= len; + if (m->head < m->buf) + panic("mbufpush"); + m->len += len; + return m->head; +} + +// Appends data to the end of the buffer and returns a pointer to it. +char * +mbufput(struct mbuf *m, unsigned int len) +{ + char *tmp = m->head + m->len; + m->len += len; + if (m->len > MBUF_SIZE) + panic("mbufput"); + return tmp; +} + +// Strips data from the end of the buffer and returns a pointer to it. +// Returns 0 if less than the full requested length is available. +char * +mbuftrim(struct mbuf *m, unsigned int len) +{ + if (len > m->len) + return 0; + m->len -= len; + return m->head + m->len; +} + +// Allocates a packet buffer. +struct mbuf * +mbufalloc(unsigned int headroom) +{ + struct mbuf *m; + + if (headroom > MBUF_SIZE) + return 0; + m = kalloc(); + if (m == 0) + return 0; + m->next = 0; + m->head = (char *)m->buf + headroom; + m->len = 0; + memset(m->buf, 0, sizeof(m->buf)); + return m; +} + +// Frees a packet buffer. +void +mbuffree(struct mbuf *m) +{ + kfree(m); +} + +// Pushes an mbuf to the end of the queue. +void +mbufq_pushtail(struct mbufq *q, struct mbuf *m) +{ + m->next = 0; + if (!q->head){ + q->head = q->tail = m; + return; + } + q->tail->next = m; + q->tail = m; +} + +// Pops an mbuf from the start of the queue. +struct mbuf * +mbufq_pophead(struct mbufq *q) +{ + struct mbuf *head = q->head; + if (!head) + return 0; + q->head = head->next; + return head; +} + +// Returns one (nonzero) if the queue is empty. +int +mbufq_empty(struct mbufq *q) +{ + return q->head == 0; +} + +// Intializes a queue of mbufs. +void +mbufq_init(struct mbufq *q) +{ + q->head = 0; +} + +// This code is lifted from FreeBSD's ping.c, and is copyright by the Regents +// of the University of California. +static unsigned short +in_cksum(const unsigned char *addr, int len) +{ + int nleft = len; + const unsigned short *w = (const unsigned short *)addr; + unsigned int sum = 0; + unsigned short answer = 0; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), we add + * sequential 16 bit words to it, and at the end, fold back all the + * carry bits from the top 16 bits into the lower 16 bits. + */ + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if (nleft == 1) { + *(unsigned char *)(&answer) = *(const unsigned char *)w; + sum += answer; + } + + /* add back carry outs from top 16 bits to low 16 bits */ + sum = (sum & 0xffff) + (sum >> 16); + sum += (sum >> 16); + /* guaranteed now that the lower 16 bits of sum are correct */ + + answer = ~sum; /* truncate to 16 bits */ + return answer; +} + +// sends an ethernet packet +static void +net_tx_eth(struct mbuf *m, uint16 ethtype) +{ + struct eth *ethhdr; + + ethhdr = mbufpushhdr(m, *ethhdr); + memmove(ethhdr->shost, local_mac, ETHADDR_LEN); + // In a real networking stack, dhost would be set to the address discovered + // through ARP. Because we don't support enough of the ARP protocol, set it + // to broadcast instead. + memmove(ethhdr->dhost, broadcast_mac, ETHADDR_LEN); + ethhdr->type = htons(ethtype); + if (e1000_transmit(m)) { + mbuffree(m); + } +} + +// sends an IP packet +static void +net_tx_ip(struct mbuf *m, uint8 proto, uint32 dip) +{ + struct ip *iphdr; + + // push the IP header + iphdr = mbufpushhdr(m, *iphdr); + memset(iphdr, 0, sizeof(*iphdr)); + iphdr->ip_vhl = (4 << 4) | (20 >> 2); + iphdr->ip_p = proto; + iphdr->ip_src = htonl(local_ip); + iphdr->ip_dst = htonl(dip); + iphdr->ip_len = htons(m->len); + iphdr->ip_ttl = 100; + iphdr->ip_sum = in_cksum((unsigned char *)iphdr, sizeof(*iphdr)); + + // now on to the ethernet layer + net_tx_eth(m, ETHTYPE_IP); +} + +// sends a UDP packet +void +net_tx_udp(struct mbuf *m, uint32 dip, + uint16 sport, uint16 dport) +{ + struct udp *udphdr; + + // put the UDP header + udphdr = mbufpushhdr(m, *udphdr); + udphdr->sport = htons(sport); + udphdr->dport = htons(dport); + udphdr->ulen = htons(m->len); + udphdr->sum = 0; // zero means no checksum is provided + + // now on to the IP layer + net_tx_ip(m, IPPROTO_UDP, dip); +} + +// sends an ARP packet +static int +net_tx_arp(uint16 op, uint8 dmac[ETHADDR_LEN], uint32 dip) +{ + struct mbuf *m; + struct arp *arphdr; + + m = mbufalloc(MBUF_DEFAULT_HEADROOM); + if (!m) + return -1; + + // generic part of ARP header + arphdr = mbufputhdr(m, *arphdr); + arphdr->hrd = htons(ARP_HRD_ETHER); + arphdr->pro = htons(ETHTYPE_IP); + arphdr->hln = ETHADDR_LEN; + arphdr->pln = sizeof(uint32); + arphdr->op = htons(op); + + // ethernet + IP part of ARP header + memmove(arphdr->sha, local_mac, ETHADDR_LEN); + arphdr->sip = htonl(local_ip); + memmove(arphdr->tha, dmac, ETHADDR_LEN); + arphdr->tip = htonl(dip); + + // header is ready, send the packet + net_tx_eth(m, ETHTYPE_ARP); + return 0; +} + +// receives an ARP packet +static void +net_rx_arp(struct mbuf *m) +{ + struct arp *arphdr; + uint8 smac[ETHADDR_LEN]; + uint32 sip, tip; + + arphdr = mbufpullhdr(m, *arphdr); + if (!arphdr) + goto done; + + // validate the ARP header + if (ntohs(arphdr->hrd) != ARP_HRD_ETHER || + ntohs(arphdr->pro) != ETHTYPE_IP || + arphdr->hln != ETHADDR_LEN || + arphdr->pln != sizeof(uint32)) { + goto done; + } + + // only requests are supported so far + // check if our IP was solicited + tip = ntohl(arphdr->tip); // target IP address + if (ntohs(arphdr->op) != ARP_OP_REQUEST || tip != local_ip) + goto done; + + // handle the ARP request + memmove(smac, arphdr->sha, ETHADDR_LEN); // sender's ethernet address + sip = ntohl(arphdr->sip); // sender's IP address (qemu's slirp) + net_tx_arp(ARP_OP_REPLY, smac, sip); + +done: + mbuffree(m); +} + +// receives a UDP packet +static void +net_rx_udp(struct mbuf *m, uint16 len, struct ip *iphdr) +{ + struct udp *udphdr; + uint32 sip; + uint16 sport, dport; + + + udphdr = mbufpullhdr(m, *udphdr); + if (!udphdr) + goto fail; + + // TODO: validate UDP checksum + + // validate lengths reported in headers + if (ntohs(udphdr->ulen) != len) + goto fail; + len -= sizeof(*udphdr); + if (len > m->len) + goto fail; + // minimum packet size could be larger than the payload + mbuftrim(m, m->len - len); + + // parse the necessary fields + sip = ntohl(iphdr->ip_src); + sport = ntohs(udphdr->sport); + dport = ntohs(udphdr->dport); + sockrecvudp(m, sip, dport, sport); + return; + +fail: + mbuffree(m); +} + +// receives an IP packet +static void +net_rx_ip(struct mbuf *m) +{ + struct ip *iphdr; + uint16 len; + + iphdr = mbufpullhdr(m, *iphdr); + if (!iphdr) + goto fail; + + // check IP version and header len + if (iphdr->ip_vhl != ((4 << 4) | (20 >> 2))) + goto fail; + // validate IP checksum + if (in_cksum((unsigned char *)iphdr, sizeof(*iphdr))) + goto fail; + // can't support fragmented IP packets + if (htons(iphdr->ip_off) != 0) + goto fail; + // is the packet addressed to us? + if (htonl(iphdr->ip_dst) != local_ip) + goto fail; + // can only support UDP + if (iphdr->ip_p != IPPROTO_UDP) + goto fail; + + len = ntohs(iphdr->ip_len) - sizeof(*iphdr); + net_rx_udp(m, len, iphdr); + return; + +fail: + mbuffree(m); +} + +// called by e1000 driver's interrupt handler to deliver a packet to the +// networking stack +void net_rx(struct mbuf *m) +{ + struct eth *ethhdr; + uint16 type; + + ethhdr = mbufpullhdr(m, *ethhdr); + if (!ethhdr) { + mbuffree(m); + return; + } + + type = ntohs(ethhdr->type); + if (type == ETHTYPE_IP) + net_rx_ip(m); + else if (type == ETHTYPE_ARP) + net_rx_arp(m); + else + mbuffree(m); +} diff --git a/kernel/net.h b/kernel/net.h new file mode 100644 index 0000000..9e6fefe --- /dev/null +++ b/kernel/net.h @@ -0,0 +1,173 @@ +// +// packet buffer management +// + +#define MBUF_SIZE 2048 +#define MBUF_DEFAULT_HEADROOM 128 + +struct mbuf { + struct mbuf *next; // the next mbuf in the chain + char *head; // the current start position of the buffer + unsigned int len; // the length of the buffer + char buf[MBUF_SIZE]; // the backing store +}; + +char *mbufpull(struct mbuf *m, unsigned int len); +char *mbufpush(struct mbuf *m, unsigned int len); +char *mbufput(struct mbuf *m, unsigned int len); +char *mbuftrim(struct mbuf *m, unsigned int len); + +// The above functions manipulate the size and position of the buffer: +// <- push <- trim +// -> pull -> put +// [-headroom-][------buffer------][-tailroom-] +// |----------------MBUF_SIZE-----------------| +// +// These marcos automatically typecast and determine the size of header structs. +// In most situations you should use these instead of the raw ops above. +#define mbufpullhdr(mbuf, hdr) (typeof(hdr)*)mbufpull(mbuf, sizeof(hdr)) +#define mbufpushhdr(mbuf, hdr) (typeof(hdr)*)mbufpush(mbuf, sizeof(hdr)) +#define mbufputhdr(mbuf, hdr) (typeof(hdr)*)mbufput(mbuf, sizeof(hdr)) +#define mbuftrimhdr(mbuf, hdr) (typeof(hdr)*)mbuftrim(mbuf, sizeof(hdr)) + +struct mbuf *mbufalloc(unsigned int headroom); +void mbuffree(struct mbuf *m); + +struct mbufq { + struct mbuf *head; // the first element in the queue + struct mbuf *tail; // the last element in the queue +}; + +void mbufq_pushtail(struct mbufq *q, struct mbuf *m); +struct mbuf *mbufq_pophead(struct mbufq *q); +int mbufq_empty(struct mbufq *q); +void mbufq_init(struct mbufq *q); + + +// +// endianness support +// + +static inline uint16 bswaps(uint16 val) +{ + return (((val & 0x00ffU) << 8) | + ((val & 0xff00U) >> 8)); +} + +static inline uint32 bswapl(uint32 val) +{ + return (((val & 0x000000ffUL) << 24) | + ((val & 0x0000ff00UL) << 8) | + ((val & 0x00ff0000UL) >> 8) | + ((val & 0xff000000UL) >> 24)); +} + +// Use these macros to convert network bytes to the native byte order. +// Note that Risc-V uses little endian while network order is big endian. +#define ntohs bswaps +#define ntohl bswapl +#define htons bswaps +#define htonl bswapl + + +// +// useful networking headers +// + +#define ETHADDR_LEN 6 + +// an Ethernet packet header (start of the packet). +struct eth { + uint8 dhost[ETHADDR_LEN]; + uint8 shost[ETHADDR_LEN]; + uint16 type; +} __attribute__((packed)); + +#define ETHTYPE_IP 0x0800 // Internet protocol +#define ETHTYPE_ARP 0x0806 // Address resolution protocol + +// an IP packet header (comes after an Ethernet header). +struct ip { + uint8 ip_vhl; // version << 4 | header length >> 2 + uint8 ip_tos; // type of service + uint16 ip_len; // total length + uint16 ip_id; // identification + uint16 ip_off; // fragment offset field + uint8 ip_ttl; // time to live + uint8 ip_p; // protocol + uint16 ip_sum; // checksum + uint32 ip_src, ip_dst; +}; + +#define IPPROTO_ICMP 1 // Control message protocol +#define IPPROTO_TCP 6 // Transmission control protocol +#define IPPROTO_UDP 17 // User datagram protocol + +#define MAKE_IP_ADDR(a, b, c, d) \ + (((uint32)a << 24) | ((uint32)b << 16) | \ + ((uint32)c << 8) | (uint32)d) + +// a UDP packet header (comes after an IP header). +struct udp { + uint16 sport; // source port + uint16 dport; // destination port + uint16 ulen; // length, including udp header, not including IP header + uint16 sum; // checksum +}; + +// an ARP packet (comes after an Ethernet header). +struct arp { + uint16 hrd; // format of hardware address + uint16 pro; // format of protocol address + uint8 hln; // length of hardware address + uint8 pln; // length of protocol address + uint16 op; // operation + + char sha[ETHADDR_LEN]; // sender hardware address + uint32 sip; // sender IP address + char tha[ETHADDR_LEN]; // target hardware address + uint32 tip; // target IP address +} __attribute__((packed)); + +#define ARP_HRD_ETHER 1 // Ethernet + +enum { + ARP_OP_REQUEST = 1, // requests hw addr given protocol addr + ARP_OP_REPLY = 2, // replies a hw addr given protocol addr +}; + +// an DNS packet (comes after an UDP header). +struct dns { + uint16 id; // request ID + + uint8 rd: 1; // recursion desired + uint8 tc: 1; // truncated + uint8 aa: 1; // authoritive + uint8 opcode: 4; + uint8 qr: 1; // query/response + uint8 rcode: 4; // response code + uint8 cd: 1; // checking disabled + uint8 ad: 1; // authenticated data + uint8 z: 1; + uint8 ra: 1; // recursion available + + uint16 qdcount; // number of question entries + uint16 ancount; // number of resource records in answer section + uint16 nscount; // number of NS resource records in authority section + uint16 arcount; // number of resource records in additional records +} __attribute__((packed)); + +struct dns_question { + uint16 qtype; + uint16 qclass; +} __attribute__((packed)); + +#define ARECORD (0x0001) +#define QCLASS (0x0001) + +struct dns_data { + uint16 type; + uint16 class; + uint32 ttl; + uint16 len; +} __attribute__((packed)); diff --git a/kernel/pci.c b/kernel/pci.c new file mode 100644 index 0000000..5cd2102 --- /dev/null +++ b/kernel/pci.c @@ -0,0 +1,61 @@ +// +// simple PCI-Express initialization, only +// works for qemu and its e1000 card. +// + +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" + +void +pci_init() +{ + // we'll place the e1000 registers at this address. + // vm.c maps this range. + uint64 e1000_regs = 0x40000000L; + + // qemu -machine virt puts PCIe config space here. + // vm.c maps this range. + uint32 *ecam = (uint32 *) 0x30000000L; + + // look at each possible PCI device on bus 0. + for(int dev = 0; dev < 32; dev++){ + int bus = 0; + int func = 0; + int offset = 0; + uint32 off = (bus << 16) | (dev << 11) | (func << 8) | (offset); + volatile uint32 *base = ecam + off; + uint32 id = base[0]; + + // 100e:8086 is an e1000 + if(id == 0x100e8086){ + // command and status register. + // bit 0 : I/O access enable + // bit 1 : memory access enable + // bit 2 : enable mastering + base[1] = 7; + __sync_synchronize(); + + for(int i = 0; i < 6; i++){ + uint32 old = base[4+i]; + + // writing all 1's to the BAR causes it to be + // replaced with its size. + base[4+i] = 0xffffffff; + __sync_synchronize(); + + base[4+i] = old; + } + + // tell the e1000 to reveal its registers at + // physical address 0x40000000. + base[4+0] = e1000_regs; + + e1000_init((uint32*)e1000_regs); + } + } +} diff --git a/kernel/sysnet.c b/kernel/sysnet.c new file mode 100644 index 0000000..1c48cb3 --- /dev/null +++ b/kernel/sysnet.c @@ -0,0 +1,185 @@ +// +// network system calls. +// + +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" +#include "fs.h" +#include "sleeplock.h" +#include "file.h" +#include "net.h" + +struct sock { + struct sock *next; // the next socket in the list + uint32 raddr; // the remote IPv4 address + uint16 lport; // the local UDP port number + uint16 rport; // the remote UDP port number + struct spinlock lock; // protects the rxq + struct mbufq rxq; // a queue of packets waiting to be received +}; + +static struct spinlock lock; +static struct sock *sockets; + +void +sockinit(void) +{ + initlock(&lock, "socktbl"); +} + +int +sockalloc(struct file **f, uint32 raddr, uint16 lport, uint16 rport) +{ + struct sock *si, *pos; + + si = 0; + *f = 0; + if ((*f = filealloc()) == 0) + goto bad; + if ((si = (struct sock*)kalloc()) == 0) + goto bad; + + // initialize objects + si->raddr = raddr; + si->lport = lport; + si->rport = rport; + initlock(&si->lock, "sock"); + mbufq_init(&si->rxq); + (*f)->type = FD_SOCK; + (*f)->readable = 1; + (*f)->writable = 1; + (*f)->sock = si; + + // add to list of sockets + acquire(&lock); + pos = sockets; + while (pos) { + if (pos->raddr == raddr && + pos->lport == lport && + pos->rport == rport) { + release(&lock); + goto bad; + } + pos = pos->next; + } + si->next = sockets; + sockets = si; + release(&lock); + return 0; + +bad: + if (si) + kfree((char*)si); + if (*f) + fileclose(*f); + return -1; +} + +void +sockclose(struct sock *si) +{ + struct sock **pos; + struct mbuf *m; + + // remove from list of sockets + acquire(&lock); + pos = &sockets; + while (*pos) { + if (*pos == si){ + *pos = si->next; + break; + } + pos = &(*pos)->next; + } + release(&lock); + + // free any pending mbufs + while (!mbufq_empty(&si->rxq)) { + m = mbufq_pophead(&si->rxq); + mbuffree(m); + } + + kfree((char*)si); +} + +int +sockread(struct sock *si, uint64 addr, int n) +{ + struct proc *pr = myproc(); + struct mbuf *m; + int len; + + acquire(&si->lock); + while (mbufq_empty(&si->rxq) && !pr->killed) { + sleep(&si->rxq, &si->lock); + } + if (pr->killed) { + release(&si->lock); + return -1; + } + m = mbufq_pophead(&si->rxq); + release(&si->lock); + + len = m->len; + if (len > n) + len = n; + if (copyout(pr->pagetable, addr, m->head, len) == -1) { + mbuffree(m); + return -1; + } + mbuffree(m); + return len; +} + +int +sockwrite(struct sock *si, uint64 addr, int n) +{ + struct proc *pr = myproc(); + struct mbuf *m; + + m = mbufalloc(MBUF_DEFAULT_HEADROOM); + if (!m) + return -1; + + if (copyin(pr->pagetable, mbufput(m, n), addr, n) == -1) { + mbuffree(m); + return -1; + } + net_tx_udp(m, si->raddr, si->lport, si->rport); + return n; +} + +// called by protocol handler layer to deliver UDP packets +void +sockrecvudp(struct mbuf *m, uint32 raddr, uint16 lport, uint16 rport) +{ + // + // Find the socket that handles this mbuf and deliver it, waking + // any sleeping reader. Free the mbuf if there are no sockets + // registered to handle it. + // + struct sock *si; + + acquire(&lock); + si = sockets; + while (si) { + if (si->raddr == raddr && si->lport == lport && si->rport == rport) + goto found; + si = si->next; + } + release(&lock); + mbuffree(m); + return; + +found: + acquire(&si->lock); + mbufq_pushtail(&si->rxq, m); + wakeup(&si->rxq); + release(&si->lock); + release(&lock); +} diff --git a/ping.py b/ping.py new file mode 100644 index 0000000..eab10f8 --- /dev/null +++ b/ping.py @@ -0,0 +1,12 @@ +import socket +import sys +import time + +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +addr = ('localhost', int(sys.argv[1])) +buf = "this is a ping!".encode('utf-8') + +while True: + print("pinging...", file=sys.stderr) + sock.sendto(buf, ("127.0.0.1", int(sys.argv[1]))) + time.sleep(1) diff --git a/server.py b/server.py new file mode 100644 index 0000000..2421c31 --- /dev/null +++ b/server.py @@ -0,0 +1,13 @@ +import socket +import sys + +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +addr = ('localhost', int(sys.argv[1])) +print('listening on %s port %s' % addr, file=sys.stderr) +sock.bind(addr) + +while True: + buf, raddr = sock.recvfrom(4096) + print(buf.decode("utf-8"), file=sys.stderr) + if buf: + sent = sock.sendto(b'this is the host!', raddr) diff --git a/user/nettests.c b/user/nettests.c new file mode 100644 index 0000000..2f7d6cd --- /dev/null +++ b/user/nettests.c @@ -0,0 +1,297 @@ +#include "kernel/types.h" +#include "kernel/net.h" +#include "kernel/stat.h" +#include "user/user.h" + +// +// send a UDP packet to the localhost (outside of qemu), +// and receive a response. +// +static void +ping(uint16 sport, uint16 dport, int attempts) +{ + int fd; + char *obuf = "a message from xv6!"; + uint32 dst; + + // 10.0.2.2, which qemu remaps to the external host, + // i.e. the machine you're running qemu on. + dst = (10 << 24) | (0 << 16) | (2 << 8) | (2 << 0); + + // you can send a UDP packet to any Internet address + // by using a different dst. + + if((fd = connect(dst, sport, dport)) < 0){ + fprintf(2, "ping: connect() failed\n"); + exit(1); + } + + for(int i = 0; i < attempts; i++) { + if(write(fd, obuf, strlen(obuf)) < 0){ + fprintf(2, "ping: send() failed\n"); + exit(1); + } + } + + char ibuf[128]; + int cc = read(fd, ibuf, sizeof(ibuf)-1); + if(cc < 0){ + fprintf(2, "ping: recv() failed\n"); + exit(1); + } + + close(fd); + ibuf[cc] = '\0'; + if(strcmp(ibuf, "this is the host!") != 0){ + fprintf(2, "ping didn't receive correct payload\n"); + exit(1); + } +} + +// Encode a DNS name +static void +encode_qname(char *qn, char *host) +{ + char *l = host; + + for(char *c = host; c < host+strlen(host)+1; c++) { + if(*c == '.') { + *qn++ = (char) (c-l); + for(char *d = l; d < c; d++) { + *qn++ = *d; + } + l = c+1; // skip . + } + } + *qn = '\0'; +} + +// Decode a DNS name +static void +decode_qname(char *qn, int max) +{ + char *qnMax = qn + max; + while(1){ + if(qn >= qnMax){ + printf("invalid DNS reply\n"); + exit(1); + } + int l = *qn; + if(l == 0) + break; + for(int i = 0; i < l; i++) { + *qn = *(qn+1); + qn++; + } + *qn++ = '.'; + } +} + +// Make a DNS request +static int +dns_req(uint8 *obuf) +{ + int len = 0; + + struct dns *hdr = (struct dns *) obuf; + hdr->id = htons(6828); + hdr->rd = 1; + hdr->qdcount = htons(1); + + len += sizeof(struct dns); + + // qname part of question + char *qname = (char *) (obuf + sizeof(struct dns)); + char *s = "pdos.csail.mit.edu."; + encode_qname(qname, s); + len += strlen(qname) + 1; + + // constants part of question + struct dns_question *h = (struct dns_question *) (qname+strlen(qname)+1); + h->qtype = htons(0x1); + h->qclass = htons(0x1); + + len += sizeof(struct dns_question); + return len; +} + +// Process DNS response +static void +dns_rep(uint8 *ibuf, int cc) +{ + struct dns *hdr = (struct dns *) ibuf; + int len; + char *qname = 0; + int record = 0; + + if(cc < sizeof(struct dns)){ + printf("DNS reply too short\n"); + exit(1); + } + + if(!hdr->qr) { + printf("Not a DNS reply for %d\n", ntohs(hdr->id)); + exit(1); + } + + if(hdr->id != htons(6828)){ + printf("DNS wrong id: %d\n", ntohs(hdr->id)); + exit(1); + } + + if(hdr->rcode != 0) { + printf("DNS rcode error: %x\n", hdr->rcode); + exit(1); + } + + //printf("qdcount: %x\n", ntohs(hdr->qdcount)); + //printf("ancount: %x\n", ntohs(hdr->ancount)); + //printf("nscount: %x\n", ntohs(hdr->nscount)); + //printf("arcount: %x\n", ntohs(hdr->arcount)); + + len = sizeof(struct dns); + + for(int i =0; i < ntohs(hdr->qdcount); i++) { + char *qn = (char *) (ibuf+len); + qname = qn; + decode_qname(qn, cc - len); + len += strlen(qn)+1; + len += sizeof(struct dns_question); + } + + for(int i = 0; i < ntohs(hdr->ancount); i++) { + if(len >= cc){ + printf("invalid DNS reply\n"); + exit(1); + } + + char *qn = (char *) (ibuf+len); + + if((int) qn[0] > 63) { // compression? + qn = (char *)(ibuf+qn[1]); + len += 2; + } else { + decode_qname(qn, cc - len); + len += strlen(qn)+1; + } + + struct dns_data *d = (struct dns_data *) (ibuf+len); + len += sizeof(struct dns_data); + //printf("type %d ttl %d len %d\n", ntohs(d->type), ntohl(d->ttl), ntohs(d->len)); + if(ntohs(d->type) == ARECORD && ntohs(d->len) == 4) { + record = 1; + printf("DNS arecord for %s is ", qname ? qname : "" ); + uint8 *ip = (ibuf+len); + printf("%d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]); + if(ip[0] != 128 || ip[1] != 52 || ip[2] != 129 || ip[3] != 126) { + printf("wrong ip address"); + exit(1); + } + len += 4; + } + } + + // needed for DNS servers with EDNS support + for(int i = 0; i < ntohs(hdr->arcount); i++) { + char *qn = (char *) (ibuf+len); + if(*qn != 0) { + printf("invalid name for EDNS\n"); + exit(1); + } + len += 1; + + struct dns_data *d = (struct dns_data *) (ibuf+len); + len += sizeof(struct dns_data); + if(ntohs(d->type) != 41) { + printf("invalid type for EDNS\n"); + exit(1); + } + len += ntohs(d->len); + } + + if(len != cc) { + printf("Processed %d data bytes but received %d\n", len, cc); + exit(1); + } + if(!record) { + printf("Didn't receive an arecord\n"); + exit(1); + } +} + +static void +dns() +{ + #define N 1000 + uint8 obuf[N]; + uint8 ibuf[N]; + uint32 dst; + int fd; + int len; + + memset(obuf, 0, N); + memset(ibuf, 0, N); + + // 8.8.8.8: google's name server + dst = (8 << 24) | (8 << 16) | (8 << 8) | (8 << 0); + + if((fd = connect(dst, 10000, 53)) < 0){ + fprintf(2, "ping: connect() failed\n"); + exit(1); + } + + len = dns_req(obuf); + + if(write(fd, obuf, len) < 0){ + fprintf(2, "dns: send() failed\n"); + exit(1); + } + int cc = read(fd, ibuf, sizeof(ibuf)); + if(cc < 0){ + fprintf(2, "dns: recv() failed\n"); + exit(1); + } + dns_rep(ibuf, cc); + + close(fd); +} + +int +main(int argc, char *argv[]) +{ + int i, ret; + uint16 dport = NET_TESTS_PORT; + + printf("nettests running on port %d\n", dport); + + printf("testing ping: "); + ping(2000, dport, 1); + printf("OK\n"); + + printf("testing single-process pings: "); + for (i = 0; i < 100; i++) + ping(2000, dport, 1); + printf("OK\n"); + + printf("testing multi-process pings: "); + for (i = 0; i < 10; i++){ + int pid = fork(); + if (pid == 0){ + ping(2000 + i + 1, dport, 1); + exit(0); + } + } + for (i = 0; i < 10; i++){ + wait(&ret); + if (ret != 0) + exit(1); + } + printf("OK\n"); + + printf("testing DNS\n"); + dns(); + printf("DNS OK\n"); + + printf("all tests passed.\n"); + exit(0); +} diff --git a/user/pingpong.c b/user/pingpong.c new file mode 100644 index 0000000..6ed12e7 --- /dev/null +++ b/user/pingpong.c @@ -0,0 +1,52 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +#define N 5 +char buf[N]; + +void +pong(int *parent_to_child, int *child_to_parent) { + if (read(parent_to_child[0], buf, N) < 0) { + printf("read failed\n"); + } + printf("%d: received %s\n", getpid(), buf); + if (write(child_to_parent[1], "pong", 4) != 4) { + printf("write failed\n"); + } +} + +void +ping(int *parent_to_child, int *child_to_parent) { + + if (write(parent_to_child[1], "ping", 4) != 4) { + printf("write failed\n"); + } + if (read(child_to_parent[0], buf, N) < 0) { + printf("read failed\n"); + } + printf("%d: received %s\n", getpid(), buf); +} + +int +main(int argc, char *argv[]) +{ + int parent_to_child[2]; + int child_to_parent[2]; + + int pid; + + if (pipe(parent_to_child) < 0 || pipe(child_to_parent) < 0) { + printf("pipe failed\n"); + } + if ((pid = fork()) < 0) { + printf("fork failed\n"); + } + if (pid == 0) { + pong(parent_to_child, child_to_parent); + } else { + ping(parent_to_child, child_to_parent); + } + + exit(0); +} -- cgit v1.2.3 From b9af3ad894701e8805dd9f63e646487a7f09f9f3 Mon Sep 17 00:00:00 2001 From: Mole Shang <135e2@135e2.dev> Date: Wed, 17 Jan 2024 11:33:12 +0800 Subject: chore: add clang-format config --- .clang-format | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 2 + 2 files changed, 227 insertions(+) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..065dcdf --- /dev/null +++ b/.clang-format @@ -0,0 +1,225 @@ +--- +Language: Cpp +# BasedOnStyle: Mozilla +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: TopLevel +AlwaysBreakAfterReturnType: TopLevel +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: false +BinPackParameters: false +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: Never + AfterEnum: true + AfterExternBlock: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: true + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: false + SplitEmptyNamespace: true +BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Mozilla +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: true +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: false +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... + diff --git a/.gitignore b/.gitignore index 5c33438..1fe0374 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ ph barrier /lab-*.json .DS_Store +riscv/ +compile_flags.txt -- cgit v1.2.3 From 0d6a64fa06ce6aae729fa05a539eadd88fa59007 Mon Sep 17 00:00:00 2001 From: Mole Shang <135e2@135e2.dev> Date: Wed, 17 Jan 2024 11:33:39 +0800 Subject: lab util: finish --- Makefile | 5 ++++ user/find.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ user/pingpong.c | 44 ++++++++++++++++++++++++++++++ user/primes.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ user/sh.c | 3 +++ user/sleep.c | 22 +++++++++++++++ user/xargs.c | 60 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 292 insertions(+) create mode 100644 user/find.c create mode 100644 user/pingpong.c create mode 100644 user/primes.c create mode 100644 user/sleep.c create mode 100644 user/xargs.c diff --git a/Makefile b/Makefile index 365c91b..4f3fd54 100644 --- a/Makefile +++ b/Makefile @@ -188,6 +188,11 @@ UPROGS=\ $U/_grind\ $U/_wc\ $U/_zombie\ + $U/_sleep\ + $U/_pingpong\ + $U/_primes\ + $U/_find\ + $U/_xargs\ diff --git a/user/find.c b/user/find.c new file mode 100644 index 0000000..e185e9d --- /dev/null +++ b/user/find.c @@ -0,0 +1,84 @@ +#include "kernel/types.h" + +#include "kernel/fcntl.h" +#include "kernel/fs.h" +#include "kernel/stat.h" +#include "user/user.h" + +char* +fmtname(char* path) +{ + char* p; + + // Find first character after last slash. + for (p = path + strlen(path); p >= path && *p != '/'; p--) + ; + p++; + return p; +} + +void +find(char* root_path, char* filename) +{ + static char buf[512]; + char* p; + int fd; + struct dirent de; + struct stat st; + + if ((fd = open(root_path, O_RDONLY)) < 0) { + fprintf(2, "find: cannot open %s\n", root_path); + return; + } + if (fstat(fd, &st) < 0) { + fprintf(2, "find: cannot stat %s\n", root_path); + close(fd); + return; + } + + switch (st.type) { + case T_FILE: + if (!strcmp(fmtname(root_path), filename)) { + printf("%s\n", root_path); + } + break; + case T_DIR: + if (strlen(root_path) + 1 + DIRSIZ + 1 > sizeof(buf)) { + printf("find: path too long\n"); + break; + } + + strcpy(buf, root_path); + + p = buf + strlen(buf); + *p++ = '/'; + while (read(fd, &de, sizeof(de)) == sizeof(de)) { + if (de.inum == 0) + continue; + memmove(p, de.name, DIRSIZ); + p[DIRSIZ] = '\0'; + + // printf("i'm finding %s!\n", fmtname(buf)); + + if (!strcmp(fmtname(buf), ".") || !strcmp(fmtname(buf), "..")) { + continue; + } + + find(buf, filename); + } + } + close(fd); +} + +int +main(int argc, char* argv[]) +{ + if (argc < 3) { + fprintf(2, "usage: find [root_path] filename...\n"); + exit(1); + } + + for (int i = 2; i < argc; i++) { + find(argv[1], argv[i]); + } +} diff --git a/user/pingpong.c b/user/pingpong.c new file mode 100644 index 0000000..7b03a76 --- /dev/null +++ b/user/pingpong.c @@ -0,0 +1,44 @@ +#include "kernel/types.h" +#include "user/user.h" + +int +main(int argc, char* argv[]) +{ + int p[2]; + + if (argc > 1) { + fprintf(2, "usage: pingpong\n"); + exit(1); + } + + pipe(p); + + int pid = fork(); + + if (pid == 0) { + short n; + read(p[0], &n, sizeof(n)); + if (n == 42) { + fprintf(1, "%d: received ping\n", getpid()); + } + n++; + write(p[1], &n, sizeof(n)); + close(p[0]); + close(p[1]); + exit(0); + } + + short n = 42; + + write(p[1], &n, sizeof(n)); + read(p[0], &n, sizeof(n)); + if (n == 43) { + fprintf(1, "%d: received pong\n", getpid()); + } + close(p[0]); + close(p[1]); + + wait(0); + + exit(0); +} diff --git a/user/primes.c b/user/primes.c new file mode 100644 index 0000000..b359524 --- /dev/null +++ b/user/primes.c @@ -0,0 +1,74 @@ +#include "kernel/types.h" +#include "user/user.h" + +#define MAX 36 +#define FIRST_PRIME 2 + +int +generate_natural(); // -> out_fd +int +prime_filter(int in_fd, int prime); // -> out_fd + +int +main(int argc, char* argv[]) +{ + int prime; + + int in = generate_natural(); + while (read(in, &prime, sizeof(int))) { + // printf("prime %d: in_fd: %d\n", prime, in); // debug + printf("prime %d\n", prime); + in = prime_filter(in, prime); + } + + close(in); + + exit(0); +} + +int +generate_natural() +{ + int out_pipe[2]; + + pipe(out_pipe); + + if (!fork()) { + for (int i = FIRST_PRIME; i < MAX; i++) { + write(out_pipe[1], &i, sizeof(int)); + } + close(out_pipe[1]); + + exit(0); + } + + close(out_pipe[1]); + + return out_pipe[0]; +} + +int +prime_filter(int in_fd, int prime) +{ + int num; + int out_pipe[2]; + + pipe(out_pipe); + + if (!fork()) { + while (read(in_fd, &num, sizeof(int))) { + if (num % prime) { + write(out_pipe[1], &num, sizeof(int)); + } + } + close(in_fd); + close(out_pipe[1]); + + exit(0); + } + + close(in_fd); + close(out_pipe[1]); + + return out_pipe[0]; +} diff --git a/user/sh.c b/user/sh.c index 836ebcb..5eceda0 100644 --- a/user/sh.c +++ b/user/sh.c @@ -165,6 +165,9 @@ main(void) fprintf(2, "cannot cd %s\n", buf+3); continue; } + if(buf[0] == 'e' && buf[1] == 'x' && buf[2] == 'i' && buf[3] == 't'){ + exit(0); + } if(fork1() == 0) runcmd(parsecmd(buf)); wait(0); diff --git a/user/sleep.c b/user/sleep.c new file mode 100644 index 0000000..961f558 --- /dev/null +++ b/user/sleep.c @@ -0,0 +1,22 @@ +#include "kernel/types.h" +#include "user/user.h" + +int +main(int argc, char* argv[]) +{ + uint sec = 0; + + if (argc <= 1) { + fprintf(2, "usage: sleep [time (ticks)]\n"); + exit(1); + } + sec = atoi(argv[1]); + + sleep(sec); + + if (argc <= 2) { + exit(0); + } + + exit(0); +} diff --git a/user/xargs.c b/user/xargs.c new file mode 100644 index 0000000..b5b9bee --- /dev/null +++ b/user/xargs.c @@ -0,0 +1,60 @@ +#include "kernel/types.h" + +#include "kernel/param.h" +#include "kernel/stat.h" +#include "user/user.h" + +#define is_blank(chr) (chr == ' ' || chr == '\t') + +int +main(int argc, char* argv[]) +{ + char buf[2048], ch; + char* p = buf; + char* v[MAXARG]; + int c; + int blanks = 0; + int offset = 0; + + if (argc <= 1) { + fprintf(2, "usage: xargs [argv...]\n"); + exit(1); + } + + for (c = 1; c < argc; c++) { + v[c - 1] = argv[c]; + } + --c; + + while (read(0, &ch, 1) > 0) { + if (is_blank(ch)) { + blanks++; + continue; + } + + if (blanks) { + buf[offset++] = 0; + + v[c++] = p; + p = buf + offset; + + blanks = 0; + } + + if (ch != '\n') { + buf[offset++] = ch; + } else { + v[c++] = p; + p = buf + offset; + + if (!fork()) { + exit(exec(v[0], v)); + } + wait(0); + + c = argc - 1; + } + } + + exit(0); +} -- cgit v1.2.3 From 283d5ab4c964ab525e45fcade06d6fd7e977c43e Mon Sep 17 00:00:00 2001 From: Mole Shang <135e2@135e2.dev> Date: Thu, 18 Jan 2024 17:35:27 +0800 Subject: lab syscall: finish Conflicts: kernel/syscall.c kernel/syscall.h user/user.h user/usys.pl --- Makefile | 3 ++ answers-syscall.txt | 9 ++++ kernel/defs.h | 6 +++ kernel/kalloc.c | 14 +++++ kernel/proc.c | 20 +++++++ kernel/proc.h | 1 + kernel/syscall.c | 38 +++++++++++++ kernel/syscall.h | 2 + kernel/sysinfo.c | 24 +++++++++ kernel/sysinfo.h | 4 ++ kernel/sysproc.c | 18 +++++++ time.txt | 1 + user/sysinfotest.c | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++ user/trace.c | 27 ++++++++++ user/user.h | 3 ++ user/usys.pl | 2 + 16 files changed, 325 insertions(+) create mode 100644 answers-syscall.txt create mode 100644 kernel/sysinfo.c create mode 100644 kernel/sysinfo.h create mode 100644 time.txt create mode 100644 user/sysinfotest.c create mode 100644 user/trace.c diff --git a/Makefile b/Makefile index 4f3fd54..def8b43 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ OBJS = \ $K/trampoline.o \ $K/trap.o \ $K/syscall.o \ + $K/sysinfo.o \ $K/sysproc.o \ $K/bio.o \ $K/fs.o \ @@ -193,6 +194,8 @@ UPROGS=\ $U/_primes\ $U/_find\ $U/_xargs\ + $U/_trace\ + $U/_sysinfotest\ diff --git a/answers-syscall.txt b/answers-syscall.txt new file mode 100644 index 0000000..ca0ce77 --- /dev/null +++ b/answers-syscall.txt @@ -0,0 +1,9 @@ +usertrap() +7 +SYS_exec +user mode +lw a3,0(zero) +a3 +no +yes, `scause` 0xd -> interrupt = 0, exception code = 13 -> instruction page fault (riscv-priviledged p71) +'initcode', 1 diff --git a/kernel/defs.h b/kernel/defs.h index a3c962b..c8eeef3 100644 --- a/kernel/defs.h +++ b/kernel/defs.h @@ -8,6 +8,7 @@ struct spinlock; struct sleeplock; struct stat; struct superblock; +struct sysinfo; // bio.c void binit(void); @@ -63,6 +64,7 @@ void ramdiskrw(struct buf*); void* kalloc(void); void kfree(void *); void kinit(void); +int get_freemem(void); // log.c void initlog(int, struct superblock*); @@ -106,6 +108,7 @@ void yield(void); int either_copyout(int user_dst, uint64 dst, void *src, uint64 len); int either_copyin(void *dst, int user_src, uint64 src, uint64 len); void procdump(void); +int get_nproc(void); // swtch.S void swtch(struct context*, struct context*); @@ -141,6 +144,9 @@ int fetchstr(uint64, char*, int); int fetchaddr(uint64, uint64*); void syscall(); +// sysinfo.c +int sys_info(uint64); + // trap.c extern uint ticks; void trapinit(void); diff --git a/kernel/kalloc.c b/kernel/kalloc.c index 0699e7e..c2fdb86 100644 --- a/kernel/kalloc.c +++ b/kernel/kalloc.c @@ -80,3 +80,17 @@ kalloc(void) memset((char*)r, 5, PGSIZE); // fill with junk return (void*)r; } + +int +get_freemem(void) +{ + int n; + struct run *r; + + acquire(&kmem.lock); + for (n = 0, r = kmem.freelist; r; r = r->next) + n++; + release(&kmem.lock); + + return n * PGSIZE; +} diff --git a/kernel/proc.c b/kernel/proc.c index 58a8a0b..5aa616f 100644 --- a/kernel/proc.c +++ b/kernel/proc.c @@ -299,6 +299,9 @@ fork(void) // copy saved user registers. *(np->trapframe) = *(p->trapframe); + // inherit trace_mask + np->trace_mask = p->trace_mask; + // Cause fork to return 0 in the child. np->trapframe->a0 = 0; @@ -686,3 +689,20 @@ procdump(void) printf("\n"); } } + +int +get_nproc(void) +{ + int n = 0; + struct proc *p; + + for(int i = 0; i < NPROC; i++) { + p = &proc[i]; + acquire(&p->lock); + if(p->state != UNUSED) + n++; + release(&p->lock); + } + + return n; +} diff --git a/kernel/proc.h b/kernel/proc.h index d021857..4a2ca6c 100644 --- a/kernel/proc.h +++ b/kernel/proc.h @@ -91,6 +91,7 @@ struct proc { int killed; // If non-zero, have been killed int xstate; // Exit status to be returned to parent's wait int pid; // Process ID + int trace_mask; // SYS_trace mask (1 << SYS_xxx) // wait_lock must be held when using this: struct proc *parent; // Parent process diff --git a/kernel/syscall.c b/kernel/syscall.c index beea0ef..4907f0f 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -101,6 +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_trace(void); +extern uint64 sys_sysinfo(void); #ifdef LAB_NET extern uint64 sys_connect(void); @@ -139,6 +141,35 @@ static uint64 (*syscalls[])(void) = { #ifdef LAB_PGTBL [SYS_pgaccess] sys_pgaccess, #endif +[SYS_trace] sys_trace, +[SYS_sysinfo] sys_sysinfo, +}; + +// syscall name maps for SYS_trace: +static char *syscall_names[] = { +[SYS_fork] "fork", +[SYS_exit] "exit", +[SYS_wait] "wait", +[SYS_pipe] "pipe", +[SYS_read] "read", +[SYS_kill] "kill", +[SYS_exec] "exec", +[SYS_fstat] "fstat", +[SYS_chdir] "chdir", +[SYS_dup] "dup", +[SYS_getpid] "getpid", +[SYS_sbrk] "sbrk", +[SYS_sleep] "sleep", +[SYS_uptime] "uptime", +[SYS_open] "open", +[SYS_write] "write", +[SYS_mknod] "mknod", +[SYS_unlink] "unlink", +[SYS_link] "link", +[SYS_mkdir] "mkdir", +[SYS_close] "close", +[SYS_trace] "trace", +[SYS_sysinfo] "sysinfo", }; @@ -154,6 +185,13 @@ syscall(void) // Use num to lookup the system call function for num, call it, // and store its return value in p->trapframe->a0 p->trapframe->a0 = syscalls[num](); + + // SYS_trace: match all the syscalls which number < mask asked + // p->trace_mask == 1 << SYS_xxx + if(p->trace_mask >> num) { + printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0); + } + } else { printf("%d %s: unknown sys call %d\n", p->pid, p->name, num); diff --git a/kernel/syscall.h b/kernel/syscall.h index 8da572e..d930252 100644 --- a/kernel/syscall.h +++ b/kernel/syscall.h @@ -31,3 +31,5 @@ #define SYS_munmap 28 #define SYS_connect 29 #define SYS_pgaccess 30 +#define SYS_trace 22 +#define SYS_sysinfo 23 diff --git a/kernel/sysinfo.c b/kernel/sysinfo.c new file mode 100644 index 0000000..c66324d --- /dev/null +++ b/kernel/sysinfo.c @@ -0,0 +1,24 @@ +#include "types.h" +#include "riscv.h" +#include "param.h" +#include "spinlock.h" +#include "defs.h" +#include "sysinfo.h" +#include "proc.h" + +// Get current system info +// addr is a user virtual address, pointing to a struct sysinfo. +int +sys_info(uint64 addr) { + struct proc *p = myproc(); + struct sysinfo info; + + // Fill nums into the sysinfo struct + info.freemem = get_freemem(); + info.nproc = get_nproc(); + + if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0) + return -1; + return 0; +} + diff --git a/kernel/sysinfo.h b/kernel/sysinfo.h new file mode 100644 index 0000000..fb878e6 --- /dev/null +++ b/kernel/sysinfo.h @@ -0,0 +1,4 @@ +struct sysinfo { + uint64 freemem; // amount of free memory (bytes) + uint64 nproc; // number of process +}; diff --git a/kernel/sysproc.c b/kernel/sysproc.c index 88644b2..4cf0697 100644 --- a/kernel/sysproc.c +++ b/kernel/sysproc.c @@ -100,3 +100,21 @@ sys_uptime(void) release(&tickslock); return xticks; } + +uint64 +sys_trace(void) +{ + argint(0, &myproc()->trace_mask); + + return -(myproc()->trace_mask <= 1); +} + +uint64 +sys_sysinfo(void) +{ + uint64 si; // user pointer to struct sysinfo + + argaddr(0, &si); + return sys_info(si); +} + diff --git a/time.txt b/time.txt new file mode 100644 index 0000000..b8626c4 --- /dev/null +++ b/time.txt @@ -0,0 +1 @@ +4 diff --git a/user/sysinfotest.c b/user/sysinfotest.c new file mode 100644 index 0000000..8a648a6 --- /dev/null +++ b/user/sysinfotest.c @@ -0,0 +1,153 @@ +#include "kernel/types.h" +#include "kernel/riscv.h" +#include "kernel/sysinfo.h" +#include "user/user.h" + + +void +sinfo(struct sysinfo *info) { + if (sysinfo(info) < 0) { + printf("FAIL: sysinfo failed"); + exit(1); + } +} + +// +// use sbrk() to count how many free physical memory pages there are. +// +int +countfree() +{ + uint64 sz0 = (uint64)sbrk(0); + struct sysinfo info; + int n = 0; + + while(1){ + if((uint64)sbrk(PGSIZE) == 0xffffffffffffffff){ + break; + } + n += PGSIZE; + } + sinfo(&info); + if (info.freemem != 0) { + printf("FAIL: there is no free mem, but sysinfo.freemem=%d\n", + info.freemem); + exit(1); + } + sbrk(-((uint64)sbrk(0) - sz0)); + return n; +} + +void +testmem() { + struct sysinfo info; + uint64 n = countfree(); + + sinfo(&info); + + if (info.freemem!= n) { + printf("FAIL: free mem %d (bytes) instead of %d\n", info.freemem, n); + exit(1); + } + + if((uint64)sbrk(PGSIZE) == 0xffffffffffffffff){ + printf("sbrk failed"); + exit(1); + } + + sinfo(&info); + + if (info.freemem != n-PGSIZE) { + printf("FAIL: free mem %d (bytes) instead of %d\n", n-PGSIZE, info.freemem); + exit(1); + } + + if((uint64)sbrk(-PGSIZE) == 0xffffffffffffffff){ + printf("sbrk failed"); + exit(1); + } + + sinfo(&info); + + if (info.freemem != n) { + printf("FAIL: free mem %d (bytes) instead of %d\n", n, info.freemem); + exit(1); + } +} + +void +testcall() { + struct sysinfo info; + + if (sysinfo(&info) < 0) { + printf("FAIL: sysinfo failed\n"); + exit(1); + } + + if (sysinfo((struct sysinfo *) 0xeaeb0b5b00002f5e) != 0xffffffffffffffff) { + printf("FAIL: sysinfo succeeded with bad argument\n"); + exit(1); + } +} + +void testproc() { + struct sysinfo info; + uint64 nproc; + int status; + int pid; + + sinfo(&info); + nproc = info.nproc; + + pid = fork(); + if(pid < 0){ + printf("sysinfotest: fork failed\n"); + exit(1); + } + if(pid == 0){ + sinfo(&info); + if(info.nproc != nproc+1) { + printf("sysinfotest: FAIL nproc is %d instead of %d\n", info.nproc, nproc+1); + exit(1); + } + exit(0); + } + wait(&status); + sinfo(&info); + if(info.nproc != nproc) { + printf("sysinfotest: FAIL nproc is %d instead of %d\n", info.nproc, nproc); + exit(1); + } +} + +void testbad() { + int pid = fork(); + int xstatus; + + if(pid < 0){ + printf("sysinfotest: fork failed\n"); + exit(1); + } + if(pid == 0){ + sinfo(0x0); + exit(0); + } + wait(&xstatus); + if(xstatus == -1) // kernel killed child? + exit(0); + else { + printf("sysinfotest: testbad succeeded %d\n", xstatus); + exit(xstatus); + } +} + +int +main(int argc, char *argv[]) +{ + printf("sysinfotest: start\n"); + testcall(); + testmem(); + testproc(); + printf("sysinfotest: OK\n"); + exit(0); +} diff --git a/user/trace.c b/user/trace.c new file mode 100644 index 0000000..dd77760 --- /dev/null +++ b/user/trace.c @@ -0,0 +1,27 @@ +#include "kernel/param.h" +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +int +main(int argc, char *argv[]) +{ + int i; + char *nargv[MAXARG]; + + if(argc < 3 || (argv[1][0] < '0' || argv[1][0] > '9')){ + fprintf(2, "Usage: %s mask command\n", argv[0]); + exit(1); + } + + if (trace(atoi(argv[1])) < 0) { + fprintf(2, "%s: trace failed\n", argv[0]); + exit(1); + } + + for(i = 2; i < argc && i < MAXARG; i++){ + nargv[i-2] = argv[i]; + } + exec(nargv[0], nargv); + exit(0); +} diff --git a/user/user.h b/user/user.h index 16cf173..a076f37 100644 --- a/user/user.h +++ b/user/user.h @@ -1,4 +1,5 @@ struct stat; +struct sysinfo; // system calls int fork(void); @@ -30,6 +31,8 @@ int pgaccess(void *base, int len, void *mask); // usyscall region int ugetpid(void); #endif +int trace(int); +int sysinfo(struct sysinfo*); // ulib.c int stat(const char*, struct stat*); diff --git a/user/usys.pl b/user/usys.pl index 6453fe9..9a93999 100755 --- a/user/usys.pl +++ b/user/usys.pl @@ -38,3 +38,5 @@ entry("sleep"); entry("uptime"); entry("connect"); entry("pgaccess"); +entry("trace"); +entry("sysinfo"); -- cgit v1.2.3 From c9284cd93525436cc823258ab309c1b27eeec714 Mon Sep 17 00:00:00 2001 From: Mole Shang <135e2@135e2.dev> Date: Sat, 10 Feb 2024 13:08:26 +0800 Subject: lab pgtbl: finish --- answers-pgtbl.txt | 0 kernel/defs.h | 2 ++ kernel/exec.c | 4 ++++ kernel/proc.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- kernel/proc.h | 1 + kernel/riscv.h | 1 + kernel/syscall.c | 7 +++++++ kernel/syscall.h | 2 -- kernel/sysproc.c | 10 ++++++++-- kernel/vm.c | 27 +++++++++++++++++++++++++++ time.txt | 2 +- user/usys.pl | 4 ++-- 12 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 answers-pgtbl.txt diff --git a/answers-pgtbl.txt b/answers-pgtbl.txt new file mode 100644 index 0000000..e69de29 diff --git a/kernel/defs.h b/kernel/defs.h index c8eeef3..bd85d1f 100644 --- a/kernel/defs.h +++ b/kernel/defs.h @@ -109,6 +109,7 @@ int either_copyout(int user_dst, uint64 dst, void *src, uint64 len); int either_copyin(void *dst, int user_src, uint64 src, uint64 len); void procdump(void); int get_nproc(void); +int pgaccess(uint64 base, int len, uint64 mask); // swtch.S void swtch(struct context*, struct context*); @@ -179,6 +180,7 @@ 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); +void vmprint(pagetable_t); // plic.c void plicinit(void); diff --git a/kernel/exec.c b/kernel/exec.c index e18bbb6..35b35f5 100644 --- a/kernel/exec.c +++ b/kernel/exec.c @@ -128,6 +128,10 @@ exec(char *path, char **argv) p->trapframe->sp = sp; // initial stack pointer proc_freepagetable(oldpagetable, oldsz); + if(p->pid == 1){ + vmprint(p->pagetable); + } + return argc; // this ends up in a0, the first argument to main(argc, argv) bad: diff --git a/kernel/proc.c b/kernel/proc.c index 5aa616f..3d215a5 100644 --- a/kernel/proc.c +++ b/kernel/proc.c @@ -132,6 +132,14 @@ found: return 0; } + // Allocate a usyscall page and fill pid. + if((p->usyscall = (struct usyscall *)kalloc()) == 0){ + freeproc(p); + release(&p->lock); + return 0; + } + p->usyscall->pid = p->pid; + // An empty user page table. p->pagetable = proc_pagetable(p); if(p->pagetable == 0){ @@ -158,6 +166,9 @@ freeproc(struct proc *p) if(p->trapframe) kfree((void*)p->trapframe); p->trapframe = 0; + if(p->usyscall) + kfree((void*)p->usyscall); + p->usyscall = 0; if(p->pagetable) proc_freepagetable(p->pagetable, p->sz); p->pagetable = 0; @@ -172,7 +183,7 @@ freeproc(struct proc *p) } // Create a user page table for a given process, with no user memory, -// but with trampoline and trapframe pages. +// but with trampoline, trapframe and usyscall pages. pagetable_t proc_pagetable(struct proc *p) { @@ -202,6 +213,14 @@ proc_pagetable(struct proc *p) return 0; } + // map the usyscall page below the trapframe page, for + // ugetpid(). + if(mappages(pagetable, USYSCALL, PGSIZE, + (uint64)(p->usyscall), PTE_R | PTE_U) < 0){ + uvmunmap(pagetable, USYSCALL, 1, 0); + uvmfree(pagetable, 0); + return 0; + } return pagetable; } @@ -212,6 +231,7 @@ proc_freepagetable(pagetable_t pagetable, uint64 sz) { uvmunmap(pagetable, TRAMPOLINE, 1, 0); uvmunmap(pagetable, TRAPFRAME, 1, 0); + uvmunmap(pagetable, USYSCALL, 1, 0); uvmfree(pagetable, sz); } @@ -706,3 +726,26 @@ get_nproc(void) return n; } + +// lab pagetable: report which pages have been accessed (r/w) +// according to PTE_A and store it in a bit mask (3rd param) +int +pgaccess(uint64 base, int len, uint64 mask_addr) +{ + struct proc *p = myproc(); + pagetable_t pgtbl = p->pagetable; + pte_t *pte; + int mask = 0; + + // iterater thru pages + for(int i = 0; i < len; i++) { + pte = walk(pgtbl, base + i * PGSIZE, 0); + if(*pte & PTE_A) { + *pte &= (~PTE_A); // clear PTE_A to avoid setting it forever + mask |= (1L << i); + } + } + + // now copyout the mask to user memory + return copyout(pgtbl, mask_addr, (char *)&mask, sizeof(mask)); +} diff --git a/kernel/proc.h b/kernel/proc.h index 4a2ca6c..c816ae2 100644 --- a/kernel/proc.h +++ b/kernel/proc.h @@ -101,6 +101,7 @@ struct proc { uint64 sz; // Size of process memory (bytes) pagetable_t pagetable; // User page table struct trapframe *trapframe; // data page for trampoline.S + struct usyscall *usyscall; // data page for usyscall struct context context; // swtch() here to run process struct file *ofile[NOFILE]; // Open files struct inode *cwd; // Current directory diff --git a/kernel/riscv.h b/kernel/riscv.h index 20a01db..33fa9ee 100644 --- a/kernel/riscv.h +++ b/kernel/riscv.h @@ -343,6 +343,7 @@ typedef uint64 *pagetable_t; // 512 PTEs #define PTE_W (1L << 2) #define PTE_X (1L << 3) #define PTE_U (1L << 4) // user can access +#define PTE_A (1L << 6) // riscv access bit // shift a physical address to the right place for a PTE. #define PA2PTE(pa) ((((uint64)pa) >> 12) << 10) diff --git a/kernel/syscall.c b/kernel/syscall.c index 4907f0f..394b980 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -168,6 +168,12 @@ static char *syscall_names[] = { [SYS_link] "link", [SYS_mkdir] "mkdir", [SYS_close] "close", +#ifdef LAB_NET +[SYS_connect] "connect", +#endif +#ifdef LAB_PGTBL +[SYS_pgaccess] "pgaccess", +#endif [SYS_trace] "trace", [SYS_sysinfo] "sysinfo", }; @@ -198,3 +204,4 @@ syscall(void) p->trapframe->a0 = -1; } } + diff --git a/kernel/syscall.h b/kernel/syscall.h index d930252..8da572e 100644 --- a/kernel/syscall.h +++ b/kernel/syscall.h @@ -31,5 +31,3 @@ #define SYS_munmap 28 #define SYS_connect 29 #define SYS_pgaccess 30 -#define SYS_trace 22 -#define SYS_sysinfo 23 diff --git a/kernel/sysproc.c b/kernel/sysproc.c index 4cf0697..bd9d0f0 100644 --- a/kernel/sysproc.c +++ b/kernel/sysproc.c @@ -74,8 +74,14 @@ sys_sleep(void) int sys_pgaccess(void) { - // lab pgtbl: your code here. - return 0; + uint64 base, mask; + int len; + + + argaddr(0, &base); + argint(1, &len); + argaddr(2, &mask); + return pgaccess(base, len, mask); } #endif diff --git a/kernel/vm.c b/kernel/vm.c index 5c31e87..9c17fe7 100644 --- a/kernel/vm.c +++ b/kernel/vm.c @@ -449,3 +449,30 @@ copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max) return -1; } } + +static void +walkprint(pagetable_t pgtbl, int level) +{ + for(int i = 0; i < 512; i++){ + pte_t pte = pgtbl[i]; + if(pte & PTE_V){ + for(int j = 0; j < level; j++){ + printf(" .."); + } + printf("%d: pte %p pa %p\n", i, pte, PTE2PA(pte)); + if((pte & (PTE_R|PTE_W|PTE_X)) == 0){ + // this PTE points to a lower-level page table. + walkprint((pagetable_t)PTE2PA(pte), level+1); + } + } + } +} + +// Print the contents of a page table +void +vmprint(pagetable_t pgtbl) +{ + printf("page table %p\n", pgtbl); + + walkprint(pgtbl, 1); +} diff --git a/time.txt b/time.txt index b8626c4..00750ed 100644 --- a/time.txt +++ b/time.txt @@ -1 +1 @@ -4 +3 diff --git a/user/usys.pl b/user/usys.pl index 9a93999..f084c63 100755 --- a/user/usys.pl +++ b/user/usys.pl @@ -36,7 +36,7 @@ entry("getpid"); entry("sbrk"); entry("sleep"); entry("uptime"); -entry("connect"); -entry("pgaccess"); entry("trace"); entry("sysinfo"); +entry("connect"); +entry("pgaccess"); -- cgit v1.2.3 From 48a5e34fcd07852b4a68825ce8e37feb6f6d04d7 Mon Sep 17 00:00:00 2001 From: Mole Shang <135e2@135e2.dev> Date: Sun, 11 Feb 2024 14:41:35 +0800 Subject: lab traps: finish --- Makefile | 3 ++- answers-traps.txt | 6 ++++++ kernel/defs.h | 1 + kernel/printf.c | 17 +++++++++++++++++ kernel/proc.c | 19 +++++++++++++++++++ kernel/proc.h | 5 +++++ kernel/riscv.h | 9 +++++++++ kernel/syscall.c | 6 ++++++ kernel/sysproc.c | 26 ++++++++++++++++++++++++++ kernel/trap.c | 23 +++++++++++++++++++++-- user/user.h | 2 ++ user/usys.pl | 2 ++ 12 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 answers-traps.txt diff --git a/Makefile b/Makefile index 043a9ac..ecb0f9e 100644 --- a/Makefile +++ b/Makefile @@ -208,7 +208,8 @@ endif ifeq ($(LAB),traps) UPROGS += \ $U/_call\ - $U/_bttest + $U/_bttest\ + $U/_alarmtest endif ifeq ($(LAB),lazy) diff --git a/answers-traps.txt b/answers-traps.txt new file mode 100644 index 0000000..dbb40f1 --- /dev/null +++ b/answers-traps.txt @@ -0,0 +1,6 @@ +a0~a7, a2 +0x26 (optimized during compile time), 0x14 (inline, unfolded) +0x666 +0x38 (next instruction) +He110 World +the value in register a2 diff --git a/kernel/defs.h b/kernel/defs.h index bd85d1f..04a0276 100644 --- a/kernel/defs.h +++ b/kernel/defs.h @@ -82,6 +82,7 @@ int pipewrite(struct pipe*, uint64, int); void printf(char*, ...); void panic(char*) __attribute__((noreturn)); void printfinit(void); +void backtrace(void); // proc.c int cpuid(void); diff --git a/kernel/printf.c b/kernel/printf.c index 1a50203..509c1c5 100644 --- a/kernel/printf.c +++ b/kernel/printf.c @@ -122,6 +122,8 @@ panic(char *s) printf("panic: "); printf(s); printf("\n"); + backtrace(); + panicked = 1; // freeze uart output from other CPUs for(;;) ; @@ -133,3 +135,18 @@ printfinit(void) initlock(&pr.lock, "pr"); pr.locking = 1; } + +void +backtrace(void) +{ + uint64 fp = r_fp(); + printf("backtrace:\n"); + uint64 stackpg = PGROUNDDOWN(fp); + // Whereever fp points to should always live in the stack page + while(PGROUNDDOWN(fp) == stackpg){ + // print the return addr (stored in fp-8) + printf("%p\n", *(uint64 *)(fp-8)); + // load previous (upper stack) fp + fp = *(uint64 *)(fp-16); + } +} diff --git a/kernel/proc.c b/kernel/proc.c index 3d215a5..9a9bae9 100644 --- a/kernel/proc.c +++ b/kernel/proc.c @@ -39,6 +39,7 @@ proc_mapstacks(pagetable_t kpgtbl) if(pa == 0) panic("kalloc"); uint64 va = KSTACK((int) (p - proc)); + p->alarm_tickspassed = 0; kvmmap(kpgtbl, va, (uint64)pa, PGSIZE, PTE_R | PTE_W); } } @@ -140,6 +141,17 @@ found: } p->usyscall->pid = p->pid; + // reset sigalarm props + p->alarm_interval = 0; + p->alarm_handler = 0; + p->alarm_tickspassed = 0; + p->alarm_caninvoke = 1; + if((p->atpfm = (struct trapframe *)kalloc()) == 0){ + freeproc(p); + release(&p->lock); + return 0; + } + // An empty user page table. p->pagetable = proc_pagetable(p); if(p->pagetable == 0){ @@ -171,6 +183,13 @@ freeproc(struct proc *p) p->usyscall = 0; if(p->pagetable) proc_freepagetable(p->pagetable, p->sz); + if(p->atpfm) + kfree((void*)p->atpfm); + p->atpfm = 0; + p->alarm_interval = 0; + p->alarm_handler = 0; + p->alarm_tickspassed = 0; + p->alarm_caninvoke = 1; p->pagetable = 0; p->sz = 0; p->pid = 0; diff --git a/kernel/proc.h b/kernel/proc.h index c816ae2..a195b02 100644 --- a/kernel/proc.h +++ b/kernel/proc.h @@ -106,4 +106,9 @@ struct proc { struct file *ofile[NOFILE]; // Open files struct inode *cwd; // Current directory char name[16]; // Process name (debugging) + int alarm_interval; // sigalarm syscall interval + uint64 alarm_handler; // sigalarm syscall handler + int alarm_tickspassed; // record how many ticks passed since last sigalarm handler call + int alarm_caninvoke; // prevent re-entrant calls to handler + struct trapframe *atpfm; // trapframe to resume after handling, must hold p->lock }; diff --git a/kernel/riscv.h b/kernel/riscv.h index 33fa9ee..5ede50a 100644 --- a/kernel/riscv.h +++ b/kernel/riscv.h @@ -327,6 +327,15 @@ sfence_vma() asm volatile("sfence.vma zero, zero"); } +// read the frame pointer of currently executing func +static inline uint64 +r_fp() +{ + uint64 x; + asm volatile("mv %0, s0" : "=r" (x) ); + return x; +} + typedef uint64 pte_t; typedef uint64 *pagetable_t; // 512 PTEs diff --git a/kernel/syscall.c b/kernel/syscall.c index 394b980..8392eb4 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -110,6 +110,8 @@ extern uint64 sys_connect(void); #ifdef LAB_PGTBL extern uint64 sys_pgaccess(void); #endif +extern uint64 sys_sigalarm(void); +extern uint64 sys_sigreturn(void); // An array mapping syscall numbers from syscall.h // to the function that handles the system call. @@ -143,6 +145,8 @@ static uint64 (*syscalls[])(void) = { #endif [SYS_trace] sys_trace, [SYS_sysinfo] sys_sysinfo, +[SYS_sigalarm] sys_sigalarm, +[SYS_sigreturn] sys_sigreturn, }; // syscall name maps for SYS_trace: @@ -176,6 +180,8 @@ static char *syscall_names[] = { #endif [SYS_trace] "trace", [SYS_sysinfo] "sysinfo", +[SYS_sigalarm] "sigalarm", +[SYS_sigreturn] "sigreturn", }; diff --git a/kernel/sysproc.c b/kernel/sysproc.c index bd9d0f0..fac1d83 100644 --- a/kernel/sysproc.c +++ b/kernel/sysproc.c @@ -65,6 +65,9 @@ sys_sleep(void) } sleep(&ticks, &tickslock); } + + backtrace(); + release(&tickslock); return 0; } @@ -124,3 +127,26 @@ sys_sysinfo(void) return sys_info(si); } +uint64 +sys_sigalarm(void) +{ + struct proc *p = myproc(); + uint64 handler; + + argint(0, &p->alarm_interval); + argaddr(1, &handler); + p->alarm_handler = handler; + + return 0; +} + +uint64 sys_sigreturn(void) +{ + struct proc *p = myproc(); + // retore saved trapframe to resume + memmove(p->trapframe, p->atpfm, sizeof(struct trapframe)); + p->alarm_tickspassed = 0; + p->alarm_caninvoke = 1; + // make sure return the original a0 in trapframe to pass test3 + return p->trapframe->a0; +} diff --git a/kernel/trap.c b/kernel/trap.c index 512c850..eb8009f 100644 --- a/kernel/trap.c +++ b/kernel/trap.c @@ -6,6 +6,12 @@ #include "proc.h" #include "defs.h" +/* + * Always remember that RISC-V disables interrupts when it starts to take a trap, + * so there's no need to call intr_off() at the beginning of trap handling. + * Reference: xv6-riscv-book 4.5 + */ + struct spinlock tickslock; uint ticks; @@ -76,9 +82,22 @@ usertrap(void) if(killed(p)) exit(-1); - // give up the CPU if this is a timer interrupt. - if(which_dev == 2) + if(which_dev == 2){ + // timer interrupt + if(p->alarm_interval > 0 && p->alarm_caninvoke){ + // record sigalarm + p->alarm_tickspassed++; + if(p->alarm_tickspassed == p->alarm_interval){ + // store original trapframe in p->atpfm + memmove(p->atpfm, p->trapframe, sizeof(struct trapframe)); + p->alarm_tickspassed = 0; + p->alarm_caninvoke = 0; + p->trapframe->epc = p->alarm_handler; + } + } + // give up the CPU. yield(); + } usertrapret(); } diff --git a/user/user.h b/user/user.h index a076f37..34591fd 100644 --- a/user/user.h +++ b/user/user.h @@ -33,6 +33,8 @@ int ugetpid(void); #endif int trace(int); int sysinfo(struct sysinfo*); +int sigalarm(int ticks, void (*handler)()); +int sigreturn(void); // ulib.c int stat(const char*, struct stat*); diff --git a/user/usys.pl b/user/usys.pl index f084c63..33af0ad 100755 --- a/user/usys.pl +++ b/user/usys.pl @@ -40,3 +40,5 @@ entry("trace"); entry("sysinfo"); entry("connect"); entry("pgaccess"); +entry("sigalarm"); +entry("sigreturn"); -- cgit v1.2.3 From 4a6593f1a6f666c618d303a4858c4c6d31b41c63 Mon Sep 17 00:00:00 2001 From: Mole Shang <135e2@135e2.dev> Date: Sun, 11 Feb 2024 17:51:28 +0800 Subject: lab cow: finish --- Makefile | 1 + kernel/cow.c | 30 ++++++++++++++++++++++++++++++ kernel/defs.h | 5 +++++ kernel/kalloc.c | 43 ++++++++++++++++++++++++++++++++++++++++++- kernel/riscv.h | 1 + kernel/sysproc.c | 2 +- kernel/trap.c | 7 +++++++ kernel/vm.c | 27 ++++++++++++++++++++------- 8 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 kernel/cow.c diff --git a/Makefile b/Makefile index ecb0f9e..217311b 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ OBJS = \ $K/kalloc.o \ $K/string.o \ $K/main.o \ + $K/cow.o \ $K/vm.o \ $K/proc.o \ $K/swtch.o \ diff --git a/kernel/cow.c b/kernel/cow.c new file mode 100644 index 0000000..b3634fc --- /dev/null +++ b/kernel/cow.c @@ -0,0 +1,30 @@ +// COW pagefault handler +#include "types.h" +#include "riscv.h" +#include "defs.h" + +int +cow_handler(pagetable_t pagetable, uint64 va) +{ + // you can't really write to rediculous pointers + if(va >= MAXVA || PGROUNDDOWN(va) == 0) + return -1; + pte_t *pte = walk(pagetable, va, 0); + if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return -1; + if(*pte & PTE_C){ + uint64 pa_orig = PTE2PA(*pte); + uint64 pa_new = (uint64)kalloc(); + if(pa_new == 0){ + printf("cow pagefault: kalloc failed\n"); + return -1; + } + // copy the page and add write permission + memmove((void*)pa_new, (void*)pa_orig, PGSIZE); + uint64 flags = (PTE_FLAGS(*pte) | PTE_W) & ~PTE_C; + *pte = PA2PTE(pa_new) | flags; + kfree((void*)pa_orig); + } else if ((*pte & PTE_W) == 0) + return -1; + return 0; +} diff --git a/kernel/defs.h b/kernel/defs.h index 04a0276..870c984 100644 --- a/kernel/defs.h +++ b/kernel/defs.h @@ -23,6 +23,9 @@ void consoleinit(void); void consoleintr(int); void consputc(int); +// cow.c +int cow_handler(pagetable_t, uint64); + // exec.c int exec(char*, char**); @@ -61,6 +64,8 @@ void ramdiskintr(void); void ramdiskrw(struct buf*); // kalloc.c +int refcnt_inc(uint64); +int refcnt_dec(uint64); void* kalloc(void); void kfree(void *); void kinit(void); diff --git a/kernel/kalloc.c b/kernel/kalloc.c index c2fdb86..581f0f6 100644 --- a/kernel/kalloc.c +++ b/kernel/kalloc.c @@ -23,10 +23,41 @@ struct { struct run *freelist; } kmem; +int phypg_refcnt[PHYSTOP/PGSIZE]; + +// Increase the refcnt +int +refcnt_inc(uint64 pa) +{ + acquire(&kmem.lock); + int *prefcnt = &phypg_refcnt[pa/PGSIZE]; + if(pa > PHYSTOP || *prefcnt < 1) + panic("increase refcnt"); + (*prefcnt)++; + release(&kmem.lock); + return *prefcnt; +} + +// Decrease the refcnt +int +refcnt_dec(uint64 pa) +{ + acquire(&kmem.lock); + int *prefcnt = &phypg_refcnt[pa/PGSIZE]; + if(pa > PHYSTOP || *prefcnt < 1) + panic("decrease refcnt"); + (*prefcnt)--; + release(&kmem.lock); + return *prefcnt; +} + void kinit() { initlock(&kmem.lock, "kmem"); + // init all refcnt to 1, which would later be freed to 0 by kfree() + for(uint64 p = PGROUNDUP((uint64)end); p + PGSIZE <= PHYSTOP; p += PGSIZE) + phypg_refcnt[p/PGSIZE] = 1; freerange(end, (void*)PHYSTOP); } @@ -51,6 +82,12 @@ kfree(void *pa) if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP) panic("kfree"); + refcnt_dec((uint64)pa); + + if(phypg_refcnt[(uint64)pa/PGSIZE] > 0) + // We still have refs to this phy page, do not actually free it + return; + // Fill with junk to catch dangling refs. memset(pa, 1, PGSIZE); @@ -72,8 +109,12 @@ kalloc(void) acquire(&kmem.lock); r = kmem.freelist; - if(r) + if(r){ + if(phypg_refcnt[(uint64)r/PGSIZE]) + panic("kalloc: invalid refcnt"); + phypg_refcnt[(uint64)r/PGSIZE] = 1; kmem.freelist = r->next; + } release(&kmem.lock); if(r) diff --git a/kernel/riscv.h b/kernel/riscv.h index 5ede50a..7d6eb6e 100644 --- a/kernel/riscv.h +++ b/kernel/riscv.h @@ -353,6 +353,7 @@ typedef uint64 *pagetable_t; // 512 PTEs #define PTE_X (1L << 3) #define PTE_U (1L << 4) // user can access #define PTE_A (1L << 6) // riscv access bit +#define PTE_C (1L << 8) // RSW low bit, use it to mark whether a page is COW // shift a physical address to the right place for a PTE. #define PA2PTE(pa) ((((uint64)pa) >> 12) << 10) diff --git a/kernel/sysproc.c b/kernel/sysproc.c index fac1d83..715a511 100644 --- a/kernel/sysproc.c +++ b/kernel/sysproc.c @@ -66,7 +66,7 @@ sys_sleep(void) sleep(&ticks, &tickslock); } - backtrace(); + // backtrace(); release(&tickslock); return 0; diff --git a/kernel/trap.c b/kernel/trap.c index eb8009f..bd3b100 100644 --- a/kernel/trap.c +++ b/kernel/trap.c @@ -73,9 +73,16 @@ usertrap(void) syscall(); } else if((which_dev = devintr()) != 0){ // ok + } else if(r_scause() == 13 || r_scause() == 15){ + // deal with page fault + uint64 va = r_stval(); + if(cow_handler(p->pagetable, va) < 0) + goto err; } else { printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid); printf(" sepc=%p stval=%p\n", r_sepc(), r_stval()); + err: + printf("killing the process...\n"); setkilled(p); } diff --git a/kernel/vm.c b/kernel/vm.c index 9c17fe7..11f9e0a 100644 --- a/kernel/vm.c +++ b/kernel/vm.c @@ -315,20 +315,26 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) pte_t *pte; uint64 pa, i; uint flags; - char *mem; + // char *mem; for(i = 0; i < sz; i += PGSIZE){ if((pte = walk(old, i, 0)) == 0) panic("uvmcopy: pte should exist"); if((*pte & PTE_V) == 0) panic("uvmcopy: page not present"); - pa = PTE2PA(*pte); - flags = PTE_FLAGS(*pte); + // do not do the actual copy, just increase the refcnt and mark pages readonly COW + /* if((mem = kalloc()) == 0) goto err; memmove(mem, (char*)pa, PGSIZE); - if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){ - kfree(mem); + */ + *pte &= ~PTE_W; + *pte |= PTE_C; + pa = PTE2PA(*pte); + refcnt_inc(pa); + flags = PTE_FLAGS(*pte); + if(mappages(new, i, PGSIZE, (uint64)pa, flags) != 0){ + // kfree(mem); goto err; } } @@ -359,17 +365,24 @@ int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) { uint64 n, va0, pa0; - pte_t *pte; + // pte_t *pte; while(len > 0){ va0 = PGROUNDDOWN(dstva); - if(va0 >= MAXVA) + + if(cow_handler(pagetable, va0) < 0) return -1; + + /* pte = walk(pagetable, va0, 0); if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0 || (*pte & PTE_W) == 0) return -1; pa0 = PTE2PA(*pte); + */ + pa0 = walkaddr(pagetable, va0); + if(pa0 == 0) + return -1; n = PGSIZE - (dstva - va0); if(n > len) n = len; -- cgit v1.2.3 From 904885a96efd1dd0221585f67477f5a39bcce7f7 Mon Sep 17 00:00:00 2001 From: Mole Shang <135e2@135e2.dev> Date: Tue, 13 Feb 2024 21:32:02 +0800 Subject: lab net: finish --- kernel/e1000.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- kernel/pci.c | 2 +- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/kernel/e1000.c b/kernel/e1000.c index 70a2adf..c9ba9e2 100644 --- a/kernel/e1000.c +++ b/kernel/e1000.c @@ -95,26 +95,75 @@ e1000_init(uint32 *xregs) int e1000_transmit(struct mbuf *m) { - // - // Your code here. - // // the mbuf contains an ethernet frame; program it into // the TX descriptor ring so that the e1000 sends it. Stash // a pointer so that it can be freed after sending. - // + acquire(&e1000_lock); + + int cur_idx = regs[E1000_TDT]; + // check if the STAT_DD bit is set in current descriptor + // if not set, means a previous tx in this descripter is still in flight, return an error. + if(!(tx_ring[cur_idx].status | E1000_TXD_STAT_DD)){ + release(&e1000_lock); + return -1; + } + + // free previous mbuf and update current descriptor + if(tx_mbufs[cur_idx]) + mbuffree(tx_mbufs[cur_idx]); + tx_ring[cur_idx].addr = (uint64)m->head; + tx_ring[cur_idx].length = (uint64)m->len; + tx_ring[cur_idx].cmd = E1000_TXD_CMD_RS | E1000_TXD_CMD_EOP; + // also clear status bits + tx_ring[cur_idx].status = 0; + + // stash current mbuf to tx_mbufs (would be freed later) + tx_mbufs[cur_idx] = m; + + // update the ring position to point to the next descriptor; + regs[E1000_TDT] = (cur_idx + 1) % TX_RING_SIZE; + + release(&e1000_lock); return 0; } static void e1000_recv(void) { - // - // Your code here. - // // Check for packets that have arrived from the e1000 // Create and deliver an mbuf for each packet (using net_rx()). - // + while(1){ + acquire(&e1000_lock); + int cur_idx = (regs[E1000_RDT]+1) % RX_RING_SIZE; + + // check if last rx is completed. If not, skip passing to net_rx() + if(!(rx_ring[cur_idx].status | E1000_RXD_STAT_DD)) + break; + + // update the mbuf's length to the len reported by rx_desc + // mbufput(rx_mbufs[cur_idx], rx_ring[cur_idx].length); + rx_mbufs[cur_idx]->len = rx_ring[cur_idx].length; + + // stash mbuf, for later net_rx() + struct mbuf *rx_buf = rx_mbufs[cur_idx]; + + // net_rx() would free the passed mbuf invisibly, so we need to re-alloc it + rx_mbufs[cur_idx] = mbufalloc(0); + if(!rx_mbufs[cur_idx]) + panic("e1000_recv: mbufalloc"); + + // update buffer addr and clear status bits + rx_ring[cur_idx].addr = (uint64)rx_mbufs[cur_idx]->head; + rx_ring[cur_idx].status = 0; + + // update the E1000_RDT register to point to next position + regs[E1000_RDT] = cur_idx; + release(&e1000_lock); + + // pass to the network stack, must not hold the lock coz it can lead to deadlocks under different cpus + net_rx(rx_buf); + } } void diff --git a/kernel/pci.c b/kernel/pci.c index 5cd2102..3e361c5 100644 --- a/kernel/pci.c +++ b/kernel/pci.c @@ -37,7 +37,7 @@ pci_init() // bit 0 : I/O access enable // bit 1 : memory access enable // bit 2 : enable mastering - base[1] = 7; + base[1] = 0b111; __sync_synchronize(); for(int i = 0; i < 6; i++){ -- cgit v1.2.3