diff options
82 files changed, 5519 insertions, 120 deletions
| diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..065dcdf --- /dev/null +++ b/.clang-format @@ -0,0 +1,225 @@ +--- +Language:        Cpp +# BasedOnStyle:  Mozilla +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: +  Enabled:         false +  AcrossEmptyLines: false +  AcrossComments:  false +  AlignCompound:   false +  PadOperators:    true +AlignConsecutiveBitFields: +  Enabled:         false +  AcrossEmptyLines: false +  AcrossComments:  false +  AlignCompound:   false +  PadOperators:    false +AlignConsecutiveDeclarations: +  Enabled:         false +  AcrossEmptyLines: false +  AcrossComments:  false +  AlignCompound:   false +  PadOperators:    false +AlignConsecutiveMacros: +  Enabled:         false +  AcrossEmptyLines: false +  AcrossComments:  false +  AlignCompound:   false +  PadOperators:    false +AlignEscapedNewlines: Right +AlignOperands:   Align +AlignTrailingComments: +  Kind:            Always +  OverEmptyLines:  0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: TopLevel +AlwaysBreakAfterReturnType: TopLevel +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: +  - __capability +BinPackArguments: false +BinPackParameters: false +BitFieldColonSpacing: Both +BraceWrapping: +  AfterCaseLabel:  false +  AfterClass:      true +  AfterControlStatement: Never +  AfterEnum:       true +  AfterExternBlock: true +  AfterFunction:   true +  AfterNamespace:  false +  AfterObjCDeclaration: false +  AfterStruct:     true +  AfterUnion:      true +  BeforeCatch:     false +  BeforeElse:      false +  BeforeLambdaBody: false +  BeforeWhile:     false +  IndentBraces:    false +  SplitEmptyFunction: true +  SplitEmptyRecord: false +  SplitEmptyNamespace: true +BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: false +BreakArrays:     true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Mozilla +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ColumnLimit:     80 +CommentPragmas:  '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat:   false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: +  - foreach +  - Q_FOREACH +  - BOOST_FOREACH +IfMacros: +  - KJ_IF_MAYBE +IncludeBlocks:   Preserve +IncludeCategories: +  - Regex:           '^"(llvm|llvm-c|clang|clang-c)/' +    Priority:        2 +    SortPriority:    0 +    CaseSensitive:   false +  - Regex:           '^(<|"(gtest|gmock|isl|json)/)' +    Priority:        3 +    SortPriority:    0 +    CaseSensitive:   false +  - Regex:           '.*' +    Priority:        1 +    SortPriority:    0 +    CaseSensitive:   false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: true +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth:     2 +IndentWrappedFunctionNames: false +InsertBraces:    false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: +  Binary:          0 +  BinaryMinDigits: 0 +  Decimal:         0 +  DecimalMinDigits: 0 +  Hex:             0 +  HexMinDigits:    0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +LineEnding:      DeriveLF +MacroBlockBegin: '' +MacroBlockEnd:   '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: false +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +PPIndentWidth:   -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments:  true +RemoveBracesLLVM: false +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes:    CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: +  AfterControlStatements: true +  AfterForeachMacros: true +  AfterFunctionDefinitionName: false +  AfterFunctionDeclarationName: false +  AfterIfMacros:   true +  AfterOverloadedOperator: false +  AfterRequiresInClause: false +  AfterRequiresInExpression: false +  BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles:  Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: +  Minimum:         1 +  Maximum:         -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard:        Latest +StatementAttributeLikeMacros: +  - Q_EMIT +StatementMacros: +  - Q_UNUSED +  - QT_REQUIRE_VERSION +TabWidth:        8 +UseTab:          Never +WhitespaceSensitiveMacros: +  - BOOST_PP_STRINGIZE +  - CF_SWIFT_NAME +  - NS_SWIFT_NAME +  - PP_STRINGIZE +  - STRINGIZE +... + @@ -25,3 +25,5 @@ barrier  .DS_Store  *.dSYM  *.pcap +riscv/ +compile_flags.txt @@ -13,12 +13,14 @@ OBJS = \    $K/kalloc.o \    $K/string.o \    $K/main.o \ +  $K/cow.o \    $K/vm.o \    $K/proc.o \    $K/swtch.o \    $K/trampoline.o \    $K/trap.o \    $K/syscall.o \ +  $K/sysinfo.o \    $K/sysproc.o \    $K/bio.o \    $K/fs.o \ @@ -44,20 +46,16 @@ 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/sysnet.o \  	$K/pci.o -endif  # riscv64-unknown-elf- or riscv64-linux-gnu- @@ -90,7 +88,7 @@ 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) +XCFLAGS += -DSOL_$(LABUPPER) -DLAB_$(LABUPPER) -DLAB_PGTBL -DLAB_NET -DLAB_LOCK  endif  CFLAGS += $(XCFLAGS) @@ -100,9 +98,7 @@ 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 @@ -141,9 +137,7 @@ 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 $@ $^ @@ -188,30 +182,32 @@ UPROGS=\  	$U/_grind\  	$U/_wc\  	$U/_zombie\ +	$U/_sleep\ +	$U/_pingpong\ +	$U/_primes\ +	$U/_find\ +	$U/_xargs\ +	$U/_trace\ +	$U/_sysinfotest\ -ifeq ($(LAB),lock)  UPROGS += \  	$U/_stats -endif -ifeq ($(LAB),traps)  UPROGS += \  	$U/_call\ -	$U/_bttest -endif +	$U/_bttest\ +	$U/_alarmtest  ifeq ($(LAB),lazy)  UPROGS += \  	$U/_lazytests  endif -ifeq ($(LAB),cow)  UPROGS += \  	$U/_cowtest -endif  ifeq ($(LAB),thread)  UPROGS += \ @@ -231,16 +227,12 @@ 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 += \ @@ -248,16 +240,11 @@ UPROGS += \  endif - -ifeq ($(LAB),net)  UPROGS += \  	$U/_nettests -endif  UEXTRA= -ifeq ($(LAB),util) -	UEXTRA += user/xargstest.sh -endif +UEXTRA += user/xargstest.sh  fs.img: mkfs/mkfs README $(UEXTRA) $(UPROGS) @@ -266,11 +253,13 @@ fs.img: mkfs/mkfs README $(UEXTRA) $(UPROGS)  -include kernel/*.d user/*.d  clean: -	rm -rf *.tex *.dvi *.idx *.aux *.log *.ind *.ilg *.dSYM *.zip *.pcap \ +	rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg *.dSYM *.zip \  	*/*.o */*.d */*.asm */*.sym \ -	$U/initcode $U/initcode.out $U/usys.S $U/_* \ -	$K/kernel \ -	mkfs/mkfs fs.img .gdbinit __pycache__ xv6.out* \ +	$U/initcode $U/initcode.out $K/kernel fs.img \ +	mkfs/mkfs .gdbinit \ +        $U/usys.S \ +	$(UPROGS) \ +	*.zip \  	ph barrier  # try to generate a unique GDB port @@ -293,10 +282,8 @@ 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) @@ -308,7 +295,6 @@ 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) @@ -317,7 +303,6 @@ server:  ping:  	python3 ping.py $(FWDPORT) -endif  ##  ##  FOR testing lab grading script diff --git a/answers-pgtbl.txt b/answers-pgtbl.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/answers-pgtbl.txt diff --git a/answers-syscall.txt b/answers-syscall.txt new file mode 100644 index 0000000..ca0ce77 --- /dev/null +++ b/answers-syscall.txt @@ -0,0 +1,9 @@ +usertrap() +7 +SYS_exec +user mode +lw	a3,0(zero) +a3 +no +yes, `scause` 0xd -> interrupt = 0, exception code = 13 -> instruction page fault (riscv-priviledged p71) +'initcode', 1 diff --git a/answers-thread.txt b/answers-thread.txt new file mode 100644 index 0000000..80dde35 --- /dev/null +++ b/answers-thread.txt @@ -0,0 +1,10 @@ +1. Why are there missing keys with 2 threads, but not with 1 thread? +    With 1 thread, the put and insert are always interleaved. +    No changes to hash table happen at the same time. +    But with 2 threads, there will be race to access the hash table, +    and this leads to the missing keys. + +2. Identify a sequence of events with 2 threads that can lead to a key being missing. +    Suppose thread No. 1 is running the insert and so does thread No. 2. +    They shall both insert a entry. But because they enter the insert function with the same hash table, +    they could only insert one key at most, which causes a key being missing. diff --git a/answers-traps.txt b/answers-traps.txt new file mode 100644 index 0000000..dbb40f1 --- /dev/null +++ b/answers-traps.txt @@ -0,0 +1,6 @@ +a0~a7, a2 +0x26 (optimized during compile time), 0x14 (inline, unfolded) +0x666 +0x38 (next instruction) +He110 World +the value in register a2 diff --git a/grade-lab-cow b/grade-lab-cow new file mode 100755 index 0000000..08b6990 --- /dev/null +++ b/grade-lab-cow @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 + +import re +from gradelib import * + +r = Runner(save("xv6.out")) + +@test(0, "running cowtest") +def test_cowtest(): +    r.run_qemu(shell_script([ +        'cowtest' +    ])) + +@test(30, "simple", parent=test_cowtest) +def test_simple(): +    matches = re.findall("^simple: ok$", r.qemu.output, re.M) +    assert_equal(len(matches), 2, "Number of appearances of 'simple: ok'") + +@test(30, "three", parent=test_cowtest) +def test_three(): +    matches = re.findall("^three: ok$", r.qemu.output, re.M) +    assert_equal(len(matches), 3, "Number of appearances of 'three: ok'") + +@test(20, "file", parent=test_cowtest) +def test_file(): +    r.match('^file: ok$') + +@test(0, "usertests") +def test_usertests(): +    r.run_qemu(shell_script([ +        'usertests -q' +    ]), timeout=1000) +    r.match('^ALL TESTS PASSED$') + +def usertest_check(testcase, nextcase, output): +    if not re.search(r'\ntest {}: [\s\S]*OK\ntest {}'.format(testcase, nextcase), output): +        raise AssertionError('Failed ' + testcase) + +@test(5, "usertests: copyin", parent=test_usertests) +def test_sbrkbugs(): +    usertest_check("copyin", "copyout", r.qemu.output) + +@test(5, "usertests: copyout", parent=test_usertests) +def test_sbrkbugs(): +    usertest_check("copyout", "copyinstr1", r.qemu.output) + +@test(19, "usertests: all tests", parent=test_usertests) +def test_usertests_all(): +    r.match('^ALL TESTS PASSED$') + +@test(1, "time") +def test_time(): +    check_time() + +run_tests() diff --git a/grade-lab-lock b/grade-lab-lock new file mode 100755 index 0000000..fa90fef --- /dev/null +++ b/grade-lab-lock @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +import re +from gradelib import * + +r = Runner(save("xv6.out")) + +@test(0, "running kalloctest") +def test_kalloctest(): +    r.run_qemu(shell_script([ +        'kalloctest' +    ]), timeout=200) +     +@test(10, "kalloctest: test1", parent=test_kalloctest) +def test_kalloctest_test1(): +    r.match('^test1 OK$') +     +@test(10, "kalloctest: test2", parent=test_kalloctest) +def test_kalloctest_test2(): +    r.match('^test2 OK$') + +@test(10, "kalloctest: test3", parent=test_kalloctest) +def test_kalloctest_test3(): +    r.match('^test3 OK$') + +@test(10, "kalloctest: sbrkmuch") +def test_sbrkmuch(): +    r.run_qemu(shell_script([ +        'usertests sbrkmuch' +    ]), timeout=90) +    r.match('^ALL TESTS PASSED$') + +@test(0, "running bcachetest") +def test_bcachetest(): +    r.run_qemu(shell_script([ +        'bcachetest' +    ]), timeout=90) +     +@test(10, "bcachetest: test0", parent=test_bcachetest) +def test_bcachetest_test0(): +    r.match('^test0: OK$') +     +@test(10, "bcachetest: test1", parent=test_bcachetest) +def test_bcachetest_test1(): +    r.match('^test1 OK$') + +@test(10, "bcachetest: test2", parent=test_bcachetest) +def test_bcachetest_test2(): +    r.match('^test2 OK$') + +@test(19, "usertests") +def test_usertests(): +    r.run_qemu(shell_script([ +        'usertests -q' +    ]), timeout=300) +    r.match('^ALL TESTS PASSED$') + +@test(1, "time") +def test_time(): +    check_time() + +run_tests() diff --git a/grade-lab-net b/grade-lab-net new file mode 100755 index 0000000..dd193e6 --- /dev/null +++ b/grade-lab-net @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +import re +import subprocess +from gradelib import * + +r = Runner(save("xv6.out")) + +@test(0, "running nettests") +def test_nettest(): +    server = subprocess.Popen(["make", "server"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) +    r.run_qemu(shell_script([ +        'nettests' +    ]), timeout=30) +    server.terminate() +    server.communicate() + +@test(40, "nettest: ping", parent=test_nettest) +def test_nettest_(): +    r.match('^testing ping: OK$') + +@test(20, "nettest: single process", parent=test_nettest) +def test_nettest_(): +    r.match('^testing single-process pings: OK$') + +@test(20, "nettest: multi-process", parent=test_nettest) +def test_nettest_fork_test(): +    r.match('^testing multi-process pings: OK$') + +@test(19, "nettest: DNS", parent=test_nettest) +def test_nettest_dns_test(): +    r.match('^DNS OK$') + +#@test(10, "answers-net.txt") +#def test_answers(): +#    # just a simple sanity check, will be graded manually +#    check_answers("answers-net.txt") + +@test(1, "time") +def test_time(): +    check_time() + +run_tests() diff --git a/grade-lab-pgtbl b/grade-lab-pgtbl new file mode 100755 index 0000000..121bb3b --- /dev/null +++ b/grade-lab-pgtbl @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +import re +from gradelib import * + +r = Runner(save("xv6.out")) + +PTE_PRINT = """page table 0x0000000087f6b000 + ..0: pte 0x0000000021fd9c01 pa 0x0000000087f67000 + .. ..0: pte 0x0000000021fd9801 pa 0x0000000087f66000 + .. .. ..0: pte 0x0000000021fda01b pa 0x0000000087f68000 + .. .. ..1: pte 0x0000000021fd9417 pa 0x0000000087f65000 + .. .. ..2: pte 0x0000000021fd9007 pa 0x0000000087f64000 + .. .. ..3: pte 0x0000000021fd8c17 pa 0x0000000087f63000 + ..255: pte 0x0000000021fda801 pa 0x0000000087f6a000 + .. ..511: pte 0x0000000021fda401 pa 0x0000000087f69000 + .. .. ..509: pte 0x0000000021fdcc13 pa 0x0000000087f73000 + .. .. ..510: pte 0x0000000021fdd007 pa 0x0000000087f74000 + .. .. ..511: pte 0x0000000020001c0b pa 0x0000000080007000""" + +VAL_RE = "(0x00000000[0-9a-f]+)" +INDENT_RE = r"\s*\.\.\s*" +INDENT_ESC = "\\\s*\.\.\\\s*" + +@test(0, "pgtbltest") +def test_pgtbltest(): +    r.run_qemu(shell_script([ +        'pgtbltest' +    ]), timeout=300) + +@test(10, "pgtbltest: ugetpid", parent=test_pgtbltest) +def test_nettest_(): +    r.match('^ugetpid_test: OK$') + +@test(10, "pgtbltest: pgaccess", parent=test_pgtbltest) +def test_nettest_(): +    r.match('^pgaccess_test: OK$') + +@test(10, "pte printout") +def test_pteprint(): +    first = True +    r.run_qemu(shell_script([ +        'echo hi' +    ])) +    r.match('^hi') +    p = re.compile(VAL_RE) +    d = re.compile(INDENT_RE) +    for l in PTE_PRINT.splitlines(): +        l = d.sub(INDENT_ESC, l) +        l = p.sub(VAL_RE, l) +        r.match(r'^{}$'.format(l)) +        if first: +            first = False +        else: +            matches = re.findall(r'^{}$'.format(l), r.qemu.output, re.MULTILINE) +            assert_equal(len(matches[0]), 2) +            pa = (int(matches[0][0], 16) >> 10) << 12 +            assert_equal(int(matches[0][1], 16), pa) + +@test(5, "answers-pgtbl.txt") +def test_answers(): +    # just a simple sanity check, will be graded manually +    check_answers("answers-pgtbl.txt") + +@test(0, "usertests") +def test_usertests(): +    r.run_qemu(shell_script([ +        'usertests -q' +    ]), timeout=300) + +@test(10, "usertests: all tests", parent=test_usertests) +def test_usertests(): +    r.match('^ALL TESTS PASSED$') + +@test(1, "time") +def test_time(): +    check_time() + +run_tests() diff --git a/grade-lab-thread b/grade-lab-thread new file mode 100755 index 0000000..18df65f --- /dev/null +++ b/grade-lab-thread @@ -0,0 +1,70 @@ +#!/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(5, "answers-thread.txt") +def test_answers(): +    # just a simple sanity check, will be graded manually +    check_answers("answers-thread.txt") +     +# 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/grade-lab-traps b/grade-lab-traps new file mode 100755 index 0000000..10613d3 --- /dev/null +++ b/grade-lab-traps @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +import os +import re +import subprocess +from gradelib import * + +r = Runner(save("xv6.out")) + +@test(5, "answers-traps.txt") +def test_answers(): +    # just a simple sanity check, will be graded manually +    check_answers("answers-traps.txt") + +BACKTRACE_RE = r"^(0x000000008[0-9a-f]+)" + +def addr2line(): +    for f in ['riscv64-unknown-elf-addr2line', 'riscv64-linux-gnu-addr2line', 'addr2line', ]: +        try: +            devnull = open(os.devnull) +            subprocess.Popen([f], stdout=devnull, stderr=devnull).communicate() +            return f +        except OSError: +            continue +    raise AssertionError('Cannot find the addr2line program') + +@test(10, "backtrace test") +def test_backtracetest(): +    r.run_qemu(shell_script([ +        'bttest' +    ])) +    a2l = addr2line() +    matches = re.findall(BACKTRACE_RE, r.qemu.output, re.MULTILINE) +    assert_equal(len(matches), 3) +    files = ['sysproc.c', 'syscall.c', 'trap.c'] +    for f, m in zip(files, matches): +        result = subprocess.run([a2l, '-e', 'kernel/kernel', m], stdout=subprocess.PIPE) +        if not f in result.stdout.decode("utf-8"): +            raise AssertionError('Trace is incorrect; no %s' % f) + +@test(0, "running alarmtest") +def test_alarmtest(): +    r.run_qemu(shell_script([ +        'alarmtest' +    ])) + +@test(20, "alarmtest: test0", parent=test_alarmtest) +def test_alarmtest_test0(): +    r.match('^test0 passed$') + +@test(20, "alarmtest: test1", parent=test_alarmtest) +def test_alarmtest_test1(): +    r.match('^\\.?test1 passed$') + +@test(10, "alarmtest: test2", parent=test_alarmtest) +def test_alarmtest_test2(): +    r.match('^\\.?test2 passed$') + +@test(10, "alarmtest: test3", parent=test_alarmtest) +def test_alarmtest_test3(): +    r.match('^test3 passed$') + +@test(19, "usertests") +def test_usertests(): +    r.run_qemu(shell_script([ +        'usertests -q' +    ]), timeout=300) +    r.match('^ALL TESTS PASSED$') + +@test(1, "time") +def test_time(): +    check_time() + +run_tests() diff --git a/kernel/bio.c b/kernel/bio.c index 60d91a6..153c2f2 100644 --- a/kernel/bio.c +++ b/kernel/bio.c @@ -23,16 +23,26 @@  #include "fs.h"  #include "buf.h" -struct { -  struct spinlock lock; -  struct buf buf[NBUF]; +#define N_BUCKETS 13 +struct bucket { +  struct spinlock lock;    // Linked list of all buffers, through prev/next.    // Sorted by how recently the buffer was used.    // head.next is most recent, head.prev is least. +  // struct buf head;    struct buf head; +}; +struct { +  struct spinlock lock; +  struct buf buf[NBUF]; +  struct bucket buckets[N_BUCKETS];  } bcache; +static inline uint hash(uint blockno) { +  return blockno % N_BUCKETS; +} +  void  binit(void)  { @@ -40,15 +50,24 @@ binit(void)    initlock(&bcache.lock, "bcache"); +  // init each bucket +  for (int i = 0; i < N_BUCKETS; i++) { +    static char lock_name[20]; +    snprintf(lock_name, sizeof(lock_name), "bcache.bucket.%d", i); +    initlock(&bcache.buckets[i].lock, lock_name); +    bcache.buckets[i].head.prev = &bcache.buckets[i].head; +    bcache.buckets[i].head.next = &bcache.buckets[i].head; +  } +    // Create linked list of buffers -  bcache.head.prev = &bcache.head; -  bcache.head.next = &bcache.head; +  // since for now, blockno is all zero,  +  // they are all linked to bucket 0     for(b = bcache.buf; b < bcache.buf+NBUF; b++){ -    b->next = bcache.head.next; -    b->prev = &bcache.head; +    b->next = bcache.buckets[0].head.next; +    b->prev = &bcache.buckets[0].head;      initsleeplock(&b->lock, "buffer"); -    bcache.head.next->prev = b; -    bcache.head.next = b; +    bcache.buckets[0].head.next->prev = b; +    bcache.buckets[0].head.next = b;    }  } @@ -59,32 +78,92 @@ static struct buf*  bget(uint dev, uint blockno)  {    struct buf *b; +  uint bucket_index = hash(blockno); -  acquire(&bcache.lock); +  acquire(&bcache.buckets[bucket_index].lock);    // Is the block already cached? -  for(b = bcache.head.next; b != &bcache.head; b = b->next){ +  for(b = bcache.buckets[bucket_index].head.next; b != &bcache.buckets[bucket_index].head; b = b->next){      if(b->dev == dev && b->blockno == blockno){        b->refcnt++; -      release(&bcache.lock); +      release(&bcache.buckets[bucket_index].lock);        acquiresleep(&b->lock);        return b;      }    }    // Not cached. -  // Recycle the least recently used (LRU) unused buffer. -  for(b = bcache.head.prev; b != &bcache.head; b = b->prev){ +  // Recycle the least recently used (LRU) unused buffer +  // of its own list first. +  struct buf *unused = (struct buf *)0; +  for(b = bcache.buckets[bucket_index].head.prev; b != &bcache.buckets[bucket_index].head; b = b->prev){      if(b->refcnt == 0) { -      b->dev = dev; -      b->blockno = blockno; -      b->valid = 0; -      b->refcnt = 1; -      release(&bcache.lock); -      acquiresleep(&b->lock); -      return b; +      if (!unused || unused->ticks > b->ticks) { +        unused = b; +      }      }    } +  if (unused) { +    unused->dev = dev; +    unused->blockno = blockno; +    unused->valid = 0; +    unused->refcnt = 1; +    unused->ticks = ticks; +    release(&bcache.buckets[bucket_index].lock); +    acquiresleep(&unused->lock); +    return unused; +  } +  // Sadly still no usable buf, +  // Steal the least recently used (LRU) unused buffer in others list +  // and move to our hash bucket. +  // To avoid deadlocks, scan with order bucket 0 -> N_BUCKETS. +   +  // release bigger bucket_index lock to acquire other locks later +  release(&bcache.buckets[bucket_index].lock); +  int i; +  for (i = 0; i < N_BUCKETS; i++) { +    if (i == bucket_index) { +      acquire(&bcache.buckets[bucket_index].lock); +    } else { +      acquire(&bcache.buckets[i].lock); +      for(b = bcache.buckets[i].head.prev; b != &bcache.buckets[i].head; b = b->prev){ +        if(b->refcnt == 0) { +          if (!unused || unused->ticks > b->ticks) { +            unused = b; +          } +        } +      } +      if (unused) { +        if (i < bucket_index) { +          acquire(&bcache.buckets[bucket_index].lock); +        } +        break; +      } else { +        release(&bcache.buckets[i].lock); +      } +    } +  } +  if (unused) { +    // remove from old bucket +    unused->prev->next = unused->next; +    unused->next->prev = unused->prev; +    release(&bcache.buckets[i].lock); + +    unused->dev = dev; +    unused->blockno = blockno; +    unused->valid = 0; +    unused->refcnt = 1; +    unused->ticks = ticks; +    // add to our hash bucket, after head +    unused->next = bcache.buckets[bucket_index].head.next; +    unused->prev = &bcache.buckets[bucket_index].head; +    bcache.buckets[bucket_index].head.next->prev = unused; +    bcache.buckets[bucket_index].head.next = unused; +    release(&bcache.buckets[bucket_index].lock); + +    acquiresleep(&unused->lock); +    return unused; +  }    panic("bget: no buffers");  } @@ -121,33 +200,38 @@ brelse(struct buf *b)    releasesleep(&b->lock); -  acquire(&bcache.lock); +  uint bucket_index = hash(b->blockno); +  acquire(&bcache.buckets[bucket_index].lock); +    b->refcnt--;    if (b->refcnt == 0) { -    // no one is waiting for it. +    // no one is waiting for it, move it to MRU      b->next->prev = b->prev;      b->prev->next = b->next; -    b->next = bcache.head.next; -    b->prev = &bcache.head; -    bcache.head.next->prev = b; -    bcache.head.next = b; +    b->next = bcache.buckets[bucket_index].head.next; +    b->prev = &bcache.buckets[bucket_index].head; +    bcache.buckets[bucket_index].head.next->prev = b; +    bcache.buckets[bucket_index].head.next = b; +    b->ticks = ticks;    } -  release(&bcache.lock); +  release(&bcache.buckets[bucket_index].lock);  }  void  bpin(struct buf *b) { -  acquire(&bcache.lock); +  uint id = hash(b->blockno); +  acquire(&bcache.buckets[id].lock);    b->refcnt++; -  release(&bcache.lock); +  release(&bcache.buckets[id].lock);  }  void  bunpin(struct buf *b) { -  acquire(&bcache.lock); +  uint id = hash(b->blockno); +  acquire(&bcache.buckets[id].lock);    b->refcnt--; -  release(&bcache.lock); +  release(&bcache.buckets[id].lock);  } diff --git a/kernel/buf.h b/kernel/buf.h index 4616e9e..bbef407 100644 --- a/kernel/buf.h +++ b/kernel/buf.h @@ -8,5 +8,6 @@ struct buf {    struct buf *prev; // LRU cache list    struct buf *next;    uchar data[BSIZE]; +  uint ticks;  // derived from global variable ticks (trap.c), record ticks when last modified  }; diff --git a/kernel/cow.c b/kernel/cow.c new file mode 100644 index 0000000..b3634fc --- /dev/null +++ b/kernel/cow.c @@ -0,0 +1,30 @@ +// COW pagefault handler +#include "types.h" +#include "riscv.h" +#include "defs.h" + +int +cow_handler(pagetable_t pagetable, uint64 va) +{ +  // you can't really write to rediculous pointers +  if(va >= MAXVA || PGROUNDDOWN(va) == 0) +    return -1; +  pte_t *pte = walk(pagetable, va, 0); +  if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) +    return -1; +  if(*pte & PTE_C){ +    uint64 pa_orig = PTE2PA(*pte); +    uint64 pa_new = (uint64)kalloc(); +    if(pa_new == 0){ +      printf("cow pagefault: kalloc failed\n"); +      return -1; +    } +    // copy the page and add write permission +    memmove((void*)pa_new, (void*)pa_orig, PGSIZE); +    uint64 flags = (PTE_FLAGS(*pte) | PTE_W) & ~PTE_C; +    *pte = PA2PTE(pa_new) | flags; +    kfree((void*)pa_orig); +  } else if ((*pte & PTE_W) == 0) +    return -1; +  return 0; +} diff --git a/kernel/defs.h b/kernel/defs.h index a3c962b..541c97e 100644 --- a/kernel/defs.h +++ b/kernel/defs.h @@ -1,3 +1,7 @@ +#ifdef LAB_MMAP +typedef unsigned long size_t; +typedef long int off_t; +#endif  struct buf;  struct context;  struct file; @@ -8,6 +12,11 @@ struct spinlock;  struct sleeplock;  struct stat;  struct superblock; +#ifdef LAB_NET +struct mbuf; +struct sock; +#endif +struct sysinfo;  // bio.c  void            binit(void); @@ -22,6 +31,9 @@ void            consoleinit(void);  void            consoleintr(int);  void            consputc(int); +// cow.c +int             cow_handler(pagetable_t, uint64); +  // exec.c  int             exec(char*, char**); @@ -60,9 +72,12 @@ void            ramdiskintr(void);  void            ramdiskrw(struct buf*);  // kalloc.c +int             refcnt_inc(uint64); +int             refcnt_dec(uint64);  void*           kalloc(void);  void            kfree(void *);  void            kinit(void); +int             get_freemem(void);  // log.c  void            initlog(int, struct superblock*); @@ -80,6 +95,7 @@ int             pipewrite(struct pipe*, uint64, int);  void            printf(char*, ...);  void            panic(char*) __attribute__((noreturn));  void            printfinit(void); +void            backtrace(void);  // proc.c  int             cpuid(void); @@ -106,6 +122,8 @@ void            yield(void);  int             either_copyout(int user_dst, uint64 dst, void *src, uint64 len);  int             either_copyin(void *dst, int user_src, uint64 src, uint64 len);  void            procdump(void); +int             get_nproc(void); +int             pgaccess(uint64 base, int len, uint64 mask);  // swtch.S  void            swtch(struct context*, struct context*); @@ -117,6 +135,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*); @@ -141,6 +163,9 @@ int             fetchstr(uint64, char*, int);  int             fetchaddr(uint64, uint64*);  void            syscall(); +// sysinfo.c +int             sys_info(uint64); +  // trap.c  extern uint     ticks;  void            trapinit(void); @@ -173,6 +198,7 @@ uint64          walkaddr(pagetable_t, uint64);  int             copyout(pagetable_t, uint64, char *, uint64);  int             copyin(pagetable_t, char *, uint64, uint64);  int             copyinstr(pagetable_t, char *, uint64, uint64); +void            vmprint(pagetable_t);  // plic.c  void            plicinit(void); @@ -187,3 +213,44 @@ void            virtio_disk_intr(void);  // number of elements in fixed-size array  #define NELEM(x) (sizeof(x)/sizeof((x)[0])) + + + +#ifdef LAB_PGTBL +// vmcopyin.c +int             copyin_new(pagetable_t, char *, uint64, uint64); +int             copyinstr_new(pagetable_t, char *, uint64, uint64); +#endif + +// stats.c +void            statsinit(void); +void            statsinc(void); + +// sprintf.c +int             snprintf(char*, int, char*, ...); + +#ifdef KCSAN +void            kcsaninit(); +#endif + +#ifdef LAB_NET +// pci.c +void            pci_init(); + +// e1000.c +void            e1000_init(uint32 *); +void            e1000_intr(void); +int             e1000_transmit(struct mbuf*); + +// net.c +void            net_rx(struct mbuf*); +void            net_tx_udp(struct mbuf*, uint32, uint16, uint16); + +// sysnet.c +void            sockinit(void); +int             sockalloc(struct file **, uint32, uint16, uint16); +void            sockclose(struct sock *); +int             sockread(struct sock *, uint64, int); +int             sockwrite(struct sock *, uint64, int); +void            sockrecvudp(struct mbuf*, uint32, uint16, uint16); +#endif diff --git a/kernel/e1000.c b/kernel/e1000.c new file mode 100644 index 0000000..c9ba9e2 --- /dev/null +++ b/kernel/e1000.c @@ -0,0 +1,178 @@ +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" +#include "e1000_dev.h" +#include "net.h" + +#define TX_RING_SIZE 16 +static struct tx_desc tx_ring[TX_RING_SIZE] __attribute__((aligned(16))); +static struct mbuf *tx_mbufs[TX_RING_SIZE]; + +#define RX_RING_SIZE 16 +static struct rx_desc rx_ring[RX_RING_SIZE] __attribute__((aligned(16))); +static struct mbuf *rx_mbufs[RX_RING_SIZE]; + +// remember where the e1000's registers live. +static volatile uint32 *regs; + +struct spinlock e1000_lock; + +// called by pci_init(). +// xregs is the memory address at which the +// e1000's registers are mapped. +void +e1000_init(uint32 *xregs) +{ +  int i; + +  initlock(&e1000_lock, "e1000"); + +  regs = xregs; + +  // Reset the device +  regs[E1000_IMS] = 0; // disable interrupts +  regs[E1000_CTL] |= E1000_CTL_RST; +  regs[E1000_IMS] = 0; // redisable interrupts +  __sync_synchronize(); + +  // [E1000 14.5] Transmit initialization +  memset(tx_ring, 0, sizeof(tx_ring)); +  for (i = 0; i < TX_RING_SIZE; i++) { +    tx_ring[i].status = E1000_TXD_STAT_DD; +    tx_mbufs[i] = 0; +  } +  regs[E1000_TDBAL] = (uint64) tx_ring; +  if(sizeof(tx_ring) % 128 != 0) +    panic("e1000"); +  regs[E1000_TDLEN] = sizeof(tx_ring); +  regs[E1000_TDH] = regs[E1000_TDT] = 0; +   +  // [E1000 14.4] Receive initialization +  memset(rx_ring, 0, sizeof(rx_ring)); +  for (i = 0; i < RX_RING_SIZE; i++) { +    rx_mbufs[i] = mbufalloc(0); +    if (!rx_mbufs[i]) +      panic("e1000"); +    rx_ring[i].addr = (uint64) rx_mbufs[i]->head; +  } +  regs[E1000_RDBAL] = (uint64) rx_ring; +  if(sizeof(rx_ring) % 128 != 0) +    panic("e1000"); +  regs[E1000_RDH] = 0; +  regs[E1000_RDT] = RX_RING_SIZE - 1; +  regs[E1000_RDLEN] = sizeof(rx_ring); + +  // filter by qemu's MAC address, 52:54:00:12:34:56 +  regs[E1000_RA] = 0x12005452; +  regs[E1000_RA+1] = 0x5634 | (1<<31); +  // multicast table +  for (int i = 0; i < 4096/32; i++) +    regs[E1000_MTA + i] = 0; + +  // transmitter control bits. +  regs[E1000_TCTL] = E1000_TCTL_EN |  // enable +    E1000_TCTL_PSP |                  // pad short packets +    (0x10 << E1000_TCTL_CT_SHIFT) |   // collision stuff +    (0x40 << E1000_TCTL_COLD_SHIFT); +  regs[E1000_TIPG] = 10 | (8<<10) | (6<<20); // inter-pkt gap + +  // receiver control bits. +  regs[E1000_RCTL] = E1000_RCTL_EN | // enable receiver +    E1000_RCTL_BAM |                 // enable broadcast +    E1000_RCTL_SZ_2048 |             // 2048-byte rx buffers +    E1000_RCTL_SECRC;                // strip CRC +   +  // ask e1000 for receive interrupts. +  regs[E1000_RDTR] = 0; // interrupt after every received packet (no timer) +  regs[E1000_RADV] = 0; // interrupt after every packet (no timer) +  regs[E1000_IMS] = (1 << 7); // RXDW -- Receiver Descriptor Write Back +} + +int +e1000_transmit(struct mbuf *m) +{ +  // the mbuf contains an ethernet frame; program it into +  // the TX descriptor ring so that the e1000 sends it. Stash +  // a pointer so that it can be freed after sending. +  acquire(&e1000_lock); + +  int cur_idx = regs[E1000_TDT]; +   +  // check if the STAT_DD bit is set in current descriptor +  // if not set, means a previous tx in this descripter is still in flight, return an error. +  if(!(tx_ring[cur_idx].status | E1000_TXD_STAT_DD)){ +    release(&e1000_lock); +    return -1; +  } + +  // free previous mbuf and update current descriptor +  if(tx_mbufs[cur_idx]) +    mbuffree(tx_mbufs[cur_idx]); +  tx_ring[cur_idx].addr = (uint64)m->head; +  tx_ring[cur_idx].length = (uint64)m->len; +  tx_ring[cur_idx].cmd = E1000_TXD_CMD_RS | E1000_TXD_CMD_EOP; +  // also clear status bits +  tx_ring[cur_idx].status = 0; + +  // stash current mbuf to tx_mbufs (would be freed later) +  tx_mbufs[cur_idx] = m; + +  // update the ring position to point to the next descriptor; +  regs[E1000_TDT] = (cur_idx + 1) % TX_RING_SIZE; + +  release(&e1000_lock); +  return 0; +} + +static void +e1000_recv(void) +{ +  // Check for packets that have arrived from the e1000 +  // Create and deliver an mbuf for each packet (using net_rx()). +  while(1){ +    acquire(&e1000_lock); +    int cur_idx = (regs[E1000_RDT]+1) % RX_RING_SIZE; + +    // check if last rx is completed. If not, skip passing to net_rx() +    if(!(rx_ring[cur_idx].status | E1000_RXD_STAT_DD)) +      break; + +    // update the mbuf's length to the len reported by rx_desc +    // mbufput(rx_mbufs[cur_idx], rx_ring[cur_idx].length); +    rx_mbufs[cur_idx]->len = rx_ring[cur_idx].length; + +    // stash mbuf, for later net_rx() +    struct mbuf *rx_buf = rx_mbufs[cur_idx]; + +    // net_rx() would free the passed mbuf invisibly, so we need to re-alloc it +    rx_mbufs[cur_idx] = mbufalloc(0); +    if(!rx_mbufs[cur_idx]) +      panic("e1000_recv: mbufalloc"); +     +    // update buffer addr and clear status bits +    rx_ring[cur_idx].addr = (uint64)rx_mbufs[cur_idx]->head; +    rx_ring[cur_idx].status = 0; + +    // update the E1000_RDT register to point to next position +    regs[E1000_RDT] = cur_idx; +    release(&e1000_lock); +     +    // pass to the network stack, must not hold the lock coz it can lead to deadlocks under different cpus +    net_rx(rx_buf); +  } +} + +void +e1000_intr(void) +{ +  // tell the e1000 we've seen this interrupt; +  // without this the e1000 won't raise any +  // further interrupts. +  regs[E1000_ICR] = 0xffffffff; + +  e1000_recv(); +} diff --git a/kernel/e1000_dev.h b/kernel/e1000_dev.h new file mode 100644 index 0000000..9b462df --- /dev/null +++ b/kernel/e1000_dev.h @@ -0,0 +1,125 @@ +// +// E1000 hardware definitions: registers and DMA ring format. +// from the Intel 82540EP/EM &c manual. +// + +/* Registers */ +#define E1000_CTL      (0x00000/4)  /* Device Control Register - RW */ +#define E1000_ICR      (0x000C0/4)  /* Interrupt Cause Read - R */ +#define E1000_IMS      (0x000D0/4)  /* Interrupt Mask Set - RW */ +#define E1000_RCTL     (0x00100/4)  /* RX Control - RW */ +#define E1000_TCTL     (0x00400/4)  /* TX Control - RW */ +#define E1000_TIPG     (0x00410/4)  /* TX Inter-packet gap -RW */ +#define E1000_RDBAL    (0x02800/4)  /* RX Descriptor Base Address Low - RW */ +#define E1000_RDTR     (0x02820/4)  /* RX Delay Timer */ +#define E1000_RADV     (0x0282C/4)  /* RX Interrupt Absolute Delay Timer */ +#define E1000_RDH      (0x02810/4)  /* RX Descriptor Head - RW */ +#define E1000_RDT      (0x02818/4)  /* RX Descriptor Tail - RW */ +#define E1000_RDLEN    (0x02808/4)  /* RX Descriptor Length - RW */ +#define E1000_RSRPD    (0x02C00/4)  /* RX Small Packet Detect Interrupt */ +#define E1000_TDBAL    (0x03800/4)  /* TX Descriptor Base Address Low - RW */ +#define E1000_TDLEN    (0x03808/4)  /* TX Descriptor Length - RW */ +#define E1000_TDH      (0x03810/4)  /* TX Descriptor Head - RW */ +#define E1000_TDT      (0x03818/4)  /* TX Descripotr Tail - RW */ +#define E1000_MTA      (0x05200/4)  /* Multicast Table Array - RW Array */ +#define E1000_RA       (0x05400/4)  /* Receive Address - RW Array */ + +/* Device Control */ +#define E1000_CTL_SLU     0x00000040    /* set link up */ +#define E1000_CTL_FRCSPD  0x00000800    /* force speed */ +#define E1000_CTL_FRCDPLX 0x00001000    /* force duplex */ +#define E1000_CTL_RST     0x00400000    /* full reset */ + +/* Transmit Control */ +#define E1000_TCTL_RST    0x00000001    /* software reset */ +#define E1000_TCTL_EN     0x00000002    /* enable tx */ +#define E1000_TCTL_BCE    0x00000004    /* busy check enable */ +#define E1000_TCTL_PSP    0x00000008    /* pad short packets */ +#define E1000_TCTL_CT     0x00000ff0    /* collision threshold */ +#define E1000_TCTL_CT_SHIFT 4 +#define E1000_TCTL_COLD   0x003ff000    /* collision distance */ +#define E1000_TCTL_COLD_SHIFT 12 +#define E1000_TCTL_SWXOFF 0x00400000    /* SW Xoff transmission */ +#define E1000_TCTL_PBE    0x00800000    /* Packet Burst Enable */ +#define E1000_TCTL_RTLC   0x01000000    /* Re-transmit on late collision */ +#define E1000_TCTL_NRTU   0x02000000    /* No Re-transmit on underrun */ +#define E1000_TCTL_MULR   0x10000000    /* Multiple request support */ + +/* Receive Control */ +#define E1000_RCTL_RST            0x00000001    /* Software reset */ +#define E1000_RCTL_EN             0x00000002    /* enable */ +#define E1000_RCTL_SBP            0x00000004    /* store bad packet */ +#define E1000_RCTL_UPE            0x00000008    /* unicast promiscuous enable */ +#define E1000_RCTL_MPE            0x00000010    /* multicast promiscuous enab */ +#define E1000_RCTL_LPE            0x00000020    /* long packet enable */ +#define E1000_RCTL_LBM_NO         0x00000000    /* no loopback mode */ +#define E1000_RCTL_LBM_MAC        0x00000040    /* MAC loopback mode */ +#define E1000_RCTL_LBM_SLP        0x00000080    /* serial link loopback mode */ +#define E1000_RCTL_LBM_TCVR       0x000000C0    /* tcvr loopback mode */ +#define E1000_RCTL_DTYP_MASK      0x00000C00    /* Descriptor type mask */ +#define E1000_RCTL_DTYP_PS        0x00000400    /* Packet Split descriptor */ +#define E1000_RCTL_RDMTS_HALF     0x00000000    /* rx desc min threshold size */ +#define E1000_RCTL_RDMTS_QUAT     0x00000100    /* rx desc min threshold size */ +#define E1000_RCTL_RDMTS_EIGTH    0x00000200    /* rx desc min threshold size */ +#define E1000_RCTL_MO_SHIFT       12            /* multicast offset shift */ +#define E1000_RCTL_MO_0           0x00000000    /* multicast offset 11:0 */ +#define E1000_RCTL_MO_1           0x00001000    /* multicast offset 12:1 */ +#define E1000_RCTL_MO_2           0x00002000    /* multicast offset 13:2 */ +#define E1000_RCTL_MO_3           0x00003000    /* multicast offset 15:4 */ +#define E1000_RCTL_MDR            0x00004000    /* multicast desc ring 0 */ +#define E1000_RCTL_BAM            0x00008000    /* broadcast enable */ +/* these buffer sizes are valid if E1000_RCTL_BSEX is 0 */ +#define E1000_RCTL_SZ_2048        0x00000000    /* rx buffer size 2048 */ +#define E1000_RCTL_SZ_1024        0x00010000    /* rx buffer size 1024 */ +#define E1000_RCTL_SZ_512         0x00020000    /* rx buffer size 512 */ +#define E1000_RCTL_SZ_256         0x00030000    /* rx buffer size 256 */ +/* these buffer sizes are valid if E1000_RCTL_BSEX is 1 */ +#define E1000_RCTL_SZ_16384       0x00010000    /* rx buffer size 16384 */ +#define E1000_RCTL_SZ_8192        0x00020000    /* rx buffer size 8192 */ +#define E1000_RCTL_SZ_4096        0x00030000    /* rx buffer size 4096 */ +#define E1000_RCTL_VFE            0x00040000    /* vlan filter enable */ +#define E1000_RCTL_CFIEN          0x00080000    /* canonical form enable */ +#define E1000_RCTL_CFI            0x00100000    /* canonical form indicator */ +#define E1000_RCTL_DPF            0x00400000    /* discard pause frames */ +#define E1000_RCTL_PMCF           0x00800000    /* pass MAC control frames */ +#define E1000_RCTL_BSEX           0x02000000    /* Buffer size extension */ +#define E1000_RCTL_SECRC          0x04000000    /* Strip Ethernet CRC */ +#define E1000_RCTL_FLXBUF_MASK    0x78000000    /* Flexible buffer size */ +#define E1000_RCTL_FLXBUF_SHIFT   27            /* Flexible buffer shift */ + +#define DATA_MAX 1518 + +/* Transmit Descriptor command definitions [E1000 3.3.3.1] */ +#define E1000_TXD_CMD_EOP    0x01 /* End of Packet */ +#define E1000_TXD_CMD_RS     0x08 /* Report Status */ + +/* Transmit Descriptor status definitions [E1000 3.3.3.2] */ +#define E1000_TXD_STAT_DD    0x00000001 /* Descriptor Done */ + +// [E1000 3.3.3] +struct tx_desc +{ +  uint64 addr; +  uint16 length; +  uint8 cso; +  uint8 cmd; +  uint8 status; +  uint8 css; +  uint16 special; +}; + +/* Receive Descriptor bit definitions [E1000 3.2.3.1] */ +#define E1000_RXD_STAT_DD       0x01    /* Descriptor Done */ +#define E1000_RXD_STAT_EOP      0x02    /* End of Packet */ + +// [E1000 3.2.3] +struct rx_desc +{ +  uint64 addr;       /* Address of the descriptor's data buffer */ +  uint16 length;     /* Length of data DMAed into data buffer */ +  uint16 csum;       /* Packet checksum */ +  uint8 status;      /* Descriptor status */ +  uint8 errors;      /* Descriptor Errors */ +  uint16 special; +}; + diff --git a/kernel/exec.c b/kernel/exec.c index e18bbb6..35b35f5 100644 --- a/kernel/exec.c +++ b/kernel/exec.c @@ -128,6 +128,10 @@ exec(char *path, char **argv)    p->trapframe->sp = sp; // initial stack pointer    proc_freepagetable(oldpagetable, oldsz); +  if(p->pid == 1){ +    vmprint(p->pagetable); +  } +    return argc; // this ends up in a0, the first argument to main(argc, argv)   bad: diff --git a/kernel/file.c b/kernel/file.c index 25fa226..0fba21b 100644 --- a/kernel/file.c +++ b/kernel/file.c @@ -80,6 +80,11 @@ fileclose(struct file *f)      iput(ff.ip);      end_op();    } +#ifdef LAB_NET +  else if(ff.type == FD_SOCK){ +    sockclose(ff.sock); +  } +#endif  }  // Get metadata about file f. @@ -122,7 +127,13 @@ fileread(struct file *f, uint64 addr, int n)      if((r = readi(f->ip, 1, addr, f->off, n)) > 0)        f->off += r;      iunlock(f->ip); -  } else { +  } +#ifdef LAB_NET +  else if(f->type == FD_SOCK){ +    r = sockread(f->sock, addr, n); +  } +#endif +  else {      panic("fileread");    } @@ -173,7 +184,13 @@ filewrite(struct file *f, uint64 addr, int n)        i += r;      }      ret = (i == n ? n : -1); -  } else { +  } +#ifdef LAB_NET +  else if(f->type == FD_SOCK){ +    ret = sockwrite(f->sock, addr, n); +  } +#endif +  else {      panic("filewrite");    } diff --git a/kernel/file.h b/kernel/file.h index b076d1d..1eb5107 100644 --- a/kernel/file.h +++ b/kernel/file.h @@ -1,10 +1,17 @@  struct file { +#ifdef LAB_NET +  enum { FD_NONE, FD_PIPE, FD_INODE, FD_DEVICE, FD_SOCK } type; +#else    enum { FD_NONE, FD_PIPE, FD_INODE, FD_DEVICE } type; +#endif    int ref; // reference count    char readable;    char writable;    struct pipe *pipe; // FD_PIPE    struct inode *ip;  // FD_INODE and FD_DEVICE +#ifdef LAB_NET +  struct sock *sock; // FD_SOCK +#endif    uint off;          // FD_INODE    short major;       // FD_DEVICE  }; @@ -38,3 +45,4 @@ struct devsw {  extern struct devsw devsw[];  #define CONSOLE 1 +#define STATS   2 diff --git a/kernel/fs.c b/kernel/fs.c index c6bab15..6c4079e 100644 --- a/kernel/fs.c +++ b/kernel/fs.c @@ -295,11 +295,11 @@ ilock(struct inode *ip)    struct buf *bp;    struct dinode *dip; -  if(ip == 0 || ip->ref < 1) +  if(ip == 0 || atomic_read4(&ip->ref) < 1)      panic("ilock");    acquiresleep(&ip->lock); - +      if(ip->valid == 0){      bp = bread(ip->dev, IBLOCK(ip->inum, sb));      dip = (struct dinode*)bp->data + ip->inum%IPB; @@ -320,7 +320,7 @@ ilock(struct inode *ip)  void  iunlock(struct inode *ip)  { -  if(ip == 0 || !holdingsleep(&ip->lock) || ip->ref < 1) +  if(ip == 0 || !holdingsleep(&ip->lock) || atomic_read4(&ip->ref) < 1)      panic("iunlock");    releasesleep(&ip->lock); @@ -416,7 +416,6 @@ bmap(struct inode *ip, uint bn)      brelse(bp);      return addr;    } -    panic("bmap: out of range");  } @@ -447,7 +446,7 @@ itrunc(struct inode *ip)      bfree(ip->dev, ip->addrs[NDIRECT]);      ip->addrs[NDIRECT] = 0;    } - +      ip->size = 0;    iupdate(ip);  } diff --git a/kernel/kalloc.c b/kernel/kalloc.c index 0699e7e..85cf6b7 100644 --- a/kernel/kalloc.c +++ b/kernel/kalloc.c @@ -9,6 +9,9 @@  #include "riscv.h"  #include "defs.h" +// NOTE: leave interrupts disabled to avoid deadlocks & race conditions when using this macro!!! +#define CUR_KMEM (kmem_list[cpuid()]) +  void freerange(void *pa_start, void *pa_end);  extern char end[]; // first address after kernel. @@ -18,15 +21,55 @@ struct run {    struct run *next;  }; -struct { +struct kmem {    struct spinlock lock;    struct run *freelist; -} kmem; +}; + +struct kmem kmem_list[NCPU]; + +int phypg_refcnt[PHYSTOP/PGSIZE]; + +struct spinlock refcnt_lock; + +// Increase the refcnt +int +refcnt_inc(uint64 pa) +{ +  acquire(&refcnt_lock); +  int *prefcnt = &phypg_refcnt[pa/PGSIZE]; +  if(pa > PHYSTOP || *prefcnt < 1) +    panic("increase refcnt"); +  (*prefcnt)++; +  release(&refcnt_lock); +  return *prefcnt; +} + +// Decrease the refcnt +int +refcnt_dec(uint64 pa) +{ +  acquire(&refcnt_lock); +  int *prefcnt = &phypg_refcnt[pa/PGSIZE]; +  if(pa > PHYSTOP || *prefcnt < 1) +    panic("decrease refcnt"); +  (*prefcnt)--; +  release(&refcnt_lock); +  return *prefcnt; +}  void  kinit()  { -  initlock(&kmem.lock, "kmem"); +  for(int i = 0; i < NCPU; i++){ +    static char lock_name[8]; +    snprintf(lock_name, sizeof(lock_name), "kmem.%d", i); +    initlock(&kmem_list[i].lock, lock_name); +  } +  // init all refcnt to 1, which would later be freed to 0 by kfree() +  for(uint64 p = PGROUNDUP((uint64)end); p + PGSIZE <= PHYSTOP; p += PGSIZE) +    phypg_refcnt[p/PGSIZE] = 1; +  initlock(&refcnt_lock, "refcnt");    freerange(end, (void*)PHYSTOP);  } @@ -51,15 +94,24 @@ kfree(void *pa)    if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)      panic("kfree"); +  refcnt_dec((uint64)pa); + +  if(phypg_refcnt[(uint64)pa/PGSIZE] > 0) +    // We still have refs to this phy page, do not actually free it +    return; +    // Fill with junk to catch dangling refs.    memset(pa, 1, PGSIZE);    r = (struct run*)pa; -  acquire(&kmem.lock); -  r->next = kmem.freelist; -  kmem.freelist = r; -  release(&kmem.lock); +  push_off(); +  struct kmem *kmem = &CUR_KMEM; +  acquire(&kmem->lock); +  r->next = kmem->freelist; +  kmem->freelist = r; +  release(&kmem->lock); +  pop_off();  }  // Allocate one 4096-byte page of physical memory. @@ -70,13 +122,67 @@ kalloc(void)  {    struct run *r; -  acquire(&kmem.lock); -  r = kmem.freelist; -  if(r) -    kmem.freelist = r->next; -  release(&kmem.lock); +  push_off(); +  struct kmem *kmem = &CUR_KMEM; +  acquire(&kmem->lock); +  r = kmem->freelist; +  if(r){ +    acquire(&refcnt_lock); +    if(phypg_refcnt[(uint64)r/PGSIZE]) +      panic("kalloc: invalid refcnt"); +    phypg_refcnt[(uint64)r/PGSIZE] = 1; +    release(&refcnt_lock); +    kmem->freelist = r->next; +  } + +  // release the origin lock to avoid deadlocks +  release(&kmem->lock); +   +  if(!r){ +    // try to steal mem from other cpu's kmem +    for(int i = 0; i < NCPU; i++){ +      if(kmem == &kmem_list[i]) +        continue; + +      acquire(&kmem_list[i].lock); +      struct run *f = kmem_list[i].freelist; +      if(f){ +        r = f; +        kmem_list[i].freelist = f->next; +      } +      if(r){ +        // acquire the refcnt lock to set refcnt +        // lock is a must to prevent refcnt races  +        acquire(&refcnt_lock); +        // release previous lock now +        release(&kmem_list[i].lock); +        if(phypg_refcnt[(uint64)r/PGSIZE]) +          panic("kalloc: invalid refcnt"); +        phypg_refcnt[(uint64)r/PGSIZE] = 1; +        release(&refcnt_lock); +        break; +      } +      release(&kmem_list[i].lock); +    } +  }    if(r)      memset((char*)r, 5, PGSIZE); // fill with junk +  pop_off();    return (void*)r;  } + +int +get_freemem(void) +{ +  int n; +  struct run *r; + +  for(int i = 0; i < NCPU; i++){ +    acquire(&kmem_list[i].lock); +    for(n = 0, r = kmem_list[i].freelist; r; r = r->next) +      n++; +    release(&kmem_list[i].lock); +  } +  return n * PGSIZE; +} diff --git a/kernel/kcsan.c b/kernel/kcsan.c new file mode 100644 index 0000000..90861ba --- /dev/null +++ b/kernel/kcsan.c @@ -0,0 +1,323 @@ +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "spinlock.h" +#include "riscv.h" +#include "proc.h" +#include "defs.h" + +// +// Race detector using gcc's thread sanitizer. It delays all stores +// and loads and monitors if any other CPU is using the same address. +// If so, we have a race and print out the backtrace of the thread +// that raced and the thread that set the watchpoint. +// + +// +// To run with kcsan: +// make clean +// make KCSAN=1 qemu +// + +// The number of watch points. +#define NWATCH (NCPU) + +// The number of cycles to delay stores, whatever that means on qemu. +//#define DELAY_CYCLES 20000 +#define DELAY_CYCLES 200000 + +#define MAXTRACE 20 + +int +trace(uint64 *trace, int maxtrace) +{ +  uint64 i = 0; +   +  push_off(); +   +  uint64 fp = r_fp(); +  uint64 ra, low = PGROUNDDOWN(fp) + 16, high = PGROUNDUP(fp); + +  while(!(fp & 7) && fp >= low && fp < high){ +    ra = *(uint64*)(fp - 8); +    fp = *(uint64*)(fp - 16); +    trace[i++] = ra; +    if(i >= maxtrace) +      break; +  } + +  pop_off(); +   +  return i; +} + +struct watch { +  uint64 addr; +  int write; +  int race; +  uint64 trace[MAXTRACE]; +  int tracesz; +}; +   +struct { +  struct spinlock lock; +  struct watch points[NWATCH]; +  int on; +} tsan; + +static struct watch* +wp_lookup(uint64 addr) +{ +  for(struct watch *w = &tsan.points[0]; w < &tsan.points[NWATCH]; w++) { +    if(w->addr == addr) { +      return w; +    } +  } +  return 0; +} + +static int +wp_install(uint64 addr, int write) +{ +  for(struct watch *w = &tsan.points[0]; w < &tsan.points[NWATCH]; w++) { +    if(w->addr == 0) { +      w->addr = addr; +      w->write = write; +      w->tracesz = trace(w->trace, MAXTRACE); +      return 1; +    } +  } +  panic("wp_install"); +  return 0; +} + +static void +wp_remove(uint64 addr) +{ +  for(struct watch *w = &tsan.points[0]; w < &tsan.points[NWATCH]; w++) { +    if(w->addr == addr) { +      w->addr = 0; +      w->tracesz = 0; +      return; +    } +  } +  panic("remove"); +} + +static void +printtrace(uint64 *t, int n) +{ +  int i; +   +  for(i = 0; i < n; i++) { +    printf("%p\n", t[i]); +  } +} + +static void +race(char *s, struct watch *w) { +  uint64 t[MAXTRACE]; +  int n; +   +  n = trace(t, MAXTRACE); +  printf("== race detected ==\n"); +  printf("backtrace for racing %s\n", s); +  printtrace(t, n); +  printf("backtrace for watchpoint:\n"); +  printtrace(w->trace, w->tracesz); +  printf("==========\n"); +} + +// cycle counter +static inline uint64 +r_cycle() +{ +  uint64 x; +  asm volatile("rdcycle %0" : "=r" (x) ); +  return x; +} + +static void delay(void) __attribute__((noinline)); +static void delay() { +  uint64 stop = r_cycle() + DELAY_CYCLES; +  uint64 c = r_cycle(); +  while(c < stop) { +    c = r_cycle(); +  } +} + +static void +kcsan_read(uint64 addr, int sz) +{ +  struct watch *w; +   +  acquire(&tsan.lock); +  if((w = wp_lookup(addr)) != 0) { +    if(w->write) { +      race("load", w); +    } +    release(&tsan.lock); +    return; +  } +  release(&tsan.lock); +} + +static void +kcsan_write(uint64 addr, int sz) +{ +  struct watch *w; +   +  acquire(&tsan.lock); +  if((w = wp_lookup(addr)) != 0) { +    race("store", w); +    release(&tsan.lock); +  } + +  // no watchpoint; try to install one +  if(wp_install(addr, 1)) { + +    release(&tsan.lock); + +    // XXX maybe read value at addr before and after delay to catch +    // races of unknown origins (e.g., device). + +    delay();  + +    acquire(&tsan.lock); + +    wp_remove(addr); +  } +  release(&tsan.lock); +} + +// tsan.on will only have effect with "make KCSAN=1" +void +kcsaninit(void) +{ +  initlock(&tsan.lock, "tsan"); +  tsan.on = 1; +  __sync_synchronize(); +} + +// +// Calls inserted by compiler into kernel binary, except for this file. +// + +void +__tsan_init(void) +{ +} + +void +__tsan_read1(uint64 addr) +{ +  if(!tsan.on) +    return; +  // kcsan_read(addr, 1); +} + +void +__tsan_read2(uint64 addr) +{ +  if(!tsan.on) +    return; +  kcsan_read(addr, 2); +} + +void +__tsan_read4(uint64 addr) +{ +  if(!tsan.on) +    return; +  kcsan_read(addr, 4); +} + +void +__tsan_read8(uint64 addr) +{ +  if(!tsan.on) +    return; +  kcsan_read(addr, 8); +} + +void +__tsan_read_range(uint64 addr, uint64 size) +{ +  if(!tsan.on) +    return; +  kcsan_read(addr, size); +} + +void +__tsan_write1(uint64 addr) +{ +  if(!tsan.on) +    return; +  // kcsan_write(addr, 1); +} + +void +__tsan_write2(uint64 addr) +{ +  if(!tsan.on) +    return; +  kcsan_write(addr, 2); +} + +void +__tsan_write4(uint64 addr) +{ +  if(!tsan.on) +    return; +  kcsan_write(addr, 4); +} + +void +__tsan_write8(uint64 addr) +{ +  if(!tsan.on) +    return; +  kcsan_write(addr, 8); +} + +void +__tsan_write_range(uint64 addr, uint64 size) +{ +  if(!tsan.on) +    return; +  kcsan_write(addr, size); +} + +void +__tsan_atomic_thread_fence(int order) +{ +  __sync_synchronize(); +} + +uint32 +__tsan_atomic32_load(uint *ptr, uint *val, int order) +{ +  uint t; +  __atomic_load(ptr, &t, __ATOMIC_SEQ_CST); +  return t; +} + +void +__tsan_atomic32_store(uint *ptr, uint val, int order) +{ +  __atomic_store(ptr, &val, __ATOMIC_SEQ_CST); +} + +// We don't use this +void +__tsan_func_entry(uint64 pc) +{ +} + +// We don't use this +void +__tsan_func_exit(void) +{ +} + + diff --git a/kernel/main.c b/kernel/main.c index f0d3171..48c9555 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -12,6 +12,9 @@ main()  {    if(cpuid() == 0){      consoleinit(); +#if defined(LAB_LOCK) +    statsinit(); +#endif      printfinit();      printf("\n");      printf("xv6 kernel is booting\n"); @@ -28,11 +31,18 @@ main()      iinit();         // inode table      fileinit();      // file table      virtio_disk_init(); // emulated hard disk +#ifdef LAB_NET +    pci_init(); +    sockinit(); +#endif          userinit();      // first user process +#ifdef KCSAN +    kcsaninit(); +#endif      __sync_synchronize();      started = 1;    } else { -    while(started == 0) +    while(atomic_read4((int *) &started) == 0)        ;      __sync_synchronize();      printf("hart %d starting\n", cpuid()); diff --git a/kernel/memlayout.h b/kernel/memlayout.h index cac3cb1..74d2fd4 100644 --- a/kernel/memlayout.h +++ b/kernel/memlayout.h @@ -25,6 +25,10 @@  #define VIRTIO0 0x10001000  #define VIRTIO0_IRQ 1 +#ifdef LAB_NET +#define E1000_IRQ 33 +#endif +  // core local interruptor (CLINT), which contains the timer.  #define CLINT 0x2000000L  #define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 8*(hartid)) @@ -34,8 +38,11 @@  #define PLIC 0x0c000000L  #define PLIC_PRIORITY (PLIC + 0x0)  #define PLIC_PENDING (PLIC + 0x1000) +#define PLIC_MENABLE(hart) (PLIC + 0x2000 + (hart)*0x100)  #define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart)*0x100) +#define PLIC_MPRIORITY(hart) (PLIC + 0x200000 + (hart)*0x2000)  #define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart)*0x2000) +#define PLIC_MCLAIM(hart) (PLIC + 0x200004 + (hart)*0x2000)  #define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart)*0x2000)  // the kernel expects there to be RAM @@ -50,7 +57,7 @@  // map kernel stacks beneath the trampoline,  // each surrounded by invalid guard pages. -#define KSTACK(p) (TRAMPOLINE - ((p)+1)* 2*PGSIZE) +#define KSTACK(p) (TRAMPOLINE - (p)*2*PGSIZE - 3*PGSIZE)  // User memory layout.  // Address zero first: @@ -59,6 +66,14 @@  //   fixed-size stack  //   expandable heap  //   ... +//   USYSCALL (shared with kernel)  //   TRAPFRAME (p->trapframe, used by the trampoline)  //   TRAMPOLINE (the same page as in the kernel)  #define TRAPFRAME (TRAMPOLINE - PGSIZE) +#ifdef LAB_PGTBL +#define USYSCALL (TRAPFRAME - PGSIZE) + +struct usyscall { +  int pid;  // Process ID +}; +#endif diff --git a/kernel/net.c b/kernel/net.c new file mode 100644 index 0000000..137ea2b --- /dev/null +++ b/kernel/net.c @@ -0,0 +1,374 @@ +// +// networking protocol support (IP, UDP, ARP, etc.). +// + +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "net.h" +#include "defs.h" + +static uint32 local_ip = MAKE_IP_ADDR(10, 0, 2, 15); // qemu's idea of the guest IP +static uint8 local_mac[ETHADDR_LEN] = { 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 }; +static uint8 broadcast_mac[ETHADDR_LEN] = { 0xFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF }; + +// Strips data from the start of the buffer and returns a pointer to it. +// Returns 0 if less than the full requested length is available. +char * +mbufpull(struct mbuf *m, unsigned int len) +{ +  char *tmp = m->head; +  if (m->len < len) +    return 0; +  m->len -= len; +  m->head += len; +  return tmp; +} + +// Prepends data to the beginning of the buffer and returns a pointer to it. +char * +mbufpush(struct mbuf *m, unsigned int len) +{ +  m->head -= len; +  if (m->head < m->buf) +    panic("mbufpush"); +  m->len += len; +  return m->head; +} + +// Appends data to the end of the buffer and returns a pointer to it. +char * +mbufput(struct mbuf *m, unsigned int len) +{ +  char *tmp = m->head + m->len; +  m->len += len; +  if (m->len > MBUF_SIZE) +    panic("mbufput"); +  return tmp; +} + +// Strips data from the end of the buffer and returns a pointer to it. +// Returns 0 if less than the full requested length is available. +char * +mbuftrim(struct mbuf *m, unsigned int len) +{ +  if (len > m->len) +    return 0; +  m->len -= len; +  return m->head + m->len; +} + +// Allocates a packet buffer. +struct mbuf * +mbufalloc(unsigned int headroom) +{ +  struct mbuf *m; +  +  if (headroom > MBUF_SIZE) +    return 0; +  m = kalloc(); +  if (m == 0) +    return 0; +  m->next = 0; +  m->head = (char *)m->buf + headroom; +  m->len = 0; +  memset(m->buf, 0, sizeof(m->buf)); +  return m; +} + +// Frees a packet buffer. +void +mbuffree(struct mbuf *m) +{ +  kfree(m); +} + +// Pushes an mbuf to the end of the queue. +void +mbufq_pushtail(struct mbufq *q, struct mbuf *m) +{ +  m->next = 0; +  if (!q->head){ +    q->head = q->tail = m; +    return; +  } +  q->tail->next = m; +  q->tail = m; +} + +// Pops an mbuf from the start of the queue. +struct mbuf * +mbufq_pophead(struct mbufq *q) +{ +  struct mbuf *head = q->head; +  if (!head) +    return 0; +  q->head = head->next; +  return head; +} + +// Returns one (nonzero) if the queue is empty. +int +mbufq_empty(struct mbufq *q) +{ +  return q->head == 0; +} + +// Intializes a queue of mbufs. +void +mbufq_init(struct mbufq *q) +{ +  q->head = 0; +} + +// This code is lifted from FreeBSD's ping.c, and is copyright by the Regents +// of the University of California. +static unsigned short +in_cksum(const unsigned char *addr, int len) +{ +  int nleft = len; +  const unsigned short *w = (const unsigned short *)addr; +  unsigned int sum = 0; +  unsigned short answer = 0; + +  /* +   * Our algorithm is simple, using a 32 bit accumulator (sum), we add +   * sequential 16 bit words to it, and at the end, fold back all the +   * carry bits from the top 16 bits into the lower 16 bits. +   */ +  while (nleft > 1)  { +    sum += *w++; +    nleft -= 2; +  } + +  /* mop up an odd byte, if necessary */ +  if (nleft == 1) { +    *(unsigned char *)(&answer) = *(const unsigned char *)w; +    sum += answer; +  } + +  /* add back carry outs from top 16 bits to low 16 bits */ +  sum = (sum & 0xffff) + (sum >> 16); +  sum += (sum >> 16); +  /* guaranteed now that the lower 16 bits of sum are correct */ + +  answer = ~sum; /* truncate to 16 bits */ +  return answer; +} + +// sends an ethernet packet +static void +net_tx_eth(struct mbuf *m, uint16 ethtype) +{ +  struct eth *ethhdr; + +  ethhdr = mbufpushhdr(m, *ethhdr); +  memmove(ethhdr->shost, local_mac, ETHADDR_LEN); +  // In a real networking stack, dhost would be set to the address discovered +  // through ARP. Because we don't support enough of the ARP protocol, set it +  // to broadcast instead. +  memmove(ethhdr->dhost, broadcast_mac, ETHADDR_LEN); +  ethhdr->type = htons(ethtype); +  if (e1000_transmit(m)) { +    mbuffree(m); +  } +} + +// sends an IP packet +static void +net_tx_ip(struct mbuf *m, uint8 proto, uint32 dip) +{ +  struct ip *iphdr; + +  // push the IP header +  iphdr = mbufpushhdr(m, *iphdr); +  memset(iphdr, 0, sizeof(*iphdr)); +  iphdr->ip_vhl = (4 << 4) | (20 >> 2); +  iphdr->ip_p = proto; +  iphdr->ip_src = htonl(local_ip); +  iphdr->ip_dst = htonl(dip); +  iphdr->ip_len = htons(m->len); +  iphdr->ip_ttl = 100; +  iphdr->ip_sum = in_cksum((unsigned char *)iphdr, sizeof(*iphdr)); + +  // now on to the ethernet layer +  net_tx_eth(m, ETHTYPE_IP); +} + +// sends a UDP packet +void +net_tx_udp(struct mbuf *m, uint32 dip, +           uint16 sport, uint16 dport) +{ +  struct udp *udphdr; + +  // put the UDP header +  udphdr = mbufpushhdr(m, *udphdr); +  udphdr->sport = htons(sport); +  udphdr->dport = htons(dport); +  udphdr->ulen = htons(m->len); +  udphdr->sum = 0; // zero means no checksum is provided + +  // now on to the IP layer +  net_tx_ip(m, IPPROTO_UDP, dip); +} + +// sends an ARP packet +static int +net_tx_arp(uint16 op, uint8 dmac[ETHADDR_LEN], uint32 dip) +{ +  struct mbuf *m; +  struct arp *arphdr; + +  m = mbufalloc(MBUF_DEFAULT_HEADROOM); +  if (!m) +    return -1; + +  // generic part of ARP header +  arphdr = mbufputhdr(m, *arphdr); +  arphdr->hrd = htons(ARP_HRD_ETHER); +  arphdr->pro = htons(ETHTYPE_IP); +  arphdr->hln = ETHADDR_LEN; +  arphdr->pln = sizeof(uint32); +  arphdr->op = htons(op); + +  // ethernet + IP part of ARP header +  memmove(arphdr->sha, local_mac, ETHADDR_LEN); +  arphdr->sip = htonl(local_ip); +  memmove(arphdr->tha, dmac, ETHADDR_LEN); +  arphdr->tip = htonl(dip); + +  // header is ready, send the packet +  net_tx_eth(m, ETHTYPE_ARP); +  return 0; +} + +// receives an ARP packet +static void +net_rx_arp(struct mbuf *m) +{ +  struct arp *arphdr; +  uint8 smac[ETHADDR_LEN]; +  uint32 sip, tip; + +  arphdr = mbufpullhdr(m, *arphdr); +  if (!arphdr) +    goto done; + +  // validate the ARP header +  if (ntohs(arphdr->hrd) != ARP_HRD_ETHER || +      ntohs(arphdr->pro) != ETHTYPE_IP || +      arphdr->hln != ETHADDR_LEN || +      arphdr->pln != sizeof(uint32)) { +    goto done; +  } + +  // only requests are supported so far +  // check if our IP was solicited +  tip = ntohl(arphdr->tip); // target IP address +  if (ntohs(arphdr->op) != ARP_OP_REQUEST || tip != local_ip) +    goto done; + +  // handle the ARP request +  memmove(smac, arphdr->sha, ETHADDR_LEN); // sender's ethernet address +  sip = ntohl(arphdr->sip); // sender's IP address (qemu's slirp) +  net_tx_arp(ARP_OP_REPLY, smac, sip); + +done: +  mbuffree(m); +} + +// receives a UDP packet +static void +net_rx_udp(struct mbuf *m, uint16 len, struct ip *iphdr) +{ +  struct udp *udphdr; +  uint32 sip; +  uint16 sport, dport; + + +  udphdr = mbufpullhdr(m, *udphdr); +  if (!udphdr) +    goto fail; + +  // TODO: validate UDP checksum + +  // validate lengths reported in headers +  if (ntohs(udphdr->ulen) != len) +    goto fail; +  len -= sizeof(*udphdr); +  if (len > m->len) +    goto fail; +  // minimum packet size could be larger than the payload +  mbuftrim(m, m->len - len); + +  // parse the necessary fields +  sip = ntohl(iphdr->ip_src); +  sport = ntohs(udphdr->sport); +  dport = ntohs(udphdr->dport); +  sockrecvudp(m, sip, dport, sport); +  return; + +fail: +  mbuffree(m); +} + +// receives an IP packet +static void +net_rx_ip(struct mbuf *m) +{ +  struct ip *iphdr; +  uint16 len; + +  iphdr = mbufpullhdr(m, *iphdr); +  if (!iphdr) +	  goto fail; + +  // check IP version and header len +  if (iphdr->ip_vhl != ((4 << 4) | (20 >> 2))) +    goto fail; +  // validate IP checksum +  if (in_cksum((unsigned char *)iphdr, sizeof(*iphdr))) +    goto fail; +  // can't support fragmented IP packets +  if (htons(iphdr->ip_off) != 0) +    goto fail; +  // is the packet addressed to us? +  if (htonl(iphdr->ip_dst) != local_ip) +    goto fail; +  // can only support UDP +  if (iphdr->ip_p != IPPROTO_UDP) +    goto fail; + +  len = ntohs(iphdr->ip_len) - sizeof(*iphdr); +  net_rx_udp(m, len, iphdr); +  return; + +fail: +  mbuffree(m); +} + +// called by e1000 driver's interrupt handler to deliver a packet to the +// networking stack +void net_rx(struct mbuf *m) +{ +  struct eth *ethhdr; +  uint16 type; + +  ethhdr = mbufpullhdr(m, *ethhdr); +  if (!ethhdr) { +    mbuffree(m); +    return; +  } + +  type = ntohs(ethhdr->type); +  if (type == ETHTYPE_IP) +    net_rx_ip(m); +  else if (type == ETHTYPE_ARP) +    net_rx_arp(m); +  else +    mbuffree(m); +} diff --git a/kernel/net.h b/kernel/net.h new file mode 100644 index 0000000..9e6fefe --- /dev/null +++ b/kernel/net.h @@ -0,0 +1,173 @@ +// +// packet buffer management +// + +#define MBUF_SIZE              2048 +#define MBUF_DEFAULT_HEADROOM  128 + +struct mbuf { +  struct mbuf  *next; // the next mbuf in the chain +  char         *head; // the current start position of the buffer +  unsigned int len;   // the length of the buffer +  char         buf[MBUF_SIZE]; // the backing store +}; + +char *mbufpull(struct mbuf *m, unsigned int len); +char *mbufpush(struct mbuf *m, unsigned int len); +char *mbufput(struct mbuf *m, unsigned int len); +char *mbuftrim(struct mbuf *m, unsigned int len); + +// The above functions manipulate the size and position of the buffer: +//            <- push            <- trim +//             -> pull            -> put +// [-headroom-][------buffer------][-tailroom-] +// |----------------MBUF_SIZE-----------------| +// +// These marcos automatically typecast and determine the size of header structs. +// In most situations you should use these instead of the raw ops above. +#define mbufpullhdr(mbuf, hdr) (typeof(hdr)*)mbufpull(mbuf, sizeof(hdr)) +#define mbufpushhdr(mbuf, hdr) (typeof(hdr)*)mbufpush(mbuf, sizeof(hdr)) +#define mbufputhdr(mbuf, hdr) (typeof(hdr)*)mbufput(mbuf, sizeof(hdr)) +#define mbuftrimhdr(mbuf, hdr) (typeof(hdr)*)mbuftrim(mbuf, sizeof(hdr)) + +struct mbuf *mbufalloc(unsigned int headroom); +void mbuffree(struct mbuf *m); + +struct mbufq { +  struct mbuf *head;  // the first element in the queue +  struct mbuf *tail;  // the last element in the queue +}; + +void mbufq_pushtail(struct mbufq *q, struct mbuf *m); +struct mbuf *mbufq_pophead(struct mbufq *q); +int mbufq_empty(struct mbufq *q); +void mbufq_init(struct mbufq *q); + + +// +// endianness support +// + +static inline uint16 bswaps(uint16 val) +{ +  return (((val & 0x00ffU) << 8) | +          ((val & 0xff00U) >> 8)); +} + +static inline uint32 bswapl(uint32 val) +{ +  return (((val & 0x000000ffUL) << 24) | +          ((val & 0x0000ff00UL) << 8) | +          ((val & 0x00ff0000UL) >> 8) | +          ((val & 0xff000000UL) >> 24)); +} + +// Use these macros to convert network bytes to the native byte order. +// Note that Risc-V uses little endian while network order is big endian. +#define ntohs bswaps +#define ntohl bswapl +#define htons bswaps +#define htonl bswapl + + +// +// useful networking headers +// + +#define ETHADDR_LEN 6 + +// an Ethernet packet header (start of the packet). +struct eth { +  uint8  dhost[ETHADDR_LEN]; +  uint8  shost[ETHADDR_LEN]; +  uint16 type; +} __attribute__((packed)); + +#define ETHTYPE_IP  0x0800 // Internet protocol +#define ETHTYPE_ARP 0x0806 // Address resolution protocol + +// an IP packet header (comes after an Ethernet header). +struct ip { +  uint8  ip_vhl; // version << 4 | header length >> 2 +  uint8  ip_tos; // type of service +  uint16 ip_len; // total length +  uint16 ip_id;  // identification +  uint16 ip_off; // fragment offset field +  uint8  ip_ttl; // time to live +  uint8  ip_p;   // protocol +  uint16 ip_sum; // checksum +  uint32 ip_src, ip_dst; +}; + +#define IPPROTO_ICMP 1  // Control message protocol +#define IPPROTO_TCP  6  // Transmission control protocol +#define IPPROTO_UDP  17 // User datagram protocol + +#define MAKE_IP_ADDR(a, b, c, d)           \ +  (((uint32)a << 24) | ((uint32)b << 16) | \ +   ((uint32)c << 8) | (uint32)d) + +// a UDP packet header (comes after an IP header). +struct udp { +  uint16 sport; // source port +  uint16 dport; // destination port +  uint16 ulen;  // length, including udp header, not including IP header +  uint16 sum;   // checksum +}; + +// an ARP packet (comes after an Ethernet header). +struct arp { +  uint16 hrd; // format of hardware address +  uint16 pro; // format of protocol address +  uint8  hln; // length of hardware address +  uint8  pln; // length of protocol address +  uint16 op;  // operation + +  char   sha[ETHADDR_LEN]; // sender hardware address +  uint32 sip;              // sender IP address +  char   tha[ETHADDR_LEN]; // target hardware address +  uint32 tip;              // target IP address +} __attribute__((packed)); + +#define ARP_HRD_ETHER 1 // Ethernet + +enum { +  ARP_OP_REQUEST = 1, // requests hw addr given protocol addr +  ARP_OP_REPLY = 2,   // replies a hw addr given protocol addr +}; + +// an DNS packet (comes after an UDP header). +struct dns { +  uint16 id;  // request ID + +  uint8 rd: 1;  // recursion desired +  uint8 tc: 1;  // truncated +  uint8 aa: 1;  // authoritive +  uint8 opcode: 4;  +  uint8 qr: 1;  // query/response +  uint8 rcode: 4; // response code +  uint8 cd: 1;  // checking disabled +  uint8 ad: 1;  // authenticated data +  uint8 z:  1;   +  uint8 ra: 1;  // recursion available +   +  uint16 qdcount; // number of question entries +  uint16 ancount; // number of resource records in answer section +  uint16 nscount; // number of NS resource records in authority section +  uint16 arcount; // number of resource records in additional records +} __attribute__((packed)); + +struct dns_question { +  uint16 qtype; +  uint16 qclass; +} __attribute__((packed)); +   +#define ARECORD (0x0001) +#define QCLASS  (0x0001) + +struct dns_data { +  uint16 type; +  uint16 class; +  uint32 ttl; +  uint16 len; +} __attribute__((packed)); diff --git a/kernel/param.h b/kernel/param.h index cd2c689..431741f 100644 --- a/kernel/param.h +++ b/kernel/param.h @@ -16,12 +16,8 @@  #ifdef LAB_FS  #define FSSIZE       200000  // size of file system in blocks  #else -#ifdef LAB_LOCK -#define FSSIZE       10000  // size of file system in blocks -#else  #define FSSIZE       2000   // size of file system in blocks  #endif -#endif  #define MAXPATH      128   // maximum file path name diff --git a/kernel/pci.c b/kernel/pci.c new file mode 100644 index 0000000..3e361c5 --- /dev/null +++ b/kernel/pci.c @@ -0,0 +1,61 @@ +// +// simple PCI-Express initialization, only +// works for qemu and its e1000 card. +// + +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" + +void +pci_init() +{ +  // we'll place the e1000 registers at this address. +  // vm.c maps this range. +  uint64 e1000_regs = 0x40000000L; + +  // qemu -machine virt puts PCIe config space here. +  // vm.c maps this range. +  uint32  *ecam = (uint32 *) 0x30000000L; +   +  // look at each possible PCI device on bus 0. +  for(int dev = 0; dev < 32; dev++){ +    int bus = 0; +    int func = 0; +    int offset = 0; +    uint32 off = (bus << 16) | (dev << 11) | (func << 8) | (offset); +    volatile uint32 *base = ecam + off; +    uint32 id = base[0]; +     +    // 100e:8086 is an e1000 +    if(id == 0x100e8086){ +      // command and status register. +      // bit 0 : I/O access enable +      // bit 1 : memory access enable +      // bit 2 : enable mastering +      base[1] = 0b111; +      __sync_synchronize(); + +      for(int i = 0; i < 6; i++){ +        uint32 old = base[4+i]; + +        // writing all 1's to the BAR causes it to be +        // replaced with its size. +        base[4+i] = 0xffffffff; +        __sync_synchronize(); + +        base[4+i] = old; +      } + +      // tell the e1000 to reveal its registers at +      // physical address 0x40000000. +      base[4+0] = e1000_regs; + +      e1000_init((uint32*)e1000_regs); +    } +  } +} diff --git a/kernel/pipe.c b/kernel/pipe.c index f6b501a..41a9c5e 100644 --- a/kernel/pipe.c +++ b/kernel/pipe.c @@ -68,6 +68,9 @@ pipeclose(struct pipe *pi, int writable)    }    if(pi->readopen == 0 && pi->writeopen == 0){      release(&pi->lock); +#ifdef LAB_LOCK +    freelock(&pi->lock); +#endif          kfree((char*)pi);    } else      release(&pi->lock); diff --git a/kernel/plic.c b/kernel/plic.c index 4175db9..5c9d96a 100644 --- a/kernel/plic.c +++ b/kernel/plic.c @@ -14,6 +14,13 @@ plicinit(void)    // set desired IRQ priorities non-zero (otherwise disabled).    *(uint32*)(PLIC + UART0_IRQ*4) = 1;    *(uint32*)(PLIC + VIRTIO0_IRQ*4) = 1; +   +#ifdef LAB_NET +  // PCIE IRQs are 32 to 35 +  for(int irq = 1; irq < 0x35; irq++){ +    *(uint32*)(PLIC + irq*4) = 1; +  } +#endif    }  void @@ -25,6 +32,11 @@ plicinithart(void)    // for the uart and virtio disk.    *(uint32*)PLIC_SENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO0_IRQ); +#ifdef LAB_NET +  // hack to get at next 32 IRQs for e1000 +  *(uint32*)(PLIC_SENABLE(hart)+4) = 0xffffffff; +#endif +      // set this hart's S-mode priority threshold to 0.    *(uint32*)PLIC_SPRIORITY(hart) = 0;  } diff --git a/kernel/printf.c b/kernel/printf.c index 1a50203..509c1c5 100644 --- a/kernel/printf.c +++ b/kernel/printf.c @@ -122,6 +122,8 @@ panic(char *s)    printf("panic: ");    printf(s);    printf("\n"); +  backtrace(); +    panicked = 1; // freeze uart output from other CPUs    for(;;)      ; @@ -133,3 +135,18 @@ printfinit(void)    initlock(&pr.lock, "pr");    pr.locking = 1;  } + +void +backtrace(void) +{ +  uint64 fp = r_fp(); +  printf("backtrace:\n"); +  uint64 stackpg = PGROUNDDOWN(fp); +  // Whereever fp points to should always live in the stack page +  while(PGROUNDDOWN(fp) == stackpg){ +    // print the return addr (stored in fp-8) +    printf("%p\n", *(uint64 *)(fp-8)); +    // load previous (upper stack) fp +    fp = *(uint64 *)(fp-16); +  } +} diff --git a/kernel/proc.c b/kernel/proc.c index 58a8a0b..9a9bae9 100644 --- a/kernel/proc.c +++ b/kernel/proc.c @@ -39,6 +39,7 @@ proc_mapstacks(pagetable_t kpgtbl)      if(pa == 0)        panic("kalloc");      uint64 va = KSTACK((int) (p - proc)); +  p->alarm_tickspassed = 0;      kvmmap(kpgtbl, va, (uint64)pa, PGSIZE, PTE_R | PTE_W);    }  } @@ -132,6 +133,25 @@ found:      return 0;    } +  // Allocate a usyscall page and fill pid. +  if((p->usyscall = (struct usyscall *)kalloc()) == 0){ +    freeproc(p); +    release(&p->lock); +    return 0; +  } +  p->usyscall->pid = p->pid; + +  // reset sigalarm props  +  p->alarm_interval = 0; +  p->alarm_handler = 0; +  p->alarm_tickspassed = 0; +  p->alarm_caninvoke = 1; +  if((p->atpfm = (struct trapframe *)kalloc()) == 0){ +    freeproc(p); +    release(&p->lock); +    return 0; +  } +    // An empty user page table.    p->pagetable = proc_pagetable(p);    if(p->pagetable == 0){ @@ -158,8 +178,18 @@ freeproc(struct proc *p)    if(p->trapframe)      kfree((void*)p->trapframe);    p->trapframe = 0; +  if(p->usyscall) +    kfree((void*)p->usyscall); +  p->usyscall = 0;    if(p->pagetable)      proc_freepagetable(p->pagetable, p->sz); +  if(p->atpfm) +    kfree((void*)p->atpfm); +  p->atpfm = 0; +  p->alarm_interval = 0; +  p->alarm_handler = 0; +  p->alarm_tickspassed = 0; +  p->alarm_caninvoke = 1;    p->pagetable = 0;    p->sz = 0;    p->pid = 0; @@ -172,7 +202,7 @@ freeproc(struct proc *p)  }  // Create a user page table for a given process, with no user memory, -// but with trampoline and trapframe pages. +// but with trampoline, trapframe and usyscall pages.  pagetable_t  proc_pagetable(struct proc *p)  { @@ -202,6 +232,14 @@ proc_pagetable(struct proc *p)      return 0;    } +  // map the usyscall page below the trapframe page, for +  // ugetpid(). +  if(mappages(pagetable, USYSCALL, PGSIZE, +              (uint64)(p->usyscall), PTE_R | PTE_U) < 0){ +    uvmunmap(pagetable, USYSCALL, 1, 0); +    uvmfree(pagetable, 0); +    return 0; +  }    return pagetable;  } @@ -212,6 +250,7 @@ proc_freepagetable(pagetable_t pagetable, uint64 sz)  {    uvmunmap(pagetable, TRAMPOLINE, 1, 0);    uvmunmap(pagetable, TRAPFRAME, 1, 0); +  uvmunmap(pagetable, USYSCALL, 1, 0);    uvmfree(pagetable, sz);  } @@ -299,6 +338,9 @@ fork(void)    // copy saved user registers.    *(np->trapframe) = *(p->trapframe); +  // inherit trace_mask +  np->trace_mask = p->trace_mask; +    // Cause fork to return 0 in the child.    np->trapframe->a0 = 0; @@ -686,3 +728,43 @@ procdump(void)      printf("\n");    }  } + +int +get_nproc(void) +{ +  int n = 0; +  struct proc *p; + +  for(int i = 0; i < NPROC; i++) { +    p = &proc[i]; +    acquire(&p->lock); +    if(p->state != UNUSED) +      n++; +    release(&p->lock); +  } + +  return n; +} + +// lab pagetable: report which pages have been accessed (r/w) +// according to PTE_A and store it in a bit mask (3rd param) +int +pgaccess(uint64 base, int len, uint64 mask_addr) +{ +  struct proc *p = myproc(); +  pagetable_t pgtbl = p->pagetable; +  pte_t *pte; +  int mask = 0; +   +  // iterater thru pages +  for(int i = 0; i < len; i++) { +    pte = walk(pgtbl, base + i * PGSIZE, 0); +    if(*pte & PTE_A) { +      *pte &= (~PTE_A); // clear PTE_A to avoid setting it forever +      mask |= (1L << i); +    } +  } + +  // now copyout the mask to user memory +  return copyout(pgtbl, mask_addr, (char *)&mask, sizeof(mask)); +} diff --git a/kernel/proc.h b/kernel/proc.h index d021857..a195b02 100644 --- a/kernel/proc.h +++ b/kernel/proc.h @@ -91,6 +91,7 @@ struct proc {    int killed;                  // If non-zero, have been killed    int xstate;                  // Exit status to be returned to parent's wait    int pid;                     // Process ID +  int trace_mask;              // SYS_trace mask (1 << SYS_xxx)    // wait_lock must be held when using this:    struct proc *parent;         // Parent process @@ -100,8 +101,14 @@ struct proc {    uint64 sz;                   // Size of process memory (bytes)    pagetable_t pagetable;       // User page table    struct trapframe *trapframe; // data page for trampoline.S +  struct usyscall *usyscall;   // data page for usyscall    struct context context;      // swtch() here to run process    struct file *ofile[NOFILE];  // Open files    struct inode *cwd;           // Current directory    char name[16];               // Process name (debugging) +  int alarm_interval;          // sigalarm syscall interval +  uint64 alarm_handler;        // sigalarm syscall handler +  int alarm_tickspassed;       // record how many ticks passed since last sigalarm handler call +  int alarm_caninvoke;         // prevent re-entrant calls to handler +  struct trapframe *atpfm;     // trapframe to resume after handling, must hold p->lock  }; diff --git a/kernel/riscv.h b/kernel/riscv.h index 20a01db..af18972 100644 --- a/kernel/riscv.h +++ b/kernel/riscv.h @@ -327,6 +327,15 @@ sfence_vma()    asm volatile("sfence.vma zero, zero");  } +// read the frame pointer of currently executing func +static inline uint64 +r_fp() +{ +  uint64 x; +  asm volatile("mv %0, s0" : "=r" (x) ); +  return x; +} +  typedef uint64 pte_t;  typedef uint64 *pagetable_t; // 512 PTEs @@ -343,6 +352,11 @@ typedef uint64 *pagetable_t; // 512 PTEs  #define PTE_W (1L << 2)  #define PTE_X (1L << 3)  #define PTE_U (1L << 4) // user can access +#define PTE_A (1L << 6) // riscv access bit                       +#define PTE_C (1L << 8) // RSW low bit, use it to mark whether a page is COW + + +  // shift a physical address to the right place for a PTE.  #define PA2PTE(pa) ((((uint64)pa) >> 12) << 10) diff --git a/kernel/spinlock.c b/kernel/spinlock.c index 9840302..266a698 100644 --- a/kernel/spinlock.c +++ b/kernel/spinlock.c @@ -8,12 +8,52 @@  #include "proc.h"  #include "defs.h" +#ifdef LAB_LOCK +#define NLOCK 500 + +static struct spinlock *locks[NLOCK]; +struct spinlock lock_locks; + +void +freelock(struct spinlock *lk) +{ +  acquire(&lock_locks); +  int i; +  for (i = 0; i < NLOCK; i++) { +    if(locks[i] == lk) { +      locks[i] = 0; +      break; +    } +  } +  release(&lock_locks); +} + +static void +findslot(struct spinlock *lk) { +  acquire(&lock_locks); +  int i; +  for (i = 0; i < NLOCK; i++) { +    if(locks[i] == 0) { +      locks[i] = lk; +      release(&lock_locks); +      return; +    } +  } +  panic("findslot"); +} +#endif +  void  initlock(struct spinlock *lk, char *name)  {    lk->name = name;    lk->locked = 0;    lk->cpu = 0; +#ifdef LAB_LOCK +  lk->nts = 0; +  lk->n = 0; +  findslot(lk); +#endif    }  // Acquire the lock. @@ -25,12 +65,21 @@ acquire(struct spinlock *lk)    if(holding(lk))      panic("acquire"); +#ifdef LAB_LOCK +    __sync_fetch_and_add(&(lk->n), 1); +#endif       +    // On RISC-V, sync_lock_test_and_set turns into an atomic swap:    //   a5 = 1    //   s1 = &lk->locked    //   amoswap.w.aq a5, a5, (s1) -  while(__sync_lock_test_and_set(&lk->locked, 1) != 0) -    ; +  while(__sync_lock_test_and_set(&lk->locked, 1) != 0) { +#ifdef LAB_LOCK +    __sync_fetch_and_add(&(lk->nts), 1); +#else +   ; +#endif +  }    // Tell the C compiler and the processor to not move loads or stores    // past this point, to ensure that the critical section's memory @@ -108,3 +157,61 @@ pop_off(void)    if(c->noff == 0 && c->intena)      intr_on();  } + +// Read a shared 32-bit value without holding a lock +int +atomic_read4(int *addr) { +  uint32 val; +  __atomic_load(addr, &val, __ATOMIC_SEQ_CST); +  return val; +} + +#ifdef LAB_LOCK +int +snprint_lock(char *buf, int sz, struct spinlock *lk) +{ +  int n = 0; +  if(lk->n > 0) { +    n = snprintf(buf, sz, "lock: %s: #test-and-set %d #acquire() %d\n", +                 lk->name, lk->nts, lk->n); +  } +  return n; +} + +int +statslock(char *buf, int sz) { +  int n; +  int tot = 0; + +  acquire(&lock_locks); +  n = snprintf(buf, sz, "--- lock kmem/bcache stats\n"); +  for(int i = 0; i < NLOCK; i++) { +    if(locks[i] == 0) +      break; +    if(strncmp(locks[i]->name, "bcache", strlen("bcache")) == 0 || +       strncmp(locks[i]->name, "kmem", strlen("kmem")) == 0) { +      tot += locks[i]->nts; +      n += snprint_lock(buf +n, sz-n, locks[i]); +    } +  } +   +  n += snprintf(buf+n, sz-n, "--- top 5 contended locks:\n"); +  int last = 100000000; +  // stupid way to compute top 5 contended locks +  for(int t = 0; t < 5; t++) { +    int top = 0; +    for(int i = 0; i < NLOCK; i++) { +      if(locks[i] == 0) +        break; +      if(locks[i]->nts > locks[top]->nts && locks[i]->nts < last) { +        top = i; +      } +    } +    n += snprint_lock(buf+n, sz-n, locks[top]); +    last = locks[top]->nts; +  } +  n += snprintf(buf+n, sz-n, "tot= %d\n", tot); +  release(&lock_locks);   +  return n; +} +#endif diff --git a/kernel/spinlock.h b/kernel/spinlock.h index 4392820..9bac216 100644 --- a/kernel/spinlock.h +++ b/kernel/spinlock.h @@ -5,5 +5,9 @@ struct spinlock {    // For debugging:    char *name;        // Name of lock.    struct cpu *cpu;   // The cpu holding the lock. +#ifdef LAB_LOCK +  int nts; +  int n; +#endif  }; diff --git a/kernel/sprintf.c b/kernel/sprintf.c new file mode 100644 index 0000000..050eb85 --- /dev/null +++ b/kernel/sprintf.c @@ -0,0 +1,91 @@ +#include <stdarg.h> + +#include "types.h" +#include "param.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "file.h" +#include "riscv.h" +#include "defs.h" + +static char digits[] = "0123456789abcdef"; + +static int +sputc(char *s, char c) +{ +  *s = c; +  return 1; +} + +static int +sprintint(char *s, int xx, int base, int sign) +{ +  char buf[16]; +  int i, n; +  uint x; + +  if(sign && (sign = xx < 0)) +    x = -xx; +  else +    x = xx; + +  i = 0; +  do { +    buf[i++] = digits[x % base]; +  } while((x /= base) != 0); + +  if(sign) +    buf[i++] = '-'; + +  n = 0; +  while(--i >= 0) +    n += sputc(s+n, buf[i]); +  return n; +} + +int +snprintf(char *buf, int sz, char *fmt, ...) +{ +  va_list ap; +  int i, c; +  int off = 0; +  char *s; + +  if (fmt == 0) +    panic("null fmt"); + +  va_start(ap, fmt); +  for(i = 0; off < sz && (c = fmt[i] & 0xff) != 0; i++){ +    if(c != '%'){ +      off += sputc(buf+off, c); +      continue; +    } +    c = fmt[++i] & 0xff; +    if(c == 0) +      break; +    switch(c){ +    case 'd': +      off += sprintint(buf+off, va_arg(ap, int), 10, 1); +      break; +    case 'x': +      off += sprintint(buf+off, va_arg(ap, int), 16, 1); +      break; +    case 's': +      if((s = va_arg(ap, char*)) == 0) +        s = "(null)"; +      for(; *s && off < sz; s++) +        off += sputc(buf+off, *s); +      break; +    case '%': +      off += sputc(buf+off, '%'); +      break; +    default: +      // Print unknown % sequence to draw attention. +      off += sputc(buf+off, '%'); +      off += sputc(buf+off, c); +      break; +    } +  } +  return off; +} diff --git a/kernel/start.c b/kernel/start.c index e16f18a..bf03bc0 100644 --- a/kernel/start.c +++ b/kernel/start.c @@ -38,6 +38,11 @@ start()    w_mideleg(0xffff);    w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE); +#ifdef KCSAN +  // allow supervisor to read cycle counter register +  w_mcounteren(r_mcounteren()|0x3); +#endif +      // configure Physical Memory Protection to give supervisor mode    // access to all of physical memory.    w_pmpaddr0(0x3fffffffffffffull); diff --git a/kernel/stats.c b/kernel/stats.c new file mode 100644 index 0000000..b7a8e5f --- /dev/null +++ b/kernel/stats.c @@ -0,0 +1,66 @@ +#include <stdarg.h> + +#include "types.h" +#include "param.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "file.h" +#include "riscv.h" +#include "defs.h" + +#define BUFSZ 4096 +static struct { +  struct spinlock lock; +  char buf[BUFSZ]; +  int sz; +  int off; +} stats; + +int statscopyin(char*, int); +int statslock(char*, int); +   +int +statswrite(int user_src, uint64 src, int n) +{ +  return -1; +} + +int +statsread(int user_dst, uint64 dst, int n) +{ +  int m; + +  acquire(&stats.lock); + +  if(stats.sz == 0) { +#ifdef LAB_LOCK +    stats.sz = statslock(stats.buf, BUFSZ); +#endif +  } +  m = stats.sz - stats.off; + +  if (m > 0) { +    if(m > n) +      m  = n; +    if(either_copyout(user_dst, dst, stats.buf+stats.off, m) != -1) { +      stats.off += m; +    } +  } else { +    m = -1; +    stats.sz = 0; +    stats.off = 0; +  } +  release(&stats.lock); +  return m; +} + +void +statsinit(void) +{ +  initlock(&stats.lock, "stats"); + +  devsw[STATS].read = statsread; +  devsw[STATS].write = statswrite; +} + diff --git a/kernel/syscall.c b/kernel/syscall.c index ed65409..172c5ea 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -101,6 +101,24 @@ extern uint64 sys_unlink(void);  extern uint64 sys_link(void);  extern uint64 sys_mkdir(void);  extern uint64 sys_close(void); +extern uint64 sys_trace(void); +extern uint64 sys_sysinfo(void); + +#ifdef LAB_NET +extern uint64 sys_connect(void); +#endif +#ifdef LAB_PGTBL +extern uint64 sys_pgaccess(void); +#endif +extern uint64 sys_sigalarm(void); +extern uint64 sys_sigreturn(void); + +#ifdef LAB_NET +extern uint64 sys_connect(void); +#endif +#ifdef LAB_PGTBL +extern uint64 sys_pgaccess(void); +#endif  // An array mapping syscall numbers from syscall.h  // to the function that handles the system call. @@ -126,8 +144,54 @@ static uint64 (*syscalls[])(void) = {  [SYS_link]    sys_link,  [SYS_mkdir]   sys_mkdir,  [SYS_close]   sys_close, +#ifdef LAB_NET +[SYS_connect] sys_connect, +#endif +#ifdef LAB_PGTBL +[SYS_pgaccess] sys_pgaccess, +#endif +[SYS_trace]   sys_trace, +[SYS_sysinfo] sys_sysinfo, +[SYS_sigalarm] sys_sigalarm, +[SYS_sigreturn] sys_sigreturn, +}; + +// syscall name maps for SYS_trace: +static char *syscall_names[] = { +[SYS_fork]    "fork", +[SYS_exit]    "exit", +[SYS_wait]    "wait", +[SYS_pipe]    "pipe", +[SYS_read]    "read", +[SYS_kill]    "kill", +[SYS_exec]    "exec", +[SYS_fstat]   "fstat", +[SYS_chdir]   "chdir", +[SYS_dup]     "dup", +[SYS_getpid]  "getpid", +[SYS_sbrk]    "sbrk", +[SYS_sleep]   "sleep", +[SYS_uptime]  "uptime", +[SYS_open]    "open", +[SYS_write]   "write", +[SYS_mknod]   "mknod", +[SYS_unlink]  "unlink", +[SYS_link]    "link", +[SYS_mkdir]   "mkdir", +[SYS_close]   "close", +#ifdef LAB_NET +[SYS_connect] "connect", +#endif +#ifdef LAB_PGTBL +[SYS_pgaccess] "pgaccess", +#endif +[SYS_trace]   "trace", +[SYS_sysinfo] "sysinfo", +[SYS_sigalarm]  "sigalarm", +[SYS_sigreturn] "sigreturn",  }; +  void  syscall(void)  { @@ -139,9 +203,17 @@ syscall(void)      // Use num to lookup the system call function for num, call it,      // and store its return value in p->trapframe->a0      p->trapframe->a0 = syscalls[num](); +     +    // SYS_trace: match all the syscalls which number < mask asked +    // p->trace_mask == 1 << SYS_xxx +    if(p->trace_mask >> num) { +      printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0); +    } +    } else {      printf("%d %s: unknown sys call %d\n",              p->pid, p->name, num);      p->trapframe->a0 = -1;    }  } + diff --git a/kernel/syscall.h b/kernel/syscall.h index bc5f356..8da572e 100644 --- a/kernel/syscall.h +++ b/kernel/syscall.h @@ -20,3 +20,14 @@  #define SYS_link   19  #define SYS_mkdir  20  #define SYS_close  21 + +// System calls for labs +#define SYS_trace     22 +#define SYS_sysinfo   23 +#define SYS_sigalarm  24 +#define SYS_sigreturn 25 +#define SYS_symlink   26 +#define SYS_mmap      27 +#define SYS_munmap    28 +#define SYS_connect   29 +#define SYS_pgaccess  30 diff --git a/kernel/sysfile.c b/kernel/sysfile.c index 16b668c..4b2189a 100644 --- a/kernel/sysfile.c +++ b/kernel/sysfile.c @@ -503,3 +503,29 @@ sys_pipe(void)    }    return 0;  } + + +#ifdef LAB_NET +int +sys_connect(void) +{ +  struct file *f; +  int fd; +  uint32 raddr; +  uint32 rport; +  uint32 lport; + +  argint(0, (int*)&raddr); +  argint(1, (int*)&lport); +  argint(2, (int*)&rport); + +  if(sockalloc(&f, raddr, lport, rport) < 0) +    return -1; +  if((fd=fdalloc(f)) < 0){ +    fileclose(f); +    return -1; +  } + +  return fd; +} +#endif diff --git a/kernel/sysinfo.c b/kernel/sysinfo.c new file mode 100644 index 0000000..c66324d --- /dev/null +++ b/kernel/sysinfo.c @@ -0,0 +1,24 @@ +#include "types.h" +#include "riscv.h" +#include "param.h" +#include "spinlock.h" +#include "defs.h" +#include "sysinfo.h" +#include "proc.h" + +// Get current system info +// addr is a user virtual address, pointing to a struct sysinfo. +int +sys_info(uint64 addr) { +  struct proc *p = myproc(); +  struct sysinfo info; + +  // Fill nums into the sysinfo struct +  info.freemem = get_freemem(); +  info.nproc = get_nproc(); + +  if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0) +    return -1; +  return 0; +} + diff --git a/kernel/sysinfo.h b/kernel/sysinfo.h new file mode 100644 index 0000000..fb878e6 --- /dev/null +++ b/kernel/sysinfo.h @@ -0,0 +1,4 @@ +struct sysinfo { +  uint64 freemem;   // amount of free memory (bytes) +  uint64 nproc;     // number of process +}; diff --git a/kernel/sysnet.c b/kernel/sysnet.c new file mode 100644 index 0000000..1c48cb3 --- /dev/null +++ b/kernel/sysnet.c @@ -0,0 +1,185 @@ +// +// network system calls. +// + +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" +#include "fs.h" +#include "sleeplock.h" +#include "file.h" +#include "net.h" + +struct sock { +  struct sock *next; // the next socket in the list +  uint32 raddr;      // the remote IPv4 address +  uint16 lport;      // the local UDP port number +  uint16 rport;      // the remote UDP port number +  struct spinlock lock; // protects the rxq +  struct mbufq rxq;  // a queue of packets waiting to be received +}; + +static struct spinlock lock; +static struct sock *sockets; + +void +sockinit(void) +{ +  initlock(&lock, "socktbl"); +} + +int +sockalloc(struct file **f, uint32 raddr, uint16 lport, uint16 rport) +{ +  struct sock *si, *pos; + +  si = 0; +  *f = 0; +  if ((*f = filealloc()) == 0) +    goto bad; +  if ((si = (struct sock*)kalloc()) == 0) +    goto bad; + +  // initialize objects +  si->raddr = raddr; +  si->lport = lport; +  si->rport = rport; +  initlock(&si->lock, "sock"); +  mbufq_init(&si->rxq); +  (*f)->type = FD_SOCK; +  (*f)->readable = 1; +  (*f)->writable = 1; +  (*f)->sock = si; + +  // add to list of sockets +  acquire(&lock); +  pos = sockets; +  while (pos) { +    if (pos->raddr == raddr && +        pos->lport == lport && +	pos->rport == rport) { +      release(&lock); +      goto bad; +    } +    pos = pos->next; +  } +  si->next = sockets; +  sockets = si; +  release(&lock); +  return 0; + +bad: +  if (si) +    kfree((char*)si); +  if (*f) +    fileclose(*f); +  return -1; +} + +void +sockclose(struct sock *si) +{ +  struct sock **pos; +  struct mbuf *m; + +  // remove from list of sockets +  acquire(&lock); +  pos = &sockets; +  while (*pos) { +    if (*pos == si){ +      *pos = si->next; +      break; +    } +    pos = &(*pos)->next; +  } +  release(&lock); + +  // free any pending mbufs +  while (!mbufq_empty(&si->rxq)) { +    m = mbufq_pophead(&si->rxq); +    mbuffree(m); +  } + +  kfree((char*)si); +} + +int +sockread(struct sock *si, uint64 addr, int n) +{ +  struct proc *pr = myproc(); +  struct mbuf *m; +  int len; + +  acquire(&si->lock); +  while (mbufq_empty(&si->rxq) && !pr->killed) { +    sleep(&si->rxq, &si->lock); +  } +  if (pr->killed) { +    release(&si->lock); +    return -1; +  } +  m = mbufq_pophead(&si->rxq); +  release(&si->lock); + +  len = m->len; +  if (len > n) +    len = n; +  if (copyout(pr->pagetable, addr, m->head, len) == -1) { +    mbuffree(m); +    return -1; +  } +  mbuffree(m); +  return len; +} + +int +sockwrite(struct sock *si, uint64 addr, int n) +{ +  struct proc *pr = myproc(); +  struct mbuf *m; + +  m = mbufalloc(MBUF_DEFAULT_HEADROOM); +  if (!m) +    return -1; + +  if (copyin(pr->pagetable, mbufput(m, n), addr, n) == -1) { +    mbuffree(m); +    return -1; +  } +  net_tx_udp(m, si->raddr, si->lport, si->rport); +  return n; +} + +// called by protocol handler layer to deliver UDP packets +void +sockrecvudp(struct mbuf *m, uint32 raddr, uint16 lport, uint16 rport) +{ +  // +  // Find the socket that handles this mbuf and deliver it, waking +  // any sleeping reader. Free the mbuf if there are no sockets +  // registered to handle it. +  // +  struct sock *si; + +  acquire(&lock); +  si = sockets; +  while (si) { +    if (si->raddr == raddr && si->lport == lport && si->rport == rport) +      goto found; +    si = si->next; +  } +  release(&lock); +  mbuffree(m); +  return; + +found: +  acquire(&si->lock); +  mbufq_pushtail(&si->rxq, m); +  wakeup(&si->rxq); +  release(&si->lock); +  release(&lock); +} diff --git a/kernel/sysproc.c b/kernel/sysproc.c index 3b4d5bd..715a511 100644 --- a/kernel/sysproc.c +++ b/kernel/sysproc.c @@ -1,7 +1,7 @@  #include "types.h"  #include "riscv.h" -#include "defs.h"  #include "param.h" +#include "defs.h"  #include "memlayout.h"  #include "spinlock.h"  #include "proc.h" @@ -54,9 +54,8 @@ sys_sleep(void)    int n;    uint ticks0; +    argint(0, &n); -  if(n < 0) -    n = 0;    acquire(&tickslock);    ticks0 = ticks;    while(ticks - ticks0 < n){ @@ -66,10 +65,29 @@ sys_sleep(void)      }      sleep(&ticks, &tickslock);    } + +  // backtrace(); +    release(&tickslock);    return 0;  } + +#ifdef LAB_PGTBL +int +sys_pgaccess(void) +{ +  uint64 base, mask; +  int len; + +   +  argaddr(0, &base); +  argint(1, &len); +  argaddr(2, &mask); +  return pgaccess(base, len, mask); +} +#endif +  uint64  sys_kill(void)  { @@ -91,3 +109,44 @@ sys_uptime(void)    release(&tickslock);    return xticks;  } + +uint64 +sys_trace(void) +{ +  argint(0, &myproc()->trace_mask); + +  return -(myproc()->trace_mask <= 1); +} + +uint64 +sys_sysinfo(void) +{ +  uint64 si; // user pointer to struct sysinfo + +  argaddr(0, &si); +  return sys_info(si); +} + +uint64 +sys_sigalarm(void) +{ +  struct proc *p = myproc(); +  uint64 handler; + +  argint(0, &p->alarm_interval); +  argaddr(1, &handler); +  p->alarm_handler = handler; + +  return 0; +} + +uint64 sys_sigreturn(void) +{ +  struct proc *p = myproc(); +  // retore saved trapframe to resume +  memmove(p->trapframe, p->atpfm, sizeof(struct trapframe)); +  p->alarm_tickspassed = 0; +  p->alarm_caninvoke = 1; +  // make sure return the original a0 in trapframe to pass test3 +  return p->trapframe->a0; +} diff --git a/kernel/trap.c b/kernel/trap.c index 512c850..7cc69b1 100644 --- a/kernel/trap.c +++ b/kernel/trap.c @@ -6,6 +6,12 @@  #include "proc.h"  #include "defs.h" +/* + * Always remember that RISC-V disables interrupts when it starts to take a trap, + * so there's no need to call intr_off() at the beginning of trap handling. + * Reference: xv6-riscv-book 4.5 + */ +  struct spinlock tickslock;  uint ticks; @@ -67,18 +73,41 @@ usertrap(void)      syscall();    } else if((which_dev = devintr()) != 0){      // ok +  } else if(r_scause() == 13 || r_scause() == 15){ +    // deal with page fault +    uint64 va = r_stval(); +    if(cow_handler(p->pagetable, va) < 0) +      goto err;    } else { + +          printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);      printf("            sepc=%p stval=%p\n", r_sepc(), r_stval()); +  err: +    printf("killing the process...\n");      setkilled(p);    }    if(killed(p))      exit(-1); +   -  // give up the CPU if this is a timer interrupt. -  if(which_dev == 2) +  if(which_dev == 2){ +    // timer interrupt +    if(p->alarm_interval > 0 && p->alarm_caninvoke){ +      // record sigalarm +      p->alarm_tickspassed++; +      if(p->alarm_tickspassed == p->alarm_interval){ +        // store original trapframe in p->atpfm +        memmove(p->atpfm, p->trapframe, sizeof(struct trapframe)); +        p->alarm_tickspassed = 0; +        p->alarm_caninvoke = 0; +        p->trapframe->epc = p->alarm_handler; +      } +    } +    // give up the CPU.      yield(); +  }    usertrapret();  } @@ -190,7 +219,13 @@ devintr()        uartintr();      } else if(irq == VIRTIO0_IRQ){        virtio_disk_intr(); -    } else if(irq){ +    } +#ifdef LAB_NET +    else if(irq == E1000_IRQ){ +      e1000_intr(); +    } +#endif +    else if(irq){        printf("unexpected interrupt irq=%d\n", irq);      } diff --git a/kernel/virtio_disk.c b/kernel/virtio_disk.c index ae6c164..dfca2bc 100644 --- a/kernel/virtio_disk.c +++ b/kernel/virtio_disk.c @@ -212,6 +212,28 @@ alloc3_desc(int *idx)    return 0;  } +#ifdef LAB_LOCK +// +// check that there are at most NBUF distinct +// struct buf's, which the lock lab requires. +// +static struct buf *xbufs[NBUF]; +static void +checkbuf(struct buf *b) +{ +  for(int i = 0; i < NBUF; i++){ +    if(xbufs[i] == b){ +      return; +    } +    if(xbufs[i] == 0){ +      xbufs[i] = b; +      return; +    } +  } +  panic("more than NBUF bufs"); +} +#endif +  void  virtio_disk_rw(struct buf *b, int write)  { @@ -219,6 +241,10 @@ virtio_disk_rw(struct buf *b, int write)    acquire(&disk.vdisk_lock); +#ifdef LAB_LOCK +  checkbuf(b); +#endif +    // the spec's Section 5.2 says that legacy block operations use    // three descriptors: one for type/reserved/sector, one for the    // data, one for a 1-byte status result. diff --git a/kernel/vm.c b/kernel/vm.c index 5c31e87..be7d042 100644 --- a/kernel/vm.c +++ b/kernel/vm.c @@ -4,6 +4,8 @@  #include "elf.h"  #include "riscv.h"  #include "defs.h" +#include "spinlock.h" +#include "proc.h"  #include "fs.h"  /* @@ -30,6 +32,14 @@ kvmmake(void)    // virtio mmio disk interface    kvmmap(kpgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W); +#ifdef LAB_NET +  // PCI-E ECAM (configuration space), for pci.c +  kvmmap(kpgtbl, 0x30000000L, 0x30000000L, 0x10000000, PTE_R | PTE_W); + +  // pci.c maps the e1000's registers here. +  kvmmap(kpgtbl, 0x40000000L, 0x40000000L, 0x20000, PTE_R | PTE_W); +#endif   +    // PLIC    kvmmap(kpgtbl, PLIC, PLIC, 0x400000, PTE_R | PTE_W); @@ -136,9 +146,8 @@ kvmmap(pagetable_t kpgtbl, uint64 va, uint64 pa, uint64 sz, int perm)  }  // Create PTEs for virtual addresses starting at va that refer to -// physical addresses starting at pa. -// va and size MUST be page-aligned. -// Returns 0 on success, -1 if walk() couldn't +// physical addresses starting at pa. va and size might not +// be page-aligned. Returns 0 on success, -1 if walk() couldn't  // allocate a needed page-table page.  int  mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm) @@ -146,17 +155,11 @@ mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)    uint64 a, last;    pte_t *pte; -  if((va % PGSIZE) != 0) -    panic("mappages: va not aligned"); - -  if((size % PGSIZE) != 0) -    panic("mappages: size not aligned"); -    if(size == 0)      panic("mappages: size"); -  a = va; -  last = va + size - PGSIZE; +  a = PGROUNDDOWN(va); +  last = PGROUNDDOWN(va + size - 1);    for(;;){      if((pte = walk(pagetable, a, 1)) == 0)        return -1; @@ -186,8 +189,10 @@ uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)    for(a = va; a < va + npages*PGSIZE; a += PGSIZE){      if((pte = walk(pagetable, a, 0)) == 0)        panic("uvmunmap: walk"); -    if((*pte & PTE_V) == 0) +    if((*pte & PTE_V) == 0) { +      printf("va=%p pte=%p\n", a, *pte);        panic("uvmunmap: not mapped"); +    }      if(PTE_FLAGS(*pte) == PTE_V)        panic("uvmunmap: not a leaf");      if(do_free){ @@ -315,20 +320,26 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)    pte_t *pte;    uint64 pa, i;    uint flags; -  char *mem; +  // char *mem;    for(i = 0; i < sz; i += PGSIZE){      if((pte = walk(old, i, 0)) == 0)        panic("uvmcopy: pte should exist");      if((*pte & PTE_V) == 0)        panic("uvmcopy: page not present"); -    pa = PTE2PA(*pte); -    flags = PTE_FLAGS(*pte); +    // do not do the actual copy, just increase the refcnt and mark pages readonly COW +    /*      if((mem = kalloc()) == 0)        goto err;      memmove(mem, (char*)pa, PGSIZE); -    if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){ -      kfree(mem); +    */ +    *pte &= ~PTE_W; +    *pte |= PTE_C; +    pa = PTE2PA(*pte); +    refcnt_inc(pa); +    flags = PTE_FLAGS(*pte); +    if(mappages(new, i, PGSIZE, (uint64)pa, flags) != 0){ +      // kfree(mem);        goto err;      }    } @@ -359,17 +370,24 @@ int  copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)  {    uint64 n, va0, pa0; -  pte_t *pte; +  // pte_t *pte;    while(len > 0){      va0 = PGROUNDDOWN(dstva); -    if(va0 >= MAXVA) + +    if(cow_handler(pagetable, va0) < 0)        return -1; +     +    /*      pte = walk(pagetable, va0, 0);      if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0 ||         (*pte & PTE_W) == 0)        return -1;      pa0 = PTE2PA(*pte); +    */ +    pa0 = walkaddr(pagetable, va0); +    if(pa0 == 0) +      return -1;      n = PGSIZE - (dstva - va0);      if(n > len)        n = len; @@ -389,7 +407,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 +467,30 @@ copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)      return -1;    }  } + +static void +walkprint(pagetable_t pgtbl, int level) +{ +  for(int i = 0; i < 512; i++){ +    pte_t pte = pgtbl[i]; +    if(pte & PTE_V){ +      for(int j = 0; j < level; j++){ +        printf(" .."); +      } +      printf("%d: pte %p pa %p\n", i, pte, PTE2PA(pte)); +      if((pte & (PTE_R|PTE_W|PTE_X)) == 0){ +        // this PTE points to a lower-level page table. +        walkprint((pagetable_t)PTE2PA(pte), level+1); +      } +    } +  } +} + +// Print the contents of a page table +void +vmprint(pagetable_t pgtbl) +{ +  printf("page table %p\n", pgtbl); + +  walkprint(pgtbl, 1); +} diff --git a/notxv6/barrier.c b/notxv6/barrier.c new file mode 100644 index 0000000..b7737a6 --- /dev/null +++ b/notxv6/barrier.c @@ -0,0 +1,86 @@ +#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() +{ +  // Block until all threads have called barrier() and +  // then increment bstate.round. +  pthread_mutex_lock(&bstate.barrier_mutex); +  bstate.nthread++; +  if(bstate.nthread != nthread) { +    pthread_cond_wait(&bstate.barrier_cond, &bstate.barrier_mutex); +  } else { +    pthread_cond_broadcast(&bstate.barrier_cond); +    // All threads have reached barrier. +    // reset and increase round +    bstate.nthread = 0; +    bstate.round++; +  } +  pthread_mutex_unlock(&bstate.barrier_mutex); +} + +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..db8a630 --- /dev/null +++ b/notxv6/ph.c @@ -0,0 +1,153 @@ +#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; +pthread_mutex_t lock[NBUCKET]; + +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; +  } +  pthread_mutex_lock(&lock[i]); +  if(e){ +    // update the existing key. +    e->value = value; +  } else { +    // the new is new. +    insert(key, value, &table[i], table[i]); +  } +  pthread_mutex_unlock(&lock[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); +  for (int i = 0; i < NBUCKET; i++) { +    pthread_mutex_init(&lock[i], NULL); +  } +  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)); +} @@ -0,0 +1,12 @@ +import socket +import sys +import time + +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +addr = ('localhost', int(sys.argv[1])) +buf = "this is a ping!".encode('utf-8') + +while True: +	print("pinging...", file=sys.stderr) +	sock.sendto(buf, ("127.0.0.1", int(sys.argv[1]))) +	time.sleep(1) diff --git a/server.py b/server.py new file mode 100644 index 0000000..2421c31 --- /dev/null +++ b/server.py @@ -0,0 +1,13 @@ +import socket +import sys + +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +addr = ('localhost', int(sys.argv[1])) +print('listening on %s port %s' % addr, file=sys.stderr) +sock.bind(addr) + +while True: +    buf, raddr = sock.recvfrom(4096) +    print(buf.decode("utf-8"), file=sys.stderr) +    if buf: +        sent = sock.sendto(b'this is the host!', raddr) diff --git a/time.txt b/time.txt new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/time.txt @@ -0,0 +1 @@ +3 diff --git a/user/alarmtest.c b/user/alarmtest.c new file mode 100644 index 0000000..b8d85f7 --- /dev/null +++ b/user/alarmtest.c @@ -0,0 +1,193 @@ +// +// test program for the alarm lab. +// you can modify this file for testing, +// but please make sure your kernel +// modifications pass the original +// versions of these tests. +// + +#include "kernel/param.h" +#include "kernel/types.h" +#include "kernel/stat.h" +#include "kernel/riscv.h" +#include "user/user.h" + +void test0(); +void test1(); +void test2(); +void test3(); +void periodic(); +void slow_handler(); +void dummy_handler(); + +int +main(int argc, char *argv[]) +{ +  test0(); +  test1(); +  test2(); +  test3(); +  exit(0); +} + +volatile static int count; + +void +periodic() +{ +  count = count + 1; +  printf("alarm!\n"); +  sigreturn(); +} + +// tests whether the kernel calls +// the alarm handler even a single time. +void +test0() +{ +  int i; +  printf("test0 start\n"); +  count = 0; +  sigalarm(2, periodic); +  for(i = 0; i < 1000*500000; i++){ +    if((i % 1000000) == 0) +      write(2, ".", 1); +    if(count > 0) +      break; +  } +  sigalarm(0, 0); +  if(count > 0){ +    printf("test0 passed\n"); +  } else { +    printf("\ntest0 failed: the kernel never called the alarm handler\n"); +  } +} + +void __attribute__ ((noinline)) foo(int i, int *j) { +  if((i % 2500000) == 0) { +    write(2, ".", 1); +  } +  *j += 1; +} + +// +// tests that the kernel calls the handler multiple times. +// +// tests that, when the handler returns, it returns to +// the point in the program where the timer interrupt +// occurred, with all registers holding the same values they +// held when the interrupt occurred. +// +void +test1() +{ +  int i; +  int j; + +  printf("test1 start\n"); +  count = 0; +  j = 0; +  sigalarm(2, periodic); +  for(i = 0; i < 500000000; i++){ +    if(count >= 10) +      break; +    foo(i, &j); +  } +  if(count < 10){ +    printf("\ntest1 failed: too few calls to the handler\n"); +  } else if(i != j){ +    // the loop should have called foo() i times, and foo() should +    // have incremented j once per call, so j should equal i. +    // once possible source of errors is that the handler may +    // return somewhere other than where the timer interrupt +    // occurred; another is that that registers may not be +    // restored correctly, causing i or j or the address ofj +    // to get an incorrect value. +    printf("\ntest1 failed: foo() executed fewer times than it was called\n"); +  } else { +    printf("test1 passed\n"); +  } +} + +// +// tests that kernel does not allow reentrant alarm calls. +void +test2() +{ +  int i; +  int pid; +  int status; + +  printf("test2 start\n"); +  if ((pid = fork()) < 0) { +    printf("test2: fork failed\n"); +  } +  if (pid == 0) { +    count = 0; +    sigalarm(2, slow_handler); +    for(i = 0; i < 1000*500000; i++){ +      if((i % 1000000) == 0) +        write(2, ".", 1); +      if(count > 0) +        break; +    } +    if (count == 0) { +      printf("\ntest2 failed: alarm not called\n"); +      exit(1); +    } +    exit(0); +  } +  wait(&status); +  if (status == 0) { +    printf("test2 passed\n"); +  } +} + +void +slow_handler() +{ +  count++; +  printf("alarm!\n"); +  if (count > 1) { +    printf("test2 failed: alarm handler called more than once\n"); +    exit(1); +  } +  for (int i = 0; i < 1000*500000; i++) { +    asm volatile("nop"); // avoid compiler optimizing away loop +  } +  sigalarm(0, 0); +  sigreturn(); +} + +// +// dummy alarm handler; after running immediately uninstall +// itself and finish signal handling +void +dummy_handler() +{ +  sigalarm(0, 0); +  sigreturn(); +} + +// +// tests that the return from sys_sigreturn() does not +// modify the a0 register +void +test3() +{ +  uint64 a0; + +  sigalarm(1, dummy_handler); +  printf("test3 start\n"); + +  asm volatile("lui a5, 0"); +  asm volatile("addi a0, a5, 0xac" : : : "a0"); +  for(int i = 0; i < 500000000; i++) +    ; +  asm volatile("mv %0, a0" : "=r" (a0) ); + +  if(a0 != 0xac) +    printf("test3 failed: register a0 changed\n"); +  else +    printf("test3 passed\n"); +} diff --git a/user/bcachetest.c b/user/bcachetest.c new file mode 100644 index 0000000..ccf7516 --- /dev/null +++ b/user/bcachetest.c @@ -0,0 +1,318 @@ +#include "kernel/fcntl.h" +#include "kernel/param.h" +#include "kernel/types.h" +#include "kernel/stat.h" +#include "kernel/riscv.h" +#include "kernel/fs.h" +#include "user/user.h" + +void test0(); +void test1(); +void test2(); + +#define SZ 4096 +char buf[SZ]; + +int +main(int argc, char *argv[]) +{ +  test0(); +  test1(); +  test2(); +  exit(0); +} + +void +createfile(char *file, int nblock) +{ +  int fd; +  char buf[BSIZE]; +  int i; +   +  fd = open(file, O_CREATE | O_RDWR); +  if(fd < 0){ +    printf("createfile %s failed\n", file); +    exit(-1); +  } +  for(i = 0; i < nblock; i++) { +    if(write(fd, buf, sizeof(buf)) != sizeof(buf)) { +      printf("write %s failed\n", file); +      exit(-1); +    } +  } +  close(fd); +} + +void +readfile(char *file, int nbytes, int inc) +{ +  char buf[BSIZE]; +  int fd; +  int i; + +  if(inc > BSIZE) { +    printf("readfile: inc too large\n"); +    exit(-1); +  } +  if ((fd = open(file, O_RDONLY)) < 0) { +    printf("readfile open %s failed\n", file); +    exit(-1); +  } +  for (i = 0; i < nbytes; i += inc) { +    if(read(fd, buf, inc) != inc) { +      printf("read %s failed for block %d (%d)\n", file, i, nbytes); +      exit(-1); +    } +  } +  close(fd); +} + +int ntas(int print) +{ +  int n; +  char *c; + +  if (statistics(buf, SZ) <= 0) { +    fprintf(2, "ntas: no stats\n"); +  } +  c = strchr(buf, '='); +  n = atoi(c+2); +  if(print) +    printf("%s", buf); +  return n; +} + +// Test reading small files concurrently +void +test0() +{ +  char file[2]; +  char dir[2]; +  enum { N = 10, NCHILD = 3 }; +  int m, n; + +  dir[0] = '0'; +  dir[1] = '\0'; +  file[0] = 'F'; +  file[1] = '\0'; + +  printf("start test0\n"); +  for(int i = 0; i < NCHILD; i++){ +    dir[0] = '0' + i; +    mkdir(dir); +    if (chdir(dir) < 0) { +      printf("chdir failed\n"); +      exit(1); +    } +    unlink(file); +    createfile(file, N); +    if (chdir("..") < 0) { +      printf("chdir failed\n"); +      exit(1); +    } +  } +  m = ntas(0); +  for(int i = 0; i < NCHILD; i++){ +    dir[0] = '0' + i; +    int pid = fork(); +    if(pid < 0){ +      printf("fork failed"); +      exit(-1); +    } +    if(pid == 0){ +      if (chdir(dir) < 0) { +        printf("chdir failed\n"); +        exit(1); +      } + +      readfile(file, N*BSIZE, 1); + +      exit(0); +    } +  } + +  for(int i = 0; i < NCHILD; i++){ +    wait(0); +  } +  printf("test0 results:\n"); +  n = ntas(1); +  if (n-m < 500) +    printf("test0: OK\n"); +  else +    printf("test0: FAIL\n"); +} + +// Test bcache evictions by reading a large file concurrently +void test1() +{ +  char file[3]; +  enum { N = 200, BIG=100, NCHILD=2 }; +   +  printf("start test1\n"); +  file[0] = 'B'; +  file[2] = '\0'; +  for(int i = 0; i < NCHILD; i++){ +    file[1] = '0' + i; +    unlink(file); +    if (i == 0) { +      createfile(file, BIG); +    } else { +      createfile(file, 1); +    } +  } +  for(int i = 0; i < NCHILD; i++){ +    file[1] = '0' + i; +    int pid = fork(); +    if(pid < 0){ +      printf("fork failed"); +      exit(-1); +    } +    if(pid == 0){ +      if (i==0) { +        for (i = 0; i < N; i++) { +          readfile(file, BIG*BSIZE, BSIZE); +        } +        unlink(file); +        exit(0); +      } else { +        for (i = 0; i < N*20; i++) { +          readfile(file, 1, BSIZE); +        } +        unlink(file); +      } +      exit(0); +    } +  } + +  for(int i = 0; i < NCHILD; i++){ +    wait(0); +  } +  printf("test1 OK\n"); +} + +// +// test concurrent creates. +// +void +test2() +{ +  int nc = 4; +  char file[16]; + +  printf("start test2\n"); +   +  mkdir("d2"); + +  file[0] = 'd'; +  file[1] = '2'; +  file[2] = '/'; + +  // remove any stale existing files. +  for(int i = 0; i < 50; i++){ +    for(int ci = 0; ci < nc; ci++){ +      file[3] = 'a' + ci; +      file[4] = '0' + i; +      file[5] = '\0'; +      unlink(file); +    } +  } + +  int pids[nc]; +  for(int ci = 0; ci < nc; ci++){ +    pids[ci] = fork(); +    if(pids[ci] < 0){ +      printf("test2: fork failed\n"); +      exit(1); +    } +    if(pids[ci] == 0){ +      char me = "abcdefghijklmnop"[ci]; +      int pid = getpid(); +      int nf = (ci == 0 ? 10 : 15); + +      // create nf files. +      for(int i = 0; i < nf; i++){ +        file[3] = me; +        file[4] = '0' + i; +        file[5] = '\0'; +        // printf("w %s\n", file); +        int fd = open(file, O_CREATE | O_RDWR); +        if(fd < 0){ +          printf("test2: create %s failed\n", file); +          exit(1); +        } +        int xx = (pid << 16) | i; +        for(int nw = 0; nw < 2; nw++){ +          // the sleep() increases the chance of simultaneous +          // calls to bget(). +          sleep(1); +          if(write(fd, &xx, sizeof(xx)) <= 0){ +            printf("test2: write %s failed\n", file); +            exit(1); +          } +        } +        close(fd); +      } + +      // read back the nf files. +      for(int i = 0; i < nf; i++){ +        file[3] = me; +        file[4] = '0' + i; +        file[5] = '\0'; +        // printf("r %s\n", file); +        int fd = open(file, O_RDWR); +        if(fd < 0){ +          printf("test2: open %s failed\n", file); +          exit(1); +        } +        int xx = (pid << 16) | i; +        for(int nr = 0; nr < 2; nr++){ +          int z = 0; +          sleep(1); +          int n = read(fd, &z, sizeof(z)); +          if(n != sizeof(z)){ +            printf("test2: read %s returned %d, expected %d\n", file, n, sizeof(z)); +            exit(1); +          } +          if(z != xx){ +            printf("test2: file %s contained %d, not %d\n", file, z, xx); +            exit(1); +          } +        } +        close(fd); +      } + +      // delete the nf files. +      for(int i = 0; i < nf; i++){ +        file[3] = me; +        file[4] = '0' + i; +        file[5] = '\0'; +        //printf("u %s\n", file); +        if(unlink(file) != 0){ +          printf("test2: unlink %s failed\n", file); +          exit(1); +        } +      } + +      exit(0); +    } +  } + +  int ok = 1; + +  for(int ci = 0; ci < nc; ci++){ +    int st = 0; +    int ret = wait(&st); +    if(ret <= 0){ +      printf("test2: wait() failed\n"); +      ok = 0; +    } +    if(st != 0) +      ok = 0; +  } +   +  if(ok){ +    printf("test2 OK\n"); +  } else { +    printf("test2 failed\n"); +  } +} diff --git a/user/bttest.c b/user/bttest.c new file mode 100644 index 0000000..05405f9 --- /dev/null +++ b/user/bttest.c @@ -0,0 +1,10 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +int +main(int argc, char *argv[]) +{ +  sleep(1); +  exit(0); +} diff --git a/user/call.c b/user/call.c new file mode 100644 index 0000000..f725dcb --- /dev/null +++ b/user/call.c @@ -0,0 +1,17 @@ +#include "kernel/param.h" +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +int g(int x) { +  return x+3; +} + +int f(int x) { +  return g(x); +} + +void main(void) { +  printf("%d %d\n", f(8)+1, 13); +  exit(0); +} diff --git a/user/cowtest.c b/user/cowtest.c new file mode 100644 index 0000000..29b918f --- /dev/null +++ b/user/cowtest.c @@ -0,0 +1,197 @@ +// +// tests for copy-on-write fork() assignment. +// + +#include "kernel/types.h" +#include "kernel/memlayout.h" +#include "user/user.h" + +// allocate more than half of physical memory, +// then fork. this will fail in the default +// kernel, which does not support copy-on-write. +void +simpletest() +{ +  uint64 phys_size = PHYSTOP - KERNBASE; +  int sz = (phys_size / 3) * 2; + +  printf("simple: "); +   +  char *p = sbrk(sz); +  if(p == (char*)0xffffffffffffffffL){ +    printf("sbrk(%d) failed\n", sz); +    exit(-1); +  } + +  for(char *q = p; q < p + sz; q += 4096){ +    *(int*)q = getpid(); +  } + +  int pid = fork(); +  if(pid < 0){ +    printf("fork() failed\n"); +    exit(-1); +  } + +  if(pid == 0) +    exit(0); + +  wait(0); + +  if(sbrk(-sz) == (char*)0xffffffffffffffffL){ +    printf("sbrk(-%d) failed\n", sz); +    exit(-1); +  } + +  printf("ok\n"); +} + +// three processes all write COW memory. +// this causes more than half of physical memory +// to be allocated, so it also checks whether +// copied pages are freed. +void +threetest() +{ +  uint64 phys_size = PHYSTOP - KERNBASE; +  int sz = phys_size / 4; +  int pid1, pid2; + +  printf("three: "); +   +  char *p = sbrk(sz); +  if(p == (char*)0xffffffffffffffffL){ +    printf("sbrk(%d) failed\n", sz); +    exit(-1); +  } + +  pid1 = fork(); +  if(pid1 < 0){ +    printf("fork failed\n"); +    exit(-1); +  } +  if(pid1 == 0){ +    pid2 = fork(); +    if(pid2 < 0){ +      printf("fork failed"); +      exit(-1); +    } +    if(pid2 == 0){ +      for(char *q = p; q < p + (sz/5)*4; q += 4096){ +        *(int*)q = getpid(); +      } +      for(char *q = p; q < p + (sz/5)*4; q += 4096){ +        if(*(int*)q != getpid()){ +          printf("wrong content\n"); +          exit(-1); +        } +      } +      exit(-1); +    } +    for(char *q = p; q < p + (sz/2); q += 4096){ +      *(int*)q = 9999; +    } +    exit(0); +  } + +  for(char *q = p; q < p + sz; q += 4096){ +    *(int*)q = getpid(); +  } + +  wait(0); + +  sleep(1); + +  for(char *q = p; q < p + sz; q += 4096){ +    if(*(int*)q != getpid()){ +      printf("wrong content\n"); +      exit(-1); +    } +  } + +  if(sbrk(-sz) == (char*)0xffffffffffffffffL){ +    printf("sbrk(-%d) failed\n", sz); +    exit(-1); +  } + +  printf("ok\n"); +} + +char junk1[4096]; +int fds[2]; +char junk2[4096]; +char buf[4096]; +char junk3[4096]; + +// test whether copyout() simulates COW faults. +void +filetest() +{ +  printf("file: "); +   +  buf[0] = 99; + +  for(int i = 0; i < 4; i++){ +    if(pipe(fds) != 0){ +      printf("pipe() failed\n"); +      exit(-1); +    } +    int pid = fork(); +    if(pid < 0){ +      printf("fork failed\n"); +      exit(-1); +    } +    if(pid == 0){ +      sleep(1); +      if(read(fds[0], buf, sizeof(i)) != sizeof(i)){ +        printf("error: read failed\n"); +        exit(1); +      } +      sleep(1); +      int j = *(int*)buf; +      if(j != i){ +        printf("error: read the wrong value\n"); +        exit(1); +      } +      exit(0); +    } +    if(write(fds[1], &i, sizeof(i)) != sizeof(i)){ +      printf("error: write failed\n"); +      exit(-1); +    } +  } + +  int xstatus = 0; +  for(int i = 0; i < 4; i++) { +    wait(&xstatus); +    if(xstatus != 0) { +      exit(1); +    } +  } + +  if(buf[0] != 99){ +    printf("error: child overwrote parent\n"); +    exit(1); +  } + +  printf("ok\n"); +} + +int +main(int argc, char *argv[]) +{ +  simpletest(); + +  // check that the first simpletest() freed the physical memory. +  simpletest(); + +  threetest(); +  threetest(); +  threetest(); + +  filetest(); + +  printf("ALL COW TESTS PASSED\n"); + +  exit(0); +} diff --git a/user/find.c b/user/find.c new file mode 100644 index 0000000..e185e9d --- /dev/null +++ b/user/find.c @@ -0,0 +1,84 @@ +#include "kernel/types.h" + +#include "kernel/fcntl.h" +#include "kernel/fs.h" +#include "kernel/stat.h" +#include "user/user.h" + +char* +fmtname(char* path) +{ +  char* p; + +  // Find first character after last slash. +  for (p = path + strlen(path); p >= path && *p != '/'; p--) +    ; +  p++; +  return p; +} + +void +find(char* root_path, char* filename) +{ +  static char buf[512]; +  char* p; +  int fd; +  struct dirent de; +  struct stat st; + +  if ((fd = open(root_path, O_RDONLY)) < 0) { +    fprintf(2, "find: cannot open %s\n", root_path); +    return; +  } +  if (fstat(fd, &st) < 0) { +    fprintf(2, "find: cannot stat %s\n", root_path); +    close(fd); +    return; +  } + +  switch (st.type) { +    case T_FILE: +      if (!strcmp(fmtname(root_path), filename)) { +        printf("%s\n", root_path); +      } +      break; +    case T_DIR: +      if (strlen(root_path) + 1 + DIRSIZ + 1 > sizeof(buf)) { +        printf("find: path too long\n"); +        break; +      } + +      strcpy(buf, root_path); + +      p = buf + strlen(buf); +      *p++ = '/'; +      while (read(fd, &de, sizeof(de)) == sizeof(de)) { +        if (de.inum == 0) +          continue; +        memmove(p, de.name, DIRSIZ); +        p[DIRSIZ] = '\0'; + +        // printf("i'm finding %s!\n", fmtname(buf)); + +        if (!strcmp(fmtname(buf), ".") || !strcmp(fmtname(buf), "..")) { +          continue; +        } + +        find(buf, filename); +      } +  } +  close(fd); +} + +int +main(int argc, char* argv[]) +{ +  if (argc < 3) { +    fprintf(2, "usage: find [root_path] filename...\n"); +    exit(1); +  } + +  for (int i = 2; i < argc; i++) { +    find(argv[1], argv[i]); +  } +} diff --git a/user/init.c b/user/init.c index e0a5689..fc1c3db 100644 --- a/user/init.c +++ b/user/init.c @@ -18,6 +18,7 @@ main(void)    if(open("console", O_RDWR) < 0){      mknod("console", CONSOLE, 0); +    mknod("statistics", STATS, 0);      open("console", O_RDWR);    }    dup(0);  // stdout diff --git a/user/kalloctest.c b/user/kalloctest.c new file mode 100644 index 0000000..a82f1d5 --- /dev/null +++ b/user/kalloctest.c @@ -0,0 +1,161 @@ +#include "kernel/param.h" +#include "kernel/types.h" +#include "kernel/stat.h" +#include "kernel/riscv.h" +#include "kernel/memlayout.h" +#include "kernel/fcntl.h" +#include "user/user.h" + +#define NCHILD 2 +#define N 100000 +#define SZ 4096 + +void test1(void); +void test2(void); +void test3(void); +char buf[SZ]; + +int +main(int argc, char *argv[]) +{ +  test1(); +  test2(); +  test3(); +  exit(0); +} + +int ntas(int print) +{ +  int n; +  char *c; + +  if (statistics(buf, SZ) <= 0) { +    fprintf(2, "ntas: no stats\n"); +  } +  c = strchr(buf, '='); +  n = atoi(c+2); +  if(print) +    printf("%s", buf); +  return n; +} + +// Test concurrent kallocs and kfrees +void test1(void) +{ +  void *a, *a1; +  int n, m; +  printf("start test1\n");   +  m = ntas(0); +  for(int i = 0; i < NCHILD; i++){ +    int pid = fork(); +    if(pid < 0){ +      printf("fork failed"); +      exit(-1); +    } +    if(pid == 0){ +      for(i = 0; i < N; i++) { +        a = sbrk(4096); +        *(int *)(a+4) = 1; +        a1 = sbrk(-4096); +        if (a1 != a + 4096) { +          printf("wrong sbrk\n"); +          exit(-1); +        } +      } +      exit(-1); +    } +  } + +  for(int i = 0; i < NCHILD; i++){ +    wait(0); +  } +  printf("test1 results:\n"); +  n = ntas(1); +  if(n-m < 10)  +    printf("test1 OK\n"); +  else +    printf("test1 FAIL\n"); +} + +// +// countfree() from usertests.c +// +int +countfree() +{ +  uint64 sz0 = (uint64)sbrk(0); +  int n = 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; +    n += 1; +  } +  sbrk(-((uint64)sbrk(0) - sz0)); +  return n; +} + +// Test stealing +void test2() { +  int free0 = countfree(); +  int free1; +  int n = (PHYSTOP-KERNBASE)/PGSIZE; +  printf("start test2\n");   +  printf("total free number of pages: %d (out of %d)\n", free0, n); +  if(n - free0 > 1000) { +    printf("test2 FAILED: cannot allocate enough memory"); +    exit(-1); +  } +  for (int i = 0; i < 50; i++) { +    free1 = countfree(); +    if(i % 10 == 9) +      printf("."); +    if(free1 != free0) { +      printf("test2 FAIL: losing pages\n"); +      exit(-1); +    } +  } +  printf("\ntest2 OK\n");   +} + +// Test concurrent kalloc/kfree and stealing +void test3(void) +{ +  void *a, *a1; +  printf("start test3\n");   +  for(int i = 0; i < NCHILD; i++){ +    int pid = fork(); +    if(pid < 0){ +      printf("fork failed"); +      exit(-1); +    } +    if(pid == 0){ +      if (i == 0) { +        for(i = 0; i < N; i++) { +          a = sbrk(4096); +          *(int *)(a+4) = 1; +          a1 = sbrk(-4096); +          if (a1 != a + 4096) { +            printf("wrong sbrk\n"); +            exit(-1); +          } +        } +        printf("child done %d\n", i); +        exit(0); +      } else { +        countfree(); +        printf("child done %d\n", i); +        exit(0); +      } +    } +  } + +  for(int i = 0; i < NCHILD; i++){ +    wait(0); +  } +  printf("test3 OK\n"); +} diff --git a/user/nettests.c b/user/nettests.c new file mode 100644 index 0000000..2f7d6cd --- /dev/null +++ b/user/nettests.c @@ -0,0 +1,297 @@ +#include "kernel/types.h" +#include "kernel/net.h" +#include "kernel/stat.h" +#include "user/user.h" + +// +// send a UDP packet to the localhost (outside of qemu), +// and receive a response. +// +static void +ping(uint16 sport, uint16 dport, int attempts) +{ +  int fd; +  char *obuf = "a message from xv6!"; +  uint32 dst; + +  // 10.0.2.2, which qemu remaps to the external host, +  // i.e. the machine you're running qemu on. +  dst = (10 << 24) | (0 << 16) | (2 << 8) | (2 << 0); + +  // you can send a UDP packet to any Internet address +  // by using a different dst. +   +  if((fd = connect(dst, sport, dport)) < 0){ +    fprintf(2, "ping: connect() failed\n"); +    exit(1); +  } + +  for(int i = 0; i < attempts; i++) { +    if(write(fd, obuf, strlen(obuf)) < 0){ +      fprintf(2, "ping: send() failed\n"); +      exit(1); +    } +  } + +  char ibuf[128]; +  int cc = read(fd, ibuf, sizeof(ibuf)-1); +  if(cc < 0){ +    fprintf(2, "ping: recv() failed\n"); +    exit(1); +  } + +  close(fd); +  ibuf[cc] = '\0'; +  if(strcmp(ibuf, "this is the host!") != 0){ +    fprintf(2, "ping didn't receive correct payload\n"); +    exit(1); +  } +} + +// Encode a DNS name +static void +encode_qname(char *qn, char *host) +{ +  char *l = host;  +   +  for(char *c = host; c < host+strlen(host)+1; c++) { +    if(*c == '.') { +      *qn++ = (char) (c-l); +      for(char *d = l; d < c; d++) { +        *qn++ = *d; +      } +      l = c+1; // skip . +    } +  } +  *qn = '\0'; +} + +// Decode a DNS name +static void +decode_qname(char *qn, int max) +{ +  char *qnMax = qn + max; +  while(1){ +    if(qn >= qnMax){ +      printf("invalid DNS reply\n"); +      exit(1); +    } +    int l = *qn; +    if(l == 0) +      break; +    for(int i = 0; i < l; i++) { +      *qn = *(qn+1); +      qn++; +    } +    *qn++ = '.'; +  } +} + +// Make a DNS request +static int +dns_req(uint8 *obuf) +{ +  int len = 0; +   +  struct dns *hdr = (struct dns *) obuf; +  hdr->id = htons(6828); +  hdr->rd = 1; +  hdr->qdcount = htons(1); +   +  len += sizeof(struct dns); +   +  // qname part of question +  char *qname = (char *) (obuf + sizeof(struct dns)); +  char *s = "pdos.csail.mit.edu."; +  encode_qname(qname, s); +  len += strlen(qname) + 1; + +  // constants part of question +  struct dns_question *h = (struct dns_question *) (qname+strlen(qname)+1); +  h->qtype = htons(0x1); +  h->qclass = htons(0x1); + +  len += sizeof(struct dns_question); +  return len; +} + +// Process DNS response +static void +dns_rep(uint8 *ibuf, int cc) +{ +  struct dns *hdr = (struct dns *) ibuf; +  int len; +  char *qname = 0; +  int record = 0; + +  if(cc < sizeof(struct dns)){ +    printf("DNS reply too short\n"); +    exit(1); +  } + +  if(!hdr->qr) { +    printf("Not a DNS reply for %d\n", ntohs(hdr->id)); +    exit(1); +  } + +  if(hdr->id != htons(6828)){ +    printf("DNS wrong id: %d\n", ntohs(hdr->id)); +    exit(1); +  } +   +  if(hdr->rcode != 0) { +    printf("DNS rcode error: %x\n", hdr->rcode); +    exit(1); +  } +   +  //printf("qdcount: %x\n", ntohs(hdr->qdcount)); +  //printf("ancount: %x\n", ntohs(hdr->ancount)); +  //printf("nscount: %x\n", ntohs(hdr->nscount)); +  //printf("arcount: %x\n", ntohs(hdr->arcount)); +   +  len = sizeof(struct dns); + +  for(int i =0; i < ntohs(hdr->qdcount); i++) { +    char *qn = (char *) (ibuf+len); +    qname = qn; +    decode_qname(qn, cc - len); +    len += strlen(qn)+1; +    len += sizeof(struct dns_question); +  } + +  for(int i = 0; i < ntohs(hdr->ancount); i++) { +    if(len >= cc){ +      printf("invalid DNS reply\n"); +      exit(1); +    } +     +    char *qn = (char *) (ibuf+len); + +    if((int) qn[0] > 63) {  // compression? +      qn = (char *)(ibuf+qn[1]); +      len += 2; +    } else { +      decode_qname(qn, cc - len); +      len += strlen(qn)+1; +    } +       +    struct dns_data *d = (struct dns_data *) (ibuf+len); +    len += sizeof(struct dns_data); +    //printf("type %d ttl %d len %d\n", ntohs(d->type), ntohl(d->ttl), ntohs(d->len)); +    if(ntohs(d->type) == ARECORD && ntohs(d->len) == 4) { +      record = 1; +      printf("DNS arecord for %s is ", qname ? qname : "" ); +      uint8 *ip = (ibuf+len); +      printf("%d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]); +      if(ip[0] != 128 || ip[1] != 52 || ip[2] != 129 || ip[3] != 126) { +        printf("wrong ip address"); +        exit(1); +      } +      len += 4; +    } +  } + +  // needed for DNS servers with EDNS support +  for(int i = 0; i < ntohs(hdr->arcount); i++) { +    char *qn = (char *) (ibuf+len); +    if(*qn != 0) { +      printf("invalid name for EDNS\n"); +      exit(1); +    } +    len += 1; + +    struct dns_data *d = (struct dns_data *) (ibuf+len); +    len += sizeof(struct dns_data); +    if(ntohs(d->type) != 41) { +      printf("invalid type for EDNS\n"); +      exit(1); +    } +    len += ntohs(d->len); +  } + +  if(len != cc) { +    printf("Processed %d data bytes but received %d\n", len, cc); +    exit(1); +  } +  if(!record) { +    printf("Didn't receive an arecord\n"); +    exit(1); +  } +} + +static void +dns() +{ +  #define N 1000 +  uint8 obuf[N]; +  uint8 ibuf[N]; +  uint32 dst; +  int fd; +  int len; + +  memset(obuf, 0, N); +  memset(ibuf, 0, N); +   +  // 8.8.8.8: google's name server +  dst = (8 << 24) | (8 << 16) | (8 << 8) | (8 << 0); + +  if((fd = connect(dst, 10000, 53)) < 0){ +    fprintf(2, "ping: connect() failed\n"); +    exit(1); +  } + +  len = dns_req(obuf); +   +  if(write(fd, obuf, len) < 0){ +    fprintf(2, "dns: send() failed\n"); +    exit(1); +  } +  int cc = read(fd, ibuf, sizeof(ibuf)); +  if(cc < 0){ +    fprintf(2, "dns: recv() failed\n"); +    exit(1); +  } +  dns_rep(ibuf, cc); + +  close(fd); +}   + +int +main(int argc, char *argv[]) +{ +  int i, ret; +  uint16 dport = NET_TESTS_PORT; + +  printf("nettests running on port %d\n", dport); +   +  printf("testing ping: "); +  ping(2000, dport, 1); +  printf("OK\n"); +   +  printf("testing single-process pings: "); +  for (i = 0; i < 100; i++) +    ping(2000, dport, 1); +  printf("OK\n"); +   +  printf("testing multi-process pings: "); +  for (i = 0; i < 10; i++){ +    int pid = fork(); +    if (pid == 0){ +      ping(2000 + i + 1, dport, 1); +      exit(0); +    } +  } +  for (i = 0; i < 10; i++){ +    wait(&ret); +    if (ret != 0) +      exit(1); +  } +  printf("OK\n"); +   +  printf("testing DNS\n"); +  dns(); +  printf("DNS OK\n"); +   +  printf("all tests passed.\n"); +  exit(0); +} diff --git a/user/pgtbltest.c b/user/pgtbltest.c new file mode 100644 index 0000000..bce158a --- /dev/null +++ b/user/pgtbltest.c @@ -0,0 +1,70 @@ +#include "kernel/param.h" +#include "kernel/fcntl.h" +#include "kernel/types.h" +#include "kernel/riscv.h" +#include "user/user.h" + +void ugetpid_test(); +void pgaccess_test(); + +int +main(int argc, char *argv[]) +{ +  ugetpid_test(); +  pgaccess_test(); +  printf("pgtbltest: all tests succeeded\n"); +  exit(0); +} + +char *testname = "???"; + +void +err(char *why) +{ +  printf("pgtbltest: %s failed: %s, pid=%d\n", testname, why, getpid()); +  exit(1); +} + +void +ugetpid_test() +{ +  int i; + +  printf("ugetpid_test starting\n"); +  testname = "ugetpid_test"; + +  for (i = 0; i < 64; i++) { +    int ret = fork(); +    if (ret != 0) { +      wait(&ret); +      if (ret != 0) +        exit(1); +      continue; +    } +    if (getpid() != ugetpid()) +      err("missmatched PID"); +    exit(0); +  } +  printf("ugetpid_test: OK\n"); +} + +void +pgaccess_test() +{ +  char *buf; +  unsigned int abits; +  printf("pgaccess_test starting\n"); +  testname = "pgaccess_test"; +  buf = malloc(32 * PGSIZE); +  if (pgaccess(buf, 32, &abits) < 0) +    err("pgaccess failed"); +  buf[PGSIZE * 1] += 1; +  buf[PGSIZE * 2] += 1; +  buf[PGSIZE * 30] += 1; +  if (pgaccess(buf, 32, &abits) < 0) +    err("pgaccess failed"); +  if (abits != ((1 << 1) | (1 << 2) | (1 << 30))) +    err("incorrect access bits set"); +  free(buf); +  printf("pgaccess_test: OK\n"); +} diff --git a/user/pingpong.c b/user/pingpong.c new file mode 100644 index 0000000..7b03a76 --- /dev/null +++ b/user/pingpong.c @@ -0,0 +1,44 @@ +#include "kernel/types.h" +#include "user/user.h" + +int +main(int argc, char* argv[]) +{ +  int p[2]; + +  if (argc > 1) { +    fprintf(2, "usage: pingpong\n"); +    exit(1); +  } + +  pipe(p); + +  int pid = fork(); + +  if (pid == 0) { +    short n; +    read(p[0], &n, sizeof(n)); +    if (n == 42) { +      fprintf(1, "%d: received ping\n", getpid()); +    } +    n++; +    write(p[1], &n, sizeof(n)); +    close(p[0]); +    close(p[1]); +    exit(0); +  } + +  short n = 42; + +  write(p[1], &n, sizeof(n)); +  read(p[0], &n, sizeof(n)); +  if (n == 43) { +    fprintf(1, "%d: received pong\n", getpid()); +  } +  close(p[0]); +  close(p[1]); + +  wait(0); + +  exit(0); +} diff --git a/user/primes.c b/user/primes.c new file mode 100644 index 0000000..b359524 --- /dev/null +++ b/user/primes.c @@ -0,0 +1,74 @@ +#include "kernel/types.h" +#include "user/user.h" + +#define MAX 36 +#define FIRST_PRIME 2 + +int +generate_natural(); // -> out_fd +int +prime_filter(int in_fd, int prime); // -> out_fd + +int +main(int argc, char* argv[]) +{ +  int prime; + +  int in = generate_natural(); +  while (read(in, &prime, sizeof(int))) { +    // printf("prime %d: in_fd: %d\n", prime, in);  // debug +    printf("prime %d\n", prime); +    in = prime_filter(in, prime); +  } + +  close(in); + +  exit(0); +} + +int +generate_natural() +{ +  int out_pipe[2]; + +  pipe(out_pipe); + +  if (!fork()) { +    for (int i = FIRST_PRIME; i < MAX; i++) { +      write(out_pipe[1], &i, sizeof(int)); +    } +    close(out_pipe[1]); + +    exit(0); +  } + +  close(out_pipe[1]); + +  return out_pipe[0]; +} + +int +prime_filter(int in_fd, int prime) +{ +  int num; +  int out_pipe[2]; + +  pipe(out_pipe); + +  if (!fork()) { +    while (read(in_fd, &num, sizeof(int))) { +      if (num % prime) { +        write(out_pipe[1], &num, sizeof(int)); +      } +    } +    close(in_fd); +    close(out_pipe[1]); + +    exit(0); +  } + +  close(in_fd); +  close(out_pipe[1]); + +  return out_pipe[0]; +} @@ -165,6 +165,9 @@ main(void)          fprintf(2, "cannot cd %s\n", buf+3);        continue;      } +    if(buf[0] == 'e' && buf[1] == 'x' && buf[2] == 'i' && buf[3] == 't'){ +      exit(0); +    }      if(fork1() == 0)        runcmd(parsecmd(buf));      wait(0); diff --git a/user/sleep.c b/user/sleep.c new file mode 100644 index 0000000..961f558 --- /dev/null +++ b/user/sleep.c @@ -0,0 +1,22 @@ +#include "kernel/types.h" +#include "user/user.h" + +int +main(int argc, char* argv[]) +{ +  uint sec = 0; + +  if (argc <= 1) { +    fprintf(2, "usage: sleep [time (ticks)]\n"); +    exit(1); +  } +  sec = atoi(argv[1]); + +  sleep(sec); + +  if (argc <= 2) { +    exit(0); +  } + +  exit(0); +} diff --git a/user/statistics.c b/user/statistics.c new file mode 100644 index 0000000..e22681a --- /dev/null +++ b/user/statistics.c @@ -0,0 +1,24 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "kernel/fcntl.h" +#include "user/user.h" + +int +statistics(void *buf, int sz) +{ +  int fd, i, n; +   +  fd = open("statistics", O_RDONLY); +  if(fd < 0) { +      fprintf(2, "stats: open failed\n"); +      exit(1); +  } +  for (i = 0; i < sz; ) { +    if ((n = read(fd, buf+i, sz-i)) < 0) { +      break; +    } +    i += n; +  } +  close(fd); +  return i; +} diff --git a/user/stats.c b/user/stats.c new file mode 100644 index 0000000..f8c9138 --- /dev/null +++ b/user/stats.c @@ -0,0 +1,24 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "kernel/fcntl.h" +#include "user/user.h" + +#define SZ 4096 +char buf[SZ]; + +int +main(void) +{ +  int i, n; +   +  while (1) { +    n = statistics(buf, SZ); +    for (i = 0; i < n; i++) { +      write(1, buf+i, 1); +    } +    if (n != SZ) +      break; +  } + +  exit(0); +} diff --git a/user/sysinfotest.c b/user/sysinfotest.c new file mode 100644 index 0000000..8a648a6 --- /dev/null +++ b/user/sysinfotest.c @@ -0,0 +1,153 @@ +#include "kernel/types.h" +#include "kernel/riscv.h" +#include "kernel/sysinfo.h" +#include "user/user.h" + + +void +sinfo(struct sysinfo *info) { +  if (sysinfo(info) < 0) { +    printf("FAIL: sysinfo failed"); +    exit(1); +  } +} + +// +// use sbrk() to count how many free physical memory pages there are. +// +int +countfree() +{ +  uint64 sz0 = (uint64)sbrk(0); +  struct sysinfo info; +  int n = 0; + +  while(1){ +    if((uint64)sbrk(PGSIZE) == 0xffffffffffffffff){ +      break; +    } +    n += PGSIZE; +  } +  sinfo(&info); +  if (info.freemem != 0) { +    printf("FAIL: there is no free mem, but sysinfo.freemem=%d\n", +      info.freemem); +    exit(1); +  } +  sbrk(-((uint64)sbrk(0) - sz0)); +  return n; +} + +void +testmem() { +  struct sysinfo info; +  uint64 n = countfree(); +   +  sinfo(&info); + +  if (info.freemem!= n) { +    printf("FAIL: free mem %d (bytes) instead of %d\n", info.freemem, n); +    exit(1); +  } +   +  if((uint64)sbrk(PGSIZE) == 0xffffffffffffffff){ +    printf("sbrk failed"); +    exit(1); +  } + +  sinfo(&info); +     +  if (info.freemem != n-PGSIZE) { +    printf("FAIL: free mem %d (bytes) instead of %d\n", n-PGSIZE, info.freemem); +    exit(1); +  } +   +  if((uint64)sbrk(-PGSIZE) == 0xffffffffffffffff){ +    printf("sbrk failed"); +    exit(1); +  } + +  sinfo(&info); +     +  if (info.freemem != n) { +    printf("FAIL: free mem %d (bytes) instead of %d\n", n, info.freemem); +    exit(1); +  } +} + +void +testcall() { +  struct sysinfo info; +   +  if (sysinfo(&info) < 0) { +    printf("FAIL: sysinfo failed\n"); +    exit(1); +  } + +  if (sysinfo((struct sysinfo *) 0xeaeb0b5b00002f5e) !=  0xffffffffffffffff) { +    printf("FAIL: sysinfo succeeded with bad argument\n"); +    exit(1); +  } +} + +void testproc() { +  struct sysinfo info; +  uint64 nproc; +  int status; +  int pid; +   +  sinfo(&info); +  nproc = info.nproc; + +  pid = fork(); +  if(pid < 0){ +    printf("sysinfotest: fork failed\n"); +    exit(1); +  } +  if(pid == 0){ +    sinfo(&info); +    if(info.nproc != nproc+1) { +      printf("sysinfotest: FAIL nproc is %d instead of %d\n", info.nproc, nproc+1); +      exit(1); +    } +    exit(0); +  } +  wait(&status); +  sinfo(&info); +  if(info.nproc != nproc) { +      printf("sysinfotest: FAIL nproc is %d instead of %d\n", info.nproc, nproc); +      exit(1); +  } +} + +void testbad() { +  int pid = fork(); +  int xstatus; +   +  if(pid < 0){ +    printf("sysinfotest: fork failed\n"); +    exit(1); +  } +  if(pid == 0){ +      sinfo(0x0); +      exit(0); +  } +  wait(&xstatus); +  if(xstatus == -1)  // kernel killed child? +    exit(0); +  else { +    printf("sysinfotest: testbad succeeded %d\n", xstatus); +    exit(xstatus); +  } +} + +int +main(int argc, char *argv[]) +{ +  printf("sysinfotest: start\n"); +  testcall(); +  testmem(); +  testproc(); +  printf("sysinfotest: OK\n"); +  exit(0); +} diff --git a/user/trace.c b/user/trace.c new file mode 100644 index 0000000..dd77760 --- /dev/null +++ b/user/trace.c @@ -0,0 +1,27 @@ +#include "kernel/param.h" +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +int +main(int argc, char *argv[]) +{ +  int i; +  char *nargv[MAXARG]; + +  if(argc < 3 || (argv[1][0] < '0' || argv[1][0] > '9')){ +    fprintf(2, "Usage: %s mask command\n", argv[0]); +    exit(1); +  } + +  if (trace(atoi(argv[1])) < 0) { +    fprintf(2, "%s: trace failed\n", argv[0]); +    exit(1); +  } +   +  for(i = 2; i < argc && i < MAXARG; i++){ +    nargv[i-2] = argv[i]; +  } +  exec(nargv[0], nargv); +  exit(0); +} diff --git a/user/ulib.c b/user/ulib.c index c7b66c4..871adc9 100644 --- a/user/ulib.c +++ b/user/ulib.c @@ -1,8 +1,13 @@  #include "kernel/types.h"  #include "kernel/stat.h"  #include "kernel/fcntl.h" +#ifdef LAB_PGTBL +#include "kernel/riscv.h" +#include "kernel/memlayout.h" +#endif  #include "user/user.h" +  //  // wrapper so that it's OK if main() does not call exit().  // @@ -145,3 +150,12 @@ memcpy(void *dst, const void *src, uint n)  {    return memmove(dst, src, n);  } + +#ifdef LAB_PGTBL +int +ugetpid(void) +{ +  struct usyscall *u = (struct usyscall *)USYSCALL; +  return u->pid; +} +#endif diff --git a/user/user.h b/user/user.h index 4d398d5..3544ac4 100644 --- a/user/user.h +++ b/user/user.h @@ -1,4 +1,9 @@ +#ifdef LAB_MMAP +typedef unsigned long size_t; +typedef long int off_t; +#endif  struct stat; +struct sysinfo;  // system calls  int fork(void); @@ -22,6 +27,18 @@ int getpid(void);  char* sbrk(int);  int sleep(int);  int uptime(void); +#ifdef LAB_NET +int connect(uint32, uint16, uint16); +#endif +#ifdef LAB_PGTBL +int pgaccess(void *base, int len, void *mask); +// usyscall region +int ugetpid(void); +#endif +int trace(int); +int sysinfo(struct sysinfo*); +int sigalarm(int ticks, void (*handler)()); +int sigreturn(void);  // ulib.c  int stat(const char*, struct stat*); @@ -39,3 +56,6 @@ void free(void*);  int atoi(const char*);  int memcmp(const void *, const void *, uint);  void *memcpy(void *, const void *, uint); + +// statistics.c +int statistics(void*, int); diff --git a/user/usys.pl b/user/usys.pl index 01e426e..33af0ad 100755 --- a/user/usys.pl +++ b/user/usys.pl @@ -36,3 +36,9 @@ entry("getpid");  entry("sbrk");  entry("sleep");  entry("uptime"); +entry("trace"); +entry("sysinfo"); +entry("connect"); +entry("pgaccess"); +entry("sigalarm"); +entry("sigreturn"); diff --git a/user/uthread.c b/user/uthread.c new file mode 100644 index 0000000..7641d77 --- /dev/null +++ b/user/uthread.c @@ -0,0 +1,184 @@ +#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 + + +// Saved registers for kernel context switches. +struct context { +  uint64 ra; +  uint64 sp; + +  // callee-saved +  uint64 s0; +  uint64 s1; +  uint64 s2; +  uint64 s3; +  uint64 s4; +  uint64 s5; +  uint64 s6; +  uint64 s7; +  uint64 s8; +  uint64 s9; +  uint64 s10; +  uint64 s11; +}; + +struct thread { +  char       stack[STACK_SIZE]; /* the thread's stack */ +  int        state;             /* FREE, RUNNING, RUNNABLE */ +  struct context context;       /* saved registers to switch thru threads */ +}; +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. +  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; +     // Invoke thread_switch to switch from t to next_thread: +    thread_switch((uint64)&t->context, (uint64)&next_thread->context); +  } 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; +  // Set up new context to start executing at func +  memset(&t->context, 0, sizeof(struct context)); +  t->context.ra = (uint64)func; +  // stack grows downward, set sp at the highest stack address in out thread +  t->context.sp = (uint64)t->stack + STACK_SIZE; +} + +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); +  current_thread->state = FREE; +  thread_schedule(); +  exit(0); +} diff --git a/user/uthread_switch.S b/user/uthread_switch.S new file mode 100644 index 0000000..19cc400 --- /dev/null +++ b/user/uthread_switch.S @@ -0,0 +1,39 @@ +	.text + +	/* +         * save the old thread's registers, +         * restore the new thread's registers. +         */ + +	.globl thread_switch +thread_switch: +        sd ra, 0(a0) +        sd sp, 8(a0) +        sd s0, 16(a0) +        sd s1, 24(a0) +        sd s2, 32(a0) +        sd s3, 40(a0) +        sd s4, 48(a0) +        sd s5, 56(a0) +        sd s6, 64(a0) +        sd s7, 72(a0) +        sd s8, 80(a0) +        sd s9, 88(a0) +        sd s10, 96(a0) +        sd s11, 104(a0) + +        ld ra, 0(a1) +        ld sp, 8(a1) +        ld s0, 16(a1) +        ld s1, 24(a1) +        ld s2, 32(a1) +        ld s3, 40(a1) +        ld s4, 48(a1) +        ld s5, 56(a1) +        ld s6, 64(a1) +        ld s7, 72(a1) +        ld s8, 80(a1) +        ld s9, 88(a1) +        ld s10, 96(a1) +        ld s11, 104(a1) +	ret    /* return to ra */ diff --git a/user/xargs.c b/user/xargs.c new file mode 100644 index 0000000..b5b9bee --- /dev/null +++ b/user/xargs.c @@ -0,0 +1,60 @@ +#include "kernel/types.h" + +#include "kernel/param.h" +#include "kernel/stat.h" +#include "user/user.h" + +#define is_blank(chr) (chr == ' ' || chr == '\t') + +int +main(int argc, char* argv[]) +{ +  char buf[2048], ch; +  char* p = buf; +  char* v[MAXARG]; +  int c; +  int blanks = 0; +  int offset = 0; + +  if (argc <= 1) { +    fprintf(2, "usage: xargs <command> [argv...]\n"); +    exit(1); +  } + +  for (c = 1; c < argc; c++) { +    v[c - 1] = argv[c]; +  } +  --c; + +  while (read(0, &ch, 1) > 0) { +    if (is_blank(ch)) { +      blanks++; +      continue; +    } + +    if (blanks) { +      buf[offset++] = 0; + +      v[c++] = p; +      p = buf + offset; + +      blanks = 0; +    } + +    if (ch != '\n') { +      buf[offset++] = ch; +    } else { +      v[c++] = p; +      p = buf + offset; + +      if (!fork()) { +        exit(exec(v[0], v)); +      } +      wait(0); + +      c = argc - 1; +    } +  } + +  exit(0); +} diff --git a/user/xargstest.sh b/user/xargstest.sh new file mode 100644 index 0000000..4362589 --- /dev/null +++ b/user/xargstest.sh @@ -0,0 +1,6 @@ +mkdir a +echo hello > a/b +mkdir c +echo hello > c/b +echo hello > b +find . b | xargs grep hello | 
