diff options
| -rw-r--r-- | .gitignore | 9 | ||||
| -rw-r--r-- | Makefile | 218 | ||||
| -rw-r--r-- | conf/lab.mk | 1 | ||||
| -rwxr-xr-x | grade-lab-thread | 65 | ||||
| -rw-r--r-- | gradelib.py | 611 | ||||
| -rw-r--r-- | notxv6/barrier.c | 78 | ||||
| -rw-r--r-- | notxv6/ph.c | 150 | ||||
| -rw-r--r-- | user/uthread.c | 162 | ||||
| -rw-r--r-- | user/uthread_switch.S | 11 | 
9 files changed, 1294 insertions, 11 deletions
| @@ -15,3 +15,12 @@ mkfs  kernel/kernel  user/usys.S  .gdbinit +myapi.key +*-handin.tar.gz +xv6.out* +.vagrant/ +submissions/ +ph +barrier +/lab-*.json +.DS_Store
\ No newline at end of file @@ -1,14 +1,16 @@ + +# To compile and run with a lab solution, set the lab name in conf/lab.mk +# (e.g., LAB=util).  Run make grade to test solution with the lab's +# grade script (e.g., grade-lab-util). + +-include conf/lab.mk +  K=kernel  U=user  OBJS = \    $K/entry.o \ -  $K/start.o \ -  $K/console.o \ -  $K/printf.o \ -  $K/uart.o \    $K/kalloc.o \ -  $K/spinlock.o \    $K/string.o \    $K/main.o \    $K/vm.o \ @@ -30,6 +32,34 @@ OBJS = \    $K/plic.o \    $K/virtio_disk.o +OBJS_KCSAN = \ +  $K/start.o \ +  $K/console.o \ +  $K/printf.o \ +  $K/uart.o \ +  $K/spinlock.o + +ifdef KCSAN +OBJS_KCSAN += \ +	$K/kcsan.o +endif + +ifeq ($(LAB),$(filter $(LAB), lock)) +OBJS += \ +	$K/stats.o\ +	$K/sprintf.o +endif + + +ifeq ($(LAB),net) +OBJS += \ +	$K/e1000.o \ +	$K/net.o \ +	$K/sysnet.o \ +	$K/pci.o +endif + +  # riscv64-unknown-elf- or riscv64-linux-gnu-  # perhaps in /opt/riscv/bin  #TOOLPREFIX =  @@ -57,12 +87,28 @@ OBJCOPY = $(TOOLPREFIX)objcopy  OBJDUMP = $(TOOLPREFIX)objdump  CFLAGS = -Wall -Werror -O -fno-omit-frame-pointer -ggdb -gdwarf-2 + +ifdef LAB +LABUPPER = $(shell echo $(LAB) | tr a-z A-Z) +XCFLAGS += -DSOL_$(LABUPPER) -DLAB_$(LABUPPER) +endif + +CFLAGS += $(XCFLAGS)  CFLAGS += -MD  CFLAGS += -mcmodel=medany  CFLAGS += -ffreestanding -fno-common -nostdlib -mno-relax  CFLAGS += -I.  CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) +ifeq ($(LAB),net) +CFLAGS += -DNET_TESTS_PORT=$(SERVERPORT) +endif + +ifdef KCSAN +CFLAGS += -DKCSAN +KCSANFLAG = -fsanitize=thread -fno-inline +endif +  # Disable PIE when possible (for Ubuntu 16.10 toolchain)  ifneq ($(shell $(CC) -dumpspecs 2>/dev/null | grep -e '[^f]no-pie'),)  CFLAGS += -fno-pie -no-pie @@ -73,11 +119,17 @@ endif  LDFLAGS = -z max-page-size=4096 -$K/kernel: $(OBJS) $K/kernel.ld $U/initcode -	$(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS)  +$K/kernel: $(OBJS) $(OBJS_KCSAN) $K/kernel.ld $U/initcode +	$(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS) $(OBJS_KCSAN)  	$(OBJDUMP) -S $K/kernel > $K/kernel.asm  	$(OBJDUMP) -t $K/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $K/kernel.sym +$(OBJS): EXTRAFLAG := $(KCSANFLAG) + +$K/%.o: $K/%.c +	$(CC) $(CFLAGS) $(EXTRAFLAG) -c -o $@ $< + +  $U/initcode: $U/initcode.S  	$(CC) $(CFLAGS) -march=rv64g -nostdinc -I. -Ikernel -c $U/initcode.S -o $U/initcode.o  	$(LD) $(LDFLAGS) -N -e start -Ttext 0 -o $U/initcode.out $U/initcode.o @@ -89,6 +141,10 @@ tags: $(OBJS) _init  ULIB = $U/ulib.o $U/usys.o $U/printf.o $U/umalloc.o +ifeq ($(LAB),$(filter $(LAB), lock)) +ULIB += $U/statistics.o +endif +  _%: %.o $(ULIB)  	$(LD) $(LDFLAGS) -T $U/user.ld -o $@ $^  	$(OBJDUMP) -S $@ > $*.asm @@ -107,7 +163,7 @@ $U/_forktest: $U/forktest.o $(ULIB)  	$(OBJDUMP) -S $U/_forktest > $U/forktest.asm  mkfs/mkfs: mkfs/mkfs.c $K/fs.h $K/param.h -	gcc -Werror -Wall -I. -o mkfs/mkfs mkfs/mkfs.c +	gcc $(XCFLAGS) -Werror -Wall -I. -o mkfs/mkfs mkfs/mkfs.c  # Prevent deletion of intermediate files, e.g. cat.o, after first build, so  # that disk image changes after first build are persistent until clean.  More @@ -133,8 +189,79 @@ UPROGS=\  	$U/_wc\  	$U/_zombie\ -fs.img: mkfs/mkfs README $(UPROGS) -	mkfs/mkfs fs.img README $(UPROGS) + + + +ifeq ($(LAB),$(filter $(LAB), lock)) +UPROGS += \ +	$U/_stats +endif + +ifeq ($(LAB),traps) +UPROGS += \ +	$U/_call\ +	$U/_bttest +endif + +ifeq ($(LAB),lazy) +UPROGS += \ +	$U/_lazytests +endif + +ifeq ($(LAB),cow) +UPROGS += \ +	$U/_cowtest +endif + +ifeq ($(LAB),thread) +UPROGS += \ +	$U/_uthread + +$U/uthread_switch.o : $U/uthread_switch.S +	$(CC) $(CFLAGS) -c -o $U/uthread_switch.o $U/uthread_switch.S + +$U/_uthread: $U/uthread.o $U/uthread_switch.o $(ULIB) +	$(LD) $(LDFLAGS) -N -e main -Ttext 0 -o $U/_uthread $U/uthread.o $U/uthread_switch.o $(ULIB) +	$(OBJDUMP) -S $U/_uthread > $U/uthread.asm + +ph: notxv6/ph.c +	gcc -o ph -g -O2 $(XCFLAGS) notxv6/ph.c -pthread + +barrier: notxv6/barrier.c +	gcc -o barrier -g -O2 $(XCFLAGS) notxv6/barrier.c -pthread +endif + +ifeq ($(LAB),pgtbl) +UPROGS += \ +	$U/_pgtbltest +endif + +ifeq ($(LAB),lock) +UPROGS += \ +	$U/_kalloctest\ +	$U/_bcachetest +endif + +ifeq ($(LAB),fs) +UPROGS += \ +	$U/_bigfile +endif + + + +ifeq ($(LAB),net) +UPROGS += \ +	$U/_nettests +endif + +UEXTRA= +ifeq ($(LAB),util) +	UEXTRA += user/xargstest.sh +endif + + +fs.img: mkfs/mkfs README $(UEXTRA) $(UPROGS) +	mkfs/mkfs fs.img README $(UEXTRA) $(UPROGS)  -include kernel/*.d user/*.d @@ -144,7 +271,8 @@ clean:  	$U/initcode $U/initcode.out $K/kernel fs.img \  	mkfs/mkfs .gdbinit \          $U/usys.S \ -	$(UPROGS) +	$(UPROGS) \ +	ph barrier  # try to generate a unique GDB port  GDBPORT = $(shell expr `id -u` % 5000 + 25000) @@ -155,12 +283,22 @@ QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \  ifndef CPUS  CPUS := 3  endif +ifeq ($(LAB),fs) +CPUS := 1 +endif + +FWDPORT = $(shell expr `id -u` % 5000 + 25999)  QEMUOPTS = -machine virt -bios none -kernel $K/kernel -m 128M -smp $(CPUS) -nographic  QEMUOPTS += -global virtio-mmio.force-legacy=false  QEMUOPTS += -drive file=fs.img,if=none,format=raw,id=x0  QEMUOPTS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 +ifeq ($(LAB),net) +QEMUOPTS += -netdev user,id=net0,hostfwd=udp::$(FWDPORT)-:2000 -object filter-dump,id=net0,netdev=net0,file=packets.pcap +QEMUOPTS += -device e1000,netdev=net0,bus=pcie.0 +endif +  qemu: $K/kernel fs.img  	$(QEMU) $(QEMUOPTS) @@ -171,3 +309,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..1e20dd0 --- /dev/null +++ b/conf/lab.mk @@ -0,0 +1 @@ +LAB=thread diff --git a/grade-lab-thread b/grade-lab-thread new file mode 100755 index 0000000..e43d7c6 --- /dev/null +++ b/grade-lab-thread @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +import re +import subprocess +from gradelib import * + + +r = Runner(save("xv6.out")) + +@test(20, "uthread") +def test_uthread(): +    r.run_qemu(shell_script([ +        'uthread' +    ])) +    expected = ['thread_a started', 'thread_b started', 'thread_c started'] +    expected.extend(['thread_%s %d' % (tid, n) for n in range(100) for tid in ('c', 'a', 'b')]) +    expected.extend(['thread_c: exit after 100', 'thread_a: exit after 100', 'thread_b: exit after 100']) +    expected.append('thread_schedule: no runnable threads') +    if not re.findall('\n'.join(expected), r.qemu.output, re.M): +        raise AssertionError('Output does not match expected output') + +# test the first ph task: add locks to eliminate the missing keys. +@test(10, "ph_safe") +def test_ph_safe(): +    subprocess.run(['make', 'ph'], check=True) +    result = subprocess.run(['./ph', '2'], stdout=subprocess.PIPE, check=True) +    out = result.stdout.decode("utf-8") +    matches = re.findall(r'^\d+: (\d+) keys missing$', out, re.MULTILINE) +    assert_equal(len(matches), 2) +    assert_equal(int(matches[0]), 0) +    assert_equal(int(matches[1]), 0) + +# test the second ph task: locking that allows put() parallelism +@test(10, "ph_fast") +def test_ph_fast(): +    subprocess.run(['make', 'ph'], check=True) +    result = subprocess.run(['./ph', '2'], stdout=subprocess.PIPE, check=True) +    out = result.stdout.decode("utf-8") +    rate2 = re.findall(r' (\d+) puts.second$', out, re.MULTILINE) +    assert_equal(len(rate2), 1) +    result = subprocess.run(['./ph', '1'], stdout=subprocess.PIPE) +    out = result.stdout.decode("utf-8") +    rate1 = re.findall(r' (\d+) puts.second$', out, re.MULTILINE) +    assert_equal(len(rate1), 1) +    rate1 = float(rate1[0]) +    rate2 = float(rate2[0]) +    # demand that 2 threads yield at least 1.25x the +    # throughput of a single thread. +    if rate2 < 1.25 * rate1: +        raise AssertionError('Parallel put() speedup is less than 1.25x') + +@test(14, "barrier") +def test_barrier(): +    subprocess.run(['make', 'barrier']) +    result = subprocess.run(['./barrier', '2'], stdout=subprocess.PIPE) +    out = result.stdout.decode("utf-8") +    if not re.match(r'^OK; passed$', out): +        raise AssertionError('Barrier failed') + +@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/notxv6/barrier.c b/notxv6/barrier.c new file mode 100644 index 0000000..12793e8 --- /dev/null +++ b/notxv6/barrier.c @@ -0,0 +1,78 @@ +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <assert.h> +#include <pthread.h> + +static int nthread = 1; +static int round = 0; + +struct barrier { +  pthread_mutex_t barrier_mutex; +  pthread_cond_t barrier_cond; +  int nthread;      // Number of threads that have reached this round of the barrier +  int round;     // Barrier round +} bstate; + +static void +barrier_init(void) +{ +  assert(pthread_mutex_init(&bstate.barrier_mutex, NULL) == 0); +  assert(pthread_cond_init(&bstate.barrier_cond, NULL) == 0); +  bstate.nthread = 0; +} + +static void  +barrier() +{ +  // YOUR CODE HERE +  // +  // Block until all threads have called barrier() and +  // then increment bstate.round. +  // +   +} + +static void * +thread(void *xa) +{ +  long n = (long) xa; +  long delay; +  int i; + +  for (i = 0; i < 20000; i++) { +    int t = bstate.round; +    assert (i == t); +    barrier(); +    usleep(random() % 100); +  } + +  return 0; +} + +int +main(int argc, char *argv[]) +{ +  pthread_t *tha; +  void *value; +  long i; +  double t1, t0; + +  if (argc < 2) { +    fprintf(stderr, "%s: %s nthread\n", argv[0], argv[0]); +    exit(-1); +  } +  nthread = atoi(argv[1]); +  tha = malloc(sizeof(pthread_t) * nthread); +  srandom(0); + +  barrier_init(); + +  for(i = 0; i < nthread; i++) { +    assert(pthread_create(&tha[i], NULL, thread, (void *) i) == 0); +  } +  for(i = 0; i < nthread; i++) { +    assert(pthread_join(tha[i], &value) == 0); +  } +  printf("OK; passed\n"); +} diff --git a/notxv6/ph.c b/notxv6/ph.c new file mode 100644 index 0000000..82afe76 --- /dev/null +++ b/notxv6/ph.c @@ -0,0 +1,150 @@ +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <assert.h> +#include <pthread.h> +#include <sys/time.h> + +#define NBUCKET 5 +#define NKEYS 100000 + +struct entry { +  int key; +  int value; +  struct entry *next; +}; +struct entry *table[NBUCKET]; +int keys[NKEYS]; +int nthread = 1; + + +double +now() +{ + struct timeval tv; + gettimeofday(&tv, 0); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +static void  +insert(int key, int value, struct entry **p, struct entry *n) +{ +  struct entry *e = malloc(sizeof(struct entry)); +  e->key = key; +  e->value = value; +  e->next = n; +  *p = e; +} + +static  +void put(int key, int value) +{ +  int i = key % NBUCKET; + +  // is the key already present? +  struct entry *e = 0; +  for (e = table[i]; e != 0; e = e->next) { +    if (e->key == key) +      break; +  } +  if(e){ +    // update the existing key. +    e->value = value; +  } else { +    // the new is new. +    insert(key, value, &table[i], table[i]); +  } + +} + +static struct entry* +get(int key) +{ +  int i = key % NBUCKET; + + +  struct entry *e = 0; +  for (e = table[i]; e != 0; e = e->next) { +    if (e->key == key) break; +  } + +  return e; +} + +static void * +put_thread(void *xa) +{ +  int n = (int) (long) xa; // thread number +  int b = NKEYS/nthread; + +  for (int i = 0; i < b; i++) { +    put(keys[b*n + i], n); +  } + +  return NULL; +} + +static void * +get_thread(void *xa) +{ +  int n = (int) (long) xa; // thread number +  int missing = 0; + +  for (int i = 0; i < NKEYS; i++) { +    struct entry *e = get(keys[i]); +    if (e == 0) missing++; +  } +  printf("%d: %d keys missing\n", n, missing); +  return NULL; +} + +int +main(int argc, char *argv[]) +{ +  pthread_t *tha; +  void *value; +  double t1, t0; + + +  if (argc < 2) { +    fprintf(stderr, "Usage: %s nthreads\n", argv[0]); +    exit(-1); +  } +  nthread = atoi(argv[1]); +  tha = malloc(sizeof(pthread_t) * nthread); +  srandom(0); +  assert(NKEYS % nthread == 0); +  for (int i = 0; i < NKEYS; i++) { +    keys[i] = random(); +  } + +  // +  // first the puts +  // +  t0 = now(); +  for(int i = 0; i < nthread; i++) { +    assert(pthread_create(&tha[i], NULL, put_thread, (void *) (long) i) == 0); +  } +  for(int i = 0; i < nthread; i++) { +    assert(pthread_join(tha[i], &value) == 0); +  } +  t1 = now(); + +  printf("%d puts, %.3f seconds, %.0f puts/second\n", +         NKEYS, t1 - t0, NKEYS / (t1 - t0)); + +  // +  // now the gets +  // +  t0 = now(); +  for(int i = 0; i < nthread; i++) { +    assert(pthread_create(&tha[i], NULL, get_thread, (void *) (long) i) == 0); +  } +  for(int i = 0; i < nthread; i++) { +    assert(pthread_join(tha[i], &value) == 0); +  } +  t1 = now(); + +  printf("%d gets, %.3f seconds, %.0f gets/second\n", +         NKEYS*nthread, t1 - t0, (NKEYS*nthread) / (t1 - t0)); +} diff --git a/user/uthread.c b/user/uthread.c new file mode 100644 index 0000000..06349f5 --- /dev/null +++ b/user/uthread.c @@ -0,0 +1,162 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +/* Possible states of a thread: */ +#define FREE        0x0 +#define RUNNING     0x1 +#define RUNNABLE    0x2 + +#define STACK_SIZE  8192 +#define MAX_THREAD  4 + + +struct thread { +  char       stack[STACK_SIZE]; /* the thread's stack */ +  int        state;             /* FREE, RUNNING, RUNNABLE */ +}; +struct thread all_thread[MAX_THREAD]; +struct thread *current_thread; +extern void thread_switch(uint64, uint64); +               +void  +thread_init(void) +{ +  // main() is thread 0, which will make the first invocation to +  // thread_schedule().  it needs a stack so that the first thread_switch() can +  // save thread 0's state.  thread_schedule() won't run the main thread ever +  // again, because its state is set to RUNNING, and thread_schedule() selects +  // a RUNNABLE thread. +  current_thread = &all_thread[0]; +  current_thread->state = RUNNING; +} + +void  +thread_schedule(void) +{ +  struct thread *t, *next_thread; + +  /* Find another runnable thread. */ +  next_thread = 0; +  t = current_thread + 1; +  for(int i = 0; i < MAX_THREAD; i++){ +    if(t >= all_thread + MAX_THREAD) +      t = all_thread; +    if(t->state == RUNNABLE) { +      next_thread = t; +      break; +    } +    t = t + 1; +  } + +  if (next_thread == 0) { +    printf("thread_schedule: no runnable threads\n"); +    exit(-1); +  } + +  if (current_thread != next_thread) {         /* switch threads?  */ +    next_thread->state = RUNNING; +    t = current_thread; +    current_thread = next_thread; +    /* YOUR CODE HERE +     * Invoke thread_switch to switch from t to next_thread: +     * thread_switch(??, ??); +     */ +  } else +    next_thread = 0; +} + +void  +thread_create(void (*func)()) +{ +  struct thread *t; + +  for (t = all_thread; t < all_thread + MAX_THREAD; t++) { +    if (t->state == FREE) break; +  } +  t->state = RUNNABLE; +  // YOUR CODE HERE +} + +void  +thread_yield(void) +{ +  current_thread->state = RUNNABLE; +  thread_schedule(); +} + +volatile int a_started, b_started, c_started; +volatile int a_n, b_n, c_n; + +void  +thread_a(void) +{ +  int i; +  printf("thread_a started\n"); +  a_started = 1; +  while(b_started == 0 || c_started == 0) +    thread_yield(); +   +  for (i = 0; i < 100; i++) { +    printf("thread_a %d\n", i); +    a_n += 1; +    thread_yield(); +  } +  printf("thread_a: exit after %d\n", a_n); + +  current_thread->state = FREE; +  thread_schedule(); +} + +void  +thread_b(void) +{ +  int i; +  printf("thread_b started\n"); +  b_started = 1; +  while(a_started == 0 || c_started == 0) +    thread_yield(); +   +  for (i = 0; i < 100; i++) { +    printf("thread_b %d\n", i); +    b_n += 1; +    thread_yield(); +  } +  printf("thread_b: exit after %d\n", b_n); + +  current_thread->state = FREE; +  thread_schedule(); +} + +void  +thread_c(void) +{ +  int i; +  printf("thread_c started\n"); +  c_started = 1; +  while(a_started == 0 || b_started == 0) +    thread_yield(); +   +  for (i = 0; i < 100; i++) { +    printf("thread_c %d\n", i); +    c_n += 1; +    thread_yield(); +  } +  printf("thread_c: exit after %d\n", c_n); + +  current_thread->state = FREE; +  thread_schedule(); +} + +int  +main(int argc, char *argv[])  +{ +  a_started = b_started = c_started = 0; +  a_n = b_n = c_n = 0; +  thread_init(); +  thread_create(thread_a); +  thread_create(thread_b); +  thread_create(thread_c); +  thread_schedule(); +  exit(0); +} diff --git a/user/uthread_switch.S b/user/uthread_switch.S new file mode 100644 index 0000000..5defb12 --- /dev/null +++ b/user/uthread_switch.S @@ -0,0 +1,11 @@ +	.text + +	/* +         * save the old thread's registers, +         * restore the new thread's registers. +         */ + +	.globl thread_switch +thread_switch: +	/* YOUR CODE HERE */ +	ret    /* return to ra */ | 
