Compare commits

...

4 Commits

Author SHA1 Message Date
whatever
0d0a728f73 net done 2024-11-23 13:35:55 +08:00
whatever
079dcbaa31 personal commit 2024-11-20 23:00:16 +08:00
Ivy Wu
0b94a6a45f update lab net 2024-10-10 22:49:32 -04:00
Frans Kaashoek
10584ccdde net lab 2024-10-07 15:05:55 -04:00
27 changed files with 3219 additions and 35 deletions

12
.gitignore vendored
View File

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

231
Makefile
View File

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

1
conf/lab.mk Normal file
View File

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

66
grade-lab-net Executable file
View File

@ -0,0 +1,66 @@
#!/usr/bin/env python3
import re
import subprocess
from gradelib import *
r = Runner(save("xv6.out"))
serverout = None
@test(0, "running nettest")
def test_nettest():
global serverout
server = subprocess.Popen(["python3", "./nettest.py", "grade"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
r.run_qemu(shell_script([
'nettest grade'
]), timeout=30)
server.terminate()
serverout = server.communicate()[0].decode()
@test(20, "nettest: txone", parent=test_nettest)
def test_nettest_():
assert_lines_match(serverout, 'txone: OK')
@test(20, "nettest: arp_rx", parent=test_nettest)
def test_nettest_():
r.match('arp_rx: received an ARP packet')
@test(20, "nettest: ip_rx", parent=test_nettest)
def test_nettest_():
r.match('ip_rx: received an IP packet')
@test(20, "nettest: ping0", parent=test_nettest)
def test_nettest_():
r.match('^ping0: OK$')
@test(20, "nettest: ping1", parent=test_nettest)
def test_nettest_():
r.match('^ping1: OK$')
@test(20, "nettest: ping2", parent=test_nettest)
def test_nettest_():
r.match('^ping2: OK$')
@test(30, "nettest: ping3", parent=test_nettest)
def test_nettest_():
r.match('^ping3: OK$')
@test(10, "nettest: dns", parent=test_nettest)
def test_nettest_():
r.match('^dns: OK$')
@test(10, "nettest: free", parent=test_nettest)
def test_nettest_():
r.match('^free: 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()

628
gradelib.py Normal file
View File

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

View File

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

153
kernel/e1000.c Normal file
View File

@ -0,0 +1,153 @@
#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"
#define TX_RING_SIZE 16
static struct tx_desc tx_ring[TX_RING_SIZE] __attribute__((aligned(16)));
static char *tx_bufs[TX_RING_SIZE];
#define RX_RING_SIZE 16
static struct rx_desc rx_ring[RX_RING_SIZE] __attribute__((aligned(16)));
static char *rx_bufs[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_bufs[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_bufs[i] = kalloc();
if (!rx_bufs[i])
panic("e1000");
rx_ring[i].addr = (uint64) rx_bufs[i];
}
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(char *buf, int len)
{
//
// Your code here.
//
// buf 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 send completes.
//
acquire(&e1000_lock);
struct tx_desc *cur=&tx_ring[regs[E1000_TDT]];
if(cur->status!=E1000_TXD_STAT_DD)
goto err;
if(cur->addr)
kfree((void*)cur->addr);
cur->length=len;
cur->addr=(uint64)buf;
cur->cmd=E1000_TXD_CMD_EOP|E1000_TXD_CMD_RS;
cur->status=0;
regs[E1000_TDT]=(regs[E1000_TDT]+1)%TX_RING_SIZE;
release(&e1000_lock);
return 0;
err:
release(&e1000_lock);
return -1;
}
static void
e1000_recv(void)
{
//
// Your code here.
//
// Check for packets that have arrived from the e1000
// Create and deliver a buf for each packet (using net_rx()).
//
while(1){
const uint32 id=(regs[E1000_RDT]+1)%RX_RING_SIZE;
struct rx_desc *cur=&rx_ring[id];
if((cur->status&E1000_RXD_STAT_DD)==0)
break;
net_rx((char*)cur->addr,cur->length);
cur->status=0;
rx_bufs[id]=kalloc();
cur->addr=(uint64)rx_bufs[id];
regs[E1000_RDT]=id;
}
}
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();
}

125
kernel/e1000_dev.h Normal file
View File

@ -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 Descriptor 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 0x04000000 /* 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;
};

View File

@ -38,3 +38,4 @@ struct devsw {
extern struct devsw devsw[];
#define CONSOLE 1
#define STATS 2

View File

@ -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,17 @@ main()
iinit(); // inode table
fileinit(); // file table
virtio_disk_init(); // emulated hard disk
#ifdef LAB_NET
pci_init();
#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());

View File

@ -25,6 +25,10 @@
#define VIRTIO0 0x10001000
#define VIRTIO0_IRQ 1
#ifdef LAB_NET
#define E1000_IRQ 33
#endif
// qemu puts platform-level interrupt controller (PLIC) here.
#define PLIC 0x0c000000L
#define PLIC_PRIORITY (PLIC + 0x0)
@ -45,7 +49,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:
@ -54,6 +58,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

324
kernel/net.c Normal file
View File

@ -0,0 +1,324 @@
#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"
// xv6's ethernet and IP addresses
static uint8 local_mac[ETHADDR_LEN] = { 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 };
static uint32 local_ip = MAKE_IP_ADDR(10, 0, 2, 15);
// qemu host's ethernet address.
static uint8 host_mac[ETHADDR_LEN] = { 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02 };
static struct spinlock netlock;
void
netinit(void)
{
initlock(&netlock, "netlock");
}
#define MAXPORT 10000+1
#define MAXCNT 16
static void *udpq[MAXPORT][MAXCNT];
static char udph[MAXPORT],udpt[MAXPORT],udpc[MAXPORT];
static int pused[MAXPORT];
//
// bind(int port)
// prepare to receive UDP packets address to the port,
// i.e. allocate any queues &c needed.
//
uint64
sys_bind(void)
{
//
// Your code here.
//
short port;
argint(0, (int *)&port);
pused[port] = 1;
return -1;
}
//
// unbind(int port)
// release any resources previously created by bind(port);
// from now on UDP packets addressed to port should be dropped.
//
uint64
sys_unbind(void)
{
//
// Optional: Your code here.
//
return 0;
}
//
// recv(int dport, int *src, short *sport, char *buf, int maxlen)
// if there's a received UDP packet already queued that was
// addressed to dport, then return it.
// otherwise wait for such a packet.
//
// sets *src to the IP source address.
// sets *sport to the UDP source port.
// copies up to maxlen bytes of UDP payload to buf.
// returns the number of bytes copied,
// and -1 if there was an error.
//
// dport, *src, and *sport are host byte order.
// bind(dport) must previously have been called.
//
uint64
sys_recv(void)
{
//
// Your code here.
//
acquire(&netlock);
struct proc *p = myproc();
int dport;
uint64 src,sport,buf;
int maxlen;
argint(0, (int*)&dport);
argaddr(1, &src);
argaddr(2, &sport);
argaddr(3, &buf);
argint(4, &maxlen);
while(!udpc[dport]){
if(p->killed)
goto err;
sleep(&udpc[dport], &netlock);
if(p->killed)
goto err;
}
struct eth *eth=(struct eth *)udpq[dport][(int)udph[dport]];
struct ip *ip=(struct ip *)(eth+1);
struct udp *udp=(struct udp *)(ip+1);
const int srcbuf=ntohl(ip->ip_src);
const short sportbuf=ntohs(udp->sport);
if(copyout(p->pagetable,src,(char *)&srcbuf,sizeof(int))<0){
goto err;
}
if(copyout(p->pagetable,sport,(char *)&sportbuf,sizeof(short))<0){
goto err;
}
char *payload=(char *)(udp+1);
const int len=ntohs(udp->ulen)-sizeof(struct udp);
if(len>maxlen){
goto err;
}
if(copyout(p->pagetable,buf,payload,len)<0){
goto err;
}
kfree(eth);
--udpc[dport];
udph[dport]=(udph[dport]+1)%MAXCNT;
release(&netlock);
return len;
err:
release(&netlock);
return -1;
}
// 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;
}
//
// send(int sport, int dst, int dport, char *buf, int len)
//
uint64
sys_send(void)
{
struct proc *p = myproc();
int sport;
int dst;
int dport;
uint64 bufaddr;
int len;
argint(0, &sport);
argint(1, &dst);
argint(2, &dport);
argaddr(3, &bufaddr);
argint(4, &len);
int total = len + sizeof(struct eth) + sizeof(struct ip) + sizeof(struct udp);
if(total > PGSIZE)
return -1;
char *buf = kalloc();
if(buf == 0){
printf("sys_send: kalloc failed\n");
return -1;
}
memset(buf, 0, PGSIZE);
struct eth *eth = (struct eth *) buf;
memmove(eth->dhost, host_mac, ETHADDR_LEN);
memmove(eth->shost, local_mac, ETHADDR_LEN);
eth->type = htons(ETHTYPE_IP);
struct ip *ip = (struct ip *)(eth + 1);
ip->ip_vhl = 0x45; // version 4, header length 4*5
ip->ip_tos = 0;
ip->ip_len = htons(sizeof(struct ip) + sizeof(struct udp) + len);
ip->ip_id = 0;
ip->ip_off = 0;
ip->ip_ttl = 100;
ip->ip_p = IPPROTO_UDP;
ip->ip_src = htonl(local_ip);
ip->ip_dst = htonl(dst);
ip->ip_sum = in_cksum((unsigned char *)ip, sizeof(*ip));
struct udp *udp = (struct udp *)(ip + 1);
udp->sport = htons(sport);
udp->dport = htons(dport);
udp->ulen = htons(len + sizeof(struct udp));
char *payload = (char *)(udp + 1);
if(copyin(p->pagetable, payload, bufaddr, len) < 0){
kfree(buf);
printf("send: copyin failed\n");
return -1;
}
e1000_transmit(buf, total);
return 0;
}
void
ip_rx(char *buf, int len)
{
// don't delete this printf; make grade depends on it.
static int seen_ip = 0;
if(seen_ip == 0)
printf("ip_rx: received an IP packet\n");
seen_ip = 1;
//
// Your code here.
//
if(len<sizeof(struct eth)+sizeof(struct ip)+sizeof(struct udp))
return;
struct eth *eth=(struct eth *)buf;
struct ip *ip=(struct ip *)(eth+1);
struct udp *udp=(struct udp *)(ip+1);
const short dport=ntohs(udp->dport);
if(!pused[dport])
goto abandon;
if(udpc[dport]==MAXCNT)
goto abandon;
udpq[dport][(int)udpt[dport]]=buf;//tail
udpt[dport]=(udpt[dport]+1)%MAXCNT;
++udpc[dport];
wakeup(&udpc[dport]);
return;
abandon:
kfree(buf);
}
//
// send an ARP reply packet to tell qemu to map
// xv6's ip address to its ethernet address.
// this is the bare minimum needed to persuade
// qemu to send IP packets to xv6; the real ARP
// protocol is more complex.
//
void
arp_rx(char *inbuf)
{
static int seen_arp = 0;
if(seen_arp){
kfree(inbuf);
return;
}
printf("arp_rx: received an ARP packet\n");
seen_arp = 1;
struct eth *ineth = (struct eth *) inbuf;
struct arp *inarp = (struct arp *) (ineth + 1);
char *buf = kalloc();
if(buf == 0)
panic("send_arp_reply");
struct eth *eth = (struct eth *) buf;
memmove(eth->dhost, ineth->shost, ETHADDR_LEN); // ethernet destination = query source
memmove(eth->shost, local_mac, ETHADDR_LEN); // ethernet source = xv6's ethernet address
eth->type = htons(ETHTYPE_ARP);
struct arp *arp = (struct arp *)(eth + 1);
arp->hrd = htons(ARP_HRD_ETHER);
arp->pro = htons(ETHTYPE_IP);
arp->hln = ETHADDR_LEN;
arp->pln = sizeof(uint32);
arp->op = htons(ARP_OP_REPLY);
memmove(arp->sha, local_mac, ETHADDR_LEN);
arp->sip = htonl(local_ip);
memmove(arp->tha, ineth->shost, ETHADDR_LEN);
arp->tip = inarp->sip;
e1000_transmit(buf, sizeof(*eth) + sizeof(*arp));
kfree(inbuf);
}
void
net_rx(char *buf, int len)
{
struct eth *eth = (struct eth *) buf;
if(len >= sizeof(struct eth) + sizeof(struct arp) &&
ntohs(eth->type) == ETHTYPE_ARP){
arp_rx(buf);
} else if(len >= sizeof(struct eth) + sizeof(struct ip) &&
ntohs(eth->type) == ETHTYPE_IP){
ip_rx(buf, len);
} else {
kfree(buf);
}
}

127
kernel/net.h Normal file
View File

@ -0,0 +1,127 @@
//
// 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, including this IP header
uint16 ip_id; // identification
uint16 ip_off; // fragment offset field
uint8 ip_ttl; // time to live
uint8 ip_p; // protocol
uint16 ip_sum; // checksum, covers just IP header
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 with the hw addr of the 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));

61
kernel/pci.c Normal file
View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -102,6 +102,17 @@ extern uint64 sys_link(void);
extern uint64 sys_mkdir(void);
extern uint64 sys_close(void);
#ifdef LAB_NET
extern uint64 sys_bind(void);
extern uint64 sys_unbind(void);
extern uint64 sys_send(void);
extern uint64 sys_recv(void);
#endif
#ifdef LAB_PGTBL
extern uint64 sys_pgpte(void);
extern uint64 sys_kpgtbl(void);
#endif
// An array mapping syscall numbers from syscall.h
// to the function that handles the system call.
static uint64 (*syscalls[])(void) = {
@ -126,8 +137,20 @@ static uint64 (*syscalls[])(void) = {
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
#ifdef LAB_NET
[SYS_bind] sys_bind,
[SYS_unbind] sys_unbind,
[SYS_send] sys_send,
[SYS_recv] sys_recv,
#endif
#ifdef LAB_PGTBL
[SYS_pgpte] sys_pgpte,
[SYS_kpgtbl] sys_kpgtbl,
#endif
};
void
syscall(void)
{

View File

@ -20,3 +20,18 @@
#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_bind 29
#define SYS_unbind 30
#define SYS_send 31
#define SYS_recv 32
#define SYS_pgpte 33
#define SYS_kpgtbl 34

View File

@ -68,6 +68,8 @@ usertrap(void)
} else if((which_dev = devintr()) != 0){
// ok
} else {
printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid);
printf(" sepc=0x%lx stval=0x%lx\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)
@ -147,6 +150,7 @@ kerneltrap()
if((which_dev = devintr()) == 0){
// interrupt or trap from an unknown source
printf("scause=0x%lx sepc=0x%lx stval=0x%lx\n", scause, r_sepc(), r_stval());
printf("pid=%d\n",myproc()->pid);
panic("kerneltrap");
}
@ -196,7 +200,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);
}

View File

@ -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, 0x4000000, PTE_R | PTE_W);
@ -92,6 +102,11 @@ walk(pagetable_t pagetable, uint64 va, int alloc)
pte_t *pte = &pagetable[PX(level, va)];
if(*pte & PTE_V) {
pagetable = (pagetable_t)PTE2PA(*pte);
#ifdef LAB_PGTBL
if(PTE_LEAF(*pte)) {
return pte;
}
#endif
} else {
if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)
return 0;
@ -125,6 +140,7 @@ walkaddr(pagetable_t pagetable, uint64 va)
return pa;
}
// add a mapping to the kernel page table.
// only used when booting.
// does not flush TLB or enable paging.
@ -179,15 +195,19 @@ uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
uint64 a;
pte_t *pte;
int sz;
if((va % PGSIZE) != 0)
panic("uvmunmap: not aligned");
for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
for(a = va; a < va + npages*PGSIZE; a += sz){
sz = PGSIZE;
if((pte = walk(pagetable, a, 0)) == 0)
panic("uvmunmap: walk");
if((*pte & PTE_V) == 0)
if((*pte & PTE_V) == 0) {
printf("va=%ld pte=%ld\n", a, *pte);
panic("uvmunmap: not mapped");
}
if(PTE_FLAGS(*pte) == PTE_V)
panic("uvmunmap: not a leaf");
if(do_free){
@ -227,6 +247,7 @@ uvmfirst(pagetable_t pagetable, uchar *src, uint sz)
memmove(mem, src, sz);
}
// Allocate PTEs and physical memory to grow process from oldsz to
// newsz, which need not be page aligned. Returns new size or 0 on error.
uint64
@ -234,19 +255,23 @@ uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz, int xperm)
{
char *mem;
uint64 a;
int sz;
if(newsz < oldsz)
return oldsz;
oldsz = PGROUNDUP(oldsz);
for(a = oldsz; a < newsz; a += PGSIZE){
for(a = oldsz; a < newsz; a += sz){
sz = PGSIZE;
mem = kalloc();
if(mem == 0){
uvmdealloc(pagetable, a, oldsz);
return 0;
}
memset(mem, 0, PGSIZE);
if(mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_R|PTE_U|xperm) != 0){
#ifndef LAB_SYSCALL
memset(mem, 0, sz);
#endif
if(mappages(pagetable, a, sz, (uint64)mem, PTE_R|PTE_U|xperm) != 0){
kfree(mem);
uvmdealloc(pagetable, a, oldsz);
return 0;
@ -316,8 +341,11 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
uint64 pa, i;
uint flags;
char *mem;
int szinc;
for(i = 0; i < sz; i += PGSIZE){
for(i = 0; i < sz; i += szinc){
szinc = PGSIZE;
szinc = PGSIZE;
if((pte = walk(old, i, 0)) == 0)
panic("uvmcopy: pte should exist");
if((*pte & PTE_V) == 0)
@ -363,13 +391,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 +425,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 +485,20 @@ copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
return -1;
}
}
#ifdef LAB_PGTBL
void
vmprint(pagetable_t pagetable) {
// your code here
}
#endif
#ifdef LAB_PGTBL
pte_t*
pgpte(pagetable_t pagetable, uint64 va) {
return walk(pagetable, va, 0);
}
#endif

169
nettest.py Executable file
View File

@ -0,0 +1,169 @@
#!/usr/bin/env python3
#
# net tests
# to be used with user/nettest.c
#
import socket
import sys
import time
import os
# qemu listens for packets sent to FWDPORT,
# and re-writes them so they arrive in
# xv6 with destination port 2000.
FWDPORT1 = (os.getuid() % 5000) + 25999
FWDPORT2 = (os.getuid() % 5000) + 30999
# xv6's nettest.c tx sends to SERVERPORT.
SERVERPORT = (os.getuid() % 5000) + 25099
def usage():
sys.stderr.write("Usage: nettest.py txone\n")
sys.stderr.write(" nettest.py rxone\n")
sys.stderr.write(" nettest.py rx\n")
sys.stderr.write(" nettest.py rx2\n")
sys.stderr.write(" nettest.py rxburst\n")
sys.stderr.write(" nettest.py tx\n")
sys.stderr.write(" nettest.py ping\n")
sys.stderr.write(" nettest.py grade\n")
sys.exit(1)
if len(sys.argv) != 2:
usage()
if sys.argv[1] == "txone":
#
# listen for a single UDP packet sent by xv6's nettest txone.
# nettest.py must be started before xv6's nettest txone.
#
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('127.0.0.1', SERVERPORT))
print("tx: listening for a UDP packet")
buf0, raddr0 = sock.recvfrom(4096)
if buf0 == b'txone':
print("txone: OK")
else:
print("txone: unexpected payload %s" % (buf0))
elif sys.argv[1] == "rxone":
#
# send a single UDP packet to xv6 to test e1000_recv().
# should result in arp_rx() printing
# arp_rx: received an ARP packet
# and ip_rx() printing
# ip_rx: received an IP packet
#
print("txone: sending one UDP packet")
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(b'xyz', ("127.0.0.1", FWDPORT1))
elif sys.argv[1] == "rx":
#
# test the xv6 receive path by sending a slow
# stream of UDP packets, which should appear
# on port 2000.
#
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
i = 0
while True:
txt = "packet %d" % (i)
sys.stderr.write("%s\n" % txt)
buf = txt.encode('ascii', 'ignore')
sock.sendto(buf, ("127.0.0.1", FWDPORT1))
time.sleep(1)
i += 1
elif sys.argv[1] == "rx2":
#
# send to two different UDP ports, to see
# if xv6 keeps them separate.
#
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
i = 0
while True:
txt = "one %d" % (i)
sys.stderr.write("%s\n" % txt)
buf = txt.encode('ascii', 'ignore')
sock.sendto(buf, ("127.0.0.1", FWDPORT1))
txt = "two %d" % (i)
sys.stderr.write("%s\n" % txt)
buf = txt.encode('ascii', 'ignore')
sock.sendto(buf, ("127.0.0.1", FWDPORT2))
time.sleep(1)
i += 1
elif sys.argv[1] == "rxburst":
#
# send a big burst of packets to 2001, then
# a packet to 2000.
#
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
i = 0
while True:
for ii in range(0, 32):
txt = "packet %d" % (i)
# sys.stderr.write("%s\n" % txt)
buf = txt.encode('ascii', 'ignore')
sock.sendto(buf, ("127.0.0.1", FWDPORT2))
txt = "packet %d" % (i)
sys.stderr.write("%s\n" % txt)
buf = txt.encode('ascii', 'ignore')
sock.sendto(buf, ("127.0.0.1", FWDPORT1))
time.sleep(1)
i += 1
elif sys.argv[1] == "tx":
#
# listen for UDP packets sent by xv6's nettest tx.
# nettest.py must be started before xv6's nettest tx.
#
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('127.0.0.1', SERVERPORT))
print("tx: listening for UDP packets")
buf0, raddr0 = sock.recvfrom(4096)
buf1, raddr1 = sock.recvfrom(4096)
if buf0 == b't 0' and buf1 == b't 1':
print("tx: OK")
else:
print("tx: unexpected packets %s and %s" % (buf0, buf1))
elif sys.argv[1] == "ping":
#
# listen for UDP packets sent by xv6's nettest ping,
# and send them back.
# nettest.py must be started before xv6's nettest.
#
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('127.0.0.1', SERVERPORT))
print("ping: listening for UDP packets")
while True:
buf, raddr = sock.recvfrom(4096)
sock.sendto(buf, raddr)
elif sys.argv[1] == "grade":
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('127.0.0.1', SERVERPORT))
# first, listen for a single UDP packet sent by xv6,
# in order to test only e1000_transmit(), in a situation
# where perhaps e1000_recv() has not yet been implemented.
buf, raddr = sock.recvfrom(4096)
if buf == b'txone':
print("txone: OK")
else:
print("txone: received incorrect payload %s" % (buf))
sys.stdout.flush()
sys.stderr.flush()
# second, send a single UDP packet, to test
# e1000_recv() -- received by user/nettest.c's rxone().
print("rxone: sending one UDP packet")
sock1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock1.sendto(b'rxone', ("127.0.0.1", FWDPORT2))
# third, act as a ping reflector.
while True:
buf, raddr = sock.recvfrom(4096)
sock.sendto(buf, raddr)
else:
usage()

1
time.txt Normal file
View File

@ -0,0 +1 @@
5

945
user/nettest.c Normal file
View File

@ -0,0 +1,945 @@
//
// network tests
// to be used with nettest.py (run outside of qemu)
//
#include "kernel/types.h"
#include "kernel/net.h"
#include "kernel/stat.h"
#include "user/user.h"
#define DEBUG_INFO printf("File: %s, Line: %d\n", __FILE__, __LINE__)
//
// send a single UDP packet (but don't recv() the reply).
// python3 nettest.py txone can be used to wait for
// this packet, and you can also see what
// happened with tcpdump -XXnr packets.pcap
//
void
txone()
{
printf("txone: sending one packet\n");
uint32 dst = 0x0A000202; // 10.0.2.2
int dport = NET_TESTS_PORT;
char buf[5];
buf[0] = 't';
buf[1] = 'x';
buf[2] = 'o';
buf[3] = 'n';
buf[4] = 'e';
if(send(2003, dst, dport, buf, 5) < 0){
printf("txone: send() failed\n");
}
}
//
// test just receive.
// outside of qemu, run
// ./nettest.py rx
//
int
rx(char *name)
{
bind(2000);
int lastseq = -1;
int ok = 0;
while(ok < 4){
char ibuf[128];
uint32 src;
uint16 sport;
int cc = recv(2000, &src, &sport, ibuf, sizeof(ibuf)-1);
if(cc < 0){
fprintf(2, "nettest %s: recv() failed\n", name);
return 0;
}
if(src != 0x0A000202){ // 10.0.2.2
printf("wrong ip src %x\n", src);
return 0;
}
if(cc < strlen("packet 1")){
printf("len %d too short\n", cc);
return 0;
}
if(cc > strlen("packet xxxxxx")){
printf("len %d too long\n", cc);
return 0;
}
if(memcmp(ibuf, "packet ", strlen("packet ")) != 0){
printf("packet doesn't start with packet\n");
return 0;
}
ibuf[cc] = '\0';
#define isdigit(x) ((x) >= '0' && (x) <= '9')
if(!isdigit(ibuf[7])){
printf("packet doesn't contain a number\n");
return 0;
}
for(int i = 7; i < cc; i++){
if(!isdigit(ibuf[i])){
printf("packet contains non-digits in the number\n");
return 0;
}
}
int seq = ibuf[7] - '0';
if(isdigit(ibuf[8])){
seq *= 10;
seq += ibuf[8] - '0';
if(isdigit(ibuf[9])){
seq *= 10;
seq += ibuf[9] - '0';
}
}
if(lastseq != -1){
if(seq != lastseq + 1){
printf("got seq %d, expecting %d\n", seq, lastseq + 1);
return 0;
}
}
lastseq = seq;
ok += 1;
}
printf("%s: OK\n", name);
return 1;
}
//
// test receive on two different ports, interleaved.
// outside of qemu, run
// ./nettest.py rx2
//
int
rx2()
{
bind(2000);
bind(2001);
for(int i = 0; i < 3; i++){
char ibuf[128];
uint32 src;
uint16 sport;
int cc = recv(2000, &src, &sport, ibuf, sizeof(ibuf)-1);
if(cc < 0){
fprintf(2, "nettest rx2: recv() failed\n");
return 0;
}
if(src != 0x0A000202){ // 10.0.2.2
printf("wrong ip src %x\n", src);
return 0;
}
if(cc < strlen("one 1")){
printf("len %d too short\n", cc);
return 0;
}
if(cc > strlen("one xxxxxx")){
printf("len %d too long\n", cc);
return 0;
}
if(memcmp(ibuf, "one ", strlen("one ")) != 0){
printf("packet doesn't start with one\n");
return 0;
}
}
for(int i = 0; i < 3; i++){
char ibuf[128];
uint32 src;
uint16 sport;
int cc = recv(2001, &src, &sport, ibuf, sizeof(ibuf)-1);
if(cc < 0){
fprintf(2, "nettest rx2: recv() failed\n");
return 0;
}
if(src != 0x0A000202){ // 10.0.2.2
printf("wrong ip src %x\n", src);
return 0;
}
if(cc < strlen("one 1")){
printf("len %d too short\n", cc);
return 0;
}
if(cc > strlen("one xxxxxx")){
printf("len %d too long\n", cc);
return 0;
}
if(memcmp(ibuf, "two ", strlen("two ")) != 0){
printf("packet doesn't start with two\n");
return 0;
}
}
for(int i = 0; i < 3; i++){
char ibuf[128];
uint32 src;
uint16 sport;
int cc = recv(2000, &src, &sport, ibuf, sizeof(ibuf)-1);
if(cc < 0){
fprintf(2, "nettest rx2: recv() failed\n");
return 0;
}
if(src != 0x0A000202){ // 10.0.2.2
printf("wrong ip src %x\n", src);
return 0;
}
if(cc < strlen("one 1")){
printf("len %d too short\n", cc);
return 0;
}
if(cc > strlen("one xxxxxx")){
printf("len %d too long\n", cc);
return 0;
}
if(memcmp(ibuf, "one ", strlen("one ")) != 0){
printf("packet doesn't start with one\n");
return 0;
}
}
printf("rx2: OK\n");
return 1;
}
//
// send some UDP packets to nettest.py tx.
//
int
tx()
{
for(int ii = 0; ii < 5; ii++){
uint32 dst = 0x0A000202; // 10.0.2.2
int dport = NET_TESTS_PORT;
char buf[3];
buf[0] = 't';
buf[1] = ' ';
buf[2] = '0' + ii;
if(send(2000, dst, dport, buf, 3) < 0){
printf("send() failed\n");
return 0;
}
sleep(10);
}
// can't actually tell if the packets arrived.
return 1;
}
//
// send just one UDP packets to nettest.py ping,
// expect a reply.
// nettest.py ping must be started first.
//
int
ping0()
{
printf("ping0: starting\n");
bind(2004);
uint32 dst = 0x0A000202; // 10.0.2.2
int dport = NET_TESTS_PORT;
char buf[5];
memcpy(buf, "ping0", sizeof(buf));
if(send(2004, dst, dport, buf, sizeof(buf)) < 0){
printf("ping0: send() failed\n");
return 0;
}
char ibuf[128];
uint32 src = 0;
uint16 sport = 0;
memset(ibuf, 0, sizeof(ibuf));
int cc = recv(2004, &src, &sport, ibuf, sizeof(ibuf)-1);
if(cc < 0){
fprintf(2, "ping0: recv() failed\n");
return 0;
}
if(src != 0x0A000202){ // 10.0.2.2
printf("ping0: wrong ip src %x, expecting %x\n", src, 0x0A000202);
return 0;
}
if(sport != NET_TESTS_PORT){
printf("ping0: wrong sport %d, expecting %d\n", sport, NET_TESTS_PORT);
return 0;
}
if(memcmp(buf, ibuf, sizeof(buf)) != 0){
printf("ping0: wrong content\n");
return 0;
}
if(cc != sizeof(buf)){
printf("ping0: wrong length %d, expecting %ld\n", cc, sizeof(buf));
return 0;
}
printf("ping0: OK\n");
return 1;
}
//
// send many UDP packets to nettest.py ping,
// expect a reply to each.
// nettest.py ping must be started first.
//
int
ping1()
{
printf("ping1: starting\n");
bind(2005);
for(int ii = 0; ii < 20; ii++){
uint32 dst = 0x0A000202; // 10.0.2.2
int dport = NET_TESTS_PORT;
char buf[3];
buf[0] = 'p';
buf[1] = ' ';
buf[2] = '0' + ii;
if(send(2005, dst, dport, buf, 3) < 0){
printf("ping1: send() failed\n");
return 0;
}
char ibuf[128];
uint32 src = 0;
uint16 sport = 0;
memset(ibuf, 0, sizeof(ibuf));
int cc = recv(2005, &src, &sport, ibuf, sizeof(ibuf)-1);
if(cc < 0){
fprintf(2, "ping1: recv() failed\n");
return 0;
}
if(src != 0x0A000202){ // 10.0.2.2
printf("ping1: wrong ip src %x, expecting %x\n", src, 0x0A000202);
return 0;
}
if(sport != NET_TESTS_PORT){
printf("ping1: wrong sport %d, expecting %d\n", sport, NET_TESTS_PORT);
return 0;
}
if(memcmp(buf, ibuf, 3) != 0){
printf("ping1: wrong content\n");
return 0;
}
if(cc != 3){
printf("ping1: wrong length %d, expecting 3\n", cc);
return 0;
}
}
printf("ping1: OK\n");
return 1;
}
//
// send UDP packets from two different ports to nettest.py ping,
// expect a reply to each to appear on the correct port.
// nettest.py ping must be started first.
//
int
ping2()
{
printf("ping2: starting\n");
bind(2006);
bind(2007);
for(int ii = 0; ii < 5; ii++){
for(int port = 2006; port <= 2007; port++){
uint32 dst = 0x0A000202; // 10.0.2.2
int dport = NET_TESTS_PORT;
char buf[4];
buf[0] = 'p';
buf[1] = ' ';
buf[2] = (port == 2006 ? 'a' : 'A') + ii;
buf[3] = '!';
if(send(port, dst, dport, buf, 4) < 0){
printf("ping2: send() failed\n");
return 0;
}
}
}
for(int port = 2006; port <= 2007; port++){
for(int ii = 0; ii < 5; ii++){
char ibuf[128];
uint32 src = 0;
uint16 sport = 0;
memset(ibuf, 0, sizeof(ibuf));
int cc = recv(port, &src, &sport, ibuf, sizeof(ibuf)-1);
if(cc < 0){
fprintf(2, "ping2: recv() failed\n");
return 0;
}
if(src != 0x0A000202){ // 10.0.2.2
printf("ping2: wrong ip src %x\n", src);
return 0;
}
if(sport != NET_TESTS_PORT){
printf("ping2: wrong sport %d\n", sport);
return 0;
}
if(cc != 4){
printf("ping2: wrong length %d\n", cc);
return 0;
}
// printf("port=%d ii=%d: %c%c%c\n", port, ii, ibuf[0], ibuf[1], ibuf[2]);
char buf[4];
buf[0] = 'p';
buf[1] = ' ';
buf[2] = (port == 2006 ? 'a' : 'A') + ii;
buf[3] = '!';
if(memcmp(buf, ibuf, 3) != 0){
// possibly recv() sees packets out of order.
printf("ping2: wrong content\n");
return 0;
}
}
}
printf("ping2: OK\n");
return 1;
}
//
// send a big burst of packets from ports 2008 and 2010,
// causing drops,
// bracketed by two packets from port 2009.
// check that the two packets can be recv()'d on port 2009.
// check that port 2008 had a finite queue length (dropped some).
// nettest.py ping must be started first.
//
int
ping3()
{
printf("ping3: starting\n");
bind(2008);
bind(2009);
//
// send one packet on 2009.
//
{
uint32 dst = 0x0A000202; // 10.0.2.2
int dport = NET_TESTS_PORT;
char buf[4];
buf[0] = 'p';
buf[1] = ' ';
buf[2] = 'A';
buf[3] = '!';
if(send(2009, dst, dport, buf, 4) < 0){
printf("ping3: send() failed\n");
return 0;
}
}
sleep(1);
//
// send so many packets from 2008 and 2010 that some of the
// replies must be dropped due to the requirement
// for finite maximum queueing.
//
for(int ii = 0; ii < 257; ii++){
uint32 dst = 0x0A000202; // 10.0.2.2
int dport = NET_TESTS_PORT;
char buf[4];
buf[0] = 'p';
buf[1] = ' ';
buf[2] = 'a' + ii;
buf[3] = '!';
int port = 2008 + (ii % 2) * 2;
if(send(port, dst, dport, buf, 4) < 0){
printf("ping3: send() failed\n");
return 0;
}
}
sleep(1);
//
// send another packet from 2009.
//
{
uint32 dst = 0x0A000202; // 10.0.2.2
int dport = NET_TESTS_PORT;
char buf[4];
buf[0] = 'p';
buf[1] = ' ';
buf[2] = 'B';
buf[3] = '!';
if(send(2009, dst, dport, buf, 4) < 0){
printf("ping3: send() failed\n");
return 0;
}
}
//
// did both reply packets for 2009 arrive?
//
for(int ii = 0; ii < 2; ii++){
char ibuf[128];
uint32 src = 0;
uint16 sport = 0;
memset(ibuf, 0, sizeof(ibuf));
int cc = recv(2009, &src, &sport, ibuf, sizeof(ibuf)-1);
if(cc < 0){
fprintf(2, "ping3: recv() failed\n");
return 0;
}
if(src != 0x0A000202){ // 10.0.2.2
printf("ping3: wrong ip src %x\n", src);
return 0;
}
if(sport != NET_TESTS_PORT){
printf("ping3: wrong sport %d\n", sport);
return 0;
}
if(cc != 4){
printf("ping3: wrong length %d\n", cc);
return 0;
}
// printf("port=%d ii=%d: %c%c%c\n", port, ii, ibuf[0], ibuf[1], ibuf[2]);
char buf[4];
buf[0] = 'p';
buf[1] = ' ';
buf[2] = 'A' + ii;
buf[3] = '!';
if(memcmp(buf, ibuf, 3) != 0){
// possibly recv() sees packets out of order.
// possibly the burst on 2008 caused 2009's
// packets to be dropped.
printf("ping3: wrong content\n");
return 0;
}
}
//
// now count how many replies were queued for 2008.
//
int fds[2];
pipe(fds);
int pid = fork();
if(pid == 0){
close(fds[0]);
write(fds[1], ":", 1); // ensure parent's read() doesn't block
while(1){
char ibuf[128];
uint32 src = 0;
uint16 sport = 0;
memset(ibuf, 0, sizeof(ibuf));
int cc = recv(2008, &src, &sport, ibuf, sizeof(ibuf)-1);
if(cc < 0){
printf("ping3: recv failed\n");
break;
}
write(fds[1], "x", 1);
}
exit(0);
}
close(fds[1]);
sleep(5);
static char nbuf[512];
int n = read(fds[0], nbuf, sizeof(nbuf));
close(fds[0]);
kill(pid);
n -= 1; // the ":"
if(n > 16){
printf("ping3: too many packets (%d) were queued on a UDP port\n", n);
return 0;
}
printf("ping3: OK\n");
return 1;
}
// Encode a DNS name
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
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
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
int
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");
return 0;
}
if(!hdr->qr) {
printf("Not a DNS reply for %d\n", ntohs(hdr->id));
return 0;
}
if(hdr->id != htons(6828)){
printf("DNS wrong id: %d\n", ntohs(hdr->id));
return 0;
}
if(hdr->rcode != 0) {
printf("DNS rcode error: %x\n", hdr->rcode);
return 0;
}
//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("dns: invalid DNS reply\n");
return 0;
}
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("dns: wrong ip address");
return 0;
}
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("dns: invalid name for EDNS\n");
return 0;
}
len += 1;
struct dns_data *d = (struct dns_data *) (ibuf+len);
len += sizeof(struct dns_data);
if(ntohs(d->type) != 41) {
printf("dns: invalid type for EDNS\n");
return 0;
}
len += ntohs(d->len);
}
if(len != cc) {
printf("dns: processed %d data bytes but received %d\n", len, cc);
return 0;
}
if(!record) {
printf("dns: didn't receive an arecord\n");
return 0;
}
return 1;
}
int
dns()
{
#define N 1000
uint8 obuf[N];
uint8 ibuf[N];
uint32 dst;
int len;
printf("dns: starting\n");
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);
len = dns_req(obuf);
bind(10000);
if(send(10000, dst, 53, (char*)obuf, len) < 0){
fprintf(2, "dns: send() failed\n");
return 0;
}
uint32 src;
uint16 sport;
int cc = recv(10000, &src, &sport, (char*)ibuf, sizeof(ibuf));
if(cc < 0){
fprintf(2, "dns: recv() failed\n");
return 0;
}
if(dns_rep(ibuf, cc)){
printf("dns: OK\n");
return 1;
} else {
return 0;
}
}
void
usage()
{
printf("Usage: nettest txone\n");
printf(" nettest tx\n");
printf(" nettest rx\n");
printf(" nettest rx2\n");
printf(" nettest rxburst\n");
printf(" nettest ping1\n");
printf(" nettest ping2\n");
printf(" nettest ping3\n");
printf(" nettest dns\n");
printf(" nettest grade\n");
exit(1);
}
//
// use sbrk() to count how many free physical memory pages there are.
// touches the pages to force allocation.
// because out of memory with lazy allocation results in the process
// taking a fault and being killed, fork and report back.
//
int
countfree()
{
int fds[2];
if(pipe(fds) < 0){
printf("pipe() failed in countfree()\n");
exit(1);
}
int pid = fork();
if(pid < 0){
printf("fork failed in countfree()\n");
exit(1);
}
if(pid == 0){
close(fds[0]);
while(1){
uint64 a = (uint64) sbrk(4096);
if(a == 0xffffffffffffffff){
break;
}
// modify the memory to make sure it's really allocated.
*(char *)(a + 4096 - 1) = 1;
// report back one more page.
if(write(fds[1], "x", 1) != 1){
printf("write() failed in countfree()\n");
exit(1);
}
}
exit(0);
}
close(fds[1]);
int n = 0;
while(1){
char c;
int cc = read(fds[0], &c, 1);
if(cc < 0){
printf("read() failed in countfree()\n");
exit(1);
}
if(cc == 0)
break;
n += 1;
}
close(fds[0]);
wait((int*)0);
return n;
}
int
main(int argc, char *argv[])
{
if(argc != 2)
usage();
if(strcmp(argv[1], "txone") == 0){
txone();
} else if(strcmp(argv[1], "rx") == 0 || strcmp(argv[1], "rxburst") == 0){
rx(argv[1]);
} else if(strcmp(argv[1], "rx2") == 0){
rx2();
} else if(strcmp(argv[1], "tx") == 0){
tx();
} else if(strcmp(argv[1], "ping0") == 0){
ping0();
} else if(strcmp(argv[1], "ping1") == 0){
ping1();
} else if(strcmp(argv[1], "ping2") == 0){
ping2();
} else if(strcmp(argv[1], "ping3") == 0){
ping3();
} else if(strcmp(argv[1], "grade") == 0){
//
// "python3 nettest.py grade" must already be running...
//
int free0 = countfree();
int free1 = 0;
txone();
sleep(2);
ping0();
sleep(2);
ping1();
sleep(2);
ping2();
sleep(2);
ping3();
sleep(2);
dns();
sleep(2);
if ((free1 = countfree()) + 32 < free0) {
printf("free: FAILED -- lost too many free pages %d (out of %d)\n", free1, free0);
} else {
printf("free: OK\n");
}
} else if(strcmp(argv[1], "dns") == 0){
dns();
} else {
usage();
}
exit(0);
}

52
user/pingpong.c Normal file
View File

@ -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);
}

View File

@ -1,3 +1,7 @@
#ifdef LAB_MMAP
typedef unsigned long size_t;
typedef long int off_t;
#endif
struct stat;
// system calls
@ -22,6 +26,17 @@ int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);
#ifdef LAB_NET
int bind(uint16);
int unbind(uint16);
int send(uint16, uint32, uint16, char *, uint32);
int recv(uint16, uint32*, uint16*, char *, uint32);
#endif
#ifdef LAB_PGTBL
int ugetpid(void);
uint64 pgpte(void*);
void kpgtbl(void);
#endif
// ulib.c
int stat(const char*, struct stat*);
@ -37,6 +52,9 @@ void* memset(void*, int, uint);
int atoi(const char*);
int memcmp(const void *, const void *, uint);
void *memcpy(void *, const void *, uint);
#ifdef LAB_LOCK
int statistics(void*, int);
#endif
// umalloc.c
void* malloc(uint);

View File

@ -36,3 +36,9 @@ entry("getpid");
entry("sbrk");
entry("sleep");
entry("uptime");
entry("bind");
entry("unbind");
entry("send");
entry("recv");
entry("pgpte");
entry("kpgtbl");

View File

@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}