From b5f0874cd96ee2a62aabc645b9626c2749cb6a01 Mon Sep 17 00:00:00 2001 From: manuel Date: Mon, 26 Mar 2012 12:54:45 +0200 Subject: initial pintos checkin --- .gitignore | 4 + doc/pintos.css | 76 + doc/pintos.html | 342 ++ doc/pintos.pdf | Bin 0 -> 514054 bytes doc/pintos_1.html | 788 +++++ doc/pintos_10.html | 286 ++ doc/pintos_11.html | 137 + doc/pintos_2.html | 1734 ++++++++++ doc/pintos_3.html | 375 +++ doc/pintos_4.html | 83 + doc/pintos_5.html | 3343 ++++++++++++++++++++ doc/pintos_6.html | 314 ++ doc/pintos_7.html | 238 ++ doc/pintos_8.html | 1041 ++++++ doc/pintos_9.html | 143 + doc/pintos_abt.html | 205 ++ doc/pintos_fot.html | 79 + doc/pintos_ovr.html | 69 + doc/pintos_tour.pdf | Bin 0 -> 257402 bytes doc/sample.tmpl | 104 + doc/start.tmpl | 101 + doc/threads.tmpl | 82 + doc/vm.tmpl | 81 + env.sh | 3 + gdb-macros | 140 + pintos-progos/LICENSE | 95 + pintos-progos/Make.config | 52 + pintos-progos/Makefile | 29 + pintos-progos/Makefile.build | 109 + pintos-progos/Makefile.kernel | 20 + pintos-progos/Makefile.userprog | 52 + pintos-progos/devices/block.c | 223 ++ pintos-progos/devices/block.h | 74 + pintos-progos/devices/ide.c | 527 +++ pintos-progos/devices/ide.h | 6 + pintos-progos/devices/input.c | 52 + pintos-progos/devices/input.h | 12 + pintos-progos/devices/intq.c | 114 + pintos-progos/devices/intq.h | 43 + pintos-progos/devices/kbd.c | 213 ++ pintos-progos/devices/kbd.h | 9 + pintos-progos/devices/partition.c | 324 ++ pintos-progos/devices/partition.h | 8 + pintos-progos/devices/pit.c | 83 + pintos-progos/devices/pit.h | 8 + pintos-progos/devices/rtc.c | 112 + pintos-progos/devices/rtc.h | 8 + pintos-progos/devices/serial.c | 228 ++ pintos-progos/devices/serial.h | 11 + pintos-progos/devices/shutdown.c | 131 + pintos-progos/devices/shutdown.h | 19 + pintos-progos/devices/speaker.c | 68 + pintos-progos/devices/speaker.h | 8 + pintos-progos/devices/timer.c | 246 ++ pintos-progos/devices/timer.h | 29 + pintos-progos/devices/vga.c | 172 + pintos-progos/devices/vga.h | 6 + pintos-progos/examples/.gitignore | 19 + pintos-progos/examples/Makefile | 36 + pintos-progos/examples/bubsort.c | 38 + pintos-progos/examples/cat.c | 34 + pintos-progos/examples/cmp.c | 68 + pintos-progos/examples/cp.c | 55 + pintos-progos/examples/echo.c | 14 + pintos-progos/examples/halt.c | 14 + pintos-progos/examples/hello.c | 9 + pintos-progos/examples/hex-dump.c | 35 + pintos-progos/examples/insult.c | 369 +++ pintos-progos/examples/lib/.gitignore | 1 + pintos-progos/examples/lib/user/.dummy | 0 pintos-progos/examples/lib/user/.gitignore | 1 + pintos-progos/examples/lineup.c | 46 + pintos-progos/examples/ls.c | 90 + pintos-progos/examples/matmult.c | 57 + pintos-progos/examples/mcat.c | 45 + pintos-progos/examples/mcp.c | 68 + pintos-progos/examples/mkdir.c | 24 + pintos-progos/examples/pwd.c | 152 + pintos-progos/examples/recursor.c | 34 + pintos-progos/examples/rm.c | 21 + pintos-progos/examples/shell.c | 104 + pintos-progos/examples/test.c | 101 + pintos-progos/filesys/.gitignore | 3 + pintos-progos/filesys/Make.vars | 13 + pintos-progos/filesys/Makefile | 1 + pintos-progos/filesys/directory.c | 236 ++ pintos-progos/filesys/directory.h | 30 + pintos-progos/filesys/file.c | 168 + pintos-progos/filesys/file.h | 29 + pintos-progos/filesys/filesys.c | 103 + pintos-progos/filesys/filesys.h | 20 + pintos-progos/filesys/free-map.c | 85 + pintos-progos/filesys/free-map.h | 17 + pintos-progos/filesys/fsutil.c | 222 ++ pintos-progos/filesys/fsutil.h | 10 + pintos-progos/filesys/inode.c | 345 ++ pintos-progos/filesys/inode.h | 23 + pintos-progos/filesys/off_t.h | 15 + pintos-progos/intro/Make.vars | 7 + pintos-progos/intro/Makefile | 1 + pintos-progos/lib/arithmetic.c | 189 ++ pintos-progos/lib/ctype.h | 28 + pintos-progos/lib/debug.c | 32 + pintos-progos/lib/debug.h | 39 + pintos-progos/lib/inttypes.h | 48 + pintos-progos/lib/kernel/bitmap.c | 371 +++ pintos-progos/lib/kernel/bitmap.h | 51 + pintos-progos/lib/kernel/console.c | 191 ++ pintos-progos/lib/kernel/console.h | 8 + pintos-progos/lib/kernel/debug.c | 123 + pintos-progos/lib/kernel/hash.c | 430 +++ pintos-progos/lib/kernel/hash.h | 103 + pintos-progos/lib/kernel/list.c | 524 +++ pintos-progos/lib/kernel/list.h | 181 ++ pintos-progos/lib/kernel/stdio.h | 6 + pintos-progos/lib/limits.h | 34 + pintos-progos/lib/packed.h | 10 + pintos-progos/lib/random.c | 83 + pintos-progos/lib/random.h | 10 + pintos-progos/lib/round.h | 18 + pintos-progos/lib/stdarg.h | 14 + pintos-progos/lib/stdbool.h | 9 + pintos-progos/lib/stddef.h | 12 + pintos-progos/lib/stdint.h | 51 + pintos-progos/lib/stdio.c | 655 ++++ pintos-progos/lib/stdio.h | 40 + pintos-progos/lib/stdlib.c | 208 ++ pintos-progos/lib/stdlib.h | 22 + pintos-progos/lib/string.c | 375 +++ pintos-progos/lib/string.h | 35 + pintos-progos/lib/syscall-nr.h | 34 + pintos-progos/lib/user/console.c | 94 + pintos-progos/lib/user/debug.c | 25 + pintos-progos/lib/user/entry.c | 10 + pintos-progos/lib/user/stdio.h | 7 + pintos-progos/lib/user/syscall.c | 184 ++ pintos-progos/lib/user/syscall.h | 48 + pintos-progos/lib/user/user.lds | 57 + pintos-progos/lib/ustar.c | 228 ++ pintos-progos/lib/ustar.h | 29 + pintos-progos/misc/0001-bochs-2.3.7-jitter.patch | 78 + .../misc/0002-bochs-2.3.7-triple-fault.patch | 87 + .../misc/0003-bochs-2.3.7-page-fault-segv.patch | 93 + pintos-progos/misc/bochs-2.3.7-build.sh | 42 + pintos-progos/misc/bochs-2.3.7-gcc43.patch | 12 + pintos-progos/misc/bochs-2.3.7-linux3x.patch | 11 + pintos-progos/misc/bochs-2.3.7-typos.patch | 24 + pintos-progos/misc/gcc-3.3.6-cross-howto | 39 + pintos-progos/misc/gdb-macros | 140 + pintos-progos/notes/1.txt | 81 + pintos-progos/notes/2.txt | 164 + pintos-progos/notes/3.txt | 241 ++ pintos-progos/tests/Algorithm/Diff.pm | 1713 ++++++++++ pintos-progos/tests/Make.tests | 76 + pintos-progos/tests/arc4.c | 53 + pintos-progos/tests/arc4.h | 17 + pintos-progos/tests/arc4.pm | 29 + pintos-progos/tests/cksum.c | 92 + pintos-progos/tests/cksum.h | 8 + pintos-progos/tests/cksum.pm | 87 + pintos-progos/tests/filesys/Grading.no-vm | 18 + pintos-progos/tests/filesys/Grading.with-vm | 22 + pintos-progos/tests/filesys/base/Make.tests | 18 + pintos-progos/tests/filesys/base/Rubric | 19 + pintos-progos/tests/filesys/base/child-syn-read.c | 44 + pintos-progos/tests/filesys/base/child-syn-wrt.c | 35 + pintos-progos/tests/filesys/base/full.inc | 20 + pintos-progos/tests/filesys/base/lg-create.c | 5 + pintos-progos/tests/filesys/base/lg-create.ck | 13 + pintos-progos/tests/filesys/base/lg-full.c | 6 + pintos-progos/tests/filesys/base/lg-full.ck | 16 + pintos-progos/tests/filesys/base/lg-random.c | 7 + pintos-progos/tests/filesys/base/lg-random.ck | 14 + pintos-progos/tests/filesys/base/lg-seq-block.c | 7 + pintos-progos/tests/filesys/base/lg-seq-block.ck | 16 + pintos-progos/tests/filesys/base/lg-seq-random.c | 6 + pintos-progos/tests/filesys/base/lg-seq-random.ck | 16 + pintos-progos/tests/filesys/base/random.inc | 59 + pintos-progos/tests/filesys/base/seq-block.inc | 20 + pintos-progos/tests/filesys/base/seq-random.inc | 22 + pintos-progos/tests/filesys/base/sm-create.c | 5 + pintos-progos/tests/filesys/base/sm-create.ck | 13 + pintos-progos/tests/filesys/base/sm-full.c | 6 + pintos-progos/tests/filesys/base/sm-full.ck | 16 + pintos-progos/tests/filesys/base/sm-random.c | 7 + pintos-progos/tests/filesys/base/sm-random.ck | 14 + pintos-progos/tests/filesys/base/sm-seq-block.c | 7 + pintos-progos/tests/filesys/base/sm-seq-block.ck | 16 + pintos-progos/tests/filesys/base/sm-seq-random.c | 6 + pintos-progos/tests/filesys/base/sm-seq-random.ck | 16 + pintos-progos/tests/filesys/base/syn-read.c | 31 + pintos-progos/tests/filesys/base/syn-read.ck | 33 + pintos-progos/tests/filesys/base/syn-read.h | 7 + pintos-progos/tests/filesys/base/syn-remove.c | 30 + pintos-progos/tests/filesys/base/syn-remove.ck | 16 + pintos-progos/tests/filesys/base/syn-write.c | 31 + pintos-progos/tests/filesys/base/syn-write.ck | 32 + pintos-progos/tests/filesys/base/syn-write.h | 9 + pintos-progos/tests/filesys/create.inc | 15 + pintos-progos/tests/filesys/extended/Make.tests | 61 + .../tests/filesys/extended/Rubric.functionality | 26 + .../tests/filesys/extended/Rubric.persistence | 24 + .../tests/filesys/extended/Rubric.robustness | 9 + .../tests/filesys/extended/child-syn-rw.c | 53 + .../filesys/extended/dir-empty-name-persistence.ck | 6 + .../tests/filesys/extended/dir-empty-name.c | 12 + .../tests/filesys/extended/dir-empty-name.ck | 10 + .../filesys/extended/dir-mk-tree-persistence.ck | 16 + pintos-progos/tests/filesys/extended/dir-mk-tree.c | 12 + .../tests/filesys/extended/dir-mk-tree.ck | 12 + .../filesys/extended/dir-mkdir-persistence.ck | 6 + pintos-progos/tests/filesys/extended/dir-mkdir.c | 15 + pintos-progos/tests/filesys/extended/dir-mkdir.ck | 13 + .../tests/filesys/extended/dir-open-persistence.ck | 6 + pintos-progos/tests/filesys/extended/dir-open.c | 21 + pintos-progos/tests/filesys/extended/dir-open.ck | 20 + .../filesys/extended/dir-over-file-persistence.ck | 6 + .../tests/filesys/extended/dir-over-file.c | 13 + .../tests/filesys/extended/dir-over-file.ck | 11 + .../filesys/extended/dir-rm-cwd-persistence.ck | 8 + pintos-progos/tests/filesys/extended/dir-rm-cwd.c | 75 + pintos-progos/tests/filesys/extended/dir-rm-cwd.ck | 51 + .../filesys/extended/dir-rm-parent-persistence.ck | 6 + .../tests/filesys/extended/dir-rm-parent.c | 16 + .../tests/filesys/extended/dir-rm-parent.ck | 14 + .../filesys/extended/dir-rm-root-persistence.ck | 6 + pintos-progos/tests/filesys/extended/dir-rm-root.c | 13 + .../tests/filesys/extended/dir-rm-root.ck | 11 + .../filesys/extended/dir-rm-tree-persistence.ck | 6 + pintos-progos/tests/filesys/extended/dir-rm-tree.c | 62 + .../tests/filesys/extended/dir-rm-tree.ck | 14 + .../filesys/extended/dir-rmdir-persistence.ck | 6 + pintos-progos/tests/filesys/extended/dir-rmdir.c | 14 + pintos-progos/tests/filesys/extended/dir-rmdir.ck | 12 + .../filesys/extended/dir-under-file-persistence.ck | 6 + .../tests/filesys/extended/dir-under-file.c | 13 + .../tests/filesys/extended/dir-under-file.ck | 11 + .../tests/filesys/extended/dir-vine-persistence.ck | 37 + pintos-progos/tests/filesys/extended/dir-vine.c | 85 + pintos-progos/tests/filesys/extended/dir-vine.ck | 11 + .../filesys/extended/grow-create-persistence.ck | 6 + pintos-progos/tests/filesys/extended/grow-create.c | 4 + .../tests/filesys/extended/grow-create.ck | 13 + .../filesys/extended/grow-dir-lg-persistence.ck | 9 + pintos-progos/tests/filesys/extended/grow-dir-lg.c | 6 + .../tests/filesys/extended/grow-dir-lg.ck | 61 + pintos-progos/tests/filesys/extended/grow-dir.inc | 41 + .../filesys/extended/grow-file-size-persistence.ck | 7 + .../tests/filesys/extended/grow-file-size.c | 33 + .../tests/filesys/extended/grow-file-size.ck | 17 + .../filesys/extended/grow-root-lg-persistence.ck | 9 + .../tests/filesys/extended/grow-root-lg.c | 4 + .../tests/filesys/extended/grow-root-lg.ck | 60 + .../filesys/extended/grow-root-sm-persistence.ck | 9 + .../tests/filesys/extended/grow-root-sm.c | 4 + .../tests/filesys/extended/grow-root-sm.ck | 30 + .../filesys/extended/grow-seq-lg-persistence.ck | 7 + pintos-progos/tests/filesys/extended/grow-seq-lg.c | 5 + .../tests/filesys/extended/grow-seq-lg.ck | 17 + .../filesys/extended/grow-seq-sm-persistence.ck | 7 + pintos-progos/tests/filesys/extended/grow-seq-sm.c | 5 + .../tests/filesys/extended/grow-seq-sm.ck | 17 + pintos-progos/tests/filesys/extended/grow-seq.inc | 20 + .../filesys/extended/grow-sparse-persistence.ck | 6 + pintos-progos/tests/filesys/extended/grow-sparse.c | 25 + .../tests/filesys/extended/grow-sparse.ck | 17 + .../filesys/extended/grow-tell-persistence.ck | 7 + pintos-progos/tests/filesys/extended/grow-tell.c | 32 + pintos-progos/tests/filesys/extended/grow-tell.ck | 17 + .../filesys/extended/grow-two-files-persistence.ck | 9 + .../tests/filesys/extended/grow-two-files.c | 62 + .../tests/filesys/extended/grow-two-files.ck | 23 + pintos-progos/tests/filesys/extended/mk-tree.c | 67 + pintos-progos/tests/filesys/extended/mk-tree.h | 6 + .../tests/filesys/extended/syn-rw-persistence.ck | 8 + pintos-progos/tests/filesys/extended/syn-rw.c | 35 + pintos-progos/tests/filesys/extended/syn-rw.ck | 20 + pintos-progos/tests/filesys/extended/syn-rw.h | 9 + pintos-progos/tests/filesys/extended/tar.c | 208 ++ pintos-progos/tests/filesys/seq-test.c | 37 + pintos-progos/tests/filesys/seq-test.h | 11 + pintos-progos/tests/internal/list.c | 174 + pintos-progos/tests/internal/stdio.c | 208 ++ pintos-progos/tests/internal/stdlib.c | 114 + pintos-progos/tests/intro/Grading | 5 + pintos-progos/tests/intro/alarm-clock/Make.tests | 15 + pintos-progos/tests/intro/alarm-clock/Rubric | 7 + .../tests/intro/alarm-clock/alarm-multiple.ck | 1 + .../tests/intro/alarm-clock/alarm-negative.c | 1 + .../tests/intro/alarm-clock/alarm-negative.ck | 1 + .../tests/intro/alarm-clock/alarm-simultaneous.c | 1 + .../tests/intro/alarm-clock/alarm-simultaneous.ck | 1 + .../tests/intro/alarm-clock/alarm-single.ck | 1 + pintos-progos/tests/intro/alarm-clock/alarm-wait.c | 1 + pintos-progos/tests/intro/alarm-clock/alarm-zero.c | 1 + .../tests/intro/alarm-clock/alarm-zero.ck | 1 + pintos-progos/tests/intro/alarm-clock/tests.c | 80 + pintos-progos/tests/intro/userprog-args/Make.tests | 27 + pintos-progos/tests/intro/userprog-args/Rubric | 7 + .../tests/intro/userprog-args/args-dbl-space.ck | 1 + .../tests/intro/userprog-args/args-limit.c | 70 + .../tests/intro/userprog-args/args-limit.ck | 11 + .../tests/intro/userprog-args/args-many.ck | 1 + .../tests/intro/userprog-args/args-multiple.ck | 1 + .../tests/intro/userprog-args/args-none.ck | 1 + .../tests/intro/userprog-args/args-single.ck | 1 + pintos-progos/tests/intro/userprog-args/args.c | 1 + .../tests/intro/userprog-args/child-simple.c | 1 + pintos-progos/tests/lib.c | 196 ++ pintos-progos/tests/lib.h | 50 + pintos-progos/tests/lib.pm | 19 + pintos-progos/tests/main.c | 15 + pintos-progos/tests/main.h | 6 + pintos-progos/tests/make-grade | 152 + pintos-progos/tests/random.pm | 27 + pintos-progos/tests/tests.pm | 625 ++++ pintos-progos/tests/threads/Grading | 11 + pintos-progos/tests/threads/Make.tests | 56 + pintos-progos/tests/threads/Rubric.alarm | 8 + pintos-progos/tests/threads/Rubric.mlfqs | 14 + pintos-progos/tests/threads/Rubric.priority | 15 + pintos-progos/tests/threads/alarm-multiple.ck | 4 + pintos-progos/tests/threads/alarm-negative.c | 15 + pintos-progos/tests/threads/alarm-negative.ck | 10 + pintos-progos/tests/threads/alarm-priority.c | 58 + pintos-progos/tests/threads/alarm-priority.ck | 19 + pintos-progos/tests/threads/alarm-simultaneous.c | 94 + pintos-progos/tests/threads/alarm-simultaneous.ck | 27 + pintos-progos/tests/threads/alarm-single.ck | 4 + pintos-progos/tests/threads/alarm-wait.c | 152 + pintos-progos/tests/threads/alarm-zero.c | 15 + pintos-progos/tests/threads/alarm-zero.ck | 10 + pintos-progos/tests/threads/alarm.pm | 32 + pintos-progos/tests/threads/mlfqs-block.c | 64 + pintos-progos/tests/threads/mlfqs-block.ck | 17 + pintos-progos/tests/threads/mlfqs-fair-2.ck | 7 + pintos-progos/tests/threads/mlfqs-fair-20.ck | 7 + pintos-progos/tests/threads/mlfqs-fair.c | 124 + pintos-progos/tests/threads/mlfqs-load-1.c | 60 + pintos-progos/tests/threads/mlfqs-load-1.ck | 15 + pintos-progos/tests/threads/mlfqs-load-60.c | 155 + pintos-progos/tests/threads/mlfqs-load-60.ck | 36 + pintos-progos/tests/threads/mlfqs-load-avg.c | 167 + pintos-progos/tests/threads/mlfqs-load-avg.ck | 36 + pintos-progos/tests/threads/mlfqs-nice-10.ck | 7 + pintos-progos/tests/threads/mlfqs-nice-2.ck | 7 + pintos-progos/tests/threads/mlfqs-recent-1.c | 144 + pintos-progos/tests/threads/mlfqs-recent-1.ck | 31 + pintos-progos/tests/threads/mlfqs.pm | 146 + pintos-progos/tests/threads/priority-change.c | 31 + pintos-progos/tests/threads/priority-change.ck | 14 + pintos-progos/tests/threads/priority-condvar.c | 53 + pintos-progos/tests/threads/priority-condvar.ck | 39 + .../tests/threads/priority-donate-chain.c | 114 + .../tests/threads/priority-donate-chain.ck | 46 + .../tests/threads/priority-donate-lower.c | 51 + .../tests/threads/priority-donate-lower.ck | 16 + .../tests/threads/priority-donate-multiple.c | 77 + .../tests/threads/priority-donate-multiple.ck | 19 + .../tests/threads/priority-donate-multiple2.c | 90 + .../tests/threads/priority-donate-multiple2.ck | 19 + pintos-progos/tests/threads/priority-donate-nest.c | 94 + .../tests/threads/priority-donate-nest.ck | 19 + pintos-progos/tests/threads/priority-donate-one.c | 65 + pintos-progos/tests/threads/priority-donate-one.ck | 17 + pintos-progos/tests/threads/priority-donate-sema.c | 82 + .../tests/threads/priority-donate-sema.ck | 16 + pintos-progos/tests/threads/priority-fifo.c | 99 + pintos-progos/tests/threads/priority-fifo.ck | 63 + pintos-progos/tests/threads/priority-preempt.c | 41 + pintos-progos/tests/threads/priority-preempt.ck | 16 + pintos-progos/tests/threads/priority-sema.c | 45 + pintos-progos/tests/threads/priority-sema.ck | 29 + pintos-progos/tests/threads/tests.c | 102 + pintos-progos/tests/threads/tests.h | 41 + pintos-progos/tests/userprog/Make.tests | 133 + pintos-progos/tests/userprog/Rubric.functionality | 52 + pintos-progos/tests/userprog/Rubric.robustness | 48 + pintos-progos/tests/userprog/args-dbl-space.ck | 15 + pintos-progos/tests/userprog/args-many.ck | 35 + pintos-progos/tests/userprog/args-multiple.ck | 17 + pintos-progos/tests/userprog/args-none.ck | 13 + pintos-progos/tests/userprog/args-single.ck | 14 + pintos-progos/tests/userprog/args.c | 25 + pintos-progos/tests/userprog/bad-jump.c | 13 + pintos-progos/tests/userprog/bad-jump.ck | 9 + pintos-progos/tests/userprog/bad-jump2.c | 13 + pintos-progos/tests/userprog/bad-jump2.ck | 9 + pintos-progos/tests/userprog/bad-read.c | 13 + pintos-progos/tests/userprog/bad-read.ck | 9 + pintos-progos/tests/userprog/bad-read2.c | 13 + pintos-progos/tests/userprog/bad-read2.ck | 9 + pintos-progos/tests/userprog/bad-write.c | 12 + pintos-progos/tests/userprog/bad-write.ck | 9 + pintos-progos/tests/userprog/bad-write2.c | 12 + pintos-progos/tests/userprog/bad-write2.ck | 9 + pintos-progos/tests/userprog/boundary.c | 33 + pintos-progos/tests/userprog/boundary.h | 7 + pintos-progos/tests/userprog/child-bad.c | 14 + pintos-progos/tests/userprog/child-close.c | 28 + pintos-progos/tests/userprog/child-rox.c | 55 + pintos-progos/tests/userprog/child-simple.c | 15 + pintos-progos/tests/userprog/close-bad-fd.c | 11 + pintos-progos/tests/userprog/close-bad-fd.ck | 13 + pintos-progos/tests/userprog/close-normal.c | 14 + pintos-progos/tests/userprog/close-normal.ck | 12 + pintos-progos/tests/userprog/close-stdin.c | 11 + pintos-progos/tests/userprog/close-stdin.ck | 13 + pintos-progos/tests/userprog/close-stdout.c | 11 + pintos-progos/tests/userprog/close-stdout.ck | 13 + pintos-progos/tests/userprog/close-twice.c | 18 + pintos-progos/tests/userprog/close-twice.ck | 19 + pintos-progos/tests/userprog/create-bad-ptr.c | 12 + pintos-progos/tests/userprog/create-bad-ptr.ck | 9 + pintos-progos/tests/userprog/create-bound.c | 14 + pintos-progos/tests/userprog/create-bound.ck | 11 + pintos-progos/tests/userprog/create-empty.c | 10 + pintos-progos/tests/userprog/create-empty.ck | 14 + pintos-progos/tests/userprog/create-exists.c | 16 + pintos-progos/tests/userprog/create-exists.ck | 15 + pintos-progos/tests/userprog/create-long.c | 17 + pintos-progos/tests/userprog/create-long.ck | 11 + pintos-progos/tests/userprog/create-normal.c | 10 + pintos-progos/tests/userprog/create-normal.ck | 11 + pintos-progos/tests/userprog/create-null.c | 11 + pintos-progos/tests/userprog/create-null.ck | 9 + pintos-progos/tests/userprog/exec-arg.c | 10 + pintos-progos/tests/userprog/exec-arg.ck | 17 + pintos-progos/tests/userprog/exec-bad-ptr.c | 11 + pintos-progos/tests/userprog/exec-bad-ptr.ck | 13 + pintos-progos/tests/userprog/exec-missing.c | 12 + pintos-progos/tests/userprog/exec-missing.ck | 31 + pintos-progos/tests/userprog/exec-multiple.c | 14 + pintos-progos/tests/userprog/exec-multiple.ck | 18 + pintos-progos/tests/userprog/exec-once.c | 11 + pintos-progos/tests/userprog/exec-once.ck | 12 + pintos-progos/tests/userprog/exit.c | 11 + pintos-progos/tests/userprog/exit.ck | 9 + pintos-progos/tests/userprog/halt.c | 11 + pintos-progos/tests/userprog/halt.ck | 15 + pintos-progos/tests/userprog/lib/.gitignore | 1 + pintos-progos/tests/userprog/lib/user/.dummy | 0 pintos-progos/tests/userprog/lib/user/.gitignore | 1 + pintos-progos/tests/userprog/multi-child-fd.c | 25 + pintos-progos/tests/userprog/multi-child-fd.ck | 25 + pintos-progos/tests/userprog/multi-recurse.c | 34 + pintos-progos/tests/userprog/multi-recurse.ck | 70 + pintos-progos/tests/userprog/no-vm/Make.tests | 8 + pintos-progos/tests/userprog/no-vm/Rubric | 3 + pintos-progos/tests/userprog/no-vm/multi-oom.c | 179 ++ pintos-progos/tests/userprog/no-vm/multi-oom.ck | 10 + pintos-progos/tests/userprog/null.ck | 8 + pintos-progos/tests/userprog/open-bad-ptr.c | 13 + pintos-progos/tests/userprog/open-bad-ptr.ck | 13 + pintos-progos/tests/userprog/open-boundary.c | 14 + pintos-progos/tests/userprog/open-boundary.ck | 11 + pintos-progos/tests/userprog/open-empty.c | 13 + pintos-progos/tests/userprog/open-empty.ck | 10 + pintos-progos/tests/userprog/open-missing.c | 13 + pintos-progos/tests/userprog/open-missing.ck | 10 + pintos-progos/tests/userprog/open-normal.c | 13 + pintos-progos/tests/userprog/open-normal.ck | 10 + pintos-progos/tests/userprog/open-null.c | 12 + pintos-progos/tests/userprog/open-null.ck | 13 + pintos-progos/tests/userprog/open-twice.c | 19 + pintos-progos/tests/userprog/open-twice.ck | 12 + pintos-progos/tests/userprog/read-bad-fd.c | 21 + pintos-progos/tests/userprog/read-bad-fd.ck | 13 + pintos-progos/tests/userprog/read-bad-ptr.c | 16 + pintos-progos/tests/userprog/read-bad-ptr.ck | 15 + pintos-progos/tests/userprog/read-boundary.c | 30 + pintos-progos/tests/userprog/read-boundary.ck | 11 + pintos-progos/tests/userprog/read-normal.c | 11 + pintos-progos/tests/userprog/read-normal.ck | 13 + pintos-progos/tests/userprog/read-stdout.c | 14 + pintos-progos/tests/userprog/read-stdout.ck | 13 + pintos-progos/tests/userprog/read-zero.c | 22 + pintos-progos/tests/userprog/read-zero.ck | 11 + pintos-progos/tests/userprog/rox-child.c | 5 + pintos-progos/tests/userprog/rox-child.ck | 20 + pintos-progos/tests/userprog/rox-child.inc | 33 + pintos-progos/tests/userprog/rox-multichild.c | 5 + pintos-progos/tests/userprog/rox-multichild.ck | 44 + pintos-progos/tests/userprog/rox-simple.c | 19 + pintos-progos/tests/userprog/rox-simple.ck | 13 + pintos-progos/tests/userprog/sample.inc | 6 + pintos-progos/tests/userprog/sample.txt | 4 + pintos-progos/tests/userprog/sc-bad-arg.c | 17 + pintos-progos/tests/userprog/sc-bad-arg.ck | 9 + pintos-progos/tests/userprog/sc-bad-sp.c | 20 + pintos-progos/tests/userprog/sc-bad-sp.ck | 9 + pintos-progos/tests/userprog/sc-boundary-2.c | 22 + pintos-progos/tests/userprog/sc-boundary-2.ck | 9 + pintos-progos/tests/userprog/sc-boundary.c | 22 + pintos-progos/tests/userprog/sc-boundary.ck | 9 + pintos-progos/tests/userprog/wait-bad-pid.c | 11 + pintos-progos/tests/userprog/wait-bad-pid.ck | 13 + pintos-progos/tests/userprog/wait-killed.c | 11 + pintos-progos/tests/userprog/wait-killed.ck | 13 + pintos-progos/tests/userprog/wait-simple.c | 11 + pintos-progos/tests/userprog/wait-simple.ck | 13 + pintos-progos/tests/userprog/wait-twice.c | 15 + pintos-progos/tests/userprog/wait-twice.ck | 14 + pintos-progos/tests/userprog/write-bad-fd.c | 20 + pintos-progos/tests/userprog/write-bad-fd.ck | 13 + pintos-progos/tests/userprog/write-bad-ptr.c | 16 + pintos-progos/tests/userprog/write-bad-ptr.ck | 15 + pintos-progos/tests/userprog/write-boundary.c | 25 + pintos-progos/tests/userprog/write-boundary.ck | 11 + pintos-progos/tests/userprog/write-normal.c | 20 + pintos-progos/tests/userprog/write-normal.ck | 12 + pintos-progos/tests/userprog/write-stdin.c | 14 + pintos-progos/tests/userprog/write-stdin.ck | 13 + pintos-progos/tests/userprog/write-zero.c | 20 + pintos-progos/tests/userprog/write-zero.ck | 11 + pintos-progos/tests/vm/Grading | 12 + pintos-progos/tests/vm/Make.tests | 107 + pintos-progos/tests/vm/Rubric.functionality | 21 + pintos-progos/tests/vm/Rubric.paging | 8 + pintos-progos/tests/vm/Rubric.robustness | 21 + pintos-progos/tests/vm/child-inherit.c | 16 + pintos-progos/tests/vm/child-linear.c | 36 + pintos-progos/tests/vm/child-mm-wrt.c | 24 + pintos-progos/tests/vm/child-qsort-mm.c | 25 + pintos-progos/tests/vm/child-qsort.c | 32 + pintos-progos/tests/vm/child-sort.c | 42 + pintos-progos/tests/vm/mmap-bad-fd.c | 15 + pintos-progos/tests/vm/mmap-bad-fd.ck | 15 + pintos-progos/tests/vm/mmap-clean.c | 53 + pintos-progos/tests/vm/mmap-clean.ck | 16 + pintos-progos/tests/vm/mmap-close.c | 27 + pintos-progos/tests/vm/mmap-close.ck | 11 + pintos-progos/tests/vm/mmap-exit.c | 22 + pintos-progos/tests/vm/mmap-exit.ck | 17 + pintos-progos/tests/vm/mmap-inherit.c | 32 + pintos-progos/tests/vm/mmap-inherit.ck | 16 + pintos-progos/tests/vm/mmap-lazy-seq.c | 52 + pintos-progos/tests/vm/mmap-lazy-seq.ck | 27 + pintos-progos/tests/vm/mmap-misalign.c | 16 + pintos-progos/tests/vm/mmap-misalign.ck | 11 + pintos-progos/tests/vm/mmap-null.c | 15 + pintos-progos/tests/vm/mmap-null.ck | 11 + pintos-progos/tests/vm/mmap-over-code.c | 19 + pintos-progos/tests/vm/mmap-over-code.ck | 11 + pintos-progos/tests/vm/mmap-over-data.c | 21 + pintos-progos/tests/vm/mmap-over-data.ck | 11 + pintos-progos/tests/vm/mmap-over-stk.c | 19 + pintos-progos/tests/vm/mmap-over-stk.ck | 11 + pintos-progos/tests/vm/mmap-overlap.c | 20 + pintos-progos/tests/vm/mmap-overlap.ck | 13 + pintos-progos/tests/vm/mmap-read.c | 32 + pintos-progos/tests/vm/mmap-read.ck | 11 + pintos-progos/tests/vm/mmap-remove.c | 43 + pintos-progos/tests/vm/mmap-remove.ck | 14 + pintos-progos/tests/vm/mmap-shuffle.c | 38 + pintos-progos/tests/vm/mmap-shuffle.ck | 47 + pintos-progos/tests/vm/mmap-twice.c | 28 + pintos-progos/tests/vm/mmap-twice.ck | 15 + pintos-progos/tests/vm/mmap-unmap.c | 23 + pintos-progos/tests/vm/mmap-unmap.ck | 7 + pintos-progos/tests/vm/mmap-write.c | 32 + pintos-progos/tests/vm/mmap-write.ck | 13 + pintos-progos/tests/vm/mmap-zero.c | 27 + pintos-progos/tests/vm/mmap-zero.ck | 12 + pintos-progos/tests/vm/page-linear.c | 44 + pintos-progos/tests/vm/page-linear.ck | 14 + pintos-progos/tests/vm/page-merge-mm.c | 8 + pintos-progos/tests/vm/page-merge-mm.ck | 29 + pintos-progos/tests/vm/page-merge-par.c | 8 + pintos-progos/tests/vm/page-merge-par.ck | 29 + pintos-progos/tests/vm/page-merge-seq.c | 137 + pintos-progos/tests/vm/page-merge-seq.ck | 29 + pintos-progos/tests/vm/page-merge-stk.c | 8 + pintos-progos/tests/vm/page-merge-stk.ck | 29 + pintos-progos/tests/vm/page-parallel.c | 21 + pintos-progos/tests/vm/page-parallel.ck | 17 + pintos-progos/tests/vm/page-shuffle.c | 30 + pintos-progos/tests/vm/page-shuffle.ck | 44 + pintos-progos/tests/vm/parallel-merge.c | 149 + pintos-progos/tests/vm/parallel-merge.h | 6 + pintos-progos/tests/vm/process_death.pm | 22 + pintos-progos/tests/vm/pt-bad-addr.c | 11 + pintos-progos/tests/vm/pt-bad-addr.ck | 7 + pintos-progos/tests/vm/pt-bad-read.c | 16 + pintos-progos/tests/vm/pt-bad-read.ck | 10 + pintos-progos/tests/vm/pt-big-stk-obj.c | 20 + pintos-progos/tests/vm/pt-big-stk-obj.ck | 10 + pintos-progos/tests/vm/pt-grow-bad.c | 14 + pintos-progos/tests/vm/pt-grow-bad.ck | 9 + pintos-progos/tests/vm/pt-grow-pusha.c | 20 + pintos-progos/tests/vm/pt-grow-pusha.ck | 9 + pintos-progos/tests/vm/pt-grow-stack.c | 20 + pintos-progos/tests/vm/pt-grow-stack.ck | 10 + pintos-progos/tests/vm/pt-grow-stk-sc.c | 32 + pintos-progos/tests/vm/pt-grow-stk-sc.ck | 15 + pintos-progos/tests/vm/pt-write-code-2.c | 15 + pintos-progos/tests/vm/pt-write-code.c | 12 + pintos-progos/tests/vm/pt-write-code.ck | 7 + pintos-progos/tests/vm/pt-write-code2.ck | 10 + pintos-progos/tests/vm/qsort.c | 136 + pintos-progos/tests/vm/qsort.h | 8 + pintos-progos/tests/vm/sample.inc | 19 + pintos-progos/tests/vm/sample.txt | 17 + pintos-progos/threads/.gitignore | 3 + pintos-progos/threads/Make.vars | 7 + pintos-progos/threads/Makefile | 1 + pintos-progos/threads/flags.h | 8 + pintos-progos/threads/init.c | 453 +++ pintos-progos/threads/init.h | 12 + pintos-progos/threads/interrupt.c | 438 +++ pintos-progos/threads/interrupt.h | 70 + pintos-progos/threads/intr-stubs.S | 203 ++ pintos-progos/threads/intr-stubs.h | 19 + pintos-progos/threads/io.h | 115 + pintos-progos/threads/kernel.lds.S | 30 + pintos-progos/threads/loader.S | 263 ++ pintos-progos/threads/loader.h | 40 + pintos-progos/threads/malloc.c | 294 ++ pintos-progos/threads/malloc.h | 13 + pintos-progos/threads/palloc.c | 199 ++ pintos-progos/threads/palloc.h | 20 + pintos-progos/threads/pte.h | 107 + pintos-progos/threads/start.S | 204 ++ pintos-progos/threads/switch.S | 65 + pintos-progos/threads/switch.h | 39 + pintos-progos/threads/synch.c | 338 ++ pintos-progos/threads/synch.h | 51 + pintos-progos/threads/thread.c | 594 ++++ pintos-progos/threads/thread.h | 144 + pintos-progos/threads/vaddr.h | 89 + pintos-progos/userprog/.gitignore | 3 + pintos-progos/userprog/Make.vars | 7 + pintos-progos/userprog/Makefile | 1 + pintos-progos/userprog/exception.c | 174 + pintos-progos/userprog/exception.h | 12 + pintos-progos/userprog/gdt.c | 146 + pintos-progos/userprog/gdt.h | 15 + pintos-progos/userprog/pagedir.c | 263 ++ pintos-progos/userprog/pagedir.h | 18 + pintos-progos/userprog/process.c | 721 +++++ pintos-progos/userprog/process.h | 47 + pintos-progos/userprog/syscall.c | 563 ++++ pintos-progos/userprog/syscall.h | 5 + pintos-progos/userprog/tss.c | 106 + pintos-progos/userprog/tss.h | 11 + pintos-progos/utils/.gitignore | 3 + pintos-progos/utils/Makefile | 11 + pintos-progos/utils/Pintos.pm | 491 +++ pintos-progos/utils/backtrace | 106 + pintos-progos/utils/pintos | 955 ++++++ pintos-progos/utils/pintos-gdb | 21 + pintos-progos/utils/pintos-mkdisk | 134 + pintos-progos/utils/pintos-set-cmdline | 42 + pintos-progos/utils/setitimer-helper.c | 49 + pintos-progos/utils/squish-pty.c | 355 +++ pintos-progos/utils/squish-unix.c | 338 ++ pintos-progos/vm/.gitignore | 3 + pintos-progos/vm/Make.vars | 7 + pintos-progos/vm/Makefile | 1 + proj0.txt | 106 + 660 files changed, 43698 insertions(+) create mode 100644 .gitignore create mode 100644 doc/pintos.css create mode 100644 doc/pintos.html create mode 100644 doc/pintos.pdf create mode 100644 doc/pintos_1.html create mode 100644 doc/pintos_10.html create mode 100644 doc/pintos_11.html create mode 100644 doc/pintos_2.html create mode 100644 doc/pintos_3.html create mode 100644 doc/pintos_4.html create mode 100644 doc/pintos_5.html create mode 100644 doc/pintos_6.html create mode 100644 doc/pintos_7.html create mode 100644 doc/pintos_8.html create mode 100644 doc/pintos_9.html create mode 100644 doc/pintos_abt.html create mode 100644 doc/pintos_fot.html create mode 100644 doc/pintos_ovr.html create mode 100644 doc/pintos_tour.pdf create mode 100644 doc/sample.tmpl create mode 100644 doc/start.tmpl create mode 100644 doc/threads.tmpl create mode 100644 doc/vm.tmpl create mode 100644 env.sh create mode 100644 gdb-macros create mode 100644 pintos-progos/LICENSE create mode 100644 pintos-progos/Make.config create mode 100644 pintos-progos/Makefile create mode 100644 pintos-progos/Makefile.build create mode 100644 pintos-progos/Makefile.kernel create mode 100644 pintos-progos/Makefile.userprog create mode 100644 pintos-progos/devices/block.c create mode 100644 pintos-progos/devices/block.h create mode 100644 pintos-progos/devices/ide.c create mode 100644 pintos-progos/devices/ide.h create mode 100644 pintos-progos/devices/input.c create mode 100644 pintos-progos/devices/input.h create mode 100644 pintos-progos/devices/intq.c create mode 100644 pintos-progos/devices/intq.h create mode 100644 pintos-progos/devices/kbd.c create mode 100644 pintos-progos/devices/kbd.h create mode 100644 pintos-progos/devices/partition.c create mode 100644 pintos-progos/devices/partition.h create mode 100644 pintos-progos/devices/pit.c create mode 100644 pintos-progos/devices/pit.h create mode 100644 pintos-progos/devices/rtc.c create mode 100644 pintos-progos/devices/rtc.h create mode 100644 pintos-progos/devices/serial.c create mode 100644 pintos-progos/devices/serial.h create mode 100644 pintos-progos/devices/shutdown.c create mode 100644 pintos-progos/devices/shutdown.h create mode 100644 pintos-progos/devices/speaker.c create mode 100644 pintos-progos/devices/speaker.h create mode 100644 pintos-progos/devices/timer.c create mode 100644 pintos-progos/devices/timer.h create mode 100644 pintos-progos/devices/vga.c create mode 100644 pintos-progos/devices/vga.h create mode 100644 pintos-progos/examples/.gitignore create mode 100644 pintos-progos/examples/Makefile create mode 100644 pintos-progos/examples/bubsort.c create mode 100644 pintos-progos/examples/cat.c create mode 100644 pintos-progos/examples/cmp.c create mode 100644 pintos-progos/examples/cp.c create mode 100644 pintos-progos/examples/echo.c create mode 100644 pintos-progos/examples/halt.c create mode 100644 pintos-progos/examples/hello.c create mode 100644 pintos-progos/examples/hex-dump.c create mode 100644 pintos-progos/examples/insult.c create mode 100644 pintos-progos/examples/lib/.gitignore create mode 100644 pintos-progos/examples/lib/user/.dummy create mode 100644 pintos-progos/examples/lib/user/.gitignore create mode 100644 pintos-progos/examples/lineup.c create mode 100644 pintos-progos/examples/ls.c create mode 100644 pintos-progos/examples/matmult.c create mode 100644 pintos-progos/examples/mcat.c create mode 100644 pintos-progos/examples/mcp.c create mode 100644 pintos-progos/examples/mkdir.c create mode 100644 pintos-progos/examples/pwd.c create mode 100644 pintos-progos/examples/recursor.c create mode 100644 pintos-progos/examples/rm.c create mode 100644 pintos-progos/examples/shell.c create mode 100644 pintos-progos/examples/test.c create mode 100644 pintos-progos/filesys/.gitignore create mode 100644 pintos-progos/filesys/Make.vars create mode 100644 pintos-progos/filesys/Makefile create mode 100644 pintos-progos/filesys/directory.c create mode 100644 pintos-progos/filesys/directory.h create mode 100644 pintos-progos/filesys/file.c create mode 100644 pintos-progos/filesys/file.h create mode 100644 pintos-progos/filesys/filesys.c create mode 100644 pintos-progos/filesys/filesys.h create mode 100644 pintos-progos/filesys/free-map.c create mode 100644 pintos-progos/filesys/free-map.h create mode 100644 pintos-progos/filesys/fsutil.c create mode 100644 pintos-progos/filesys/fsutil.h create mode 100644 pintos-progos/filesys/inode.c create mode 100644 pintos-progos/filesys/inode.h create mode 100644 pintos-progos/filesys/off_t.h create mode 100644 pintos-progos/intro/Make.vars create mode 100644 pintos-progos/intro/Makefile create mode 100644 pintos-progos/lib/arithmetic.c create mode 100644 pintos-progos/lib/ctype.h create mode 100644 pintos-progos/lib/debug.c create mode 100644 pintos-progos/lib/debug.h create mode 100644 pintos-progos/lib/inttypes.h create mode 100644 pintos-progos/lib/kernel/bitmap.c create mode 100644 pintos-progos/lib/kernel/bitmap.h create mode 100644 pintos-progos/lib/kernel/console.c create mode 100644 pintos-progos/lib/kernel/console.h create mode 100644 pintos-progos/lib/kernel/debug.c create mode 100644 pintos-progos/lib/kernel/hash.c create mode 100644 pintos-progos/lib/kernel/hash.h create mode 100644 pintos-progos/lib/kernel/list.c create mode 100644 pintos-progos/lib/kernel/list.h create mode 100644 pintos-progos/lib/kernel/stdio.h create mode 100644 pintos-progos/lib/limits.h create mode 100644 pintos-progos/lib/packed.h create mode 100644 pintos-progos/lib/random.c create mode 100644 pintos-progos/lib/random.h create mode 100644 pintos-progos/lib/round.h create mode 100644 pintos-progos/lib/stdarg.h create mode 100644 pintos-progos/lib/stdbool.h create mode 100644 pintos-progos/lib/stddef.h create mode 100644 pintos-progos/lib/stdint.h create mode 100644 pintos-progos/lib/stdio.c create mode 100644 pintos-progos/lib/stdio.h create mode 100644 pintos-progos/lib/stdlib.c create mode 100644 pintos-progos/lib/stdlib.h create mode 100644 pintos-progos/lib/string.c create mode 100644 pintos-progos/lib/string.h create mode 100644 pintos-progos/lib/syscall-nr.h create mode 100644 pintos-progos/lib/user/console.c create mode 100644 pintos-progos/lib/user/debug.c create mode 100644 pintos-progos/lib/user/entry.c create mode 100644 pintos-progos/lib/user/stdio.h create mode 100644 pintos-progos/lib/user/syscall.c create mode 100644 pintos-progos/lib/user/syscall.h create mode 100644 pintos-progos/lib/user/user.lds create mode 100644 pintos-progos/lib/ustar.c create mode 100644 pintos-progos/lib/ustar.h create mode 100644 pintos-progos/misc/0001-bochs-2.3.7-jitter.patch create mode 100644 pintos-progos/misc/0002-bochs-2.3.7-triple-fault.patch create mode 100644 pintos-progos/misc/0003-bochs-2.3.7-page-fault-segv.patch create mode 100755 pintos-progos/misc/bochs-2.3.7-build.sh create mode 100644 pintos-progos/misc/bochs-2.3.7-gcc43.patch create mode 100644 pintos-progos/misc/bochs-2.3.7-linux3x.patch create mode 100644 pintos-progos/misc/bochs-2.3.7-typos.patch create mode 100644 pintos-progos/misc/gcc-3.3.6-cross-howto create mode 100644 pintos-progos/misc/gdb-macros create mode 100644 pintos-progos/notes/1.txt create mode 100644 pintos-progos/notes/2.txt create mode 100644 pintos-progos/notes/3.txt create mode 100644 pintos-progos/tests/Algorithm/Diff.pm create mode 100644 pintos-progos/tests/Make.tests create mode 100644 pintos-progos/tests/arc4.c create mode 100644 pintos-progos/tests/arc4.h create mode 100644 pintos-progos/tests/arc4.pm create mode 100644 pintos-progos/tests/cksum.c create mode 100644 pintos-progos/tests/cksum.h create mode 100644 pintos-progos/tests/cksum.pm create mode 100644 pintos-progos/tests/filesys/Grading.no-vm create mode 100644 pintos-progos/tests/filesys/Grading.with-vm create mode 100644 pintos-progos/tests/filesys/base/Make.tests create mode 100644 pintos-progos/tests/filesys/base/Rubric create mode 100644 pintos-progos/tests/filesys/base/child-syn-read.c create mode 100644 pintos-progos/tests/filesys/base/child-syn-wrt.c create mode 100644 pintos-progos/tests/filesys/base/full.inc create mode 100644 pintos-progos/tests/filesys/base/lg-create.c create mode 100644 pintos-progos/tests/filesys/base/lg-create.ck create mode 100644 pintos-progos/tests/filesys/base/lg-full.c create mode 100644 pintos-progos/tests/filesys/base/lg-full.ck create mode 100644 pintos-progos/tests/filesys/base/lg-random.c create mode 100644 pintos-progos/tests/filesys/base/lg-random.ck create mode 100644 pintos-progos/tests/filesys/base/lg-seq-block.c create mode 100644 pintos-progos/tests/filesys/base/lg-seq-block.ck create mode 100644 pintos-progos/tests/filesys/base/lg-seq-random.c create mode 100644 pintos-progos/tests/filesys/base/lg-seq-random.ck create mode 100644 pintos-progos/tests/filesys/base/random.inc create mode 100644 pintos-progos/tests/filesys/base/seq-block.inc create mode 100644 pintos-progos/tests/filesys/base/seq-random.inc create mode 100644 pintos-progos/tests/filesys/base/sm-create.c create mode 100644 pintos-progos/tests/filesys/base/sm-create.ck create mode 100644 pintos-progos/tests/filesys/base/sm-full.c create mode 100644 pintos-progos/tests/filesys/base/sm-full.ck create mode 100644 pintos-progos/tests/filesys/base/sm-random.c create mode 100644 pintos-progos/tests/filesys/base/sm-random.ck create mode 100644 pintos-progos/tests/filesys/base/sm-seq-block.c create mode 100644 pintos-progos/tests/filesys/base/sm-seq-block.ck create mode 100644 pintos-progos/tests/filesys/base/sm-seq-random.c create mode 100644 pintos-progos/tests/filesys/base/sm-seq-random.ck create mode 100644 pintos-progos/tests/filesys/base/syn-read.c create mode 100644 pintos-progos/tests/filesys/base/syn-read.ck create mode 100644 pintos-progos/tests/filesys/base/syn-read.h create mode 100644 pintos-progos/tests/filesys/base/syn-remove.c create mode 100644 pintos-progos/tests/filesys/base/syn-remove.ck create mode 100644 pintos-progos/tests/filesys/base/syn-write.c create mode 100644 pintos-progos/tests/filesys/base/syn-write.ck create mode 100644 pintos-progos/tests/filesys/base/syn-write.h create mode 100644 pintos-progos/tests/filesys/create.inc create mode 100644 pintos-progos/tests/filesys/extended/Make.tests create mode 100644 pintos-progos/tests/filesys/extended/Rubric.functionality create mode 100644 pintos-progos/tests/filesys/extended/Rubric.persistence create mode 100644 pintos-progos/tests/filesys/extended/Rubric.robustness create mode 100644 pintos-progos/tests/filesys/extended/child-syn-rw.c create mode 100644 pintos-progos/tests/filesys/extended/dir-empty-name-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-empty-name.c create mode 100644 pintos-progos/tests/filesys/extended/dir-empty-name.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-mk-tree-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-mk-tree.c create mode 100644 pintos-progos/tests/filesys/extended/dir-mk-tree.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-mkdir-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-mkdir.c create mode 100644 pintos-progos/tests/filesys/extended/dir-mkdir.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-open-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-open.c create mode 100644 pintos-progos/tests/filesys/extended/dir-open.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-over-file-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-over-file.c create mode 100644 pintos-progos/tests/filesys/extended/dir-over-file.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-rm-cwd-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-rm-cwd.c create mode 100644 pintos-progos/tests/filesys/extended/dir-rm-cwd.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-rm-parent-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-rm-parent.c create mode 100644 pintos-progos/tests/filesys/extended/dir-rm-parent.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-rm-root-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-rm-root.c create mode 100644 pintos-progos/tests/filesys/extended/dir-rm-root.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-rm-tree-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-rm-tree.c create mode 100644 pintos-progos/tests/filesys/extended/dir-rm-tree.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-rmdir-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-rmdir.c create mode 100644 pintos-progos/tests/filesys/extended/dir-rmdir.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-under-file-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-under-file.c create mode 100644 pintos-progos/tests/filesys/extended/dir-under-file.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-vine-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/dir-vine.c create mode 100644 pintos-progos/tests/filesys/extended/dir-vine.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-create-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-create.c create mode 100644 pintos-progos/tests/filesys/extended/grow-create.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-dir-lg-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-dir-lg.c create mode 100644 pintos-progos/tests/filesys/extended/grow-dir-lg.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-dir.inc create mode 100644 pintos-progos/tests/filesys/extended/grow-file-size-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-file-size.c create mode 100644 pintos-progos/tests/filesys/extended/grow-file-size.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-root-lg-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-root-lg.c create mode 100644 pintos-progos/tests/filesys/extended/grow-root-lg.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-root-sm-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-root-sm.c create mode 100644 pintos-progos/tests/filesys/extended/grow-root-sm.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-seq-lg-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-seq-lg.c create mode 100644 pintos-progos/tests/filesys/extended/grow-seq-lg.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-seq-sm-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-seq-sm.c create mode 100644 pintos-progos/tests/filesys/extended/grow-seq-sm.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-seq.inc create mode 100644 pintos-progos/tests/filesys/extended/grow-sparse-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-sparse.c create mode 100644 pintos-progos/tests/filesys/extended/grow-sparse.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-tell-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-tell.c create mode 100644 pintos-progos/tests/filesys/extended/grow-tell.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-two-files-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/grow-two-files.c create mode 100644 pintos-progos/tests/filesys/extended/grow-two-files.ck create mode 100644 pintos-progos/tests/filesys/extended/mk-tree.c create mode 100644 pintos-progos/tests/filesys/extended/mk-tree.h create mode 100644 pintos-progos/tests/filesys/extended/syn-rw-persistence.ck create mode 100644 pintos-progos/tests/filesys/extended/syn-rw.c create mode 100644 pintos-progos/tests/filesys/extended/syn-rw.ck create mode 100644 pintos-progos/tests/filesys/extended/syn-rw.h create mode 100644 pintos-progos/tests/filesys/extended/tar.c create mode 100644 pintos-progos/tests/filesys/seq-test.c create mode 100644 pintos-progos/tests/filesys/seq-test.h create mode 100644 pintos-progos/tests/internal/list.c create mode 100644 pintos-progos/tests/internal/stdio.c create mode 100644 pintos-progos/tests/internal/stdlib.c create mode 100644 pintos-progos/tests/intro/Grading create mode 100644 pintos-progos/tests/intro/alarm-clock/Make.tests create mode 100644 pintos-progos/tests/intro/alarm-clock/Rubric create mode 120000 pintos-progos/tests/intro/alarm-clock/alarm-multiple.ck create mode 120000 pintos-progos/tests/intro/alarm-clock/alarm-negative.c create mode 120000 pintos-progos/tests/intro/alarm-clock/alarm-negative.ck create mode 120000 pintos-progos/tests/intro/alarm-clock/alarm-simultaneous.c create mode 120000 pintos-progos/tests/intro/alarm-clock/alarm-simultaneous.ck create mode 120000 pintos-progos/tests/intro/alarm-clock/alarm-single.ck create mode 120000 pintos-progos/tests/intro/alarm-clock/alarm-wait.c create mode 120000 pintos-progos/tests/intro/alarm-clock/alarm-zero.c create mode 120000 pintos-progos/tests/intro/alarm-clock/alarm-zero.ck create mode 100644 pintos-progos/tests/intro/alarm-clock/tests.c create mode 100644 pintos-progos/tests/intro/userprog-args/Make.tests create mode 100644 pintos-progos/tests/intro/userprog-args/Rubric create mode 120000 pintos-progos/tests/intro/userprog-args/args-dbl-space.ck create mode 100644 pintos-progos/tests/intro/userprog-args/args-limit.c create mode 100644 pintos-progos/tests/intro/userprog-args/args-limit.ck create mode 120000 pintos-progos/tests/intro/userprog-args/args-many.ck create mode 120000 pintos-progos/tests/intro/userprog-args/args-multiple.ck create mode 120000 pintos-progos/tests/intro/userprog-args/args-none.ck create mode 120000 pintos-progos/tests/intro/userprog-args/args-single.ck create mode 120000 pintos-progos/tests/intro/userprog-args/args.c create mode 120000 pintos-progos/tests/intro/userprog-args/child-simple.c create mode 100644 pintos-progos/tests/lib.c create mode 100644 pintos-progos/tests/lib.h create mode 100644 pintos-progos/tests/lib.pm create mode 100644 pintos-progos/tests/main.c create mode 100644 pintos-progos/tests/main.h create mode 100755 pintos-progos/tests/make-grade create mode 100644 pintos-progos/tests/random.pm create mode 100644 pintos-progos/tests/tests.pm create mode 100644 pintos-progos/tests/threads/Grading create mode 100644 pintos-progos/tests/threads/Make.tests create mode 100644 pintos-progos/tests/threads/Rubric.alarm create mode 100644 pintos-progos/tests/threads/Rubric.mlfqs create mode 100644 pintos-progos/tests/threads/Rubric.priority create mode 100644 pintos-progos/tests/threads/alarm-multiple.ck create mode 100644 pintos-progos/tests/threads/alarm-negative.c create mode 100644 pintos-progos/tests/threads/alarm-negative.ck create mode 100644 pintos-progos/tests/threads/alarm-priority.c create mode 100644 pintos-progos/tests/threads/alarm-priority.ck create mode 100644 pintos-progos/tests/threads/alarm-simultaneous.c create mode 100644 pintos-progos/tests/threads/alarm-simultaneous.ck create mode 100644 pintos-progos/tests/threads/alarm-single.ck create mode 100644 pintos-progos/tests/threads/alarm-wait.c create mode 100644 pintos-progos/tests/threads/alarm-zero.c create mode 100644 pintos-progos/tests/threads/alarm-zero.ck create mode 100644 pintos-progos/tests/threads/alarm.pm create mode 100644 pintos-progos/tests/threads/mlfqs-block.c create mode 100644 pintos-progos/tests/threads/mlfqs-block.ck create mode 100644 pintos-progos/tests/threads/mlfqs-fair-2.ck create mode 100644 pintos-progos/tests/threads/mlfqs-fair-20.ck create mode 100644 pintos-progos/tests/threads/mlfqs-fair.c create mode 100644 pintos-progos/tests/threads/mlfqs-load-1.c create mode 100644 pintos-progos/tests/threads/mlfqs-load-1.ck create mode 100644 pintos-progos/tests/threads/mlfqs-load-60.c create mode 100644 pintos-progos/tests/threads/mlfqs-load-60.ck create mode 100644 pintos-progos/tests/threads/mlfqs-load-avg.c create mode 100644 pintos-progos/tests/threads/mlfqs-load-avg.ck create mode 100644 pintos-progos/tests/threads/mlfqs-nice-10.ck create mode 100644 pintos-progos/tests/threads/mlfqs-nice-2.ck create mode 100644 pintos-progos/tests/threads/mlfqs-recent-1.c create mode 100644 pintos-progos/tests/threads/mlfqs-recent-1.ck create mode 100644 pintos-progos/tests/threads/mlfqs.pm create mode 100644 pintos-progos/tests/threads/priority-change.c create mode 100644 pintos-progos/tests/threads/priority-change.ck create mode 100644 pintos-progos/tests/threads/priority-condvar.c create mode 100644 pintos-progos/tests/threads/priority-condvar.ck create mode 100644 pintos-progos/tests/threads/priority-donate-chain.c create mode 100644 pintos-progos/tests/threads/priority-donate-chain.ck create mode 100644 pintos-progos/tests/threads/priority-donate-lower.c create mode 100644 pintos-progos/tests/threads/priority-donate-lower.ck create mode 100644 pintos-progos/tests/threads/priority-donate-multiple.c create mode 100644 pintos-progos/tests/threads/priority-donate-multiple.ck create mode 100644 pintos-progos/tests/threads/priority-donate-multiple2.c create mode 100644 pintos-progos/tests/threads/priority-donate-multiple2.ck create mode 100644 pintos-progos/tests/threads/priority-donate-nest.c create mode 100644 pintos-progos/tests/threads/priority-donate-nest.ck create mode 100644 pintos-progos/tests/threads/priority-donate-one.c create mode 100644 pintos-progos/tests/threads/priority-donate-one.ck create mode 100644 pintos-progos/tests/threads/priority-donate-sema.c create mode 100644 pintos-progos/tests/threads/priority-donate-sema.ck create mode 100644 pintos-progos/tests/threads/priority-fifo.c create mode 100644 pintos-progos/tests/threads/priority-fifo.ck create mode 100644 pintos-progos/tests/threads/priority-preempt.c create mode 100644 pintos-progos/tests/threads/priority-preempt.ck create mode 100644 pintos-progos/tests/threads/priority-sema.c create mode 100644 pintos-progos/tests/threads/priority-sema.ck create mode 100644 pintos-progos/tests/threads/tests.c create mode 100644 pintos-progos/tests/threads/tests.h create mode 100644 pintos-progos/tests/userprog/Make.tests create mode 100644 pintos-progos/tests/userprog/Rubric.functionality create mode 100644 pintos-progos/tests/userprog/Rubric.robustness create mode 100644 pintos-progos/tests/userprog/args-dbl-space.ck create mode 100644 pintos-progos/tests/userprog/args-many.ck create mode 100644 pintos-progos/tests/userprog/args-multiple.ck create mode 100644 pintos-progos/tests/userprog/args-none.ck create mode 100644 pintos-progos/tests/userprog/args-single.ck create mode 100644 pintos-progos/tests/userprog/args.c create mode 100644 pintos-progos/tests/userprog/bad-jump.c create mode 100644 pintos-progos/tests/userprog/bad-jump.ck create mode 100644 pintos-progos/tests/userprog/bad-jump2.c create mode 100644 pintos-progos/tests/userprog/bad-jump2.ck create mode 100644 pintos-progos/tests/userprog/bad-read.c create mode 100644 pintos-progos/tests/userprog/bad-read.ck create mode 100644 pintos-progos/tests/userprog/bad-read2.c create mode 100644 pintos-progos/tests/userprog/bad-read2.ck create mode 100644 pintos-progos/tests/userprog/bad-write.c create mode 100644 pintos-progos/tests/userprog/bad-write.ck create mode 100644 pintos-progos/tests/userprog/bad-write2.c create mode 100644 pintos-progos/tests/userprog/bad-write2.ck create mode 100644 pintos-progos/tests/userprog/boundary.c create mode 100644 pintos-progos/tests/userprog/boundary.h create mode 100644 pintos-progos/tests/userprog/child-bad.c create mode 100644 pintos-progos/tests/userprog/child-close.c create mode 100644 pintos-progos/tests/userprog/child-rox.c create mode 100644 pintos-progos/tests/userprog/child-simple.c create mode 100644 pintos-progos/tests/userprog/close-bad-fd.c create mode 100644 pintos-progos/tests/userprog/close-bad-fd.ck create mode 100644 pintos-progos/tests/userprog/close-normal.c create mode 100644 pintos-progos/tests/userprog/close-normal.ck create mode 100644 pintos-progos/tests/userprog/close-stdin.c create mode 100644 pintos-progos/tests/userprog/close-stdin.ck create mode 100644 pintos-progos/tests/userprog/close-stdout.c create mode 100644 pintos-progos/tests/userprog/close-stdout.ck create mode 100644 pintos-progos/tests/userprog/close-twice.c create mode 100644 pintos-progos/tests/userprog/close-twice.ck create mode 100644 pintos-progos/tests/userprog/create-bad-ptr.c create mode 100644 pintos-progos/tests/userprog/create-bad-ptr.ck create mode 100644 pintos-progos/tests/userprog/create-bound.c create mode 100644 pintos-progos/tests/userprog/create-bound.ck create mode 100644 pintos-progos/tests/userprog/create-empty.c create mode 100644 pintos-progos/tests/userprog/create-empty.ck create mode 100644 pintos-progos/tests/userprog/create-exists.c create mode 100644 pintos-progos/tests/userprog/create-exists.ck create mode 100644 pintos-progos/tests/userprog/create-long.c create mode 100644 pintos-progos/tests/userprog/create-long.ck create mode 100644 pintos-progos/tests/userprog/create-normal.c create mode 100644 pintos-progos/tests/userprog/create-normal.ck create mode 100644 pintos-progos/tests/userprog/create-null.c create mode 100644 pintos-progos/tests/userprog/create-null.ck create mode 100644 pintos-progos/tests/userprog/exec-arg.c create mode 100644 pintos-progos/tests/userprog/exec-arg.ck create mode 100644 pintos-progos/tests/userprog/exec-bad-ptr.c create mode 100644 pintos-progos/tests/userprog/exec-bad-ptr.ck create mode 100644 pintos-progos/tests/userprog/exec-missing.c create mode 100644 pintos-progos/tests/userprog/exec-missing.ck create mode 100644 pintos-progos/tests/userprog/exec-multiple.c create mode 100644 pintos-progos/tests/userprog/exec-multiple.ck create mode 100644 pintos-progos/tests/userprog/exec-once.c create mode 100644 pintos-progos/tests/userprog/exec-once.ck create mode 100644 pintos-progos/tests/userprog/exit.c create mode 100644 pintos-progos/tests/userprog/exit.ck create mode 100644 pintos-progos/tests/userprog/halt.c create mode 100644 pintos-progos/tests/userprog/halt.ck create mode 100644 pintos-progos/tests/userprog/lib/.gitignore create mode 100644 pintos-progos/tests/userprog/lib/user/.dummy create mode 100644 pintos-progos/tests/userprog/lib/user/.gitignore create mode 100644 pintos-progos/tests/userprog/multi-child-fd.c create mode 100644 pintos-progos/tests/userprog/multi-child-fd.ck create mode 100644 pintos-progos/tests/userprog/multi-recurse.c create mode 100644 pintos-progos/tests/userprog/multi-recurse.ck create mode 100644 pintos-progos/tests/userprog/no-vm/Make.tests create mode 100644 pintos-progos/tests/userprog/no-vm/Rubric create mode 100644 pintos-progos/tests/userprog/no-vm/multi-oom.c create mode 100644 pintos-progos/tests/userprog/no-vm/multi-oom.ck create mode 100644 pintos-progos/tests/userprog/null.ck create mode 100644 pintos-progos/tests/userprog/open-bad-ptr.c create mode 100644 pintos-progos/tests/userprog/open-bad-ptr.ck create mode 100644 pintos-progos/tests/userprog/open-boundary.c create mode 100644 pintos-progos/tests/userprog/open-boundary.ck create mode 100644 pintos-progos/tests/userprog/open-empty.c create mode 100644 pintos-progos/tests/userprog/open-empty.ck create mode 100644 pintos-progos/tests/userprog/open-missing.c create mode 100644 pintos-progos/tests/userprog/open-missing.ck create mode 100644 pintos-progos/tests/userprog/open-normal.c create mode 100644 pintos-progos/tests/userprog/open-normal.ck create mode 100644 pintos-progos/tests/userprog/open-null.c create mode 100644 pintos-progos/tests/userprog/open-null.ck create mode 100644 pintos-progos/tests/userprog/open-twice.c create mode 100644 pintos-progos/tests/userprog/open-twice.ck create mode 100644 pintos-progos/tests/userprog/read-bad-fd.c create mode 100644 pintos-progos/tests/userprog/read-bad-fd.ck create mode 100644 pintos-progos/tests/userprog/read-bad-ptr.c create mode 100644 pintos-progos/tests/userprog/read-bad-ptr.ck create mode 100644 pintos-progos/tests/userprog/read-boundary.c create mode 100644 pintos-progos/tests/userprog/read-boundary.ck create mode 100644 pintos-progos/tests/userprog/read-normal.c create mode 100644 pintos-progos/tests/userprog/read-normal.ck create mode 100644 pintos-progos/tests/userprog/read-stdout.c create mode 100644 pintos-progos/tests/userprog/read-stdout.ck create mode 100644 pintos-progos/tests/userprog/read-zero.c create mode 100644 pintos-progos/tests/userprog/read-zero.ck create mode 100644 pintos-progos/tests/userprog/rox-child.c create mode 100644 pintos-progos/tests/userprog/rox-child.ck create mode 100644 pintos-progos/tests/userprog/rox-child.inc create mode 100644 pintos-progos/tests/userprog/rox-multichild.c create mode 100644 pintos-progos/tests/userprog/rox-multichild.ck create mode 100644 pintos-progos/tests/userprog/rox-simple.c create mode 100644 pintos-progos/tests/userprog/rox-simple.ck create mode 100644 pintos-progos/tests/userprog/sample.inc create mode 100644 pintos-progos/tests/userprog/sample.txt create mode 100644 pintos-progos/tests/userprog/sc-bad-arg.c create mode 100644 pintos-progos/tests/userprog/sc-bad-arg.ck create mode 100644 pintos-progos/tests/userprog/sc-bad-sp.c create mode 100644 pintos-progos/tests/userprog/sc-bad-sp.ck create mode 100644 pintos-progos/tests/userprog/sc-boundary-2.c create mode 100644 pintos-progos/tests/userprog/sc-boundary-2.ck create mode 100644 pintos-progos/tests/userprog/sc-boundary.c create mode 100644 pintos-progos/tests/userprog/sc-boundary.ck create mode 100644 pintos-progos/tests/userprog/wait-bad-pid.c create mode 100644 pintos-progos/tests/userprog/wait-bad-pid.ck create mode 100644 pintos-progos/tests/userprog/wait-killed.c create mode 100644 pintos-progos/tests/userprog/wait-killed.ck create mode 100644 pintos-progos/tests/userprog/wait-simple.c create mode 100644 pintos-progos/tests/userprog/wait-simple.ck create mode 100644 pintos-progos/tests/userprog/wait-twice.c create mode 100644 pintos-progos/tests/userprog/wait-twice.ck create mode 100644 pintos-progos/tests/userprog/write-bad-fd.c create mode 100644 pintos-progos/tests/userprog/write-bad-fd.ck create mode 100644 pintos-progos/tests/userprog/write-bad-ptr.c create mode 100644 pintos-progos/tests/userprog/write-bad-ptr.ck create mode 100644 pintos-progos/tests/userprog/write-boundary.c create mode 100644 pintos-progos/tests/userprog/write-boundary.ck create mode 100644 pintos-progos/tests/userprog/write-normal.c create mode 100644 pintos-progos/tests/userprog/write-normal.ck create mode 100644 pintos-progos/tests/userprog/write-stdin.c create mode 100644 pintos-progos/tests/userprog/write-stdin.ck create mode 100644 pintos-progos/tests/userprog/write-zero.c create mode 100644 pintos-progos/tests/userprog/write-zero.ck create mode 100644 pintos-progos/tests/vm/Grading create mode 100644 pintos-progos/tests/vm/Make.tests create mode 100644 pintos-progos/tests/vm/Rubric.functionality create mode 100644 pintos-progos/tests/vm/Rubric.paging create mode 100644 pintos-progos/tests/vm/Rubric.robustness create mode 100644 pintos-progos/tests/vm/child-inherit.c create mode 100644 pintos-progos/tests/vm/child-linear.c create mode 100644 pintos-progos/tests/vm/child-mm-wrt.c create mode 100644 pintos-progos/tests/vm/child-qsort-mm.c create mode 100644 pintos-progos/tests/vm/child-qsort.c create mode 100644 pintos-progos/tests/vm/child-sort.c create mode 100644 pintos-progos/tests/vm/mmap-bad-fd.c create mode 100644 pintos-progos/tests/vm/mmap-bad-fd.ck create mode 100644 pintos-progos/tests/vm/mmap-clean.c create mode 100644 pintos-progos/tests/vm/mmap-clean.ck create mode 100644 pintos-progos/tests/vm/mmap-close.c create mode 100644 pintos-progos/tests/vm/mmap-close.ck create mode 100644 pintos-progos/tests/vm/mmap-exit.c create mode 100644 pintos-progos/tests/vm/mmap-exit.ck create mode 100644 pintos-progos/tests/vm/mmap-inherit.c create mode 100644 pintos-progos/tests/vm/mmap-inherit.ck create mode 100644 pintos-progos/tests/vm/mmap-lazy-seq.c create mode 100644 pintos-progos/tests/vm/mmap-lazy-seq.ck create mode 100644 pintos-progos/tests/vm/mmap-misalign.c create mode 100644 pintos-progos/tests/vm/mmap-misalign.ck create mode 100644 pintos-progos/tests/vm/mmap-null.c create mode 100644 pintos-progos/tests/vm/mmap-null.ck create mode 100644 pintos-progos/tests/vm/mmap-over-code.c create mode 100644 pintos-progos/tests/vm/mmap-over-code.ck create mode 100644 pintos-progos/tests/vm/mmap-over-data.c create mode 100644 pintos-progos/tests/vm/mmap-over-data.ck create mode 100644 pintos-progos/tests/vm/mmap-over-stk.c create mode 100644 pintos-progos/tests/vm/mmap-over-stk.ck create mode 100644 pintos-progos/tests/vm/mmap-overlap.c create mode 100644 pintos-progos/tests/vm/mmap-overlap.ck create mode 100644 pintos-progos/tests/vm/mmap-read.c create mode 100644 pintos-progos/tests/vm/mmap-read.ck create mode 100644 pintos-progos/tests/vm/mmap-remove.c create mode 100644 pintos-progos/tests/vm/mmap-remove.ck create mode 100644 pintos-progos/tests/vm/mmap-shuffle.c create mode 100644 pintos-progos/tests/vm/mmap-shuffle.ck create mode 100644 pintos-progos/tests/vm/mmap-twice.c create mode 100644 pintos-progos/tests/vm/mmap-twice.ck create mode 100644 pintos-progos/tests/vm/mmap-unmap.c create mode 100644 pintos-progos/tests/vm/mmap-unmap.ck create mode 100644 pintos-progos/tests/vm/mmap-write.c create mode 100644 pintos-progos/tests/vm/mmap-write.ck create mode 100644 pintos-progos/tests/vm/mmap-zero.c create mode 100644 pintos-progos/tests/vm/mmap-zero.ck create mode 100644 pintos-progos/tests/vm/page-linear.c create mode 100644 pintos-progos/tests/vm/page-linear.ck create mode 100644 pintos-progos/tests/vm/page-merge-mm.c create mode 100644 pintos-progos/tests/vm/page-merge-mm.ck create mode 100644 pintos-progos/tests/vm/page-merge-par.c create mode 100644 pintos-progos/tests/vm/page-merge-par.ck create mode 100644 pintos-progos/tests/vm/page-merge-seq.c create mode 100644 pintos-progos/tests/vm/page-merge-seq.ck create mode 100644 pintos-progos/tests/vm/page-merge-stk.c create mode 100644 pintos-progos/tests/vm/page-merge-stk.ck create mode 100644 pintos-progos/tests/vm/page-parallel.c create mode 100644 pintos-progos/tests/vm/page-parallel.ck create mode 100644 pintos-progos/tests/vm/page-shuffle.c create mode 100644 pintos-progos/tests/vm/page-shuffle.ck create mode 100644 pintos-progos/tests/vm/parallel-merge.c create mode 100644 pintos-progos/tests/vm/parallel-merge.h create mode 100644 pintos-progos/tests/vm/process_death.pm create mode 100644 pintos-progos/tests/vm/pt-bad-addr.c create mode 100644 pintos-progos/tests/vm/pt-bad-addr.ck create mode 100644 pintos-progos/tests/vm/pt-bad-read.c create mode 100644 pintos-progos/tests/vm/pt-bad-read.ck create mode 100644 pintos-progos/tests/vm/pt-big-stk-obj.c create mode 100644 pintos-progos/tests/vm/pt-big-stk-obj.ck create mode 100644 pintos-progos/tests/vm/pt-grow-bad.c create mode 100644 pintos-progos/tests/vm/pt-grow-bad.ck create mode 100644 pintos-progos/tests/vm/pt-grow-pusha.c create mode 100644 pintos-progos/tests/vm/pt-grow-pusha.ck create mode 100644 pintos-progos/tests/vm/pt-grow-stack.c create mode 100644 pintos-progos/tests/vm/pt-grow-stack.ck create mode 100644 pintos-progos/tests/vm/pt-grow-stk-sc.c create mode 100644 pintos-progos/tests/vm/pt-grow-stk-sc.ck create mode 100644 pintos-progos/tests/vm/pt-write-code-2.c create mode 100644 pintos-progos/tests/vm/pt-write-code.c create mode 100644 pintos-progos/tests/vm/pt-write-code.ck create mode 100644 pintos-progos/tests/vm/pt-write-code2.ck create mode 100644 pintos-progos/tests/vm/qsort.c create mode 100644 pintos-progos/tests/vm/qsort.h create mode 100644 pintos-progos/tests/vm/sample.inc create mode 100644 pintos-progos/tests/vm/sample.txt create mode 100644 pintos-progos/threads/.gitignore create mode 100644 pintos-progos/threads/Make.vars create mode 100644 pintos-progos/threads/Makefile create mode 100644 pintos-progos/threads/flags.h create mode 100644 pintos-progos/threads/init.c create mode 100644 pintos-progos/threads/init.h create mode 100644 pintos-progos/threads/interrupt.c create mode 100644 pintos-progos/threads/interrupt.h create mode 100644 pintos-progos/threads/intr-stubs.S create mode 100644 pintos-progos/threads/intr-stubs.h create mode 100644 pintos-progos/threads/io.h create mode 100644 pintos-progos/threads/kernel.lds.S create mode 100644 pintos-progos/threads/loader.S create mode 100644 pintos-progos/threads/loader.h create mode 100644 pintos-progos/threads/malloc.c create mode 100644 pintos-progos/threads/malloc.h create mode 100644 pintos-progos/threads/palloc.c create mode 100644 pintos-progos/threads/palloc.h create mode 100644 pintos-progos/threads/pte.h create mode 100644 pintos-progos/threads/start.S create mode 100644 pintos-progos/threads/switch.S create mode 100644 pintos-progos/threads/switch.h create mode 100644 pintos-progos/threads/synch.c create mode 100644 pintos-progos/threads/synch.h create mode 100644 pintos-progos/threads/thread.c create mode 100644 pintos-progos/threads/thread.h create mode 100644 pintos-progos/threads/vaddr.h create mode 100644 pintos-progos/userprog/.gitignore create mode 100644 pintos-progos/userprog/Make.vars create mode 100644 pintos-progos/userprog/Makefile create mode 100644 pintos-progos/userprog/exception.c create mode 100644 pintos-progos/userprog/exception.h create mode 100644 pintos-progos/userprog/gdt.c create mode 100644 pintos-progos/userprog/gdt.h create mode 100644 pintos-progos/userprog/pagedir.c create mode 100644 pintos-progos/userprog/pagedir.h create mode 100644 pintos-progos/userprog/process.c create mode 100644 pintos-progos/userprog/process.h create mode 100644 pintos-progos/userprog/syscall.c create mode 100644 pintos-progos/userprog/syscall.h create mode 100644 pintos-progos/userprog/tss.c create mode 100644 pintos-progos/userprog/tss.h create mode 100644 pintos-progos/utils/.gitignore create mode 100644 pintos-progos/utils/Makefile create mode 100644 pintos-progos/utils/Pintos.pm create mode 100755 pintos-progos/utils/backtrace create mode 100755 pintos-progos/utils/pintos create mode 100755 pintos-progos/utils/pintos-gdb create mode 100755 pintos-progos/utils/pintos-mkdisk create mode 100644 pintos-progos/utils/pintos-set-cmdline create mode 100644 pintos-progos/utils/setitimer-helper.c create mode 100644 pintos-progos/utils/squish-pty.c create mode 100644 pintos-progos/utils/squish-unix.c create mode 100644 pintos-progos/vm/.gitignore create mode 100644 pintos-progos/vm/Make.vars create mode 100644 pintos-progos/vm/Makefile create mode 100644 proj0.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ab34c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +cscope.files +cscope.out +TAGS +tags diff --git a/doc/pintos.css b/doc/pintos.css new file mode 100644 index 0000000..019a33d --- /dev/null +++ b/doc/pintos.css @@ -0,0 +1,76 @@ +body { + background: white; + color: black; + padding: 0em 1em 0em 3em; + margin: 0; + margin-left: auto; + margin-right: auto; + max-width: 8in; + text-align: justify +} +body>p { + margin: 0pt 0pt 0pt 0em; + text-align: justify +} +body>p + p { + margin: .75em 0pt 0pt 0pt +} +H1 { + font-size: 150%; + margin-left: -1.33em +} +H2 { + font-size: 125%; + font-weight: bold; + margin-left: -.8em +} +H3 { + font-size: 100%; + font-weight: bold; + margin-left: -.5em } +H4 { + font-size: 100%; + margin-left: 0em +} +H1, H2, H3, H4, H5, H6 { + font-family: sans-serif; + color: blue +} +H1, H2 { + text-decoration: underline +} +html { + margin: 0; + font-weight: lighter +} +tt, code { + font-family: sans-serif +} +b, strong { + font-weight: bold +} + +a:link { + color: blue; + text-decoration: none; +} +a:visited { + color: gray; + text-decoration: none; +} +a:active { + color: black; + text-decoration: none; +} +a:hover { + text-decoration: underline +} + +address { + font-size: 90%; + font-style: normal +} + +HR { + display: none +} diff --git a/doc/pintos.html b/doc/pintos.html new file mode 100644 index 0000000..8060c46 --- /dev/null +++ b/doc/pintos.html @@ -0,0 +1,342 @@ + + + + + +Pintos Projects: Table of Contents + + + + + + + + + + + + + + + + + +
[Top][Contents][Index][ ? ]
+

Table of Contents

+
+1. Introduction +
+
+1.1 Getting Started +
+
+1.1.1 Source Tree Overview +
+1.1.2 Building Pintos +
+1.1.3 Running Pintos +
+1.1.4 Debugging versus Testing +
+
+1.2 Grading +
+
+1.2.1 Testing +
+1.2.2 Design +
+
+1.2.2.1 Design Document +
+1.2.2.2 Source Code +
+
+
+1.3 Legal and Ethical Issues +
+1.4 Acknowledgements +
+1.5 Trivia +
+
+2. Project 0: Introducing Pintos +
+
+2.1 Understanding Threads +
+
+2.1.1 Source Files +
+
+2.1.1.1 devices code +
+2.1.1.2 lib files +
+
+2.1.2 Synchronization +
+2.1.3 Development Suggestions +
+
+2.2 Understanding User Programs +
+
+2.2.1 Source Files +
+2.2.2 Using the File System +
+2.2.3 How User Programs Work +
+2.2.4 Virtual Memory Layout +
+
+2.2.4.1 Typical Memory Layout +
+
+2.2.5 Accessing User Memory +
+
+2.3 Requirements +
+
+2.3.1 Design Document +
+2.3.2 Alarm Clock +
+2.3.3 Argument Passing +
+
+2.4 FAQ +
+
+2.4.1 Threads FAQ +
+2.4.2 Alarm Clock FAQ +
+2.4.3 Userprog FAQ +
+2.4.4 Argument Passing FAQ +
+
+2.5 80x86 Calling Convention +
+
+2.5.1 Program Startup Details +
+2.5.2 System Call Details +
+
+
+3. Project 1: Threads +
+
+3.1 Background +
+3.2 Requirements +
+
+3.2.1 Design Document +
+3.2.2 Priority Scheduling +
+
+3.3 FAQ +
+
+4. Project 2: Virtual Memory +
+A. Reference Guide +
+
+A.1 Loading +
+
+A.1.1 The Loader +
+A.1.2 Low-Level Kernel Initialization +
+A.1.3 High-Level Kernel Initialization +
+A.1.4 Physical Memory Map +
+
+A.2 Threads +
+
+A.2.1 struct thread +
+A.2.2 Thread Functions +
+A.2.3 Thread Switching +
+
+A.3 Synchronization +
+
+A.3.1 Disabling Interrupts +
+A.3.2 Semaphores +
+A.3.3 Locks +
+A.3.4 Monitors +
+
+A.3.4.1 Monitor Example +
+
+A.3.5 Optimization Barriers +
+
+A.4 Interrupt Handling +
+
+A.4.1 Interrupt Infrastructure +
+A.4.2 Internal Interrupt Handling +
+A.4.3 External Interrupt Handling +
+
+A.5 Memory Allocation +
+
+A.5.1 Page Allocator +
+A.5.2 Block Allocator +
+
+A.6 Virtual Addresses +
+A.7 Page Table +
+
+A.7.1 Creation, Destruction, and Activation +
+A.7.2 Inspection and Updates +
+A.7.3 Accessed and Dirty Bits +
+A.7.4 Page Table Details +
+
+A.7.4.1 Structure +
+A.7.4.2 Page Table Entry Format +
+A.7.4.3 Page Directory Entry Format +
+
+
+A.8 Hash Table +
+
+A.8.1 Data Types +
+A.8.2 Basic Functions +
+A.8.3 Search Functions +
+A.8.4 Iteration Functions +
+A.8.5 Hash Table Example +
+A.8.6 Auxiliary Data +
+A.8.7 Synchronization +
+
+
+B. Coding Standards +
+
+B.1 Style +
+B.2 C99 +
+B.3 Unsafe String Functions +
+
+C. Project Documentation +
+
+C.1 Sample Assignment +
+C.2 Sample Design Document +
+
+D. Debugging Tools +
+
+D.1 printf() +
+D.2 ASSERT +
+D.3 Function and Parameter Attributes +
+D.4 Backtraces +
+
+D.4.1 Example +
+
+D.5 GDB +
+
+D.5.1 Using GDB +
+D.5.2 Example GDB Session +
+D.5.3 FAQ +
+
+D.6 Triple Faults +
+D.7 Modifying Bochs +
+D.8 Tips +
+
+E. Development Tools +
+
+E.1 Tags +
+E.2 cscope +
+E.3 git +
+
+Bibliography +
+
+E.4 Hardware References +
+E.5 Software References +
+E.6 Operating System Design References +
+
+License +
+
+
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos.pdf b/doc/pintos.pdf new file mode 100644 index 0000000..3549982 Binary files /dev/null and b/doc/pintos.pdf differ diff --git a/doc/pintos_1.html b/doc/pintos_1.html new file mode 100644 index 0000000..e921154 --- /dev/null +++ b/doc/pintos_1.html @@ -0,0 +1,788 @@ + + + + + +Pintos Projects: Introduction + + + + + + + + + + + + + + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+ +
+ +

1. Introduction

+ +Welcome to the Operating System Development course at Vienna. In this +course, you will be working with an adapted version of the Pintos +operating system, which was written by Ben Pfaff (See section 1.4 Acknowledgements.) +

+ +So ... Welcome to Pintos. Pintos is a simple operating system framework for +the 80x86 architecture. It supports kernel threads, loading and +running user programs, and a file system, but it implements all of +these in a very simple way. In the Pintos projects, you and your +project team will strengthen its support in some of these areas. +

+

+ +Pintos could, theoretically, run on a regular IBM-compatible PC. +For simplicity, we will run Pintos projects in a system simulator, that is, +a program that simulates an 80x86 CPU and its peripheral devices accurately +enough that unmodified operating systems and software can run under it. +In class we will use the +Bochs and +QEMU simulators. Pintos has also been tested with +VMware Player. +

+

+ +The course at the University of Stanford, where pintos originated, has a +reputation of taking a lot of time -- we suppose deservedly so. +We will do what we can to reduce the workload, such as providing a lot +of support material, but there is plenty of hard work that needs to be done. +We welcome your feedback. If you have suggestions on how we can reduce the +unnecessary overhead of assignments, cutting them down to the important +underlying issues, please let us know. +

+

+ +This chapter explains how to get started working with Pintos. You +should read the entire chapter before you start work on any of the +projects. +

+

+ + +


+ +

1.1 Getting Started

+ +

+ +To get started, you'll have to log into a machine that Pintos can be +built on. There are two possibilities: Either you work on one of the +machines in the TILAB (ssh.tilab.tuwien.ac.at), or you use +a virtual machine such as KVM, VMWare Player or VirtualBox. You may +also setup your own machine to build pintos, but in this case we +cannot provide support for problems you might encounter. +We will test your submission in the TILAB environment, and thus you should +ensure that your code works there. +

+

+ +Once you've logged into one of these machines, either locally or +remotely, start out by extracting the pintos tarball, and adding +our binaries directory to your PATH environment variable. +

+

+ +Assuming that you extracted the pintos tarball to $HOME/pintos, +you need to add pintos' utils directory to your PATH +environment variable. +
 
PATH=$HOME/pintos/src/utils:$PATH
+
It is a good idea to add this line to your $HOME/.bash_profile +startup script (or an equivalent script, if you do not happen to use bash). +Otherwise, you'll have to type it every time you log in. +

+ + +


+ +

1.1.1 Source Tree Overview

+ +

+ +Here's the directory structure that you should see in pintos/src: +

+

+ +

+
+ +
intro/ +
This directory is used to build the system and run tests for project 0. +It only contains a Makefile, which describes how to build the system +and run tests. +

+ +

+
threads/ +
Source code for the base kernel, which is the focus of project 1. +

+ +

+
userprog/ +
Source code for the user programs. You will complete the user program +loader in project 0, and rely on the code in this directory in +project 2. +

+ +

+
vm/ +
An almost empty directory. You will implement virtual memory here in +project 2. +

+ +

+
filesys/ +
Source code for a basic file system. You will use this file system, +but do not need to modify it in this course. +

+ +

+
devices/ +
Source code for I/O device interfacing: keyboard, timer, disk, etc. +You will modify the timer implementation in project 0. Otherwise +you should have no need to change this code. +

+ +

+
lib/ +
An implementation of a subset of the standard C library. The code in +this directory is compiled into both the Pintos kernel and user +programs that run under it. In both kernel code +and user programs, headers in this directory can be included using the +#include <...> notation. You should have little need to +modify this code. +

+ +

+
lib/kernel/ +
Parts of the C library that are included only in the Pintos kernel. +This also includes implementations of some data types that you are +free to use in your kernel code: bitmaps, doubly linked lists, and +hash tables. In the kernel, headers in this +directory can be included using the #include <...> +notation. +

+ +

+
lib/user/ +
Parts of the C library that are included only in Pintos user programs. +In user programs, headers in this directory can be included using the +#include <...> notation. +

+ +

+
tests/ +
Tests for each project. You can modify this code if it helps you test +your submission, but we will replace it with the originals before we run +the tests. +

+ +

+
examples/ +
Example user programs for use in project 0, and also project 2. +

+ +

+
misc/ +
utils/ +
These files may come in handy if you decide to try working with Pintos +on your own machine. Otherwise, you can ignore them. +
+

+ + +


+ +

1.1.2 Building Pintos

+ +

+ +As the next step, build the source code supplied for +the first project. First, cd into the intro +directory. Then, issue the make command. This will create a +build directory under intro, populate it with a +Makefile and a few subdirectories, and then build the kernel +inside. The entire build should take less than 30 seconds. +

+

+ +Following the build, the following are the interesting files in the +build directory: +

+

+ +

+
+
Makefile +
A copy of pintos/src/Makefile.build. It describes how to build +the kernel. See Adding Source Files, for more information. +

+ +

+
kernel.o +
Object file for the entire kernel. This is the result of linking +object files compiled from each individual kernel source file into a +single object file. It contains debug information, so you can run +GDB (see section D.5 GDB) or backtrace (see section D.4 Backtraces) on it. +

+ +

+
kernel.bin +
Memory image of the kernel, that is, the exact bytes loaded into +memory to run the Pintos kernel. This is just kernel.o with +debug information stripped out, which saves a lot of space, which in +turn keeps the kernel from bumping up against a 512 kB size limit +imposed by the kernel loader's design. +

+ +

+
loader.bin +
Memory image for the kernel loader, a small chunk of code written in +assembly language that reads the kernel from disk into memory and +starts it up. It is exactly 512 bytes long, a size fixed by the +PC BIOS. +
+

+ +Subdirectories of build contain object files (.o) and +dependency files (.d), both produced by the compiler. The +dependency files tell make which source files need to be +recompiled when other source or header files are changed. +

+

+ + +


+ +

1.1.3 Running Pintos

+ +

+ +We've supplied a program for conveniently running Pintos in a simulator, +called pintos. In the simplest case, you can invoke +pintos as pintos argument.... Each +argument is passed to the Pintos kernel for it to act on. +

+

+ +Try it out. First cd into the newly created build +directory. Then issue the command +pintos -kernel-test run alarm-multiple, +which passes the arguments run alarm-multiple to the Pintos +kernel. In these arguments, run, together with the kernel +option -kernel-test, instructs the kernel to run a test and +alarm-multiple is the test to run. +

+

+ +This command creates a bochsrc.txt file, which is needed for +running Bochs, and then invoke Bochs. Bochs opens a new window that +represents the simulated machine's display, and a BIOS message briefly +flashes. Then Pintos boots and runs the alarm-multiple test +program, which outputs a few screenfuls of text. When it's done, you +can close Bochs by clicking on the "Power" button in the window's top +right corner, or rerun the whole process by clicking on the "Reset" +button just to its left. The other buttons are not very useful for our +purposes. +

+

+ +(If no window appeared at all, then you're probably logged in remotely and X +forwarding is not set up correctly. In this case, you can fix your X +setup, or you can use the -v option to disable X output: +pintos -v -- -kernel-test run alarm-multiple.) +

+

+ +The text printed by Pintos inside Bochs probably went by too quickly to +read. However, you've probably noticed by now that the same text was +displayed in the terminal you used to run pintos. This is +because Pintos sends all output both to the VGA display and to the first +serial port, and by default the serial port is connected to Bochs's +stdin and stdout. You can log serial output to a file by +redirecting at the +command line, e.g. pintos run alarm-multiple > logfile. +

+

+ +The pintos program offers several options for configuring the +simulator or the virtual hardware. If you specify any options, they +must precede the commands passed to the Pintos kernel and be separated +from them by --, so that the whole command looks like +pintos option... -- argument.... Invoke +pintos without any arguments to see a list of available options. +Options can select a simulator to use: the default is Bochs, but +--qemu selects QEMU. You can run the simulator +with a debugger (see section D.5 GDB). You can set the amount of memory to give +the VM. Finally, you can select how you want VM output to be displayed: +use -v to turn off the VGA display, -t to use your +terminal window as the VGA display instead of opening a new window +(Bochs only), or -s to suppress serial input from stdin +and output to stdout. +

+

+ +The Pintos kernel has commands and options other than run. +These are not very interesting for now, but you can see a list of them +using -h, e.g. pintos -h. +

+

+ + +


+ +

1.1.4 Debugging versus Testing

+ +

+ +When you're debugging code, it's useful to be able to run a +program twice and have it do exactly the same thing. On second and +later runs, you can make new observations without having to discard or +verify your old observations. This property is called +"reproducibility." One of the simulators that Pintos supports, Bochs, +can be set up for +reproducibility, and that's the way that pintos invokes it +by default. +

+

+ +Of course, a simulation can only be reproducible from one run to the +next if its input is the same each time. For simulating an entire +computer, as we do, this means that every part of the computer must be +the same. For example, you must use the same command-line argument, the +same disks, the same version +of Bochs, and you must not hit any keys on the keyboard (because you +could not be sure to hit them at exactly the same point each time) +during the runs. +

+

+ +While reproducibility is useful for debugging, it is a problem for +testing thread synchronization, an important part of most of the projects. In +particular, when Bochs is set up for reproducibility, timer interrupts +will come at perfectly reproducible points, and therefore so will +thread switches. That means that running the same test several times +doesn't give you any greater confidence in your code's correctness +than does running it only once. +

+

+ +So, to make your code easier to test, we've added a feature, called +"jitter," to Bochs, that makes timer interrupts come at random +intervals, but in a perfectly predictable way. In particular, if you +invoke pintos with the option -j seed, timer +interrupts will come at irregularly spaced intervals. Within a single +seed value, execution will still be reproducible, but timer +behavior will change as seed is varied. Thus, for the highest +degree of confidence you should test your code with many seed values. +

+

+ +On the other hand, when Bochs runs in reproducible mode, timings are not +realistic, meaning that a "one-second" delay may be much shorter or +even much longer than one second. You can invoke pintos with +a different option, -r, to set up Bochs for realistic +timings, in which a one-second delay should take approximately one +second of real time. Simulation in real-time mode is not reproducible, +and options -j and -r are mutually exclusive. +

+

+ +The QEMU simulator is available as an +alternative to Bochs (use --qemu when invoking +pintos). The QEMU simulator is much faster than Bochs, but it +only supports real-time simulation and does not have a reproducible +mode. +

+

+ + +


+ +

1.2 Grading

+ +

+ +We will grade your assignments based on test results and design quality, +inspecting both your implementation and your design documents. +

+

+ + +


+ +

1.2.1 Testing

+ +

+ +Your test result grade will be based on our tests. Each project has +several tests, each of which has a name beginning with tests. +To completely test your submission, invoke make check from the +project build directory. This will build and run each test and +print a "pass" or "fail" message for each one. When a test fails, +make check also prints some details of the reason for failure. +After running all the tests, make check also prints a summary +of the test results. +

+

+ +For project 1, the tests will probably run faster in Bochs. For the +other projects, they will run much faster in QEMU. +make check will select the faster simulator by default, but +you can override its choice by specifying SIMULATOR=--bochs or +SIMULATOR=--qemu on the make command line. +

+

+ +You can also run individual tests one at a time. A given test t +writes its output to t.output, then a script scores the +output as "pass" or "fail" and writes the verdict to +t.result. To run and grade a single test, make +the .result file explicitly from the build directory, e.g. +make tests/threads/alarm-multiple.result. If make says +that the test result is up-to-date, but you want to re-run it anyway, +either run make clean or delete the .output file by hand. +

+

+ +By default, each test provides feedback only at completion, not during +its run. If you prefer, you can observe the progress of each test by +specifying VERBOSE=1 on the make command line, as in +make check VERBOSE=1. You can also provide arbitrary options to the +pintos run by the tests with PINTOSOPTS='...', +e.g. make check PINTOSOPTS='-j 1' to select a jitter value of 1 +(see section 1.1.4 Debugging versus Testing). +

+

+ +All of the tests and related files are in pintos/src/tests. +Before we test your submission, we will replace the contents of that +directory by a pristine, unmodified copy, to ensure that the correct +tests are used. Thus, you can modify some of the tests if that helps in +debugging, but we will run the originals. +

+

+ +All software has bugs, so some of our tests may be flawed. If you think +a test failure is a bug in the test, not a bug in your code, +please point it out. We will look at it and fix it if necessary. +

+

+ +Please don't try to take advantage of our generosity in giving out our +test suite. Your code has to work properly in the general case, not +just for the test cases we supply. For example, it would be unacceptable +to explicitly base the kernel's behavior on the name of the running +test case. Such attempts to side-step the test cases will receive no +credit. If you think your solution may be in a gray area here, please +ask us about it. +

+

+ + +


+ +

1.2.2 Design

+ +

+ +We will judge your design based on the design document and the source +code that you submit. We will read your entire design document and much +of your source code. +

+

+ +Don't forget that design quality, including the design document, is 30% +of your project grade. It +is better to spend one or two hours writing a good design document than +it is to spend that time getting the last 5% of the points for tests and +then trying to rush through writing the design document in the last 15 +minutes. +

+

+ + +


+ +

1.2.2.1 Design Document

+ +

+ +We provide a design document template for each project. For each +significant part of a project, the template asks questions in four +areas: +

+

+ +

+
+
Data Structures +

+ +The instructions for this section are always the same: +

+

+ +

+Copy here the declaration of each new or changed struct or +struct member, global or static variable, typedef, or +enumeration. Identify the purpose of each in 25 words or less. +
+

+ +The first part is mechanical. Just copy new or modified declarations +into the design document, to highlight for us the actual changes to data +structures. Each declaration should include the comment that should +accompany it in the source code (see below). +

+

+ +We also ask for a very brief description of the purpose of each new or +changed data structure. The limit of 25 words or less is a guideline +intended to save your time and avoid duplication with later areas. +

+

+ +

+
Algorithms +

+ +This is where you tell us how your code works, through questions that +probe your understanding of your code. We might not be able to easily +figure it out from the code, because many creative solutions exist for +most OS problems. Help us out a little. +

+

+ +Your answers should be at a level below the high level description of +requirements given in the assignment. We have read the assignment too, +so it is unnecessary to repeat or rephrase what is stated there. On the +other hand, your answers should be at a level above the low level of the +code itself. Don't give a line-by-line run-down of what your code does. +Instead, use your answers to explain how your code works to implement +the requirements. +

+

+ +

+
Synchronization +

+ +An operating system kernel is a complex, multithreaded program, in which +synchronizing multiple threads can be difficult. This section asks +about how you chose to synchronize this particular type of activity. +

+

+ +

+
Rationale +

+ +Whereas the other sections primarily ask "what" and "how," the +rationale section concentrates on "why." This is where we ask you to +justify some design decisions, by explaining why the choices you made +are better than alternatives. You may be able to state these in terms +of time and space complexity, which can be made as rough or informal +arguments (formal language or proofs are unnecessary). +

+

+ +An incomplete, evasive, or non-responsive design document or one that +strays from the template without good reason may be penalized. +Incorrect capitalization, punctuation, spelling, or grammar can also +cost points. See section C. Project Documentation, for a sample design document +for a fictitious project. +

+

+ + +


+ +

1.2.2.2 Source Code

+ +

+ +Your design will also be judged by looking at your source code. We will +typically look at the differences between the original Pintos source +tree and your submission, based on the output of a command like +diff -urpb pintos.orig pintos.submitted. We will try to match up your +description of the design with the code submitted. Important +discrepancies between the description and the actual code will be +penalized, as will be any bugs we find by spot checks. +

+

+ +The most important aspects of source code design are those that specifically +relate to the operating system issues at stake in the project. For +example, the organization of the supplemental page table is an important +part of virtual memory design, so in the virtual memory project a poorly designed +pagetable would lose points. Other issues are much less important. For +example, multiple Pintos design problems call for a "priority +queue," that is, a dynamic collection from which the minimum (or +maximum) item can quickly be extracted. Fast priority queues can be +implemented many ways, but we do not expect you to build a fancy data +structure even if it might improve performance. Instead, you are +welcome to use a linked list (and Pintos even provides one with +convenient functions for sorting and finding minimums and maximums). +

+

+ +Pintos is written in a consistent style. Make your additions and +modifications in existing Pintos source files blend in, not stick out. +In new source files, adopt the existing Pintos style by preference, but +make your code self-consistent at the very least. There should not be a +patchwork of different styles that makes it obvious that three different +people wrote the code. Use horizontal and vertical white space to make +code readable. Add a brief comment on every structure, structure +member, global or static variable, typedef, enumeration, and function +definition. Update +existing comments as you modify code. Don't comment out or use the +preprocessor to ignore blocks of code (instead, remove it entirely). +Use assertions to document key invariants. Decompose code into +functions for clarity. Code that is difficult to understand because it +violates these or other "common sense" software engineering practices +will be penalized. +

+

+ +In the end, remember your audience. Code is written primarily to be +read by humans. It has to be acceptable to the compiler too, but the +compiler doesn't care about how it looks or how well it is written. +

+

+ + +


+ +

1.3 Legal and Ethical Issues

+ +

+ +Pintos is distributed under a liberal license that allows free use, +modification, and distribution. Students and others who work on Pintos +own the code that they write and may use it for any purpose. +Pintos comes with NO WARRANTY, not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. +See section License, for details of the license and lack of warranty. +

+

+ +In the context of the Operating System Development at Vienna University +of Technology, please respect the spirit and the letter of the honor code +by refraining from reading any homework solutions available online or +elsewhere. Reading the source code for other operating system kernels, +such as Linux or FreeBSD, is allowed, but do not copy code from them literally. +Please cite the code that inspired your own in your design documentation. +Additionally, please do not redistribute the modified Pintos environment +used in this course. It contains partial solutions which might spoil the +fun for people at other universities. +

+

+ + +


+ +

1.4 Acknowledgements

+ +The Pintos core and this documentation were originally written by Ben +Pfaff blp@cs.stanford.edu. +

+ +Additional features were contributed by Anthony Romano +chz@vt.edu. +

+

+ +The GDB macros supplied with Pintos were written by Godmar Back +gback@cs.vt.edu, and their documentation is adapted from his +work. +

+

+ +The original structure and form of Pintos was inspired by the Nachos +instructional operating system from the University of California, +Berkeley ([ Christopher]). +

+

+ +The Pintos projects and documentation originated with those designed for +Nachos by current and former CS 140 teaching assistants at Stanford +University, including at least Yu Ping, Greg Hutchins, Kelly Shaw, Paul +Twohey, Sameer Qureshi, and John Rector. +

+

+ +Example code for monitors (see section A.3.4 Monitors) is +from classroom slides originally by Dawson Engler and updated by Mendel +Rosenblum. +

+

+ +For the undergraduate OS Development course at Vienna UT, Rene Freingruber +renefreing@yahoo.de evaluated Pintos, and provided information on +expected work hours and typical pitfalls. Benedikt Huber +benedikt@vmars.tuwien.ac.at adapted Pintos and its documentation to +meet the requirements of the course; Roland Kammerer +kammerer@vmars.tuwien.ac.at created the virtual machine environments +to simplify working outside the lab. +

+

+ + +


+ +

1.5 Trivia

+ +

+ +Pintos originated as a replacement for Nachos with a similar design. +Since then Pintos has greatly diverged from the Nachos design. Pintos +differs from Nachos in two important ways. First, Pintos runs on real +or simulated 80x86 hardware, but Nachos runs as a process on a +host operating system. Second, Pintos is written in C like most +real-world operating systems, but Nachos is written in C++. +

+

+ +Why the name "Pintos"? First, like nachos, pinto beans are a common +Mexican food. Second, Pintos is small and a "pint" is a small amount. +Third, like drivers of the eponymous car, students are likely to have +trouble with blow-ups. + +


+ + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_10.html b/doc/pintos_10.html new file mode 100644 index 0000000..8bd02bc --- /dev/null +++ b/doc/pintos_10.html @@ -0,0 +1,286 @@ + + + + + +Pintos Projects: Bibliography + + + + + + + + + + + + + + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+ +
+

Bibliography

+ +

+ + +


+ +

E.4 Hardware References

+ +

+ + +[IA32-v1]. +IA-32 Intel Architecture Software Developer's Manual Volume 1: Basic +Architecture. Basic 80x86 architecture and programming +environment. Available via developer.intel.com. Section numbers +in this document refer to revision 18. +

+

+ + +[IA32-v2a]. +IA-32 Intel Architecture Software Developer's Manual +Volume 2A: Instruction Set Reference A-M. 80x86 instructions +whose names begin with A through M. Available via +developer.intel.com. Section numbers in this document refer to +revision 18. +

+

+ + +[IA32-v2b]. +IA-32 Intel Architecture Software Developer's Manual Volume 2B: +Instruction Set Reference N-Z. 80x86 instructions whose names +begin with N through Z. Available via developer.intel.com. +Section numbers in this document refer to revision 18. +

+

+ + +[IA32-v3a]. +IA-32 Intel Architecture Software Developer's Manual Volume 3A: System +Programming Guide. Operating system support, including segmentation, +paging, tasks, interrupt and exception handling. Available via +developer.intel.com. Section numbers in this document refer to +revision 18. +

+

+ + +[FreeVGA]. +FreeVGA Project. Documents the VGA video +hardware used in PCs. +

+

+ + +[kbd]. +Keyboard scancodes. Documents PC keyboard +interface. +

+

+ + +[ATA-3]. +AT Attachment-3 Interface (ATA-3) Working +Draft. Draft of an old version of the ATA aka IDE interface for the +disks used in most desktop PCs. +

+

+ + +[PC16550D]. +National Semiconductor PC16550D Universal +Asynchronous Receiver/Transmitter with FIFOs. Datasheet for a chip +used for PC serial ports. +

+

+ + +[8254]. +Intel 8254 Programmable Interval Timer. +Datasheet for PC timer chip. +

+

+ + +[8259A]. +Intel 8259A Programmable Interrupt Controller +(8259A/8259A-2). Datasheet for PC interrupt controller chip. +

+

+ + +[MC146818A]. +Motorola MC146818A Real Time Clock Plus +Ram (RTC). Datasheet for PC real-time clock chip. +

+

+ + +


+ +

E.5 Software References

+ +

+ + +[ELF1]. +Tool Interface Standard (TIS) Executable and +Linking Format (ELF) Specification Version 1.2 Book I: Executable and +Linking Format. The ubiquitous format for executables in modern Unix +systems. +

+

+ + +[ELF2]. +Tool Interface Standard (TIS) Executable and +Linking Format (ELF) Specification Version 1.2 Book II: Processor +Specific (Intel Architecture). 80x86-specific parts of ELF. +

+

+ + +[ELF3]. +Tool Interface Standard (TIS) Executable and +Linking Format (ELF) Specification Version 1.2 Book III: Operating +System Specific (UNIX System V Release 4). Unix-specific parts of +ELF. +

+

+ + +[SysV-ABI]. +System V Application Binary Interface: +Edition 4.1. Specifies how applications interface with the OS under +Unix. +

+

+ + +[SysV-i386]. +System V Application Binary +Interface: Intel386 Architecture Processor Supplement: Fourth +Edition. 80x86-specific parts of the Unix interface. +

+

+ + +[SysV-ABI-update]. +System V Application Binary +Interface--DRAFT--24 April 2001. A draft of a revised version of +[ SysV-ABI] which was never completed. +

+

+ + +[SUSv3]. +The Open Group, Single UNIX Specification V3, 2001. +

+

+ + +[Partitions]. +A. E. Brouwer, Minimal partition table specification, 1999. +

+

+ + +[IntrList]. +R. Brown, Ralf Brown's +Interrupt List, 2000. +

+

+ + +


+ +

E.6 Operating System Design References

+ +

+ + +[Christopher]. +W. A. Christopher, S. J. Procter, T. E. Anderson, +The Nachos instructional operating system. +Proceedings of the USENIX Winter 1993 Conference. +http://portal.acm.org/citation.cfm?id=1267307. +

+

+ + +[Dijkstra]. +E. W. Dijkstra, The structure of the "THE" +multiprogramming system. Communications of the ACM 11(5):341--346, +1968. http://doi.acm.org/10.1145/363095.363143. +

+

+ + +[Hoare]. +C. A. R. Hoare, Monitors: An Operating System +Structuring Concept. Communications of the ACM, 17(10):549--557, +1974. http://www.acm.org/classics/feb96/. +

+

+ + +[Lampson]. +B. W. Lampson, D. D. Redell, Experience with processes and +monitors in Mesa. Communications of the ACM, 23(2):105--117, 1980. +http://doi.acm.org/10.1145/358818.358824. +

+

+ + +[McKusick]. +M. K. McKusick, K. Bostic, M. J. Karels, J. S. Quarterman, +The Design and Implementation of the 4.4BSD Operating +System. Addison-Wesley, 1996. +

+

+ + +[Wilson]. +P. R. Wilson, M. S. Johnstone, M. Neely, D. Boles, +Dynamic Storage Allocation: A Survey and Critical Review. +International Workshop on Memory Management, 1995. +http://www.cs.utexas.edu/users/oops/papers.html#allocsrv. + +


+ + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_11.html b/doc/pintos_11.html new file mode 100644 index 0000000..d34b895 --- /dev/null +++ b/doc/pintos_11.html @@ -0,0 +1,137 @@ + + + + + +Pintos Projects: License + + + + + + + + + + + + + + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+ +
+

License

+ +

+ +Pintos, including its documentation, is subject to the following +license: +

+

+ +

+Copyright © 2004, 2005, 2006 Board of Trustees, Leland +Stanford Jr. University. All rights reserved. +

+ +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: +

+

+ +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. +

+

+ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +

+

+ +A few individual files in Pintos were originally derived from other +projects, but they have been extensively modified for use in Pintos. +The original code falls under the original license, and modifications +for Pintos are additionally covered by the Pintos license above. +

+

+ +In particular, code derived from Nachos is subject to the following +license: +

+

+ +

+Copyright © 1992-1996 The Regents of the University of California. +All rights reserved. +

+ +Permission to use, copy, modify, and distribute this software +and its documentation for any purpose, without fee, and +without written agreement is hereby granted, provided that the +above copyright notice and the following two paragraphs appear +in all copies of this software. +

+

+ +IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO +ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE +AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA +HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +

+

+ +THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" +BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO +PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. +

+

+ +


+ + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_2.html b/doc/pintos_2.html new file mode 100644 index 0000000..ae51974 --- /dev/null +++ b/doc/pintos_2.html @@ -0,0 +1,1734 @@ + + + + + +Pintos Projects: Project 0--Introducing Pintos + + + + + + + + + + + + + + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+ +
+

2. Project 0: Introducing Pintos

+ +

+ +In this assignment, you will learn about the existing functionality +in Pintos, and add two small features to the system: a more efficient +implementation of sleep, and the ability to pass command line +arguments to user programs. +

+

+ +You will be working in the threads directory for the first part +of this assignment (with some work in the devices directory on +the side), and modify the file userprog/process.c in the second part. +

+

+ +The tests for Project 0 are executed by changing the working directory +to intro and running make followed by make check. +

+

+ +Before you read the description of this project, you should read all of +the following sections: 1. Introduction, B. Coding Standards, +D. Debugging Tools, and E. Development Tools. You should at least +skim the material from A.1 Loading through A.5 Memory Allocation, especially A.3 Synchronization. +

+

+ + +


+ +

2.1 Understanding Threads

+ +

+ +The first step is to read and understand the code for the initial thread +system. +Pintos already implements thread creation and thread completion, +a simple scheduler to switch between threads, and synchronization +primitives (semaphores, locks, condition variables, and optimization +barriers). +

+

+ +Some of this code might seem slightly mysterious. If +you haven't already compiled and run the base system, as described in +the introduction (see section 1. Introduction), you should do so now. You +can read through parts of the source code to see what's going +on. If you like, you can add calls to printf() almost +anywhere, then recompile and run to see what happens and in what +order. You can also run the kernel in a debugger and set breakpoints +at interesting spots, single-step through code and examine data, and +so on. +

+

+ +When a thread is created, you are creating a new context to be +scheduled. You provide a function to be run in this context as an +argument to thread_create(). The first time the thread is +scheduled and runs, it starts from the beginning of that function +and executes in that context. When the function returns, the thread +terminates. Each thread, therefore, acts like a mini-program running +inside Pintos, with the function passed to thread_create() +acting like main(). +

+

+ +At any given time, exactly one thread runs and the rest, if any, +become inactive. The scheduler decides which thread to +run next. (If no thread is ready to run +at any given time, then the special "idle" thread, implemented in +idle(), runs.) +Synchronization primitives can force context switches when one +thread needs to wait for another thread to do something. +

+

+ +The mechanics of a context switch are +in threads/switch.S, which is 80x86 +assembly code. (You don't have to understand it.) It saves the +state of the currently running thread and restores the state of the +thread we're switching to. +

+

+ +Using the GDB debugger, slowly trace through a context +switch to see what happens (see section D.5 GDB). You can set a +breakpoint on schedule() to start out, and then +single-step from there.(1) Be sure +to keep track of each thread's address +and state, and what procedures are on the call stack for each thread. +You will notice that when one thread calls switch_threads(), +another thread starts running, and the first thing the new thread does +is to return from switch_threads(). You will understand the thread +system once you understand why and how the switch_threads() that +gets called is different from the switch_threads() that returns. +See section A.2.3 Thread Switching, for more information. +

+

+ +Warning: In Pintos, each thread is assigned a small, +fixed-size execution stack just under 4 kB in size. The kernel +tries to detect stack overflow, but it cannot do so perfectly. You +may cause bizarre problems, such as mysterious kernel panics, if you +declare large data structures as non-static local variables, +e.g. int buf[1000];. Alternatives to stack allocation include +the page allocator and the block allocator (see section A.5 Memory Allocation). +

+

+ + +


+ +

2.1.1 Source Files

+ +

+ +Here is a brief overview of the files in the threads +directory. You will not need to modify most of this code, but the +hope is that presenting this overview will give you a start on what +code to look at. +

+

+ +

+
+
loader.S +
loader.h +
The kernel loader. Assembles to 512 bytes of code and data that the +PC BIOS loads into memory and which in turn finds the kernel on disk, +loads it into memory, and jumps to start() in start.S. +See section A.1.1 The Loader, for details. You should not need to look at +this code or modify it. +

+ +

+
start.S +
Does basic setup needed for memory protection and 32-bit +operation on 80x86 CPUs. Unlike the loader, this code is +actually part of the kernel. See section A.1.2 Low-Level Kernel Initialization, +for details. +

+ +

+
kernel.lds.S +
The linker script used to link the kernel. Sets the load address of +the kernel and arranges for start.S to be near the beginning +of the kernel image. See section A.1.1 The Loader, for details. Again, you +should not need to look at this code +or modify it, but it's here in case you're curious. +

+ +

+
init.c +
init.h +
Kernel initialization, including main(), the kernel's "main +program." You should look over main() at least to see what +gets initialized. You might want to add your own initialization code +here. See section A.1.3 High-Level Kernel Initialization, for details. +

+ +

+
thread.c +
thread.h +
Basic thread support. Much of your work will take place in these files. +thread.h defines struct thread, which you are likely to modify +in all three projects. See A.2.1 struct thread and A.2 Threads for +more information. +

+ +

+
switch.S +
switch.h +
Assembly language routine for switching threads. Already discussed +above. See section A.2.2 Thread Functions, for more information. +

+ +

+
palloc.c +
palloc.h +
Page allocator, which hands out system memory in multiples of 4 kB +pages. See section A.5.1 Page Allocator, for more information. +

+ +

+
malloc.c +
malloc.h +
A simple implementation of malloc() and free() for +the kernel. See section A.5.2 Block Allocator, for more information. +

+ +

+
interrupt.c +
interrupt.h +
Basic interrupt handling and functions for turning interrupts on and +off. See section A.4 Interrupt Handling, for more information. +

+ +

+
intr-stubs.S +
intr-stubs.h +
Assembly code for low-level interrupt handling. See section A.4.1 Interrupt Infrastructure, for more information. +

+ +

+
synch.c +
synch.h +
Basic synchronization primitives: semaphores, locks, condition +variables, and optimization barriers. You will need to use these for +synchronization in all +four projects. See section A.3 Synchronization, for more information. +

+ +

+
io.h +
Functions for I/O port access. This is mostly used by source code in +the devices directory that you won't have to touch. +

+ +

+
vaddr.h +
pte.h +
Functions and macros for working with virtual addresses and page table +entries. These will be more important to you in project 2. For now, +you can ignore them. +

+ +

+
flags.h +
Macros that define a few bits in the 80x86 "flags" register. +Probably of no interest. See [ IA32-v1], section 3.4.3, "EFLAGS +Register," for more information. +
+

+ + +


+ +

2.1.1.1 devices code

+ +

+ +The basic threaded kernel also includes these files in the +devices directory: +

+

+ +

+
+
timer.c +
timer.h +
System timer that ticks, by default, 100 times per second. You will +modify this code in this project. +

+ +

+
vga.c +
vga.h +
VGA display driver. Responsible for writing text to the screen. +You should have no need to look at this code. printf() +calls into the VGA display driver for you, so there's little reason to +call this code yourself. +

+ +

+
serial.c +
serial.h +
Serial port driver. Again, printf() calls this code for you, +so you don't need to do so yourself. +It handles serial input by passing it to the input layer (see below). +

+ +

+
block.c +
block.h +
An abstraction layer for block devices, that is, random-access, +disk-like devices that are organized as arrays of fixed-size blocks. +Out of the box, Pintos supports two types of block devices: IDE disks +and partitions. +

+ +

+
ide.c +
ide.h +
Supports reading and writing sectors on up to 4 IDE disks. +

+ +

+
partition.c +
partition.h +
Understands the structure of partitions on disks, allowing a single +disk to be carved up into multiple regions (partitions) for +independent use. +

+ +

+
kbd.c +
kbd.h +
Keyboard driver. Handles keystrokes passing them to the input layer +(see below). +

+ +

+
input.c +
input.h +
Input layer. Queues input characters passed along by the keyboard or +serial drivers. +

+ +

+
intq.c +
intq.h +
Interrupt queue, for managing a circular queue that both kernel +threads and interrupt handlers want to access. Used by the keyboard +and serial drivers. +

+ +

+
rtc.c +
rtc.h +
Real-time clock driver, to enable the kernel to determine the current +date and time. By default, this is only used by thread/init.c +to choose an initial seed for the random number generator. +

+ +

+
speaker.c +
speaker.h +
Driver that can produce tones on the PC speaker. +

+ +

+
pit.c +
pit.h +
Code to configure the 8254 Programmable Interrupt Timer. This code is +used by both devices/timer.c and devices/speaker.c +because each device uses one of the PIT's output channel. +
+

+ + +


+ +

2.1.1.2 lib files

+ +

+ +Finally, lib and lib/kernel contain useful library +routines. (lib/user will be used by user programs, starting in +project 2, but it is not part of the kernel.) Here's a few more +details: +

+

+ +

+
+
ctype.h +
inttypes.h +
limits.h +
stdarg.h +
stdbool.h +
stddef.h +
stdint.h +
stdio.c +
stdio.h +
stdlib.c +
stdlib.h +
string.c +
string.h +
A subset of the standard C library. See section B.2 C99, for +information +on a few recently introduced pieces of the C library that you might +not have encountered before. See section B.3 Unsafe String Functions, for +information on what's been intentionally left out for safety. +

+ +

+
debug.c +
debug.h +
Functions and macros to aid debugging. See section D. Debugging Tools, for +more information. +

+ +

+
random.c +
random.h +
Pseudo-random number generator. The actual sequence of random values +will not vary from one Pintos run to another, unless you do one of +three things: specify a new random seed value on the -rs +kernel command-line option on each run, or use a simulator other than +Bochs, or specify the -r option to pintos. +

+ +

+
round.h +
Macros for rounding. +

+ +

+
syscall-nr.h +
System call numbers. Not used until project 2. +

+ +

+
kernel/list.c +
kernel/list.h +
Doubly linked list implementation. Used all over the Pintos code, and +you'll probably want to use it a few places yourself. +

+ +

+
kernel/bitmap.c +
kernel/bitmap.h +
Bitmap implementation. You can use this in your code if you like, but +you probably won't have any need for it in project 0 and project 1. +

+ +

+
kernel/hash.c +
kernel/hash.h +
Hash table implementation. +

+ +

+
kernel/console.c +
kernel/console.h +
kernel/stdio.h +
Implements printf() and a few other functions. +
+

+ + +


+ +

2.1.2 Synchronization

+ +

+ +Proper synchronization is an important part of the solutions to these +problems. Any synchronization problem can be easily solved by turning +interrupts off: while interrupts are off, there is no concurrency, so +there's no possibility for race conditions. Therefore, it's tempting to +solve all synchronization problems this way, but don't. +Instead, use semaphores, locks, and condition variables to solve the +bulk of your synchronization problems. Read the tour section on +synchronization (see section A.3 Synchronization) or the comments in +threads/synch.c if you're unsure what synchronization primitives +may be used in what situations. +

+

+ +In the Pintos projects, the only class of problem best solved by +disabling interrupts is coordinating data shared between a kernel thread +and an interrupt handler. Because interrupt handlers can't sleep, they +can't acquire locks. This means that data shared between kernel threads +and an interrupt handler must be protected within a kernel thread by +turning off interrupts. +

+

+ +This project only requires accessing a little bit of thread state from +interrupt handlers. For the alarm clock, the timer interrupt needs to +wake up sleeping threads. In the advanced scheduler, the timer +interrupt needs to access a few global and per-thread variables. When +you access these variables from kernel threads, you will need to disable +interrupts to prevent the timer interrupt from interfering. +

+

+ +When you do turn off interrupts, take care to do so for the least amount +of code possible, or you can end up losing important things such as +timer ticks or input events. Turning off interrupts also increases the +interrupt handling latency, which can make a machine feel sluggish if +taken too far. +

+

+ +The synchronization primitives themselves in synch.c are +implemented by disabling interrupts. You may need to increase the +amount of code that runs with interrupts disabled here, but you should +still try to keep it to a minimum. +

+

+ +Disabling interrupts can be useful for debugging, if you want to make +sure that a section of code is not interrupted. You should remove +debugging code before turning in your project. (Don't just comment it +out, because that can make the code difficult to read.) +

+

+ +There should be no busy waiting in your submission. A tight loop that +calls thread_yield() is one form of busy waiting. +

+

+ + +


+ +

2.1.3 Development Suggestions

+ +

+ +In the past, many groups divided the assignment into pieces, then each +group member worked on his or her piece until just before the +deadline, at which time the group reconvened to combine their code and +submit. This is a bad idea. We do not recommend this +approach. Groups that do this often find that two changes conflict +with each other, requiring lots of last-minute debugging. Some groups +who have done this have turned in code that did not even compile or +boot, much less pass any tests. +

+

+ +Instead, we recommend integrating your team's changes early and often, +using the source code control system git. +This is less likely to produce surprises, because everyone can see +everyone else's code as it is written, instead of just when it is +finished. These systems also make it possible to review changes and, +when a change introduces a bug, drop back to working versions of code. +

+

+ +You should expect to run into bugs that you simply don't understand +while working on this and subsequent projects. When you do, +reread the appendix on debugging tools, which is filled with +useful debugging tips that should help you to get back up to speed +(see section D. Debugging Tools). Be sure to read the section on backtraces +(see section D.4 Backtraces), which will help you to get the most out of every +kernel panic or assertion failure. +

+

+ + +


+ +

2.2 Understanding User Programs

+ +

+ +The tests for both the alarm clock assignment in Project 0, and the +priority scheduler in Project 1, run as part of the operating system +kernel, with full access to privileged parts of the system. +Once we start running user programs on top of the operating system, this +is no longer true. +

+

+ +We allow more than one process to run at a time. Each process has one +thread (multithreaded processes are not supported). User programs are +written under the illusion that they have the entire machine. This +means that when you load and run multiple processes at a time, you must +manage memory, scheduling, and other state correctly to maintain this +illusion. +

+

+ +In Project 2, we will test your operating system by running +user programs. This gives you much greater freedom. You must make sure +that the user program interface meets the specifications described here, +but given that constraint you are free to restructure or rewrite kernel +code however you wish. +

+

+ + +


+ +

2.2.1 Source Files

+ +

+ +

+
+
process.c +
process.h +
Loads ELF binaries and starts processes. +

+ +

+
pagedir.c +
pagedir.h +
A simple manager for 80x86 hardware page tables. +Although you probably won't want to modify this code for this project, +you may want to call some of its functions. +See Page Tables, for more information. +

+ +

+
syscall.c +
syscall.h +
Whenever a user process wants to access some kernel functionality, it +invokes a system call. +

+ +

+
exception.c +
exception.h +
When a user process performs a privileged or prohibited operation, it +traps into the kernel as an "exception" or "fault."(2) These files handle +exceptions. In project 2, you will need to modify the page fault +handler to support lazy page loading and stack growth. +

+ +

+
gdt.c +
gdt.h +
The 80x86 is a segmented architecture. The Global Descriptor +Table (GDT) is a table that describes the segments in use. These +files set up the GDT. You should not need to modify these +files for any of the projects. You can read the code if +you're interested in how the GDT works. +

+ +

+
tss.c +
tss.h +
The Task-State Segment (TSS) is used for 80x86 architectural +task switching. Pintos uses the TSS only for switching stacks when a +user process enters an interrupt handler, as does Linux. You +should not need to modify these files for any of the projects. +You can read the code if you're interested in how the TSS +works. +
+

+ + +


+ +

2.2.2 Using the File System

+ +

+ +You will need to interface to the file system code, because +user programs are loaded from the file system and most of the +system calls you must implement deal with the file system. +You will want to look over the filesys.h and file.h +interfaces to understand how to use the file system, and especially +its many limitations. +

+

+ +There is no need to modify the file system code in this course, and so +we recommend that you do not. Working on the file system is likely to +distract you from the project's foci. +

+

+ +You will have to tolerate the following limitations, however: +

+

+ +

+

+ +One important feature is included: +

+

+ +

+

+ +You need to be able to create a simulated disk with a file system +partition. The pintos-mkdisk program provides this +functionality. From the userprog/build directory, execute +pintos-mkdisk filesys.dsk --filesys-size=2. This command +creates a simulated disk named filesys.dsk that contains a 2 +MB Pintos file system partition. Then format the file system +partition by passing -f -q on the kernel's command line: +pintos -f -q. The -f option causes the file system to +be formatted, and -q causes Pintos to exit as soon as the +format is done. +

+

+ +You'll need a way to copy files in and out of the simulated file system. +The pintos -p ("put") and -g ("get") +options do this. To copy file into the +Pintos file system, use the command pintos -p file -- -q. +(The -- is needed because -p is for the pintos +script, not for the simulated kernel.) To copy it to the Pintos file +system under the name newname, add -a +newname: pintos -p file -a newname -- -q. The +commands for copying files out of a VM are similar, but substitute +-g for -p. +

+

+ +Incidentally, these commands work by passing special commands +extract and append on the kernel's command line and copying +to and from a special simulated "scratch" partition. If you're very +curious, you can look at the pintos script as well as +filesys/fsutil.c to learn the implementation details. +

+

+ +Here's a summary of how to create a disk with a file system partition, +format the file system, copy the echo program into the new +disk, and then run echo, passing argument x. +(Argument passing won't work until you implemented it.) It assumes +that you've already built the examples in examples and that the +current directory is userprog/build: +

+

+ +
 
pintos-mkdisk filesys.dsk --filesys-size=2
+pintos -f -q
+pintos -p ../../examples/echo -a echo -- -q
+pintos -q run 'echo x'
+

+ +The three final steps can actually be combined into a single command: +

+

+ +
 
pintos-mkdisk filesys.dsk --filesys-size=2
+pintos -p ../../examples/echo -a echo -- -f -q run 'echo x'
+

+ +If you don't want to keep the file system disk around for later use or +inspection, you can even combine all four steps into a single command. +The --filesys-size=n option creates a temporary file +system partition +approximately n megabytes in size just for the duration of the +pintos run. The Pintos automatic test suite makes extensive +use of this syntax: +

+

+ +
 
pintos --filesys-size=2 -p ../../examples/echo -a echo -- -f -q run 'echo x'
+

+ +You can delete a file from the Pintos file system using the rm +file kernel action, e.g. pintos -q rm file. Also, +ls lists the files in the file system and cat +file prints a file's contents to the display. +

+

+ + +


+ +

2.2.3 How User Programs Work

+ +

+ +Pintos can run normal C programs, as long as they fit into memory and use +only the system calls you implement. Notably, malloc() cannot be +implemented because none of the system calls required for this project +allow for memory allocation. Pintos also can't run programs that use +floating point operations, since the kernel doesn't save and restore the +processor's floating-point unit when switching threads. +

+

+ +The src/examples directory contains a few sample user +programs. The Makefile in this directory +compiles the provided examples, and you can edit it +compile your own programs as well. Some of the example programs will +not work with the current implementation of Pintos. +

+

+ +Pintos can load ELF executables with the loader provided for you +in userprog/process.c. ELF is a file format used by Linux, +Solaris, and many other operating systems for object files, +shared libraries, and executables. You can actually use any compiler +and linker that output 80x86 ELF executables to produce programs +for Pintos. (We've provided compilers and linkers that should do just +fine.) +

+

+ +You should realize immediately that, until you copy a +test program to the simulated file system, Pintos will be unable to do +useful work. You won't be able to do +interesting things until you copy a variety of programs to the file system. +You might want to create a clean reference file system disk and copy that +over whenever you trash your filesys.dsk beyond a useful state, +which may happen occasionally while debugging. +

+

+ + +


+ +

2.2.4 Virtual Memory Layout

+ +

+ +Virtual memory in Pintos is divided into two regions: user virtual +memory and kernel virtual memory. User virtual memory ranges from +virtual address 0 up to PHYS_BASE, which is defined in +threads/vaddr.h and defaults to 0xc0000000 (3 GB). Kernel +virtual memory occupies the rest of the virtual address space, from +PHYS_BASE up to 4 GB. +

+

+ +User virtual memory is per-process. +When the kernel switches from one process to another, it +also switches user virtual address spaces by changing the processor's +page directory base register (see pagedir_activate() in +userprog/pagedir.c). struct thread contains a pointer to a +process's page table. +

+

+ +Kernel virtual memory is global. It is always mapped the same way, +regardless of what user process or kernel thread is running. In +Pintos, kernel virtual memory is mapped one-to-one to physical +memory, starting at PHYS_BASE. That is, virtual address +PHYS_BASE accesses physical +address 0, virtual address PHYS_BASE + 0x1234 accesses +physical address 0x1234, and so on up to the size of the machine's +physical memory. +

+

+ +A user program can only access its own user virtual memory. An attempt to +access kernel virtual memory causes a page fault, handled by +page_fault() in userprog/exception.c, and the process +will be terminated. Kernel threads can access both kernel virtual +memory and, if a user process is running, the user virtual memory of +the running process. However, even in the kernel, an attempt to +access memory at an unmapped user virtual address +will cause a page fault. +

+

+ + +


+ +

2.2.4.1 Typical Memory Layout

+ +

+ +Conceptually, each process is +free to lay out its own user virtual memory however it +chooses. In practice, user virtual memory is laid out like this: +

+

+ +

+
 
   PHYS_BASE +----------------------------------+
+             |            user stack            |
+             |                 |                |
+             |                 |                |
+             |                 V                |
+             |          grows downward          |
+             |                                  |
+             |                                  |
+             |                                  |
+             |                                  |
+             |           grows upward           |
+             |                 ^                |
+             |                 |                |
+             |                 |                |
+             +----------------------------------+
+             | uninitialized data segment (BSS) |
+             +----------------------------------+
+             |     initialized data segment     |
+             +----------------------------------+
+             |           code segment           |
+  0x08048000 +----------------------------------+
+             |                                  |
+             |                                  |
+             |                                  |
+             |                                  |
+             |                                  |
+           0 +----------------------------------+
+
+

+ +In this project, the user stack is fixed in size, but in project 2 it +will be allowed to grow. Traditionally, the size of the uninitialized +data segment can be adjusted with a system call, but you will not have +to implement this. +

+

+ +The code segment in Pintos starts at user virtual address +0x08084000, approximately 128 MB from the bottom of the address +space. This value is specified in [ SysV-i386] and has no deep +significance. +

+

+ +The linker sets the layout of a user program in memory, as directed by a +"linker script" that tells it the names and locations of the various +program segments. You can learn more about linker scripts by reading +the "Scripts" chapter in the linker manual, accessible via info +ld. +

+

+ +To view the layout of a particular executable, run objdump +(80x86) or i386-elf-objdump (SPARC) with the -p +option. +

+

+ + +


+ +

2.2.5 Accessing User Memory

+ +

+ +As part of a system +call, the kernel must often access memory through pointers provided by a user +program. The kernel must be very careful about doing so, because +the user can pass a null pointer, a pointer to +unmapped virtual memory, or a pointer to kernel virtual address space +(above PHYS_BASE). All of these types of invalid pointers must +be rejected without harm to the kernel or other running processes, by +terminating the offending process and freeing its resources. +

+

+ +There are at least two reasonable ways to do this correctly. The +first method is to verify +the validity of a user-provided pointer, then dereference it. +The second method is to check only that a user +pointer points below PHYS_BASE, then dereference it. +An invalid user pointer will cause a "page fault" that you can +handle by modifying the code for page_fault() in +userprog/exception.c. This technique is normally faster +because it takes advantage of the processor's MMU, so it tends to be +used in real kernels (including Linux). It is also the way +access to user pointers is implemented in the Pintos version provided. +

+

+ +In either case, one needs to make sure not to "leak" resources. For +example, suppose that your system call has acquired a lock or +allocated memory with malloc(). If you encounter an invalid user pointer +afterward, you must still be sure to release the lock or free the page +of memory. If you choose to verify user pointers before dereferencing +them, this should be straightforward. It's more difficult to handle +if an invalid pointer causes a page fault, +because there's no way to return an error code from a memory access. +

+

+ + +


+ +

2.3 Requirements

+ +

+ + +


+ +

2.3.1 Design Document

+ +

+ +Before you turn in your project, you must copy the +project 0 design document template into your source tree under the name +pintos/src/intro/DESIGNDOC and fill it in. We recommend that +you read the design document template before you start working on the +project. See section C. Project Documentation, for a sample design document +that goes along with a fictitious project. +

+

+ + +


+ +

2.3.2 Alarm Clock

+ +

+ +Reimplement timer_sleep(), defined in devices/timer.c. +Although a working implementation is provided, it "busy waits," that +is, it spins in a loop checking the current time and calling +thread_yield() until enough time has gone by. Reimplement it to +avoid busy waiting. +

+

+ + +

+
+
Function: void timer_sleep (int64_t ticks) +
Suspends execution of the calling thread until time has advanced by at +least x timer ticks. Unless the system is otherwise idle, the +thread need not wake up after exactly x ticks. Just put it on +the ready queue after they have waited for the right amount of time. +

+ +timer_sleep() is useful for threads that operate in real-time, +e.g. for blinking the cursor once per second. +

+

+ +The argument to timer_sleep() is expressed in timer ticks, not in +milliseconds or any another unit. There are TIMER_FREQ timer +ticks per second, where TIMER_FREQ is a macro defined in +devices/timer.h. The default value is 100. We don't recommend +changing this value, because any change is likely to cause many of +the tests to fail. +

+
+

+ +Separate functions timer_msleep(), timer_usleep(), and +timer_nsleep() do exist for sleeping a specific number of +milliseconds, microseconds, or nanoseconds, respectively, but these will +call timer_sleep() automatically when necessary. You do not need +to modify them. +

+

+ +If your delays seem too short or too long, reread the explanation of the +-r option to pintos (see section 1.1.4 Debugging versus Testing). +

+

+ +The tests for the 2.3.2 Alarm Clock assignment are executed by changing the +working directory intro. The run make to build the +Pintos kernel. Finally run make check to run the tests, +followed by make grade to obtain your score. +

+

+ +The alarm clock implementation is not needed for later projects. +

+

+ + +


+ +

2.3.3 Argument Passing

+ +

+ +Currently, process_execute() does not support passing arguments to +new processes. Implement this functionality, by extending +process_execute() so that instead of simply taking a program file +name as its argument, it divides it into words at spaces. The first +word is the program name, the second word is the first argument, and so +on. That is, process_execute("grep foo bar") should run +grep passing two arguments foo and bar. +

+

+ +Within a command line, multiple spaces are equivalent to a single +space, so that process_execute("grep foo bar") +is equivalent to our original example. You can impose a reasonable +limit on the length of the command line arguments. For example, you +could limit the arguments to those that will fit in a single page (4 +kB). (There is an unrelated limit of 128 bytes on command-line +arguments that the pintos utility can pass to the kernel.) +

+

+ +You can parse argument strings any way you like. If you're lost, +look at strtok_r(), prototyped in lib/string.h and +implemented with thorough comments in lib/string.c. You can +find more about it by looking at the man page (run man strtok_r +at the prompt). +

+

+ +See section 2.5.1 Program Startup Details, for information on exactly how you +need to set up the stack. +

+

+ + +


+ +

2.4 FAQ

+ +

+ +

+
+
How much code will I need to write? +

+ +Here's a summary of our reference solution, produced by the +diffstat program. The final row gives total lines inserted +and deleted; a changed line counts as both an insertion and a deletion. +

+

+ +The reference solution represents just one possible solution. Many +other solutions are also possible and many of those differ greatly from +the reference solution. Some excellent solutions may not modify all the +files modified by the reference solution, and some may modify files not +modified by the reference solution. +

+

+ +
 
 devices/timer.c    |   40 +++++++++++-
+ threads/thread.h   |    3 +
+ userprog/process.c |  148 ++++++++++++++++++++++++++++++-----------
+ 3 files changed, 150 insertions(+), 41 deletions(-)
+

+

+ + +


+ +

2.4.1 Threads FAQ

+ +

+ +

+
+
How do I update the Makefiles when I add a new source file? +

+ + +To add a .c file, edit the top-level Makefile.build. +Add the new file to variable dir_SRC, where +dir is the directory where you added the file. For this +project, that means you should add it to threads_SRC or +devices_SRC. Then run make. If your new file +doesn't get +compiled, run make clean and then try again. +

+

+ +When you modify the top-level Makefile.build and re-run +make, the modified +version should be automatically copied to +threads/build/Makefile. The converse is +not true, so any changes will be lost the next time you run make +clean from the threads directory. Unless your changes are +truly temporary, you should prefer to edit Makefile.build. +

+

+ +A new .h file does not require editing the Makefiles. +

+

+ +

+
What does warning: no previous prototype for `func' mean? +

+ +It means that you defined a non-static function without +preceding it by a prototype. Because non-static functions are +intended for use by other .c files, for safety they should be +prototyped in a header file included before their definition. To fix +the problem, add a prototype in a header file that you include, or, if +the function isn't actually used by other .c files, make it +static. +

+

+ +

+
What is the interval between timer interrupts? +

+ +Timer interrupts occur TIMER_FREQ times per second. You can +adjust this value by editing devices/timer.h. The default is +100 Hz. +

+

+ +We don't recommend changing this value, because any changes are likely +to cause many of the tests to fail. +

+

+ +

+
How long is a time slice? +

+ +There are TIME_SLICE ticks per time slice. This macro is +declared in threads/thread.c. The default is 4 ticks. +

+

+ +We don't recommend changing this value, because any changes are likely +to cause many of the tests to fail. +

+

+ +

+
How do I run the tests? +

+ +See section 1.2.1 Testing. +

+

+ +See section D.4 Backtraces, for more information. +

+

+ + +


+ +

2.4.2 Alarm Clock FAQ

+ +

+ +

+
+
Do I need to account for timer values overflowing? +

+ +Don't worry about the possibility of timer values overflowing. Timer +values are expressed as signed 64-bit numbers, which at 100 ticks per +second should be good for almost 2,924,712,087 years. By then, we +expect Pintos to have been phased out of the curriculum. +

+

+ + +


+ +

2.4.3 Userprog FAQ

+ +

+ +

+
+
The kernel always panics when I run pintos -p file -- -q. +

+ +Did you format the file system (with pintos -f)? +

+

+ +Is your file name too long? The file system limits file names to 14 +characters. A command like pintos -p ../../examples/echo -- -q +will exceed the limit. Use pintos -p ../../examples/echo -a echo +-- -q to put the file under the name echo instead. +

+

+ +Is the file system full? +

+

+ +Does the file system already contain 16 files? The base Pintos file +system has a 16-file limit. +

+

+ +The file system may be so fragmented that there's not enough contiguous +space for your file. +

+

+ +

+
When I run pintos -p ../file --, file isn't copied. +

+ +Files are written under the name you refer to them, by default, so in +this case the file copied in would be named ../file. You +probably want to run pintos -p ../file -a file -- instead. +

+

+ +You can list the files in your file system with pintos -q ls. +

+

+ +

+
All my user programs die with page faults. +

+ +This will happen if you haven't implemented argument passing +(or haven't done so correctly). The basic C library for user programs tries +to read argc and argv off the stack. If the stack +isn't properly set up, this causes a page fault. +

+

+ +

+
How can I disassemble user programs? +

+ +The objdump (80x86) or i386-elf-objdump +(SPARC) utility can disassemble entire user +programs or object files. Invoke it as objdump -d +file. You can use GDB's +disassemble command to disassemble individual functions +(see section D.5 GDB). +

+

+ +

+
Why do many C include files not work in Pintos programs? +
Can I use libfoo in my Pintos programs? +

+ +The C library we provide is very limited. It does not include many of +the features that are expected of a real operating system's C library. +The C library must be built specifically for the operating system (and +architecture), since it must make system calls for I/O and memory +allocation. (Not all functions do, of course, but usually the library +is compiled as a unit.) +

+

+ +The chances are good that the library you want uses parts of the C library +that Pintos doesn't implement. It will probably take at least some +porting effort to make it work under Pintos. Notably, the Pintos +user program C library does not have a malloc() implementation. +

+

+ +

+
How do I compile new user programs? +

+ +Modify src/examples/Makefile, then run make. +

+

+ +

+
Can I run user programs under a debugger? +

+ +Yes, with some limitations. See section D.5 GDB. +

+

+ +

+
How can I run user programs that need more than 4 kB stack space? +

+ +You may modify the stack setup code to allocate more than one page of +stack space for each process. In project 2, you will implement a better +solution. +

+

+ +

+
What happens when an open file is removed? +
+

+ +You should implement the standard Unix semantics for files. That is, when +a file is removed any process which has a file descriptor for that file +may continue to use that descriptor. This means that +they can read and write from the file. The file will not have a name, +and no other processes will be able to open it, but it will continue +to exist until all file descriptors referring to the file are closed +or the machine shuts down. +

+

+ +

+

+ + +


+ +

2.4.4 Argument Passing FAQ

+ +

+ +

+
+
Isn't the top of stack in kernel virtual memory? +

+ +The top of stack is at PHYS_BASE, typically 0xc0000000, which +is also where kernel virtual memory starts. +But before the processor pushes data on the stack, it decrements the stack +pointer. Thus, the first (4-byte) value pushed on the stack +will be at address 0xbffffffc. +

+

+ +

+
Is PHYS_BASE fixed? +

+ +No. You should be able to support PHYS_BASE values that are +any multiple of 0x10000000 from 0x80000000 to 0xf0000000, +simply via recompilation. +

+

+ + +


+ +

2.5 80x86 Calling Convention

+ +

+ +This section summarizes important points of the convention used for +normal function calls on 32-bit 80x86 implementations of Unix. +Some details are omitted for brevity. If you do want all the details, +refer to [ SysV-i386]. +

+

+ +The calling convention works like this: +

+

+ +

    +
  1. +The caller pushes each of the function's arguments on the stack one by +one, normally using the PUSH assembly language instruction. +Arguments are pushed in right-to-left order. +

    + +The stack grows downward: each push decrements the stack pointer, then +stores into the location it now points to, like the C expression +*--sp = value. +

    +

    + +

    +
  2. +The caller pushes the address of its next instruction (the return +address) on the stack and jumps to the first instruction of the callee. +A single 80x86 instruction, CALL, does both. +

    + +

    +
  3. +The callee executes. When it takes control, the stack pointer points to +the return address, the first argument is just above it, the second +argument is just above the first argument, and so on. +

    + +

    +
  4. +If the callee has a return value, it stores it into register EAX. +

    + +

    +
  5. +The callee returns by popping the return address from the stack and +jumping to the location it specifies, using the 80x86 RET +instruction. +

    + +

    +
  6. +The caller pops the arguments off the stack. +
+

+ +Consider a function f() that takes three int arguments. +This diagram shows a sample stack frame as seen by the callee at the +beginning of step 3 above, supposing that f() is invoked as +f(1, 2, 3). The initial stack address is arbitrary: +

+

+ +

+
 
                             +----------------+
+                  0xbffffe7c |        3       |
+                  0xbffffe78 |        2       |
+                  0xbffffe74 |        1       |
+stack pointer --> 0xbffffe70 | return address |
+                             +----------------+
+
+

+ + +


+ +

2.5.1 Program Startup Details

+ +

+ +The Pintos C library for user programs designates _start(), in +lib/user/entry.c, as the entry point for user programs. This +function is a wrapper around main() that calls exit() if +main() returns: +

+

+ +
 
void
+_start (int argc, char *argv[])
+{
+  exit (main (argc, argv));
+}
+

+ +The kernel must put the arguments for the initial function on the stack +before it allows the user program to begin executing. The arguments are +passed in the same way as the normal calling convention (see section 2.5 80x86 Calling Convention). +

+

+ +Consider how to handle arguments for the following example command: +/bin/ls -l foo bar. +First, break the command into words: /bin/ls, +-l, foo, bar. Place the words at the top of the +stack. Order doesn't matter, because they will be referenced through +pointers. +

+

+ +Then, push the address of each string plus a null pointer sentinel, on +the stack, in right-to-left order. These are the elements of +argv. The null pointer sentinel ensures that argv[argc] +is a null pointer, as required by the C standard. The order ensures +that argv[0] is at the lowest virtual address. Word-aligned +accesses are faster than unaligned accesses, so for best performance +round the stack pointer down to a multiple of 4 before the first push. +

+

+ +Then, push argv (the address of argv[0]) and argc, +in that order. Finally, push a fake "return address": although the +entry function will never return, its stack frame must have the same +structure as any other. +

+

+ +The table below shows the state of the stack and the relevant registers +right before the beginning of the user program, assuming +PHYS_BASE is 0xc0000000: +

+

+ +

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Address Name Data Type
0xbffffffc argv[3][...] bar\0 char[4]
0xbffffff8 argv[2][...] foo\0 char[4]
0xbffffff5 argv[1][...] -l\0 char[3]
0xbfffffed argv[0][...] /bin/ls\0 char[8]
0xbfffffec word-align 0 uint8_t
0xbfffffe8 argv[4] 0 char *
0xbfffffe4 argv[3] 0xbffffffc char *
0xbfffffe0 argv[2] 0xbffffff8 char *
0xbfffffdc argv[1] 0xbffffff5 char *
0xbfffffd8 argv[0] 0xbfffffed char *
0xbfffffd4 argv 0xbfffffd8 char **
0xbfffffd0 argc 4 int
0xbfffffcc return address 0 void (*) ()
+
+

+ +In this example, the stack pointer would be initialized to +0xbfffffcc. +

+

+ +As shown above, your code should start the stack at the very top of +the user virtual address space, in the page just below virtual address +PHYS_BASE (defined in threads/vaddr.h). +

+

+ +You may find the non-standard hex_dump() function, declared in +<stdio.h>, useful for debugging your argument passing code. +Here's what it would show in the above example: +

+

+ +
 
bfffffc0                                      00 00 00 00 |            ....|
+bfffffd0  04 00 00 00 d8 ff ff bf-ed ff ff bf f5 ff ff bf |................|
+bfffffe0  f8 ff ff bf fc ff ff bf-00 00 00 00 00 2f 62 69 |............./bi|
+bffffff0  6e 2f 6c 73 00 2d 6c 00-66 6f 6f 00 62 61 72 00 |n/ls.-l.foo.bar.|
+

+ + +


+ +

2.5.2 System Call Details

+ +

+ +We already know one way that the operating system +can regain control from a user program: interrupts from timers and I/O +devices. These are "external" interrupts, because they are caused +by entities outside the CPU (see section A.4.3 External Interrupt Handling). +

+

+ +The operating system also deals with software exceptions, which are +events that occur in program code (see section A.4.2 Internal Interrupt Handling). These can be errors such as a page fault or division by +zero. Exceptions are also the means by which a user program +can request services ("system calls") from the operating system. +

+

+ +In the 80x86 architecture, the int instruction is the +most commonly used means for invoking system calls. This instruction +is handled in the same way as other software exceptions. In Pintos, +user programs invoke int $0x30 to make a system call. The +system call number and any additional arguments are expected to be +pushed on the stack in the normal fashion before invoking the +interrupt (see section 2.5 80x86 Calling Convention). +

+

+ +Thus, when the system call handler syscall_handler() gets control, +the system call number is in the 32-bit word at the caller's stack +pointer, the first argument is in the 32-bit word at the next higher +address, and so on. The caller's stack pointer is accessible to +syscall_handler() as the esp member of the +struct intr_frame passed to it. (struct intr_frame is on the kernel +stack.) +

+

+ +The 80x86 convention for function return values is to place them +in the EAX register. System calls that return a value can do +so by modifying the eax member of struct intr_frame. +

+

+ +You should try to avoid writing large amounts of repetitive code for +implementing system calls. Each system call argument, whether an +integer or a pointer, takes up 4 bytes on the stack. You should be able +to take advantage of this to avoid writing much near-identical code for +retrieving each system call's arguments from the stack. + +


+ + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_3.html b/doc/pintos_3.html new file mode 100644 index 0000000..c90148d --- /dev/null +++ b/doc/pintos_3.html @@ -0,0 +1,375 @@ + + + + + +Pintos Projects: Project 1--Threads + + + + + + + + + + + + + + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+ +
+

3. Project 1: Threads

+ +

+ +In this assignment, we give you a minimally functional thread system. +Your job is to extend the functionality of this system to gain a +better understanding of synchronization problems. +

+

+ +You will be working primarily in the threads directory for +this assignment, with some work in the devices directory on the +side. Compilation should be done in the threads directory. +

+

+ +Before you read the description of this project, you should read all of +the following sections: 1. Introduction, B. Coding Standards, +D. Debugging Tools, and E. Development Tools. You should at least +skim the material from A.1 Loading through A.5 Memory Allocation, especially A.3 Synchronization. +

+

+ + +


+ +

3.1 Background

+ +

+ +Before you start with project 1, be sure to refresh your knowledge +on the thread subsystem introduced in the last project +(2.1 Understanding Threads). +

+

+ + +


+ +

3.2 Requirements

+ +

+ + +


+ +

3.2.1 Design Document

+ +

+ +Before you turn in your project, you must copy the +project 1 design document template into your source tree under the name +pintos/src/threads/DESIGNDOC and fill it in. We recommend that +you read the design document template before you start working on the +project. +

+

+ + +


+ +

3.2.2 Priority Scheduling

+ +

+ +Implement priority scheduling in Pintos. +When a thread is added to the ready list that has a higher priority +than the currently running thread, the current thread should +immediately yield the processor to the new thread. Similarly, when +threads are waiting for a lock, semaphore, or condition variable, the +highest priority waiting thread should be awakened first. A thread +may raise or lower its own priority at any time, but lowering its +priority such that it no longer has the highest priority must cause it +to immediately yield the CPU. +

+

+ +Thread priorities range from PRI_MIN (0) to PRI_MAX (63). +Lower numbers correspond to lower priorities, so that priority 0 +is the lowest priority and priority 63 is the highest. +The initial thread priority is passed as an argument to +thread_create(). If there's no reason to choose another +priority, use PRI_DEFAULT (31). The PRI_ macros are +defined in threads/thread.h, and you should not change their +values. +

+

+ +One issue with priority scheduling is "priority inversion". Consider +high, medium, and low priority threads H, M, and L, +respectively. If H needs to wait for L (for instance, for a +lock held by L), and M is on the ready list, then H +will never get the CPU because the low priority thread will not get any +CPU time. A partial fix for this problem is for H to "donate" +its priority to L while L is holding the lock, then recall +the donation once L releases (and thus H acquires) the lock. +

+

+ +Implement priority donation. You will need to account for all different +situations in which priority donation is required. Be sure to handle +multiple donations, in which multiple priorities are donated to a single +thread. You must also handle nested donation: if H is waiting on +a lock that M holds and M is waiting on a lock that L +holds, then both M and L should be boosted to H's +priority. If necessary, you may impose a reasonable limit on depth of +nested priority donation, such as 8 levels. +

+

+ +You must implement priority donation for locks. You need not +implement priority donation for the other Pintos synchronization +constructs. You do need to implement priority scheduling in all +cases. +

+

+ +Finally, implement the following functions that allow a thread to +examine and modify its own priority. Skeletons for these functions are +provided in threads/thread.c. +

+

+ + +

+
+
Function: void thread_set_priority (int new_priority) +
Sets the current thread's priority to new_priority. If the +current thread no longer has the highest priority, yields. +
+

+ + +

+
+
Function: int thread_get_priority (void) +
Returns the current thread's priority. In the presence of priority +donation, returns the higher (donated) priority. +
+

+ +You need not provide any interface to allow a thread to directly modify +other threads' priorities. +

+

+ +The priority scheduler is not a necessary for project 2. +

+

+ + +


+ +

3.3 FAQ

+ +

+ +

+
+
How much code will I need to write? +

+ +Here's a summary of our reference solution, produced by the +diffstat program. The final row gives total lines inserted +and deleted; a changed line counts as both an insertion and a deletion. +

+

+ +The reference solution represents just one possible solution. Many +other solutions are also possible and many of those differ greatly from +the reference solution. Some excellent solutions may not modify all the +files modified by the reference solution, and some may modify files not +modified by the reference solution. +

+

+ +
 
 threads/interrupt.c |    3 +-
+ threads/synch.c     |   55 ++++++++++++++++++--
+ threads/synch.h     |    2 +
+ threads/thread.c    |  111 +++++++++++++++++++++++++++++++++++-----
+ threads/thread.h    |   10 +++-
+ 5 files changed, 160 insertions(+), 21 deletions(-)
+

+ +

+
Doesn't priority scheduling lead to starvation? +

+ +Yes, strict priority scheduling can lead to starvation +because a thread will not run if any higher-priority thread is runnable. +The advanced scheduler introduces a mechanism for dynamically +changing thread priorities. +

+

+ +Strict priority scheduling is valuable in real-time systems because it +offers the programmer more control over which jobs get processing +time. High priorities are generally reserved for time-critical +tasks. It's not "fair," but it addresses other concerns not +applicable to a general-purpose operating system. +

+

+ +

+
What thread should run after a lock has been released? +

+ +When a lock is released, the highest priority thread waiting for that +lock should be unblocked and put on the list of ready threads. The +scheduler should then run the highest priority thread on the ready +list. +

+

+ +

+
If the highest-priority thread yields, does it continue running? +

+ +Yes. If there is a single highest-priority thread, it continues +running until it blocks or finishes, even if it calls +thread_yield(). +If multiple threads have the same highest priority, +thread_yield() should switch among them in "round robin" order. +

+

+ +

+
What happens to the priority of a donating thread? +

+ +Priority donation only changes the priority of the donee +thread. The donor thread's priority is unchanged. +Priority donation is not additive: if thread A (with priority 5) donates +to thread B (with priority 3), then B's new priority is 5, not 8. +

+

+ +

+
Can a thread's priority change while it is on the ready queue? +

+ +Yes. Consider a ready, low-priority thread L that holds a lock. +High-priority thread H attempts to acquire the lock and blocks, +thereby donating its priority to ready thread L. +

+

+ +

+
Can a thread's priority change while it is blocked? +

+ +Yes. While a thread that has acquired lock L is blocked for any +reason, its priority can increase by priority donation if a +higher-priority thread attempts to acquire L. This case is +checked by the priority-donate-sema test. +

+

+ +

+
Can a thread added to the ready list preempt the processor? +

+ +Yes. If a thread added to the ready list has higher priority than the +running thread, the correct behavior is to immediately yield the +processor. It is not acceptable to wait for the next timer interrupt. +The highest priority thread should run as soon as it is runnable, +preempting whatever thread is currently running. +

+

+ +

+
How does thread_set_priority() affect a thread receiving donations? +

+ +It sets the thread's base priority. The thread's effective priority +becomes the higher of the newly set priority or the highest donated +priority. When the donations are released, the thread's priority +becomes the one set through the function call. This behavior is checked +by the priority-donate-lower test. +

+

+ +

+
Doubled test names in output make them fail. +

+ +Suppose you are seeing output in which some test names are doubled, +like this: +

+

+ +
 
(alarm-priority) begin
+(alarm-priority) (alarm-priority) Thread priority 30 woke up.
+Thread priority 29 woke up.
+(alarm-priority) Thread priority 28 woke up.
+

+ +What is happening is that output from two threads is being +interleaved. That is, one thread is printing "(alarm-priority) +Thread priority 29 woke up.\n" and another thread is printing +"(alarm-priority) Thread priority 30 woke up.\n", but the first +thread is being preempted by the second in the middle of its output. +

+

+ +This problem indicates a bug in your priority scheduler. After all, a +thread with priority 29 should not be able to run while a thread with +priority 30 has work to do. +

+

+ +Normally, the implementation of the printf() function in the +Pintos kernel attempts to prevent such interleaved output by acquiring +a console lock during the duration of the printf call and +releasing it afterwards. However, the output of the test name, +e.g., (alarm-priority), and the message following it is output +using two calls to printf, resulting in the console lock being +acquired and released twice. +

+ +
+ + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_4.html b/doc/pintos_4.html new file mode 100644 index 0000000..70be2de --- /dev/null +++ b/doc/pintos_4.html @@ -0,0 +1,83 @@ + + + + + +Pintos Projects: Project 2--Virtual Memory + + + + + + + + + + + + + + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+ +
+

4. Project 2: Virtual Memory

+ +

+ +By now you should have some familiarity with the inner workings of +Pintos. Pintos can properly handle multiple threads of execution with proper +synchronization, and can load multiple user programs at once. In this assignment, +you will improve the memory management of Pintos. +

+

+ +This assignment requires user programs (and in particular argument passing, +which you implemented in project 0) to work. You will continue to handle +Pintos disks and file systems the same way you did before +(see section 2.2.2 Using the File System). +

+

+ +The documentation for this assignment will be released when Project 2 +is about to start. +

+

+ + + + + + +


+ + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_5.html b/doc/pintos_5.html new file mode 100644 index 0000000..670f351 --- /dev/null +++ b/doc/pintos_5.html @@ -0,0 +1,3343 @@ + + + + + +Pintos Projects: Reference Guide + + + + + + + + + + + + + + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+ +
+

A. Reference Guide

+ +

+ +This chapter is a reference for the Pintos code. The reference guide +does not cover all of the code in Pintos, but it does cover those +pieces that students most often find troublesome. You may find that +you want to read each part of the reference guide as you work on the +project where it becomes important. +

+

+ +We recommend using "tags" to follow along with references to function +and variable names (see section E.1 Tags). +

+

+ + +


+ +

A.1 Loading

+ +

+ +This section covers the Pintos loader and basic kernel +initialization. +

+

+ + +


+ +

A.1.1 The Loader

+ +

+ +The first part of Pintos that runs is the loader, in +threads/loader.S. The PC BIOS loads the loader into memory. +The loader, in turn, is responsible for finding the kernel on disk, +loading it into memory, and then jumping to its start. It's +not important to understand exactly how the loader works, but if +you're interested, read on. You should probably read along with the +loader's source. You should also understand the basics of the +80x86 architecture as described by chapter 3, "Basic Execution +Environment," of [ IA32-v1]. +

+

+ +The PC BIOS loads the loader from the first sector of the first hard +disk, called the master boot record (MBR). PC conventions +reserve 64 bytes of the MBR for the partition table, and Pintos uses +about 128 additional bytes for kernel command-line arguments. This +leaves a little over 300 bytes for the loader's own code. This is a +severe restriction that means, practically speaking, the loader must +be written in assembly language. +

+

+ +The Pintos loader and kernel don't have to be on the same disk, nor +does is the kernel required to be in any particular location on a +given disk. The loader's first job, then, is to find the kernel by +reading the partition table on each hard disk, looking for a bootable +partition of the type used for a Pintos kernel. +

+

+ +When the loader finds a bootable kernel partition, it reads the +partition's contents into memory at physical address 128 kB. The +kernel is at the beginning of the partition, which might be larger +than necessary due to partition boundary alignment conventions, so the +loader reads no more than 512 kB (and the Pintos build process +will refuse to produce kernels larger than that). Reading more data +than this would cross into the region from 640 kB to 1 MB that +the PC architecture reserves for hardware and the BIOS, and a standard +PC BIOS does not provide any means to load the kernel above 1 MB. +

+

+ +The loader's final job is to extract the entry point from the loaded +kernel image and transfer control to it. The entry point is not at a +predictable location, but the kernel's ELF header contains a pointer +to it. The loader extracts the pointer and jumps to the location it +points to. +

+

+ +The Pintos kernel command line +is stored in the boot loader. The pintos program actually +modifies a copy of the boot loader on disk each time it runs the kernel, +inserting whatever command-line arguments the user supplies to the kernel, +and then the kernel at boot time reads those arguments out of the boot +loader in memory. This is not an elegant solution, but it is simple +and effective. +

+

+ + +


+ +

A.1.2 Low-Level Kernel Initialization

+ +

+ +The loader's last action is to transfer control to the kernel's entry +point, which is start() in threads/start.S. The job of +this code is to switch the CPU from legacy 16-bit "real mode" into +the 32-bit "protected mode" used by all modern 80x86 operating +systems. +

+

+ +The startup code's first task is actually to obtain the machine's +memory size, by asking the BIOS for the PC's memory size. The +simplest BIOS function to do this can only detect up to 64 MB of RAM, +so that's the practical limit that Pintos can support. The function +stores the memory size, in pages, in global variable +init_ram_pages. +

+

+ +The first part of CPU initialization is to enable the A20 line, that +is, the CPU's address line numbered 20. For historical reasons, PCs +boot with this address line fixed at 0, which means that attempts to +access memory beyond the first 1 MB (2 raised to the 20th power) will +fail. Pintos wants to access more memory than this, so we have to +enable it. +

+

+ +Next, the loader creates a basic page table. This page table maps +the 64 MB at the base of virtual memory (starting at virtual address +0) directly to the identical physical addresses. It also maps the +same physical memory starting at virtual address +LOADER_PHYS_BASE, which defaults to 0xc0000000 (3 GB). The +Pintos kernel only wants the latter mapping, but there's a +chicken-and-egg problem if we don't include the former: our current +virtual address is roughly 0x20000, the location where the loader +put us, and we can't jump to 0xc0020000 until we turn on the +page table, but if we turn on the page table without jumping there, +then we've just pulled the rug out from under ourselves. +

+

+ +After the page table is initialized, we load the CPU's control +registers to turn on protected mode and paging, and set up the segment +registers. We aren't yet equipped to handle interrupts in protected +mode, so we disable interrupts. The final step is to call main(). +

+

+ + +


+ +

A.1.3 High-Level Kernel Initialization

+ +

+ +The kernel proper starts with the main() function. The +main() function is written in C, as will be most of the code we +encounter in Pintos from here on out. +

+

+ +When main() starts, the system is in a pretty raw state. We're +in 32-bit protected mode with paging enabled, but hardly anything else is +ready. Thus, the main() function consists primarily of calls +into other Pintos modules' initialization functions. +These are usually named module_init(), where +module is the module's name, module.c is the +module's source code, and module.h is the module's +header. +

+

+ +The first step in main() is to call bss_init(), which clears +out the kernel's "BSS", which is the traditional name for a +segment that should be initialized to all zeros. In most C +implementations, whenever you +declare a variable outside a function without providing an +initializer, that variable goes into the BSS. Because it's all zeros, the +BSS isn't stored in the image that the loader brought into memory. We +just use memset() to zero it out. +

+

+ +Next, main() calls read_command_line() to break the kernel command +line into arguments, then parse_options() to read any options at +the beginning of the command line. (Actions specified on the +command line execute later.) +

+

+ +thread_init() initializes the thread system. We will defer full +discussion to our discussion of Pintos threads below. It is called so +early in initialization because a valid thread structure is a +prerequisite for acquiring a lock, and lock acquisition in turn is +important to other Pintos subsystems. Then we initialize the console +and print a startup message to the console. +

+

+ +The next block of functions we call initializes the kernel's memory +system. palloc_init() sets up the kernel page allocator, which +doles out memory one or more pages at a time (see section A.5.1 Page Allocator). +malloc_init() sets +up the allocator that handles allocations of arbitrary-size blocks of +memory (see section A.5.2 Block Allocator). +paging_init() sets up a page table for the kernel (see section A.7 Page Table). +

+

+ +In projects 2 and later, main() also calls tss_init() and +gdt_init(). +

+

+ +The next set of calls initializes the interrupt system. +intr_init() sets up the CPU's interrupt descriptor table +(IDT) to ready it for interrupt handling (see section A.4.1 Interrupt Infrastructure), then timer_init() and kbd_init() prepare for +handling timer interrupts and keyboard interrupts, respectively. +input_init() sets up to merge serial and keyboard input into one +stream. In +projects 2 and later, we also prepare to handle interrupts caused by +user programs using exception_init() and syscall_init(). +

+

+ +Now that interrupts are set up, we can start the scheduler +with thread_start(), which creates the idle thread and enables +interrupts. +With interrupts enabled, interrupt-driven serial port I/O becomes +possible, so we use +serial_init_queue() to switch to that mode. Finally, +timer_calibrate() calibrates the timer for accurate short delays. +

+

+ +If the file system is compiled in, as it will starting in project 2, we +initialize the IDE disks with ide_init(), then the +file system with filesys_init(). +

+

+ +Boot is complete, so we print a message. +

+

+ +Function run_actions() now parses and executes actions specified on +the kernel command line, such as run to run a test (in project +1) or a user program (in later projects). +

+

+ +Finally, if -q was specified on the kernel command line, we +call shutdown_power_off() to terminate the machine simulator. Otherwise, +main() calls thread_exit(), which allows any other running +threads to continue running. +

+

+ + +


+ +

A.1.4 Physical Memory Map

+ +

+ +

+ +@headitem Memory Range + + + + + + + + + + + + + + + + + + + + + + + +
Owner + Contents + +
00000000--000003ff CPU Real mode interrupt table.
00000400--000005ff BIOS Miscellaneous data area.
00000600--00007bff -- ---
00007c00--00007dff Pintos Loader.
0000e000--0000efff Pintos Stack for loader; kernel stack and struct thread for initial +kernel thread. +
0000f000--0000ffff Pintos Page directory for startup code. +
00010000--00020000 Pintos Page tables for startup code. +
00020000--0009ffff Pintos Kernel code, data, and uninitialized data segments. +
000a0000--000bffff Video VGA display memory.
000c0000--000effff Hardware Reserved for expansion card RAM and ROM. +
000f0000--000fffff BIOS ROM BIOS.
00100000--03ffffff Pintos Dynamic memory allocation.
+

+ + +


+ +

A.2 Threads

+ +

+ + +


+ +

A.2.1 struct thread

+ +

+ +The main Pintos data structure for threads is struct thread, +declared in threads/thread.h. +

+

+ + +

+
+
Structure: struct thread +
Represents a thread or a user process. In the projects, you will have +to add your own members to struct thread. You may also change or +delete the definitions of existing members. +

+ +Every struct thread occupies the beginning of its own page of +memory. The rest of the page is used for the thread's stack, which +grows downward from the end of the page. It looks like this: +

+

+ +
 
                  4 kB +---------------------------------+
+                       |          kernel stack           |
+                       |                |                |
+                       |                |                |
+                       |                V                |
+                       |         grows downward          |
+                       |                                 |
+                       |                                 |
+                       |                                 |
+                       |                                 |
+                       |                                 |
+                       |                                 |
+                       |                                 |
+                       |                                 |
+sizeof (struct thread) +---------------------------------+
+                       |              magic              |
+                       |                :                |
+                       |                :                |
+                       |              status             |
+                       |               tid               |
+                  0 kB +---------------------------------+
+

+ +This has two consequences. First, struct thread must not be allowed +to grow too big. If it does, then there will not be enough room for the +kernel stack. The base struct thread is only a few bytes in size. It +probably should stay well under 1 kB. +

+

+ +Second, kernel stacks must not be allowed to grow too large. If a stack +overflows, it will corrupt the thread state. Thus, kernel functions +should not allocate large structures or arrays as non-static local +variables. Use dynamic allocation with malloc() or +palloc_get_page() instead (see section A.5 Memory Allocation). +

+
+

+ + +

+
+
Member of struct thread: tid_t tid +
The thread's thread identifier or tid. Every thread must have a +tid that is unique over the entire lifetime of the kernel. By +default, tid_t is a typedef for int and each new +thread receives the numerically next higher tid, starting from 1 for +the initial process. You can change the type and the numbering scheme +if you like. +
+

+ + +

+
+
Member of struct thread: enum thread_status status +
+The thread's state, one of the following: +

+ + +

+
+
Thread State: THREAD_RUNNING +
The thread is running. Exactly one thread is running at a given time. +thread_current() returns the running thread. +
+

+ + +

+
+
Thread State: THREAD_READY +
The thread is ready to run, but it's not running right now. The +thread could be selected to run the next time the scheduler is +invoked. Ready threads are kept in a doubly linked list called +ready_list. +
+

+ + +

+
+
Thread State: THREAD_BLOCKED +
The thread is waiting for something, e.g. a lock to become +available, an interrupt to be invoked. The thread won't be scheduled +again until it transitions to the THREAD_READY state with a +call to thread_unblock(). This is most conveniently done +indirectly, using one of the Pintos synchronization primitives that +block and unblock threads automatically (see section A.3 Synchronization). +

+ +There is no a priori way to tell what a blocked thread is waiting +for, but a backtrace can help (see section D.4 Backtraces). +

+
+

+ + +

+
+
Thread State: THREAD_DYING +
The thread will be destroyed by the scheduler after switching to the +next thread. +
+
+

+ + +

+
+
Member of struct thread: char name[16] +
The thread's name as a string, or at least the first few characters of +it. +
+

+ + +

+
+
Member of struct thread: uint8_t *stack +
Every thread has its own stack to keep track of its state. When the +thread is running, the CPU's stack pointer register tracks the top of +the stack and this member is unused. But when the CPU switches to +another thread, this member saves the thread's stack pointer. No +other members are needed to save the thread's registers, because the +other registers that must be saved are saved on the stack. +

+ +When an interrupt occurs, whether in the kernel or a user program, an +struct intr_frame is pushed onto the stack. When the interrupt occurs +in a user program, the struct intr_frame is always at the very top of +the page. See section A.4 Interrupt Handling, for more information. +

+
+

+ + +

+
+
Member of struct thread: int priority +
A thread priority, ranging from PRI_MIN (0) to PRI_MAX +(63). Lower numbers correspond to lower priorities, so that +priority 0 is the lowest priority and priority 63 is the highest. +Pintos as provided ignores thread priorities, but you will implement +priority scheduling in project 1 (see section 3.2.2 Priority Scheduling). +
+

+ + +

+
+
Member of struct thread: struct list_elem allelem +
This "list element" is used to link the thread into the list of all +threads. Each thread is inserted into this list when it is created +and removed when it exits. The thread_foreach() function should +be used to iterate over all threads. +
+

+ + +

+
+
Member of struct thread: struct list_elem elem +
A "list element" used to put the thread into doubly linked lists, +either ready_list (the list of threads ready to run) or a list of +threads waiting on a semaphore in sema_down(). It can do double +duty because a thread waiting on a semaphore is not ready, and vice +versa. +
+

+ + +

+
+
Member of struct thread: uint32_t *pagedir +
Only present in project 2 and later. See Page Tables. +
+

+ + +

+
+
Member of struct thread: unsigned magic +
Always set to THREAD_MAGIC, which is just an arbitrary number defined +in threads/thread.c, and used to detect stack overflow. +thread_current() checks that the magic member of the running +thread's struct thread is set to THREAD_MAGIC. Stack overflow +tends to change this value, triggering the assertion. For greatest +benefit, as you add members to struct thread, leave magic at +the end. +
+

+ + +


+ +

A.2.2 Thread Functions

+ +

+ +threads/thread.c implements several public functions for thread +support. Let's take a look at the most useful: +

+

+ + +

+
+
Function: void thread_init (void) +
Called by main() to initialize the thread system. Its main +purpose is to create a struct thread for Pintos's initial thread. +This is possible because the Pintos loader puts the initial +thread's stack at the top of a page, in the same position as any other +Pintos thread. +

+ +Before thread_init() runs, +thread_current() will fail because the running thread's +magic value is incorrect. Lots of functions call +thread_current() directly or indirectly, including +lock_acquire() for locking a lock, so thread_init() is +called early in Pintos initialization. +

+
+

+ + +

+
+
Function: void thread_start (void) +
Called by main() to start the scheduler. Creates the idle +thread, that is, the thread that is scheduled when no other thread is +ready. Then enables interrupts, which as a side effect enables the +scheduler because the scheduler runs on return from the timer interrupt, using +intr_yield_on_return() (see section A.4.3 External Interrupt Handling). +
+

+ + +

+
+
Function: void thread_tick (void) +
Called by the timer interrupt at each timer tick. It keeps track of +thread statistics and triggers the scheduler when a time slice expires. +
+

+ + +

+
+
Function: void thread_print_stats (void) +
Called during Pintos shutdown to print thread statistics. +
+

+ + +

+
+
Function: tid_t thread_create (const char *name, int priority, thread_func *func, void *aux) +
Creates and starts a new thread named name with the given +priority, returning the new thread's tid. The thread executes +func, passing aux as the function's single argument. +

+ +thread_create() allocates a page for the thread's +struct thread and stack and initializes its members, then it sets +up a set of fake stack frames for it (see section A.2.3 Thread Switching). The +thread is initialized in the blocked state, then unblocked just before +returning, which allows the new thread to +be scheduled (see Thread States). +

+

+ + +

+
+
Type: void thread_func (void *aux) +
This is the type of the function passed to thread_create(), whose +aux argument is passed along as the function's argument. +
+
+

+ + +

+
+
Function: void thread_block (void) +
Transitions the running thread from the running state to the blocked +state (see Thread States). The thread will not run again until +thread_unblock() is +called on it, so you'd better have some way arranged for that to happen. +Because thread_block() is so low-level, you should prefer to use +one of the synchronization primitives instead (see section A.3 Synchronization). +
+

+ + +

+
+
Function: void thread_unblock (struct thread *thread) +
Transitions thread, which must be in the blocked state, to the +ready state, allowing it to resume running (see Thread States). +This is called when the event that the thread is waiting for occurs, +e.g. when the lock that +the thread is waiting on becomes available. +
+

+ + +

+
+
Function: struct thread *thread_current (void) +
Returns the running thread. +
+

+ + +

+
+
Function: tid_t thread_tid (void) +
Returns the running thread's thread id. Equivalent to +thread_current ()->tid. +
+

+ + +

+
+
Function: const char *thread_name (void) +
Returns the running thread's name. Equivalent to thread_current +()->name. +
+

+ + +

+
+
Function: void thread_exit (void) NO_RETURN +
Causes the current thread to exit. Never returns, hence +NO_RETURN (see section D.3 Function and Parameter Attributes). +
+

+ + +

+
+
Function: void thread_yield (void) +
Yields the CPU to the scheduler, which picks a new thread to run. The +new thread might be the current thread, so you can't depend on this +function to keep this thread from running for any particular length of +time. +
+

+ + +

+
+
Function: void thread_foreach (thread_action_func *action, void *aux) +
Iterates over all threads t and invokes action(t, aux) on each. +action must refer to a function that matches the signature +given by thread_action_func(): +

+ + +

+
+
Type: void thread_action_func (struct thread *thread, void *aux) +
Performs some action on a thread, given aux. +
+
+

+ + +

+
+
Function: int thread_get_priority (void) +
+
Function: void thread_set_priority (int new_priority) +
Stub to set and get thread priority. See section 3.2.2 Priority Scheduling. +
+

+ + +

+
+
Function: int thread_get_nice (void) +
+
Function: void thread_set_nice (int new_nice) +
+
Function: int thread_get_recent_cpu (void) +
+
Function: int thread_get_load_avg (void) +
Stubs for the advanced scheduler (not used in this course). +
+

+ + +


+ +

A.2.3 Thread Switching

+ +

+ +schedule() is responsible for switching threads. It +is internal to threads/thread.c and called only by the three +public thread functions that need to switch threads: +thread_block(), thread_exit(), and thread_yield(). +Before any of these functions call schedule(), they disable +interrupts (or ensure that they are already disabled) and then change +the running thread's state to something other than running. +

+

+ +schedule() is short but tricky. It records the +current thread in local variable cur, determines the next thread +to run as local variable next (by calling +next_thread_to_run()), and then calls switch_threads() to do +the actual thread switch. The thread we switched to was also running +inside switch_threads(), as are all the threads not currently +running, so the new thread now returns out of +switch_threads(), returning the previously running thread. +

+

+ +switch_threads() is an assembly language routine in +threads/switch.S. It saves registers on the stack, saves the +CPU's current stack pointer in the current struct thread's stack +member, restores the new thread's stack into the CPU's stack +pointer, restores registers from the stack, and returns. +

+

+ +The rest of the scheduler is implemented in thread_schedule_tail(). It +marks the new thread as running. If the thread we just switched from +is in the dying state, then it also frees the page that contained the +dying thread's struct thread and stack. These couldn't be freed +prior to the thread switch because the switch needed to use it. +

+

+ +Running a thread for the first time is a special case. When +thread_create() creates a new thread, it goes through a fair +amount of trouble to get it started properly. In particular, the new +thread hasn't started running yet, so there's no way for it to be +running inside switch_threads() as the scheduler expects. To +solve the problem, thread_create() creates some fake stack frames +in the new thread's stack: +

+

+ +

+

+ + +


+ +

A.3 Synchronization

+ +

+ +If sharing of resources between threads is not handled in a careful, +controlled fashion, the result is usually a big mess. +This is especially the case in operating system kernels, where +faulty sharing can crash the entire machine. Pintos provides several +synchronization primitives to help out. +

+

+ + +


+ +

A.3.1 Disabling Interrupts

+ +

+ +The crudest way to do synchronization is to disable interrupts, that +is, to temporarily prevent the CPU from responding to interrupts. If +interrupts are off, no other thread will preempt the running thread, +because thread preemption is driven by the timer interrupt. If +interrupts are on, as they normally are, then the running thread may +be preempted by another at any time, whether between two C statements +or even within the execution of one. +

+

+ +Incidentally, this means that Pintos is a "preemptible kernel," that +is, kernel threads can be preempted at any time. Traditional Unix +systems are "nonpreemptible," that is, kernel threads can only be +preempted at points where they explicitly call into the scheduler. +(User programs can be preempted at any time in both models.) As you +might imagine, preemptible kernels require more explicit +synchronization. +

+

+ +You should have little need to set the interrupt state directly. Most +of the time you should use the other synchronization primitives +described in the following sections. The main reason to disable +interrupts is to synchronize kernel threads with external interrupt +handlers, which cannot sleep and thus cannot use most other forms of +synchronization (see section A.4.3 External Interrupt Handling). +

+

+ +Some external interrupts cannot be postponed, even by disabling +interrupts. These interrupts, called non-maskable interrupts +(NMIs), are supposed to be used only in emergencies, e.g. when the +computer is on fire. Pintos does not handle non-maskable interrupts. +

+

+ +Types and functions for disabling and enabling interrupts are in +threads/interrupt.h. +

+

+ + +

+
+
Type: enum intr_level +
One of INTR_OFF or INTR_ON, denoting that interrupts are +disabled or enabled, respectively. +
+

+ + +

+
+
Function: enum intr_level intr_get_level (void) +
Returns the current interrupt state. +
+

+ + +

+
+
Function: enum intr_level intr_set_level (enum intr_level level) +
Turns interrupts on or off according to level. Returns the +previous interrupt state. +
+

+ + +

+
+
Function: enum intr_level intr_enable (void) +
Turns interrupts on. Returns the previous interrupt state. +
+

+ + +

+
+
Function: enum intr_level intr_disable (void) +
Turns interrupts off. Returns the previous interrupt state. +
+

+ + +


+ +

A.3.2 Semaphores

+ +

+ +A semaphore is a nonnegative integer together with two operators +that manipulate it atomically, which are: +

+

+ +

+

+ +A semaphore initialized to 0 may be used to wait for an event +that will happen exactly once. For example, suppose thread A +starts another thread B and wants to wait for B to signal +that some activity is complete. A can create a semaphore +initialized to 0, pass it to B as it starts it, and then +"down" the semaphore. When B finishes its activity, it +"ups" the semaphore. This works regardless of whether A +"downs" the semaphore or B "ups" it first. +

+

+ +A semaphore initialized to 1 is typically used for controlling access +to a resource. Before a block of code starts using the resource, it +"downs" the semaphore, then after it is done with the resource it +"ups" the resource. In such a case a lock, described below, may be +more appropriate. +

+

+ +Semaphores can also be initialized to values larger than 1. These are +rarely used. +

+

+ +Semaphores were invented by Edsger Dijkstra and first used in the THE +operating system ([ Dijkstra]). +

+

+ +Pintos' semaphore type and operations are declared in +threads/synch.h. +

+

+ + +

+
+
Type: struct semaphore +
Represents a semaphore. +
+

+ + +

+
+
Function: void sema_init (struct semaphore *sema, unsigned value) +
Initializes sema as a new semaphore with the given initial +value. +
+

+ + +

+
+
Function: void sema_down (struct semaphore *sema) +
Executes the "down" or "P" operation on sema, waiting for +its value to become positive and then decrementing it by one. +
+

+ + +

+
+
Function: bool sema_try_down (struct semaphore *sema) +
Tries to execute the "down" or "P" operation on sema, +without waiting. Returns true if sema +was successfully decremented, or false if it was already +zero and thus could not be decremented without waiting. Calling this +function in a +tight loop wastes CPU time, so use sema_down() or find a +different approach instead. +
+

+ + +

+
+
Function: void sema_up (struct semaphore *sema) +
Executes the "up" or "V" operation on sema, +incrementing its value. If any threads are waiting on +sema, wakes one of them up. +

+ +Unlike most synchronization primitives, sema_up() may be called +inside an external interrupt handler (see section A.4.3 External Interrupt Handling). +

+
+

+ +Semaphores are internally built out of disabling interrupt +(see section A.3.1 Disabling Interrupts) and thread blocking and unblocking +(thread_block() and thread_unblock()). Each semaphore maintains +a list of waiting threads, using the linked list +implementation in lib/kernel/list.c. +

+

+ + +


+ +

A.3.3 Locks

+ +

+ +A lock is like a semaphore with an initial value of 1 +(see section A.3.2 Semaphores). A lock's equivalent of "up" is called +"release", and the "down" operation is called "acquire". +

+

+ +Compared to a semaphore, a lock has one added restriction: only the +thread that acquires a lock, called the lock's "owner", is allowed to +release it. If this restriction is a problem, it's a good sign that a +semaphore should be used, instead of a lock. +

+

+ +Locks in Pintos are not "recursive," that is, it is an error for the +thread currently holding a lock to try to acquire that lock. +

+

+ +Lock types and functions are declared in threads/synch.h. +

+

+ + +

+
+
Type: struct lock +
Represents a lock. +
+

+ + +

+
+
Function: void lock_init (struct lock *lock) +
Initializes lock as a new lock. +The lock is not initially owned by any thread. +
+

+ + +

+
+
Function: void lock_acquire (struct lock *lock) +
Acquires lock for the current thread, first waiting for +any current owner to release it if necessary. +
+

+ + +

+
+
Function: bool lock_try_acquire (struct lock *lock) +
Tries to acquire lock for use by the current thread, without +waiting. Returns true if successful, false if the lock is already +owned. Calling this function in a tight loop is a bad idea because it +wastes CPU time, so use lock_acquire() instead. +
+

+ + +

+
+
Function: void lock_release (struct lock *lock) +
Releases lock, which the current thread must own. +
+

+ + +

+
+
Function: bool lock_held_by_current_thread (const struct lock *lock) +
Returns true if the running thread owns lock, +false otherwise. +There is no function to test whether an arbitrary thread owns a lock, +because the answer could change before the caller could act on it. +
+

+ + +


+ +

A.3.4 Monitors

+ +

+ +A monitor is a higher-level form of synchronization than a +semaphore or a lock. A monitor consists of data being synchronized, +plus a lock, called the monitor lock, and one or more +condition variables. Before it accesses the protected data, a +thread first acquires the monitor lock. It is then said to be "in the +monitor". While in the monitor, the thread has control over all the +protected data, which it may freely examine or modify. When access to +the protected data is complete, it releases the monitor lock. +

+

+ +Condition variables allow code in the monitor to wait for a condition to +become true. Each condition variable is associated with an abstract +condition, e.g. "some data has arrived for processing" or "over 10 +seconds has passed since the user's last keystroke". When code in the +monitor needs to wait for a condition to become true, it "waits" on +the associated condition variable, which releases the lock and waits for +the condition to be signaled. If, on the other hand, it has caused one +of these conditions to become true, it "signals" the condition to wake +up one waiter, or "broadcasts" the condition to wake all of them. +

+

+ +The theoretical framework for monitors was laid out by C. A. R. +Hoare ([ Hoare]). Their practical usage was later elaborated in a +paper on the Mesa operating system ([ Lampson]). +

+

+ +Condition variable types and functions are declared in +threads/synch.h. +

+

+ + +

+
+
Type: struct condition +
Represents a condition variable. +
+

+ + +

+
+
Function: void cond_init (struct condition *cond) +
Initializes cond as a new condition variable. +
+

+ + +

+
+
Function: void cond_wait (struct condition *cond, struct lock *lock) +
Atomically releases lock (the monitor lock) and waits for +cond to be signaled by some other piece of code. After +cond is signaled, reacquires lock before returning. +lock must be held before calling this function. +

+ +Sending a signal and waking up from a wait are not an atomic operation. +Thus, typically cond_wait()'s caller must recheck the condition +after the wait completes and, if necessary, wait again. See the next +section for an example. +

+
+

+ + +

+
+
Function: void cond_signal (struct condition *cond, struct lock *lock) +
If any threads are waiting on cond (protected by monitor lock +lock), then this function wakes up one of them. If no threads are +waiting, returns without performing any action. +lock must be held before calling this function. +
+

+ + +

+
+
Function: void cond_broadcast (struct condition *cond, struct lock *lock) +
Wakes up all threads, if any, waiting on cond (protected by +monitor lock lock). lock must be held before calling this +function. +
+

+ +


+ +

A.3.4.1 Monitor Example

+ +

+ +The classical example of a monitor is handling a buffer into which one +or more +"producer" threads write characters and out of which one or more +"consumer" threads read characters. To implement this we need, +besides the monitor lock, two condition variables which we will call +not_full and not_empty: +

+

+ +
 
char buf[BUF_SIZE];     /* Buffer. */
+size_t n = 0;           /* 0 <= n <= BUF_SIZE: # of characters in buffer. */
+size_t head = 0;        /* buf index of next char to write (mod BUF_SIZE). */
+size_t tail = 0;        /* buf index of next char to read (mod BUF_SIZE). */
+struct lock lock;       /* Monitor lock. */
+struct condition not_empty; /* Signaled when the buffer is not empty. */
+struct condition not_full; /* Signaled when the buffer is not full. */
+
+...initialize the locks and condition variables...
+
+void put (char ch) {
+  lock_acquire (&lock);
+  while (n == BUF_SIZE)            /* Can't add to buf as long as it's full. */
+    cond_wait (&not_full, &lock);
+  buf[head++ % BUF_SIZE] = ch;     /* Add ch to buf. */
+  n++;
+  cond_signal (&not_empty, &lock); /* buf can't be empty anymore. */
+  lock_release (&lock);
+}
+
+char get (void) {
+  char ch;
+  lock_acquire (&lock);
+  while (n == 0)                  /* Can't read buf as long as it's empty. */
+    cond_wait (&not_empty, &lock);
+  ch = buf[tail++ % BUF_SIZE];    /* Get ch from buf. */
+  n--;
+  cond_signal (&not_full, &lock); /* buf can't be full anymore. */
+  lock_release (&lock);
+}
+

+ +Note that BUF_SIZE must divide evenly into SIZE_MAX + 1 +for the above code to be completely correct. Otherwise, it will fail +the first time head wraps around to 0. In practice, +BUF_SIZE would ordinarily be a power of 2. +

+

+ + +


+ +

A.3.5 Optimization Barriers

+ +

+ +An optimization barrier is a special statement that prevents the +compiler from making assumptions about the state of memory across the +barrier. The compiler will not reorder reads or writes of variables +across the barrier or assume that a variable's value is unmodified +across the barrier, except for local variables whose address is never +taken. In Pintos, threads/synch.h defines the barrier() +macro as an optimization barrier. +

+

+ +One reason to use an optimization barrier is when data can change +asynchronously, without the compiler's knowledge, e.g. by another +thread or an interrupt handler. The too_many_loops() function in +devices/timer.c is an example. This function starts out by +busy-waiting in a loop until a timer tick occurs: +

+

+ +
 
/* Wait for a timer tick. */
+int64_t start = ticks;
+while (ticks == start)
+  barrier ();
+

+ +Without an optimization barrier in the loop, the compiler could +conclude that the loop would never terminate, because start and +ticks start out equal and the loop itself never changes them. +It could then "optimize" the function into an infinite loop, which +would definitely be undesirable. +

+

+ +Optimization barriers can be used to avoid other compiler +optimizations. The busy_wait() function, also in +devices/timer.c, is an example. It contains this loop: +

+

+ +
 
while (loops-- > 0)
+  barrier ();
+

+ +The goal of this loop is to busy-wait by counting loops down +from its original value to 0. Without the barrier, the compiler could +delete the loop entirely, because it produces no useful output and has +no side effects. The barrier forces the compiler to pretend that the +loop body has an important effect. +

+

+ +Finally, optimization barriers can be used to force the ordering of +memory reads or writes. For example, suppose we add a "feature" +that, whenever a timer interrupt occurs, the character in global +variable timer_put_char is printed on the console, but only if +global Boolean variable timer_do_put is true. The best way to +set up x to be printed is then to use an optimization barrier, +like this: +

+

+ +
 
timer_put_char = 'x';
+barrier ();
+timer_do_put = true;
+

+ +Without the barrier, the code is buggy because the compiler is free to +reorder operations when it doesn't see a reason to keep them in the +same order. In this case, the compiler doesn't know that the order of +assignments is important, so its optimizer is permitted to exchange +their order. There's no telling whether it will actually do this, and +it is possible that passing the compiler different optimization flags +or using a different version of the compiler will produce different +behavior. +

+

+ +Another solution is to disable interrupts around the assignments. +This does not prevent reordering, but it prevents the interrupt +handler from intervening between the assignments. It also has the +extra runtime cost of disabling and re-enabling interrupts: +

+

+ +
 
enum intr_level old_level = intr_disable ();
+timer_put_char = 'x';
+timer_do_put = true;
+intr_set_level (old_level);
+

+ +A second solution is to mark the declarations of +timer_put_char and timer_do_put as volatile. This +keyword tells the compiler that the variables are externally observable +and restricts its latitude for optimization. However, the semantics of +volatile are not well-defined, so it is not a good general +solution. The base Pintos code does not use volatile at all. +

+

+ +The following is not a solution, because locks neither prevent +interrupts nor prevent the compiler from reordering the code within the +region where the lock is held: +

+

+ +
 
lock_acquire (&timer_lock);     /* INCORRECT CODE */
+timer_put_char = 'x';
+timer_do_put = true;
+lock_release (&timer_lock);
+

+ +The compiler treats invocation of any function defined externally, +that is, in another source file, as a limited form of optimization +barrier. Specifically, the compiler assumes that any externally +defined function may access any statically or dynamically allocated +data and any local variable whose address is taken. This often means +that explicit barriers can be omitted. It is one reason that Pintos +contains few explicit barriers. +

+

+ +A function defined in the same source file, or in a header included by +the source file, cannot be relied upon as a optimization barrier. +This applies even to invocation of a function before its +definition, because the compiler may read and parse the entire source +file before performing optimization. +

+

+ + +


+ +

A.4 Interrupt Handling

+ +

+ +An interrupt notifies the CPU of some event. Much of the work +of an operating system relates to interrupts in one way or another. +For our purposes, we classify interrupts into two broad categories: +

+

+ +

+

+ +The CPU treats both classes of interrupts largely the same way, +so Pintos has common infrastructure to handle both classes. +The following section describes this +common infrastructure. The sections after that give the specifics of +external and internal interrupts. +

+

+ +If you haven't already read chapter 3, "Basic Execution Environment," +in [ IA32-v1], it is recommended that you do so now. You might +also want to skim chapter 5, "Interrupt and Exception Handling," in +[ IA32-v3a]. +

+

+ + +


+ +

A.4.1 Interrupt Infrastructure

+ +

+ +When an interrupt occurs, the CPU saves +its most essential state on a stack and jumps to an interrupt +handler routine. The 80x86 architecture supports 256 +interrupts, numbered 0 through 255, each with an independent +handler defined in an array called the interrupt +descriptor table or IDT. +

+

+ +In Pintos, intr_init() in threads/interrupt.c sets up the +IDT so that each entry points to a unique entry point in +threads/intr-stubs.S named intrNN_stub(), where +NN is the interrupt number in +hexadecimal. Because the CPU doesn't give +us any other way to find out the interrupt number, this entry point +pushes the interrupt number on the stack. Then it jumps to +intr_entry(), which pushes all the registers that the processor +didn't already push for us, and then calls intr_handler(), which +brings us back into C in threads/interrupt.c. +

+

+ +The main job of intr_handler() is to call the function +registered for handling the particular interrupt. (If no +function is registered, it dumps some information to the console and +panics.) It also does some extra processing for external +interrupts (see section A.4.3 External Interrupt Handling). +

+

+ +When intr_handler() returns, the assembly code in +threads/intr-stubs.S restores all the CPU registers saved +earlier and directs the CPU to return from the interrupt. +

+

+ +The following types and functions are common to all +interrupts. +

+

+ + +

+
+
Type: void intr_handler_func (struct intr_frame *frame) +
This is how an interrupt handler function must be declared. Its frame +argument (see below) allows it to determine the cause of the interrupt +and the state of the thread that was interrupted. +
+

+ + +

+
+
Type: struct intr_frame +
The stack frame of an interrupt handler, as saved by the CPU, the interrupt +stubs, and intr_entry(). Its most interesting members are described +below. +
+

+ + +

+
+
Member of struct intr_frame: uint32_t edi +
+
Member of struct intr_frame: uint32_t esi +
+
Member of struct intr_frame: uint32_t ebp +
+
Member of struct intr_frame: uint32_t esp_dummy +
+
Member of struct intr_frame: uint32_t ebx +
+
Member of struct intr_frame: uint32_t edx +
+
Member of struct intr_frame: uint32_t ecx +
+
Member of struct intr_frame: uint32_t eax +
+
Member of struct intr_frame: uint16_t es +
+
Member of struct intr_frame: uint16_t ds +
Register values in the interrupted thread, pushed by intr_entry(). +The esp_dummy value isn't actually used (refer to the +description of PUSHA in [ IA32-v2b] for details). +
+

+ + +

+
+
Member of struct intr_frame: uint32_t vec_no +
The interrupt vector number, ranging from 0 to 255. +
+

+ + +

+
+
Member of struct intr_frame: uint32_t error_code +
The "error code" pushed on the stack by the CPU for some internal +interrupts. +
+

+ + +

+
+
Member of struct intr_frame: void (*eip) (void) +
The address of the next instruction to be executed by the interrupted +thread. +
+

+ + +

+
+
Member of struct intr_frame: void *esp +
The interrupted thread's stack pointer. +
+

+ + +

+
+
Function: const char *intr_name (uint8_t vec) +
Returns the name of the interrupt numbered vec, or +"unknown" if the interrupt has no registered name. +
+

+ + +


+ +

A.4.2 Internal Interrupt Handling

+ +

+ +Internal interrupts are caused directly by CPU instructions executed by +the running kernel thread or user process (from project 2 onward). An +internal interrupt is therefore said to arise in a "process context." +

+

+ +In an internal interrupt's handler, it can make sense to examine the +struct intr_frame passed to the interrupt handler, or even to modify +it. When the interrupt returns, modifications in struct intr_frame +become changes to the calling thread or process's state. For example, +the Pintos system call handler returns a value to the user program by +modifying the saved EAX register (see section 2.5.2 System Call Details). +

+

+ +There are no special restrictions on what an internal interrupt +handler can or can't do. Generally they should run with interrupts +enabled, just like other code, and so they can be preempted by other +kernel threads. Thus, they do need to synchronize with other threads +on shared data and other resources (see section A.3 Synchronization). +

+

+ +Internal interrupt handlers can be invoked recursively. For example, +the system call handler might cause a page fault while attempting to +read user memory. Deep recursion would risk overflowing the limited +kernel stack (see section A.2.1 struct thread), but should be unnecessary. +

+

+ + +

+
+
Function: void intr_register_int (uint8_t vec, int dpl, enum intr_level level, intr_handler_func *handler, const char *name) +
Registers handler to be called when internal interrupt numbered +vec is triggered. Names the interrupt name for debugging +purposes. +

+ +If level is INTR_ON, external interrupts will be processed +normally during the interrupt handler's execution, which is normally +desirable. Specifying INTR_OFF will cause the CPU to disable +external interrupts when it invokes the interrupt handler. The effect +is slightly different from calling intr_disable() inside the +handler, because that leaves a window of one or more CPU instructions in +which external interrupts are still enabled. This is important for the +page fault handler; refer to the comments in userprog/exception.c +for details. +

+

+ +dpl determines how the interrupt can be invoked. If dpl is +0, then the interrupt can be invoked only by kernel threads. Otherwise +dpl should be 3, which allows user processes to invoke the +interrupt with an explicit INT instruction. The value of dpl +doesn't affect user processes' ability to invoke the interrupt +indirectly, e.g. an invalid memory reference will cause a page fault +regardless of dpl. +

+
+

+ + +


+ +

A.4.3 External Interrupt Handling

+ +

+ +External interrupts are caused by events outside the CPU. +They are asynchronous, so they can be invoked at any time that +interrupts have not been disabled. We say that an external interrupt +runs in an "interrupt context." +

+

+ +In an external interrupt, the struct intr_frame passed to the +handler is not very meaningful. It describes the state of the thread +or process that was interrupted, but there is no way to predict which +one that is. It is possible, although rarely useful, to examine it, but +modifying it is a recipe for disaster. +

+

+ +Only one external interrupt may be processed at a time. Neither +internal nor external interrupt may nest within an external interrupt +handler. Thus, an external interrupt's handler must run with interrupts +disabled (see section A.3.1 Disabling Interrupts). +

+

+ +An external interrupt handler must not sleep or yield, which rules out +calling lock_acquire(), thread_yield(), and many other +functions. Sleeping in interrupt context would effectively put the +interrupted thread to sleep, too, until the interrupt handler was again +scheduled and returned. This would be unfair to the unlucky thread, and +it would deadlock if the handler were waiting for the sleeping thread +to, e.g., release a lock. +

+

+ +An external interrupt handler +effectively monopolizes the machine and delays all other activities. +Therefore, external interrupt handlers should complete as quickly as +they can. Anything that require much CPU time should instead run in a +kernel thread, possibly one that the interrupt triggers using a +synchronization primitive. +

+

+ +External interrupts are controlled by a +pair of devices outside the CPU called programmable interrupt +controllers, PICs for short. When intr_init() sets up the +CPU's IDT, it also initializes the PICs for interrupt handling. The +PICs also must be "acknowledged" at the end of processing for each +external interrupt. intr_handler() takes care of that by calling +pic_end_of_interrupt(), which properly signals the PICs. +

+

+ +The following functions relate to external +interrupts. +

+

+ + +

+
+
Function: void intr_register_ext (uint8_t vec, intr_handler_func *handler, const char *name) +
Registers handler to be called when external interrupt numbered +vec is triggered. Names the interrupt name for debugging +purposes. The handler will run with interrupts disabled. +
+

+ + +

+
+
Function: bool intr_context (void) +
Returns true if we are running in an interrupt context, otherwise +false. Mainly used in functions that might sleep +or that otherwise should not be called from interrupt context, in this +form: +
 
ASSERT (!intr_context ());
+
+

+ + +

+
+
Function: void intr_yield_on_return (void) +
When called in an interrupt context, causes thread_yield() to be +called just before the interrupt returns. Used +in the timer interrupt handler when a thread's time slice expires, to +cause a new thread to be scheduled. +
+

+ + +


+ +

A.5 Memory Allocation

+ +

+ +Pintos contains two memory allocators, one that allocates memory in +units of a page, and one that can allocate blocks of any size. +

+

+ + +


+ +

A.5.1 Page Allocator

+ +

+ +The page allocator declared in threads/palloc.h allocates +memory in units of a page. It is most often used to allocate memory +one page at a time, but it can also allocate multiple contiguous pages +at once. +

+

+ +The page allocator divides the memory it allocates into two pools, +called the kernel and user pools. By default, each pool gets half of +system memory above 1 MB, but the division can be changed with the +-ul kernel +command line +option (see Why PAL_USER?). An allocation request draws from one +pool or the other. If one pool becomes empty, the other may still +have free pages. The user pool should be used for allocating memory +for user processes and the kernel pool for all other allocations. +This will only become important starting with project 3. Until then, +all allocations should be made from the kernel pool. +

+

+ +Each pool's usage is tracked with a bitmap, one bit per page in +the pool. A request to allocate n pages scans the bitmap +for n consecutive bits set to +false, indicating that those pages are free, and then sets those bits +to true to mark them as used. This is a "first fit" allocation +strategy (see Wilson). +

+

+ +The page allocator is subject to fragmentation. That is, it may not +be possible to allocate n contiguous pages even though n +or more pages are free, because the free pages are separated by used +pages. In fact, in pathological cases it may be impossible to +allocate 2 contiguous pages even though half of the pool's pages are free. +Single-page requests can't fail due to fragmentation, so +requests for multiple contiguous pages should be limited as much as +possible. +

+

+ +Pages may not be allocated from interrupt context, but they may be +freed. +

+

+ +When a page is freed, all of its bytes are cleared to 0xcc, as +a debugging aid (see section D.8 Tips). +

+

+ +Page allocator types and functions are described below. +

+

+ + +

+
+
Function: void *palloc_get_page (enum palloc_flags flags) +
+
Function: void *palloc_get_multiple (enum palloc_flags flags, size_t page_cnt) +
Obtains and returns one page, or page_cnt contiguous pages, +respectively. Returns a null pointer if the pages cannot be allocated. +

+ +The flags argument may be any combination of the following flags: +

+

+ + +

+
+
Page Allocator Flag: PAL_ASSERT +
If the pages cannot be allocated, panic the kernel. This is only +appropriate during kernel initialization. User processes +should never be permitted to panic the kernel. +
+

+ + +

+
+
Page Allocator Flag: PAL_ZERO +
Zero all the bytes in the allocated pages before returning them. If not +set, the contents of newly allocated pages are unpredictable. +
+

+ + +

+
+
Page Allocator Flag: PAL_USER +
Obtain the pages from the user pool. If not set, pages are allocated +from the kernel pool. +
+
+

+ + +

+
+
Function: void palloc_free_page (void *page) +
+
Function: void palloc_free_multiple (void *pages, size_t page_cnt) +
Frees one page, or page_cnt contiguous pages, respectively, +starting at pages. All of the pages must have been obtained using +palloc_get_page() or palloc_get_multiple(). +
+

+ + +


+ +

A.5.2 Block Allocator

+ +

+ +The block allocator, declared in threads/malloc.h, can allocate +blocks of any size. It is layered on top of the page allocator +described in the previous section. Blocks returned by the block +allocator are obtained from the kernel pool. +

+

+ +The block allocator uses two different strategies for allocating memory. +The first strategy applies to blocks that are 1 kB or smaller +(one-fourth of the page size). These allocations are rounded up to the +nearest power of 2, or 16 bytes, whichever is larger. Then they are +grouped into a page used only for allocations of that size. +

+

+ +The second strategy applies to blocks larger than 1 kB. +These allocations (plus a small amount of overhead) are rounded up to +the nearest page in size, and then the block allocator requests that +number of contiguous pages from the page allocator. +

+

+ +In either case, the difference between the allocation requested size +and the actual block size is wasted. A real operating system would +carefully tune its allocator to minimize this waste, but this is +unimportant in an instructional system like Pintos. +

+

+ +As long as a page can be obtained from the page allocator, small +allocations always succeed. Most small allocations do not require a +new page from the page allocator at all, because they are satisfied +using part of a page already allocated. However, large allocations +always require calling into the page allocator, and any allocation +that needs more than one contiguous page can fail due to fragmentation, +as already discussed in the previous section. Thus, you should +minimize the number of large allocations in your code, especially +those over approximately 4 kB each. +

+

+ +When a block is freed, all of its bytes are cleared to 0xcc, as +a debugging aid (see section D.8 Tips). +

+

+ +The block allocator may not be called from interrupt context. +

+

+ +The block allocator functions are described below. Their interfaces are +the same as the standard C library functions of the same names. +

+

+ + +

+
+
Function: void *malloc (size_t size) +
Obtains and returns a new block, from the kernel pool, at least +size bytes long. Returns a null pointer if size is zero or +if memory is not available. +
+

+ + +

+
+
Function: void *calloc (size_t a, size_t b) +
Obtains a returns a new block, from the kernel pool, at least +a * b bytes long. The block's contents will be +cleared to zeros. Returns a null pointer if a or b is zero +or if insufficient memory is available. +
+

+ + +

+
+
Function: void *realloc (void *block, size_t new_size) +
Attempts to resize block to new_size bytes, possibly moving +it in the process. If successful, returns the new block, in which case +the old block must no longer be accessed. On failure, returns a null +pointer, and the old block remains valid. +

+ +A call with block null is equivalent to malloc(). A call +with new_size zero is equivalent to free(). +

+
+

+ + +

+
+
Function: void free (void *block) +
Frees block, which must have been previously returned by +malloc(), calloc(), or realloc() (and not yet freed). +
+

+ + +


+ +

A.6 Virtual Addresses

+ +

+ +A 32-bit virtual address can be divided into a 20-bit page number +and a 12-bit page offset (or just offset), like this: +

+

+ +
 
               31               12 11        0
+              +-------------------+-----------+
+              |    Page Number    |   Offset  |
+              +-------------------+-----------+
+                       Virtual Address
+

+ +Header threads/vaddr.h defines these functions and macros for +working with virtual addresses: +

+

+ + +

+
+
Macro: PGSHIFT +
+
Macro: PGBITS +
The bit index (0) and number of bits (12) of the offset part of a +virtual address, respectively. +
+

+ + +

+
+
Macro: PGMASK +
A bit mask with the bits in the page offset set to 1, the rest set to 0 +(0xfff). +
+

+ + +

+
+
Macro: PGSIZE +
The page size in bytes (4,096). +
+

+ + +

+
+
Function: unsigned pg_ofs (const void *va) +
Extracts and returns the page offset in virtual address va. +
+

+ + +

+
+
Function: uintptr_t pg_no (const void *va) +
Extracts and returns the page number in virtual address va. +
+

+ + +

+
+
Function: void *pg_round_down (const void *va) +
Returns the start of the virtual page that va points within, that +is, va with the page offset set to 0. +
+

+ + +

+
+
Function: void *pg_round_up (const void *va) +
Returns va rounded up to the nearest page boundary. +
+

+ +Virtual memory in Pintos is divided into two regions: user virtual +memory and kernel virtual memory (see section 2.2.4 Virtual Memory Layout). The +boundary between them is PHYS_BASE: +

+

+ + +

+
+
Macro: PHYS_BASE +
Base address of kernel virtual memory. It defaults to 0xc0000000 (3 +GB), but it may be changed to any multiple of 0x10000000 from +0x80000000 to 0xf0000000. +

+ +User virtual memory ranges from virtual address 0 up to +PHYS_BASE. Kernel virtual memory occupies the rest of the +virtual address space, from PHYS_BASE up to 4 GB. +

+
+

+ + +

+
+
Function: bool is_user_vaddr (const void *va) +
+
Function: bool is_kernel_vaddr (const void *va) +
Returns true if va is a user or kernel virtual address, +respectively, false otherwise. +
+

+ +The 80x86 doesn't provide any way to directly access memory given +a physical address. This ability is often necessary in an operating +system kernel, so Pintos works around it by mapping kernel virtual +memory one-to-one to physical memory. That is, virtual address +PHYS_BASE accesses physical address 0, virtual address +PHYS_BASE + 0x1234 accesses physical address 0x1234, and +so on up to the size of the machine's physical memory. Thus, adding +PHYS_BASE to a physical address obtains a kernel virtual address +that accesses that address; conversely, subtracting PHYS_BASE +from a kernel virtual address obtains the corresponding physical +address. Header threads/vaddr.h provides a pair of functions to +do these translations: +

+

+ + +

+
+
Function: void *ptov (uintptr_t pa) +
Returns the kernel virtual address corresponding to physical address +pa, which should be between 0 and the number of bytes of physical +memory. +
+

+ + +

+
+
Function: uintptr_t vtop (void *va) +
Returns the physical address corresponding to va, which must be a +kernel virtual address. +
+

+ + +


+ +

A.7 Page Table

+ +

+ +The code in pagedir.c is an abstract interface to the 80x86 +hardware page table, also called a "page directory" by Intel processor +documentation. The page table interface uses a uint32_t * to +represent a page table because this is convenient for accessing their +internal structure. +

+

+ +The sections below describe the page table interface and internals. +

+

+ + +


+ +

A.7.1 Creation, Destruction, and Activation

+ +

+ +These functions create, destroy, and activate page tables. The base +Pintos code already calls these functions where necessary, so it should +not be necessary to call them yourself. +

+

+ + +

+
+
Function: uint32_t *pagedir_create (void) +
Creates and returns a new page table. The new page table contains +Pintos's normal kernel virtual page mappings, but no user virtual +mappings. +

+ +Returns a null pointer if memory cannot be obtained. +

+
+

+ + +

+
+
Function: void pagedir_destroy (uint32_t *pd) +
Frees all of the resources held by pd, including the page table +itself and the frames that it maps. +
+

+ + +

+
+
Function: void pagedir_activate (uint32_t *pd) +
Activates pd. The active page table is the one used by the CPU to +translate memory references. +
+

+ + +


+ +

A.7.2 Inspection and Updates

+ +

+ +These functions examine or update the mappings from pages to frames +encapsulated by a page table. They work on both active and inactive +page tables (that is, those for running and suspended processes), +flushing the TLB as necessary. +

+

+ + +

+
+
Function: bool pagedir_set_page (uint32_t *pd, void *upage, void *kpage, bool writable) +
Adds to pd a mapping from user page upage to the frame identified +by kernel virtual address kpage. If writable is true, the +page is mapped read/write; otherwise, it is mapped read-only. +

+ +User page upage must not already be mapped in pd. +

+

+ +Kernel page kpage should be a kernel virtual address obtained from +the user pool with palloc_get_page(PAL_USER) (see Why PAL_USER?). +

+

+ +Returns true if successful, false on failure. Failure will occur if +additional memory required for the page table cannot be obtained. +

+
+

+ + +

+
+
Function: void *pagedir_get_page (uint32_t *pd, const void *uaddr) +
Looks up the frame mapped to uaddr in pd. Returns the +kernel virtual address for that frame, if uaddr is mapped, or a +null pointer if it is not. +
+

+ + +

+
+
Function: void pagedir_clear_page (uint32_t *pd, void *page) +
Marks page "not present" in pd. Later accesses to +the page will fault. +

+ +Other bits in the page table for page are preserved, permitting +the accessed and dirty bits (see the next section) to be checked. +

+

+ +This function has no effect if page is not mapped. +

+
+

+ + +


+ +

A.7.3 Accessed and Dirty Bits

+ +

+ +80x86 hardware provides some assistance for implementing page +replacement algorithms, through a pair of bits in the page table entry +(PTE) for each page. On any read or write to a page, the CPU sets the +accessed bit to 1 in the page's PTE, and on any write, the CPU +sets the dirty bit to 1. The CPU never resets these bits to 0, +but the OS may do so. +

+

+ +Proper interpretation of these bits requires understanding of +aliases, that is, two (or more) pages that refer to the same +frame. When an aliased frame is accessed, the accessed and dirty bits +are updated in only one page table entry (the one for the page used for +access). The accessed and dirty bits for the other aliases are not +updated. +

+

+ +See Accessed and Dirty Bits, on applying these bits in implementing +page replacement algorithms. +

+

+ + +

+
+
Function: bool pagedir_is_dirty (uint32_t *pd, const void *page) +
+
Function: bool pagedir_is_accessed (uint32_t *pd, const void *page) +
Returns true if page directory pd contains a page table entry for +page that is marked dirty (or accessed). Otherwise, +returns false. +
+

+ + +

+
+
Function: void pagedir_set_dirty (uint32_t *pd, const void *page, bool value) +
+
Function: void pagedir_set_accessed (uint32_t *pd, const void *page, bool value) +
If page directory pd has a page table entry for page, then +its dirty (or accessed) bit is set to value. +
+

+ + +


+ +

A.7.4 Page Table Details

+ +

+ +The functions provided with Pintos are sufficient to implement the +projects. However, you may still find it worthwhile to understand the +hardware page table format, so we'll go into a little detail in this +section. +

+

+ + +


+ +

A.7.4.1 Structure

+ +

+ +The top-level paging data structure is a page called the "page +directory" (PD) arranged as an array of 1,024 32-bit page directory +entries (PDEs), each of which represents 4 MB of virtual memory. Each +PDE may point to the physical address of another page called a +"page table" (PT) arranged, similarly, as an array of 1,024 +32-bit page table entries (PTEs), each of which translates a single 4 +kB virtual page to a physical page. +

+

+ +Translation of a virtual address into a physical address follows +the three-step process illustrated in the diagram +below:(4) +

+

+ +

    +
  1. +The most-significant 10 bits of the virtual address (bits 22...31) +index the page directory. If the PDE is marked "present," the +physical address of a page table is read from the PDE thus obtained. +If the PDE is marked "not present" then a page fault occurs. +

    + +

    +
  2. +The next 10 bits of the virtual address (bits 12...21) index +the page table. If the PTE is marked "present," the physical +address of a data page is read from the PTE thus obtained. If the PTE +is marked "not present" then a page fault occurs. +

    + +

    +
  3. +The least-significant 12 bits of the virtual address (bits 0...11) +are added to the data page's physical base address, yielding the final +physical address. +
+

+ +
 
 31                  22 21                  12 11                   0
++----------------------+----------------------+----------------------+
+| Page Directory Index |   Page Table Index   |    Page Offset       |
++----------------------+----------------------+----------------------+
+             |                    |                     |
+     _______/             _______/                _____/
+    /                    /                       /
+   /    Page Directory  /      Page Table       /    Data Page
+  /     .____________. /     .____________.    /   .____________.
+  |1,023|____________| |1,023|____________|    |   |____________|
+  |1,022|____________| |1,022|____________|    |   |____________|
+  |1,021|____________| |1,021|____________|    \__\|____________|
+  |1,020|____________| |1,020|____________|       /|____________|
+  |     |            | |     |            |        |            |
+  |     |            | \____\|            |_       |            |
+  |     |      .     |      /|      .     | \      |      .     |
+  \____\|      .     |_      |      .     |  |     |      .     |
+       /|      .     | \     |      .     |  |     |      .     |
+        |      .     |  |    |      .     |  |     |      .     |
+        |            |  |    |            |  |     |            |
+        |____________|  |    |____________|  |     |____________|
+       4|____________|  |   4|____________|  |     |____________|
+       3|____________|  |   3|____________|  |     |____________|
+       2|____________|  |   2|____________|  |     |____________|
+       1|____________|  |   1|____________|  |     |____________|
+       0|____________|  \__\0|____________|  \____\|____________|
+                           /                      /
+

+ +Pintos provides some macros and functions that are useful for working +with raw page tables: +

+

+ + +

+
+
Macro: PTSHIFT +
+
Macro: PTBITS +
The starting bit index (12) and number of bits (10), respectively, in a +page table index. +
+

+ + +

+
+
Macro: PTMASK +
A bit mask with the bits in the page table index set to 1 and the rest +set to 0 (0x3ff000). +
+

+ + +

+
+
Macro: PTSPAN +
The number of bytes of virtual address space that a single page table +page covers (4,194,304 bytes, or 4 MB). +
+

+ + +

+
+
Macro: PDSHIFT +
+
Macro: PDBITS +
The starting bit index (22) and number of bits (10), respectively, in a +page directory index. +
+

+ + +

+
+
Macro: PDMASK +
A bit mask with the bits in the page directory index set to 1 and other +bits set to 0 (0xffc00000). +
+

+ + +

+
+
Function: uintptr_t pd_no (const void *va) +
+
Function: uintptr_t pt_no (const void *va) +
Returns the page directory index or page table index, respectively, for +virtual address va. These functions are defined in +threads/pte.h. +
+

+ + +

+
+
Function: unsigned pg_ofs (const void *va) +
Returns the page offset for virtual address va. This function is +defined in threads/vaddr.h. +
+

+ + +


+ +

A.7.4.2 Page Table Entry Format

+ +

+ +You do not need to understand the PTE format to do the Pintos +projects, unless you wish to incorporate the page table into your +supplemental page table (see Managing the Supplemental Page Table). +

+

+ +The actual format of a page table entry is summarized below. For +complete information, refer to section 3.7, "Page Translation Using +32-Bit Physical Addressing," in [ IA32-v3a]. +

+

+ +
 
 31                                   12 11 9      6 5     2 1 0
++---------------------------------------+----+----+-+-+---+-+-+-+
+|           Physical Address            | AVL|    |D|A|   |U|W|P|
++---------------------------------------+----+----+-+-+---+-+-+-+
+

+ +Some more information on each bit is given below. The names are +threads/pte.h macros that represent the bits' values: +

+

+ + +

+
+
Macro: PTE_P +
Bit 0, the "present" bit. When this bit is 1, the +other bits are interpreted as described below. When this bit is 0, any +attempt to access the page will page fault. The remaining bits are then +not used by the CPU and may be used by the OS for any purpose. +
+

+ + +

+
+
Macro: PTE_W +
Bit 1, the "read/write" bit. When it is 1, the page +is writable. When it is 0, write attempts will page fault. +
+

+ + +

+
+
Macro: PTE_U +
Bit 2, the "user/supervisor" bit. When it is 1, user +processes may access the page. When it is 0, only the kernel may access +the page (user accesses will page fault). +

+ +Pintos clears this bit in PTEs for kernel virtual memory, to prevent +user processes from accessing them. +

+
+

+ + +

+
+
Macro: PTE_A +
Bit 5, the "accessed" bit. See section A.7.3 Accessed and Dirty Bits. +
+

+ + +

+
+
Macro: PTE_D +
Bit 6, the "dirty" bit. See section A.7.3 Accessed and Dirty Bits. +
+

+ + +

+
+
Macro: PTE_AVL +
Bits 9...11, available for operating system use. +Pintos, as provided, does not use them and sets them to 0. +
+

+ + +

+
+
Macro: PTE_ADDR +
Bits 12...31, the top 20 bits of the physical address of a frame. +The low 12 bits of the frame's address are always 0. +
+

+ +Other bits are either reserved or uninteresting in a Pintos context and +should be set to@tie{}0. +

+

+ +Header threads/pte.h defines three functions for working with +page table entries: +

+

+ + +

+
+
Function: uint32_t pte_create_kernel (uint32_t *page, bool writable) +
Returns a page table entry that points to page, which should be a +kernel virtual address. The PTE's present bit will be set. It will be +marked for kernel-only access. If writable is true, the PTE will +also be marked read/write; otherwise, it will be read-only. +
+

+ + +

+
+
Function: uint32_t pte_create_user (uint32_t *page, bool writable) +
Returns a page table entry that points to page, which should be +the kernel virtual address of a frame in the user pool (see Why PAL_USER?). The PTE's present bit will be set and it will be marked to +allow user-mode access. If writable is true, the PTE will also be +marked read/write; otherwise, it will be read-only. +
+

+ + +

+
+
Function: void *pte_get_page (uint32_t pte) +
Returns the kernel virtual address for the frame that pte points +to. The pte may be present or not-present; if it is not-present +then the pointer returned is only meaningful if the address bits in the PTE +actually represent a physical address. +
+

+ + +


+ +

A.7.4.3 Page Directory Entry Format

+ +

+ +Page directory entries have the same format as PTEs, except that the +physical address points to a page table page instead of a frame. Header +threads/pte.h defines two functions for working with page +directory entries: +

+

+ + +

+
+
Function: uint32_t pde_create (uint32_t *pt) +
Returns a page directory that points to page, which should be the +kernel virtual address of a page table page. The PDE's present bit will +be set, it will be marked to allow user-mode access, and it will be +marked read/write. +
+

+ + +

+
+
Function: uint32_t *pde_get_pt (uint32_t pde) +
Returns the kernel virtual address for the page table page that +pde, which must be marked present, points to. +
+

+ + +


+ +

A.8 Hash Table

+ +

+ +Pintos provides a hash table data structure in lib/kernel/hash.c. +To use it you will need to include its header file, +lib/kernel/hash.h, with #include <hash.h>. +No code provided with Pintos uses the hash table, which means that you +are free to use it as is, modify its implementation for your own +purposes, or ignore it, as you wish. +

+

+ +Most implementations of the virtual memory project use a hash table to +translate pages to frames. You may find other uses for hash tables as +well. +

+

+ + +


+ +

A.8.1 Data Types

+ +

+ +A hash table is represented by struct hash. +

+

+ + +

+
+
Type: struct hash +
Represents an entire hash table. The actual members of struct hash +are "opaque." That is, code that uses a hash table should not access +struct hash members directly, nor should it need to. Instead, use +hash table functions and macros. +
+

+ +The hash table operates on elements of type struct hash_elem. +

+

+ + +

+
+
Type: struct hash_elem +
Embed a struct hash_elem member in the structure you want to include +in a hash table. Like struct hash, struct hash_elem is opaque. +All functions for operating on hash table elements actually take and +return pointers to struct hash_elem, not pointers to your hash table's +real element type. +
+

+ +You will often need to obtain a struct hash_elem given a real element +of the hash table, and vice versa. Given a real element of the hash +table, you may use the & operator to obtain a pointer to its +struct hash_elem. Use the hash_entry() macro to go the other +direction. +

+

+ + +

+
+
Macro: type *hash_entry (struct hash_elem *elem, type, member) +
Returns a pointer to the structure that elem, a pointer to a +struct hash_elem, is embedded within. You must provide type, +the name of the structure that elem is inside, and member, +the name of the member in type that elem points to. +

+ +For example, suppose h is a struct hash_elem * variable +that points to a struct thread member (of type struct hash_elem) +named h_elem. Then, hash_entry@tie{(h, struct thread, h_elem)} +yields the address of the struct thread that h points within. +

+
+

+ +See section A.8.5 Hash Table Example, for an example. +

+

+ +Each hash table element must contain a key, that is, data that +identifies and distinguishes elements, which must be unique +among elements in the hash table. (Elements may +also contain non-key data that need not be unique.) While an element is +in a hash table, its key data must not be changed. Instead, if need be, +remove the element from the hash table, modify its key, then reinsert +the element. +

+

+ +For each hash table, you must write two functions that act on keys: a +hash function and a comparison function. These functions must match the +following prototypes: +

+

+ + +

+
+
Type: unsigned hash_hash_func (const struct hash_elem *element, void *aux) +
Returns a hash of element's data, as a value anywhere in the range +of unsigned int. The hash of an element should be a +pseudo-random function of the element's key. It must not depend on +non-key data in the element or on any non-constant data other than the +key. Pintos provides the following functions as a suitable basis for +hash functions. +

+ + +

+
+
Function: unsigned hash_bytes (const void *buf, size_t *size) +
Returns a hash of the size bytes starting at buf. The +implementation is the general-purpose +Fowler-Noll-Vo +hash for 32-bit words. +
+

+ + +

+
+
Function: unsigned hash_string (const char *s) +
Returns a hash of null-terminated string s. +
+

+ + +

+
+
Function: unsigned hash_int (int i) +
Returns a hash of integer i. +
+

+ +If your key is a single piece of data of an appropriate type, it is +sensible for your hash function to directly return the output of one of +these functions. For multiple pieces of data, you may wish to combine +the output of more than one call to them using, e.g., the ^ +(exclusive or) +operator. Finally, you may entirely ignore these functions and write +your own hash function from scratch, but remember that your goal is to +build an operating system kernel, not to design a hash function. +

+

+ +See section A.8.6 Auxiliary Data, for an explanation of aux. +

+
+

+ + +

+
+
Type: bool hash_less_func (const struct hash_elem *a, const struct hash_elem *b, void *aux) +
Compares the keys stored in elements a and b. Returns +true if a is less than b, false if a is greater than +or equal to b. +

+ +If two elements compare equal, then they must hash to equal values. +

+

+ +See section A.8.6 Auxiliary Data, for an explanation of aux. +

+
+

+ +See section A.8.5 Hash Table Example, for hash and comparison function examples. +

+

+ +A few functions accept a pointer to a third kind of +function as an argument: +

+

+ + +

+
+
Type: void hash_action_func (struct hash_elem *element, void *aux) +
Performs some kind of action, chosen by the caller, on element. +

+ +See section A.8.6 Auxiliary Data, for an explanation of aux. +

+
+

+ + +


+ +

A.8.2 Basic Functions

+ +

+ +These functions create, destroy, and inspect hash tables. +

+

+ + +

+
+
Function: bool hash_init (struct hash *hash, hash_hash_func *hash_func, hash_less_func *less_func, void *aux) +
Initializes hash as a hash table with hash_func as hash +function, less_func as comparison function, and aux as +auxiliary data. +Returns true if successful, false on failure. hash_init() calls +malloc() and fails if memory cannot be allocated. +

+ +See section A.8.6 Auxiliary Data, for an explanation of aux, which is +most often a null pointer. +

+
+

+ + +

+
+
Function: void hash_clear (struct hash *hash, hash_action_func *action) +
Removes all the elements from hash, which must have been +previously initialized with hash_init(). +

+ +If action is non-null, then it is called once for each element in +the hash table, which gives the caller an opportunity to deallocate any +memory or other resources used by the element. For example, if the hash +table elements are dynamically allocated using malloc(), then +action could free() the element. This is safe because +hash_clear() will not access the memory in a given hash element +after calling action on it. However, action must not call +any function that may modify the hash table, such as hash_insert() +or hash_delete(). +

+
+

+ + +

+
+
Function: void hash_destroy (struct hash *hash, hash_action_func *action) +
If action is non-null, calls it for each element in the hash, with +the same semantics as a call to hash_clear(). Then, frees the +memory held by hash. Afterward, hash must not be passed to +any hash table function, absent an intervening call to hash_init(). +
+

+ + +

+
+
Function: size_t hash_size (struct hash *hash) +
Returns the number of elements currently stored in hash. +
+

+ + +

+
+
Function: bool hash_empty (struct hash *hash) +
Returns true if hash currently contains no elements, +false if hash contains at least one element. +
+

+ + +


+ +

A.8.3 Search Functions

+ +

+ +Each of these functions searches a hash table for an element that +compares equal to one provided. Based on the success of the search, +they perform some action, such as inserting a new element into the hash +table, or simply return the result of the search. +

+

+ + +

+
+
Function: struct hash_elem *hash_insert (struct hash *hash, struct hash_elem *element) +
Searches hash for an element equal to element. If none is +found, inserts element into hash and returns a null pointer. +If the table already contains an element equal to element, it is +returned without modifying hash. +
+

+ + +

+
+
Function: struct hash_elem *hash_replace (struct hash *hash, struct hash_elem *element) +
Inserts element into hash. Any element equal to +element already in hash is removed. Returns the element +removed, or a null pointer if hash did not contain an element +equal to element. +

+ +The caller is responsible for deallocating any resources associated with +the returned element, as appropriate. For example, if the hash table +elements are dynamically allocated using malloc(), then the caller +must free() the element after it is no longer needed. +

+
+

+ +The element passed to the following functions is only used for hashing +and comparison purposes. It is never actually inserted into the hash +table. Thus, only key data in the element needs to be initialized, and +other data in the element will not be used. It often makes sense to +declare an instance of the element type as a local variable, initialize +the key data, and then pass the address of its struct hash_elem to +hash_find() or hash_delete(). See section A.8.5 Hash Table Example, for +an example. (Large structures should not be +allocated as local variables. See section A.2.1 struct thread, for more +information.) +

+

+ + +

+
+
Function: struct hash_elem *hash_find (struct hash *hash, struct hash_elem *element) +
Searches hash for an element equal to element. Returns the +element found, if any, or a null pointer otherwise. +
+

+ + +

+
+
Function: struct hash_elem *hash_delete (struct hash *hash, struct hash_elem *element) +
Searches hash for an element equal to element. If one is +found, it is removed from hash and returned. Otherwise, a null +pointer is returned and hash is unchanged. +

+ +The caller is responsible for deallocating any resources associated with +the returned element, as appropriate. For example, if the hash table +elements are dynamically allocated using malloc(), then the caller +must free() the element after it is no longer needed. +

+
+

+ + +


+ +

A.8.4 Iteration Functions

+ +

+ +These functions allow iterating through the elements in a hash table. +Two interfaces are supplied. The first requires writing and supplying a +hash_action_func to act on each element (see section A.8.1 Data Types). +

+

+ + +

+
+
Function: void hash_apply (struct hash *hash, hash_action_func *action) +
Calls action once for each element in hash, in arbitrary +order. action must not call any function that may modify the hash +table, such as hash_insert() or hash_delete(). action +must not modify key data in elements, although it may modify any other +data. +
+

+ +The second interface is based on an "iterator" data type. +Idiomatically, iterators are used as follows: +

+

+ +
 
struct hash_iterator i;
+
+hash_first (&i, h);
+while (hash_next (&i))
+  {
+    struct foo *f = hash_entry (hash_cur (&i), struct foo, elem);
+    ...do something with f...
+  }
+

+ + +

+
+
Type: struct hash_iterator +
Represents a position within a hash table. Calling any function that +may modify a hash table, such as hash_insert() or +hash_delete(), invalidates all iterators within that hash table. +

+ +Like struct hash and struct hash_elem, struct hash_elem is opaque. +

+
+

+ + +

+
+
Function: void hash_first (struct hash_iterator *iterator, struct hash *hash) +
Initializes iterator to just before the first element in +hash. +
+

+ + +

+
+
Function: struct hash_elem *hash_next (struct hash_iterator *iterator) +
Advances iterator to the next element in hash, and returns +that element. Returns a null pointer if no elements remain. After +hash_next() returns null for iterator, calling it again +yields undefined behavior. +
+

+ + +

+
+
Function: struct hash_elem *hash_cur (struct hash_iterator *iterator) +
Returns the value most recently returned by hash_next() for +iterator. Yields undefined behavior after hash_first() has +been called on iterator but before hash_next() has been +called for the first time. +
+

+ + +


+ +

A.8.5 Hash Table Example

+ +

+ +Suppose you have a structure, called struct page, that you +want to put into a hash table. First, define struct page to include a +struct hash_elem member: +

+

+ +
 
struct page
+  {
+    struct hash_elem hash_elem; /* Hash table element. */
+    void *addr;                 /* Virtual address. */
+    /* ...other members... */
+  };
+

+ +We write a hash function and a comparison function using addr as +the key. A pointer can be hashed based on its bytes, and the < +operator works fine for comparing pointers: +

+

+ +
 
/* Returns a hash value for page p. */
+unsigned
+page_hash (const struct hash_elem *p_, void *aux UNUSED)
+{
+  const struct page *p = hash_entry (p_, struct page, hash_elem);
+  return hash_bytes (&p->addr, sizeof p->addr);
+}
+
+/* Returns true if page a precedes page b. */
+bool
+page_less (const struct hash_elem *a_, const struct hash_elem *b_,
+           void *aux UNUSED)
+{
+  const struct page *a = hash_entry (a_, struct page, hash_elem);
+  const struct page *b = hash_entry (b_, struct page, hash_elem);
+
+  return a->addr < b->addr;
+}
+

+ +(The use of UNUSED in these functions' prototypes suppresses a +warning that aux is unused. See section D.3 Function and Parameter Attributes, for information about UNUSED. See section A.8.6 Auxiliary Data, for an explanation of aux.) +

+

+ +Then, we can create a hash table like this: +

+

+ +
 
struct hash pages;
+
+hash_init (&pages, page_hash, page_less, NULL);
+

+ +Now we can manipulate the hash table we've created. If p +is a pointer to a struct page, we can insert it into the hash table +with: +

+

+ +
 
hash_insert (&pages, &p->hash_elem);
+

+ +If there's a chance that pages might already contain a +page with the same addr, then we should check hash_insert()'s +return value. +

+

+ +To search for an element in the hash table, use hash_find(). This +takes a little setup, because hash_find() takes an element to +compare against. Here's a function that will find and return a page +based on a virtual address, assuming that pages is defined at file +scope: +

+

+ +
 
/* Returns the page containing the given virtual address,
+   or a null pointer if no such page exists. */
+struct page *
+page_lookup (const void *address)
+{
+  struct page p;
+  struct hash_elem *e;
+
+  p.addr = address;
+  e = hash_find (&pages, &p.hash_elem);
+  return e != NULL ? hash_entry (e, struct page, hash_elem) : NULL;
+}
+

+ +struct page is allocated as a local variable here on the assumption +that it is fairly small. Large structures should not be allocated as +local variables. See section A.2.1 struct thread, for more information. +

+

+ +A similar function could delete a page by address using +hash_delete(). +

+

+ + +


+ +

A.8.6 Auxiliary Data

+ +

+ +In simple cases like the example above, there's no need for the +aux parameters. In these cases, just pass a null pointer to +hash_init() for aux and ignore the values passed to the hash +function and comparison functions. (You'll get a compiler warning if +you don't use the aux parameter, but you can turn that off with +the UNUSED macro, as shown in the example, or you can just ignore +it.) +

+

+ +aux is useful when you have some property of the data in the +hash table is both constant and needed for hashing or comparison, +but not stored in the data items themselves. For example, if +the items in a hash table are fixed-length strings, but the items +themselves don't indicate what that fixed length is, you could pass +the length as an aux parameter. +

+

+ + +


+ +

A.8.7 Synchronization

+ +

+ +The hash table does not do any internal synchronization. It is the +caller's responsibility to synchronize calls to hash table functions. +In general, any number of functions that examine but do not modify the +hash table, such as hash_find() or hash_next(), may execute +simultaneously. However, these function cannot safely execute at the +same time as any function that may modify a given hash table, such as +hash_insert() or hash_delete(), nor may more than one function +that can modify a given hash table execute safely at once. +

+

+ +It is also the caller's responsibility to synchronize access to data in +hash table elements. How to synchronize access to this data depends on +how it is designed and organized, as with any other data structure. +

+

+ + +


+ + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_6.html b/doc/pintos_6.html new file mode 100644 index 0000000..afe7d8a --- /dev/null +++ b/doc/pintos_6.html @@ -0,0 +1,314 @@ + + + + + +Pintos Projects: Coding Standards + + + + + + + + + + + + + + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+ +
+

B. Coding Standards

+ +

+ +TODO: TUW coding standards +We expect you to be +familiar with some set of coding standards such as +CS 107 Coding Standards. Even if you've taken 107, we recommend +reviewing that document. We expect code at the "Peer-Review Quality" +level described there. +

+

+ +Our standards for coding are most important for grading. We want to +stress that aside from the fact that we are explicitly basing part of +your grade on these things, good coding practices will improve the +quality of your code. This makes it easier for your partners to +interact with it, and ultimately, will improve your chances of having a +good working program. That said once, the rest of this document will +discuss only the ways in which our coding standards will affect our +grading. +

+

+ + +


+ +

B.1 Style

+ +

+ +Style, for the purposes of our grading, refers to how readable your +code is. At minimum, this means that your code is well formatted, your +variable names are descriptive and your functions are decomposed and +well commented. Any other factors which make it hard (or easy) for us +to read or use your code will be reflected in your style grade. +

+

+ +The existing Pintos code is written in the GNU style and largely +follows the GNU +Coding Standards. We encourage you to follow the applicable parts of +them too, especially chapter 5, "Making the Best Use of C." Using a +different style won't cause actual problems, but it's ugly to see +gratuitous differences in style from one function to another. If your +code is too ugly, it will cost you points. +

+

+ +Please limit C source file lines to at most 79 characters long. +

+

+ +Pintos comments sometimes refer to external standards or +specifications by writing a name inside square brackets, like this: +[IA32-v3a]. These names refer to the reference names used in +this documentation (see section Bibliography). +

+

+ +If you remove existing Pintos code, please delete it from your source +file entirely. Don't just put it into a comment or a conditional +compilation directive, because that makes the resulting code hard to +read. +

+

+ +We're only going to do a compile in the directory for the project being +submitted. You don't need to make sure that the previous projects also +compile. +

+

+ +Project code should be written so that all of the subproblems for the +project function together, that is, without the need to rebuild with +different macros defined, etc. If you do extra credit work that +changes normal Pintos behavior so as to interfere with grading, then +you must implement it so that it only acts that way when given a +special command-line option of the form -name, where +name is a name of your choice. You can add such an option by +modifying parse_options() in threads/init.c. +

+

+ +The introduction describes additional coding style requirements +(see section 1.2.2 Design). +

+

+ + +


+ +

B.2 C99

+ +

+ +The Pintos source code uses a few features of the "C99" standard +library that were not in the original 1989 standard for C. Many +programmers are unaware of these feature, so we will describe them. The +new features used in Pintos are +mostly in new headers: +

+

+ +

+
+
<stdbool.h> +
Defines macros bool, a 1-bit type that takes on only the values +0 and 1, true, which expands to 1, and false, which +expands to 0. +

+ +

+
<stdint.h> +
On systems that support them, this header defines types +intn_t and uintn_t for n = 8, 16, 32, +64, and possibly other values. These are 2's complement signed and unsigned +types, respectively, with the given number of bits. +

+ +On systems where it is possible, this header also defines types +intptr_t and uintptr_t, which are integer types big +enough to hold a pointer. +

+

+ +On all systems, this header defines types intmax_t and +uintmax_t, which are the system's signed and unsigned integer +types with the widest ranges. +

+

+ +For every signed integer type type_t defined here, as well +as for ptrdiff_t defined in <stddef.h>, this header also +defines macros TYPE_MAX and TYPE_MIN that +give the type's range. Similarly, for every unsigned integer type +type_t defined here, as well as for size_t defined +in <stddef.h>, this header defines a TYPE_MAX +macro giving its maximum value. +

+

+ +

+
<inttypes.h> +
<stdint.h> provides no straightforward way to format +the types it defines with printf() and related functions. This +header provides macros to help with that. For every +intn_t defined by <stdint.h>, it provides macros +PRIdn and PRIin for formatting values of +that type with "%d" and "%i". Similarly, for every +uintn_t, it provides PRIon, +PRIun, PRIux, and PRIuX. +

+ +You use these something like this, taking advantage of the fact that +the C compiler concatenates adjacent string literals: +
 
#include <inttypes.h>
+...
+int32_t value = ...;
+printf ("value=%08"PRId32"\n", value);
+
The % is not supplied by the PRI macros. As shown +above, you supply it yourself and follow it by any flags, field +width, etc. +

+ +

+
<stdio.h> +
The printf() function has some new type modifiers for printing +standard types: +

+ +

+
+
j +
For intmax_t (e.g. %jd) or uintmax_t (e.g. +%ju). +

+ +

+
z +
For size_t (e.g. %zu). +

+ +

+
t +
For ptrdiff_t (e.g. %td). +
+

+ +Pintos printf() also implements a nonstandard ' flag that +groups large numbers with commas to make them easier to read. +

+

+ + +


+ +

B.3 Unsafe String Functions

+ +

+ +A few of the string functions declared in the standard +<string.h> and <stdio.h> headers are notoriously unsafe. +The worst offenders are intentionally not included in the Pintos C +library: +

+

+ +

+
+
strcpy +
When used carelessly this function can overflow the buffer reserved +for its output string. Use strlcpy() instead. Refer to +comments in its source code in lib/string.c for documentation. +

+ +

+
strncpy +
This function can leave its destination buffer without a null string +terminator. It also has performance problems. Again, use +strlcpy(). +

+ +

+
strcat +
Same issue as strcpy(). Use strlcat() instead. +Again, refer to comments in its source code in lib/string.c for +documentation. +

+ +

+
strncat +
The meaning of its buffer size argument is surprising. +Again, use strlcat(). +

+ +

+
strtok +
Uses global data, so it is unsafe in threaded programs such as +kernels. Use strtok_r() instead, and see its source code in +lib/string.c for documentation and an example. +

+ +

+
sprintf +
Same issue as strcpy(). Use snprintf() instead. Refer +to comments in lib/stdio.h for documentation. +

+ +

+
vsprintf +
Same issue as strcpy(). Use vsnprintf() instead. +
+

+ +If you try to use any of these functions, the error message will give +you a hint by referring to an identifier like +dont_use_sprintf_use_snprintf. + +


+ + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_7.html b/doc/pintos_7.html new file mode 100644 index 0000000..e7ac7b5 --- /dev/null +++ b/doc/pintos_7.html @@ -0,0 +1,238 @@ + + + + + +Pintos Projects: Project Documentation + + + + + + + + + + + + + + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+ +
+

C. Project Documentation

+ +

+ +This chapter presents a sample assignment and a filled-in design +document for one possible implementation. Its purpose is to give you an +idea of what we expect to see in your own design documents. +

+

+ + +


+ +

C.1 Sample Assignment

+ +

+ +Implement thread_join(). +

+

+ + +

+
+
Function: void thread_join (tid_t tid) +
Blocks the current thread until thread tid exits. If A is +the running thread and B is the argument, then we say that +"A joins B." +

+ +Incidentally, the argument is a thread id, instead of a thread pointer, +because a thread pointer is not unique over time. That is, when a +thread dies, its memory may be, whether immediately or much later, +reused for another thread. If thread A over time had two children +B and C that were stored at the same address, then +thread_join(B) and thread_join(C) would be +ambiguous. +

+

+ +A thread may only join its immediate children. Calling +thread_join() on a thread that is not the caller's child should +cause the caller to return immediately. Children are not "inherited," +that is, if A has child B and B has child C, +then A always returns immediately should it try to join C, +even if B is dead. +

+

+ +A thread need not ever be joined. Your solution should properly free +all of a thread's resources, including its struct thread, +whether it is ever joined or not, and regardless of whether the child +exits before or after its parent. That is, a thread should be freed +exactly once in all cases. +

+

+ +Joining a given thread is idempotent. That is, joining a thread +multiple times is equivalent to joining it once, because it has already +exited at the time of the later joins. Thus, joins on a given thread +after the first should return immediately. +

+

+ +You must handle all the ways a join can occur: nested joins (A +joins B, then B joins C), multiple joins (A +joins B, then A joins C), and so on. +

+
+

+ + +


+ +

C.2 Sample Design Document

+ +

+ +
 
+                         +-----------------+
+                         |      CS 140     |
+                         |  SAMPLE PROJECT |
+                         | DESIGN DOCUMENT |
+                         +-----------------+
+
+---- GROUP ----
+
+Ben Pfaff <blp@stanford.edu>
+
+---- PRELIMINARIES ----
+
+>> If you have any preliminary comments on your submission, notes for
+>> the TAs, or extra credit, please give them here.
+
+(This is a sample design document.)
+
+>> Please cite any offline or online sources you consulted while
+>> preparing your submission, other than the Pintos documentation,
+>> course text, and lecture notes.
+
+None.
+
+                                 JOIN
+                                 ====
+
+---- DATA STRUCTURES ----
+
+>> Copy here the declaration of each new or changed `struct' or `struct'
+>> member, global or static variable, `typedef', or enumeration.
+>> Identify the purpose of each in 25 words or less.
+
+A "latch" is a new synchronization primitive.  Acquires block
+until the first release.  Afterward, all ongoing and future
+acquires pass immediately.
+
+    /* Latch. */
+    struct latch
+      {
+        bool released;              /* Released yet? */
+        struct lock monitor_lock;   /* Monitor lock. */
+        struct condition rel_cond;  /* Signaled when released. */
+      };
+
+Added to struct thread:
+
+    /* Members for implementing thread_join(). */
+    struct latch ready_to_die;   /* Release when thread about to die. */
+    struct semaphore can_die;    /* Up when thread allowed to die. */
+    struct list children;        /* List of child threads. */
+    list_elem children_elem;     /* Element of `children' list. */
+
+---- ALGORITHMS ----
+
+>> Briefly describe your implementation of thread_join() and how it
+>> interacts with thread termination.
+
+thread_join() finds the joined child on the thread's list of
+children and waits for the child to exit by acquiring the child's
+ready_to_die latch.  When thread_exit() is called, the thread
+releases its ready_to_die latch, allowing the parent to continue.
+
+---- SYNCHRONIZATION ----
+
+>> Consider parent thread P with child thread C.  How do you ensure
+>> proper synchronization and avoid race conditions when P calls wait(C)
+>> before C exits?  After C exits?  How do you ensure that all resources
+>> are freed in each case?  How about when P terminates without waiting,
+>> before C exits?  After C exits?  Are there any special cases?
+
+C waits in thread_exit() for P to die before it finishes its own
+exit, using the can_die semaphore "down"ed by C and "up"ed by P as
+it exits.  Regardless of whether whether C has terminated, there
+is no race on wait(C), because C waits for P's permission before
+it frees itself.
+
+Regardless of whether P waits for C, P still "up"s C's can_die
+semaphore when P dies, so C will always be freed.  (However,
+freeing C's resources is delayed until P's death.)
+
+The initial thread is a special case because it has no parent to
+wait for it or to "up" its can_die semaphore.  Therefore, its
+can_die semaphore is initialized to 1.
+
+---- RATIONALE ----
+
+>> Critique your design, pointing out advantages and disadvantages in
+>> your design choices.
+
+This design has the advantage of simplicity.  Encapsulating most
+of the synchronization logic into a new "latch" structure
+abstracts what little complexity there is into a separate layer,
+making the design easier to reason about.  Also, all the new data
+members are in `struct thread', with no need for any extra dynamic
+allocation, etc., that would require extra management code.
+
+On the other hand, this design is wasteful in that a child thread
+cannot free itself before its parent has terminated.  A parent
+thread that creates a large number of short-lived child threads
+could unnecessarily exhaust kernel memory.  This is probably
+acceptable for implementing kernel threads, but it may be a bad
+idea for use with user processes because of the larger number of
+resources that user processes tend to own.
+
+


+ + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_8.html b/doc/pintos_8.html new file mode 100644 index 0000000..e354458 --- /dev/null +++ b/doc/pintos_8.html @@ -0,0 +1,1041 @@ + + + + + +Pintos Projects: Debugging Tools + + + + + + + + + + + + + + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+ +
+

D. Debugging Tools

+ +

+ +Many tools lie at your disposal for debugging Pintos. This appendix +introduces you to a few of them. +

+

+ + +


+ +

D.1 printf()

+ +

+ +Don't underestimate the value of printf(). The way +printf() is implemented in Pintos, you can call it from +practically anywhere in the kernel, whether it's in a kernel thread or +an interrupt handler, almost regardless of what locks are held. +

+

+ +printf() is useful for more than just examining data. +It can also help figure out when and where something goes wrong, even +when the kernel crashes or panics without a useful error message. The +strategy is to sprinkle calls to printf() with different strings +(e.g. "<1>", "<2>", ...) throughout the pieces of +code you suspect are failing. If you don't even see <1> printed, +then something bad happened before that point, if you see <1> +but not <2>, then something bad happened between those two +points, and so on. Based on what you learn, you can then insert more +printf() calls in the new, smaller region of code you suspect. +Eventually you can narrow the problem down to a single statement. +See section D.6 Triple Faults, for a related technique. +

+

+ + +


+ +

D.2 ASSERT

+ +

+ +Assertions are useful because they can catch problems early, before +they'd otherwise be noticed. Ideally, each function should begin with a +set of assertions that check its arguments for validity. (Initializers +for functions' local variables are evaluated before assertions are +checked, so be careful not to assume that an argument is valid in an +initializer.) You can also sprinkle assertions throughout the body of +functions in places where you suspect things are likely to go wrong. +They are especially useful for checking loop invariants. +

+

+ +Pintos provides the ASSERT macro, defined in <debug.h>, +for checking assertions. +

+

+ + +

+
+
Macro: ASSERT (expression) +
Tests the value of expression. If it evaluates to zero (false), +the kernel panics. The panic message includes the expression that +failed, its file and line number, and a backtrace, which should help you +to find the problem. See section D.4 Backtraces, for more information. +
+

+ + +


+ +

D.3 Function and Parameter Attributes

+ +

+ +These macros defined in <debug.h> tell the compiler special +attributes of a function or function parameter. Their expansions are +GCC-specific. +

+

+ + +

+
+
Macro: UNUSED +
Appended to a function parameter to tell the compiler that the +parameter might not be used within the function. It suppresses the +warning that would otherwise appear. +
+

+ + +

+
+
Macro: NO_RETURN +
Appended to a function prototype to tell the compiler that the +function never returns. It allows the compiler to fine-tune its +warnings and its code generation. +
+

+ + +

+
+
Macro: NO_INLINE +
Appended to a function prototype to tell the compiler to never emit +the function in-line. Occasionally useful to improve the quality of +backtraces (see below). +
+

+ + +

+
+
Macro: PRINTF_FORMAT (format, first) +
Appended to a function prototype to tell the compiler that the function +takes a printf()-like format string as the argument numbered +format (starting from 1) and that the corresponding value +arguments start at the argument numbered first. This lets the +compiler tell you if you pass the wrong argument types. +
+

+ + +


+ +

D.4 Backtraces

+ +

+ +When the kernel panics, it prints a "backtrace," that is, a summary +of how your program got where it is, as a list of addresses inside the +functions that were running at the time of the panic. You can also +insert a call to debug_backtrace(), prototyped in +<debug.h>, to print a backtrace at any point in your code. +debug_backtrace_all(), also declared in <debug.h>, +prints backtraces of all threads. +

+

+ +The addresses in a backtrace are listed as raw hexadecimal numbers, +which are difficult to interpret. We provide a tool called +backtrace to translate these into function names and source +file line numbers. +Give it the name of your kernel.o as the first argument and the +hexadecimal numbers composing the backtrace (including the 0x +prefixes) as the remaining arguments. It outputs the function name +and source file line numbers that correspond to each address. +

+

+ +If the translated form of a backtrace is garbled, or doesn't make +sense (e.g. function A is listed above function B, but B doesn't +call A), then it's a good sign that you're corrupting a kernel +thread's stack, because the backtrace is extracted from the stack. +Alternatively, it could be that the kernel.o you passed to +backtrace is not the same kernel that produced +the backtrace. +

+

+ +Sometimes backtraces can be confusing without any corruption. +Compiler optimizations can cause surprising behavior. When a function +has called another function as its final action (a tail call), the +calling function may not appear in a backtrace at all. Similarly, when +function A calls another function B that never returns, the compiler may +optimize such that an unrelated function C appears in the backtrace +instead of A. Function C is simply the function that happens to be in +memory just after A. In the threads project, this is commonly seen in +backtraces for test failures. +

+

+ + +


+ +

D.4.1 Example

+ +

+ +Here's an example. Suppose that Pintos printed out this following call +stack, which is taken from an actual Pintos submission for the file +system project: +

+

+ +
 
Call stack: 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319
+0xc010325a 0x804812c 0x8048a96 0x8048ac8.
+

+ +You would then invoke the backtrace utility like shown below, +cutting and pasting the backtrace information into the command line. +This assumes that kernel.o is in the current directory. You +would of course enter all of the following on a single shell command +line, even though that would overflow our margins here: +

+

+ +
 
backtrace kernel.o 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67
+0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8
+

+ +The backtrace output would then look something like this: +

+

+ +
 
0xc0106eff: debug_panic (lib/debug.c:86)
+0xc01102fb: file_seek (filesys/file.c:405)
+0xc010dc22: seek (userprog/syscall.c:744)
+0xc010cf67: syscall_handler (userprog/syscall.c:444)
+0xc0102319: intr_handler (threads/interrupt.c:334)
+0xc010325a: intr_entry (threads/intr-stubs.S:38)
+0x0804812c: (unknown)
+0x08048a96: (unknown)
+0x08048ac8: (unknown)
+

+ +(You will probably not see exactly the same addresses if you run the +command above on your own kernel binary, because the source code you +compiled and the compiler you used are probably different.) +

+

+ +The first line in the backtrace refers to debug_panic(), the +function that implements kernel panics. Because backtraces commonly +result from kernel panics, debug_panic() will often be the first +function shown in a backtrace. +

+

+ +The second line shows file_seek() as the function that panicked, +in this case as the result of an assertion failure. In the source code +tree used for this example, line 405 of filesys/file.c is the +assertion +

+

+ +
 
ASSERT (file_ofs >= 0);
+

+ +(This line was also cited in the assertion failure message.) +Thus, file_seek() panicked because it passed a negative file offset +argument. +

+

+ +The third line indicates that seek() called file_seek(), +presumably without validating the offset argument. In this submission, +seek() implements the seek system call. +

+

+ +The fourth line shows that syscall_handler(), the system call +handler, invoked seek(). +

+

+ +The fifth and sixth lines are the interrupt handler entry path. +

+

+ +The remaining lines are for addresses below PHYS_BASE. This +means that they refer to addresses in the user program, not in the +kernel. If you know what user program was running when the kernel +panicked, you can re-run backtrace on the user program, like +so: (typing the command on a single line, of course): +

+

+ +
 
backtrace tests/filesys/extended/grow-too-big 0xc0106eff 0xc01102fb
+0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96
+0x8048ac8
+

+ +The results look like this: +

+

+ +
 
0xc0106eff: (unknown)
+0xc01102fb: (unknown)
+0xc010dc22: (unknown)
+0xc010cf67: (unknown)
+0xc0102319: (unknown)
+0xc010325a: (unknown)
+0x0804812c: test_main (...xtended/grow-too-big.c:20)
+0x08048a96: main (tests/main.c:10)
+0x08048ac8: _start (lib/user/entry.c:9)
+

+ +You can even specify both the kernel and the user program names on +the command line, like so: +

+

+ +
 
backtrace kernel.o tests/filesys/extended/grow-too-big 0xc0106eff
+0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c
+0x8048a96 0x8048ac8
+

+ +The result is a combined backtrace: +

+

+ +
 
In kernel.o:
+0xc0106eff: debug_panic (lib/debug.c:86)
+0xc01102fb: file_seek (filesys/file.c:405)
+0xc010dc22: seek (userprog/syscall.c:744)
+0xc010cf67: syscall_handler (userprog/syscall.c:444)
+0xc0102319: intr_handler (threads/interrupt.c:334)
+0xc010325a: intr_entry (threads/intr-stubs.S:38)
+In tests/filesys/extended/grow-too-big:
+0x0804812c: test_main (...xtended/grow-too-big.c:20)
+0x08048a96: main (tests/main.c:10)
+0x08048ac8: _start (lib/user/entry.c:9)
+

+ +Here's an extra tip for anyone who read this far: backtrace +is smart enough to strip the Call stack: header and . +trailer from the command line if you include them. This can save you +a little bit of trouble in cutting and pasting. Thus, the following +command prints the same output as the first one we used: +

+

+ +
 
backtrace kernel.o Call stack: 0xc0106eff 0xc01102fb 0xc010dc22
+0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8.
+

+ + +


+ +

D.5 GDB

+ +

+ +You can run Pintos under the supervision of the GDB debugger. +First, start Pintos with the --gdb option, e.g. +pintos --gdb -- run mytest. Second, open a second terminal on +the same machine and +use pintos-gdb to invoke GDB on +kernel.o:(5) +
 
pintos-gdb kernel.o
+
and issue the following GDB command: +
 
target remote localhost:1234
+

+ +Now GDB is connected to the simulator over a local +network connection. You can now issue any normal GDB +commands. If you issue the c command, the simulated BIOS will take +control, load Pintos, and then Pintos will run in the usual way. You +can pause the process at any point with Ctrl+C. +

+

+ + +


+ +

D.5.1 Using GDB

+ +

+ +You can read the GDB manual by typing info gdb at a +terminal command prompt. Here's a few commonly useful GDB commands: +

+

+ + +

+
+
GDB Command: c +
Continues execution until Ctrl+C or the next breakpoint. +
+

+ + +

+
+
GDB Command: break function +
+
GDB Command: break file:line +
+
GDB Command: break *address +
Sets a breakpoint at function, at line within file, or +address. +(Use a 0x prefix to specify an address in hex.) +

+ +Use break main to make GDB stop when Pintos starts running. +

+
+

+ + +

+
+
GDB Command: p expression +
Evaluates the given expression and prints its value. +If the expression contains a function call, that function will actually +be executed. +
+

+ + +

+
+
GDB Command: l *address +
Lists a few lines of code around address. +(Use a 0x prefix to specify an address in hex.) +
+

+ + +

+
+
GDB Command: bt +
Prints a stack backtrace similar to that output by the +backtrace program described above. +
+

+ + +

+
+
GDB Command: p/a address +
Prints the name of the function or variable that occupies address. +(Use a 0x prefix to specify an address in hex.) +
+

+ + +

+
+
GDB Command: diassemble function +
Disassembles function. +
+

+ +We also provide a set of macros specialized for debugging Pintos, +written by Godmar Back gback@cs.vt.edu. You can type +help user-defined for basic help with the macros. Here is an +overview of their functionality, based on Godmar's documentation: +

+

+ + +

+
+
GDB Macro: debugpintos +
Attach debugger to a waiting pintos process on the same machine. +Shorthand for target remote localhost:1234. +
+

+ + +

+
+
GDB Macro: dumplist list type element +
Prints the elements of list, which should be a struct list +that contains elements of the given type (without the word +struct) in which element is the struct list_elem member +that links the elements. +

+ +Example: dumplist all_list thread allelem prints all elements of +struct thread that are linked in struct list all_list using the +struct list_elem allelem which is part of struct thread. +

+
+

+ + +

+
+
GDB Macro: btthread thread +
Shows the backtrace of thread, which is a pointer to the +struct thread of the thread whose backtrace it should show. For the +current thread, this is identical to the bt (backtrace) command. +It also works for any thread suspended in schedule(), +provided you know where its kernel stack page is located. +
+

+ + +

+
+
GDB Macro: btthreadlist list element +
Shows the backtraces of all threads in list, the struct list in +which the threads are kept. Specify element as the +struct list_elem field used inside struct thread to link the threads +together. +

+ +Example: btthreadlist all_list allelem shows the backtraces of +all threads contained in struct list all_list, linked together by +allelem. This command is useful to determine where your threads +are stuck when a deadlock occurs. Please see the example scenario below. +

+
+

+ + +

+
+
GDB Macro: btthreadall +
Short-hand for btthreadlist all_list allelem. +
+

+ + +

+
+
GDB Macro: btpagefault +
Print a backtrace of the current thread after a page fault exception. +Normally, when a page fault exception occurs, GDB will stop +with a message that might say:(6) +

+ +
 
Program received signal 0, Signal 0.
+0xc0102320 in intr0e_stub ()
+

+ +In that case, the bt command might not give a useful +backtrace. Use btpagefault instead. +

+

+ +You may also use btpagefault for page faults that occur in a user +process. In this case, you may wish to also load the user program's +symbol table using the loadusersymbols macro, as described above. +

+
+

+ + +

+
+
GDB Macro: hook-stop +
GDB invokes this macro every time the simulation stops, which Bochs will +do for every processor exception, among other reasons. If the +simulation stops due to a page fault, hook-stop will print a +message that says and explains further whether the page fault occurred +in the kernel or in user code. +

+ +If the exception occurred from user code, hook-stop will say: +
 
pintos-debug: a page fault exception occurred in user mode
+pintos-debug: hit 'c' to continue, or 's' to step to intr_handler
+

+ +In Project 2, a page fault in a user process leads to the termination of +the process. You should expect those page faults to occur in the +robustness tests where we test that your kernel properly terminates +processes that try to access invalid addresses. To debug those, set a +break point in page_fault() in exception.c, which you will +need to modify accordingly. +

+

+ +In Project 3, a page fault in a user process no longer automatically +leads to the termination of a process. Instead, it may require reading in +data for the page the process was trying to access, either +because it was swapped out or because this is the first time it's +accessed. In either case, you will reach page_fault() and need to +take the appropriate action there. +

+

+ +If the page fault did not occur in user mode while executing a user +process, then it occurred in kernel mode while executing kernel code. +In this case, hook-stop will print this message: +
 
pintos-debug: a page fault occurred in kernel mode
+
followed by the output of the btpagefault command. +

+ +Before Project 3, a page fault exception in kernel code is always a bug +in your kernel, because your kernel should never crash. Starting with +Project 3, the situation will change if you use the get_user() and +put_user() strategy to verify user memory accesses +(see section 2.2.5 Accessing User Memory). +

+

+ +

+
+

+ + +


+ +

D.5.2 Example GDB Session

+ +

+ +This section narrates a sample GDB session, provided by Godmar Back. +This example illustrates how one might debug a Project 1 solution in +which occasionally a thread that calls timer_sleep() is not woken +up. With this bug, tests such as mlfqs_load_1 get stuck. +

+

+ +This session was captured with a slightly older version of Bochs and the +GDB macros for Pintos, so it looks slightly different than it would now. +Program output is shown in normal type, user input in strong +type. +

+

+ +First, I start Pintos: +

+

+ +
 
$ pintos -v --gdb -- -q -mlfqs run mlfqs-load-1
+Writing command line to /tmp/gDAlqTB5Uf.dsk...
+bochs -q
+========================================================================
+                       Bochs x86 Emulator 2.2.5
+             Build from CVS snapshot on December 30, 2005
+========================================================================
+00000000000i[     ] reading configuration from bochsrc.txt
+00000000000i[     ] Enabled gdbstub
+00000000000i[     ] installing nogui module as the Bochs GUI
+00000000000i[     ] using log file bochsout.txt
+Waiting for gdb connection on localhost:1234
+

+ +Then, I open a second window on the same machine and start GDB: +

+

+ +
 
$ pintos-gdb kernel.o
+GNU gdb Red Hat Linux (6.3.0.0-1.84rh)
+Copyright 2004 Free Software Foundation, Inc.
+GDB is free software, covered by the GNU General Public License, and you are
+welcome to change it and/or distribute copies of it under certain conditions.
+Type "show copying" to see the conditions.
+There is absolutely no warranty for GDB.  Type "show warranty" for details.
+This GDB was configured as "i386-redhat-linux-gnu"...
+Using host libthread_db library "/lib/libthread_db.so.1".
+

+ +Then, I tell GDB to attach to the waiting Pintos emulator: +

+

+ +
 
(gdb) debugpintos
+Remote debugging using localhost:1234
+0x0000fff0 in ?? ()
+Reply contains invalid hex digit 78
+

+ +Now I tell Pintos to run by executing c (short for +continue) twice: +

+

+ +
 
(gdb) c
+Continuing.
+Reply contains invalid hex digit 78
+(gdb) c
+Continuing.
+

+ +Now Pintos will continue and output: +

+

+ +
 
Pintos booting with 4,096 kB RAM...
+Kernel command line: -q -mlfqs run mlfqs-load-1
+374 pages available in kernel pool.
+373 pages available in user pool.
+Calibrating timer...  102,400 loops/s.
+Boot complete.
+Executing 'mlfqs-load-1':
+(mlfqs-load-1) begin
+(mlfqs-load-1) spinning for up to 45 seconds, please wait...
+(mlfqs-load-1) load average rose to 0.5 after 42 seconds
+(mlfqs-load-1) sleeping for another 10 seconds, please wait...
+

+ +...until it gets stuck because of the bug I had introduced. I hit +Ctrl+C in the debugger window: +

+

+ +
 
Program received signal 0, Signal 0.
+0xc010168c in next_thread_to_run () at ../../threads/thread.c:649
+649        while (i <= PRI_MAX && list_empty (&ready_list[i]))
+(gdb)
+

+ +The thread that was running when I interrupted Pintos was the idle +thread. If I run backtrace, it shows this backtrace: +

+

+ +
 
(gdb) bt
+#0  0xc010168c in next_thread_to_run () at ../../threads/thread.c:649
+#1  0xc0101778 in schedule () at ../../threads/thread.c:714
+#2  0xc0100f8f in thread_block () at ../../threads/thread.c:324
+#3  0xc0101419 in idle (aux=0x0) at ../../threads/thread.c:551
+#4  0xc010145a in kernel_thread (function=0xc01013ff , aux=0x0)
+    at ../../threads/thread.c:575
+#5  0x00000000 in ?? ()
+

+ +Not terribly useful. What I really like to know is what's up with the +other thread (or threads). Since I keep all threads in a linked list +called all_list, linked together by a struct list_elem member +named allelem, I can use the btthreadlist macro from the +macro library I wrote. btthreadlist iterates through the list of +threads and prints the backtrace for each thread: +

+

+ +
 
(gdb) btthreadlist all_list allelem
+pintos-debug: dumping backtrace of thread 'main' @0xc002f000
+#0  0xc0101820 in schedule () at ../../threads/thread.c:722
+#1  0xc0100f8f in thread_block () at ../../threads/thread.c:324
+#2  0xc0104755 in timer_sleep (ticks=1000) at ../../devices/timer.c:141
+#3  0xc010bf7c in test_mlfqs_load_1 () at ../../tests/threads/mlfqs-load-1.c:49
+#4  0xc010aabb in run_test (name=0xc0007d8c "mlfqs-load-1")
+    at ../../tests/threads/tests.c:50
+#5  0xc0100647 in run_task (argv=0xc0110d28) at ../../threads/init.c:281
+#6  0xc0100721 in run_actions (argv=0xc0110d28) at ../../threads/init.c:331
+#7  0xc01000c7 in main () at ../../threads/init.c:140
+
+pintos-debug: dumping backtrace of thread 'idle' @0xc0116000
+#0  0xc010168c in next_thread_to_run () at ../../threads/thread.c:649
+#1  0xc0101778 in schedule () at ../../threads/thread.c:714
+#2  0xc0100f8f in thread_block () at ../../threads/thread.c:324
+#3  0xc0101419 in idle (aux=0x0) at ../../threads/thread.c:551
+#4  0xc010145a in kernel_thread (function=0xc01013ff , aux=0x0)
+    at ../../threads/thread.c:575
+#5  0x00000000 in ?? ()
+

+ +In this case, there are only two threads, the idle thread and the main +thread. The kernel stack pages (to which the struct thread points) +are at 0xc0116000 and 0xc002f000, respectively. The main thread +is stuck in timer_sleep(), called from test_mlfqs_load_1. +

+

+ +Knowing where threads are stuck can be tremendously useful, for instance +when diagnosing deadlocks or unexplained hangs. +

+

+ + +

+
+
GDB Macro: loadusersymbols +

+ +You can also use GDB to debug a user program running under Pintos. +To do that, use the loadusersymbols macro to load the program's +symbol table: +
 
loadusersymbols program
+
where program is the name of the program's executable (in the host +file system, not in the Pintos file system). For example, you may issue: +
 
(gdb) loadusersymbols tests/userprog/exec-multiple
+add symbol table from file "tests/userprog/exec-multiple" at
+    .text_addr = 0x80480a0
+(gdb)
+

+ +After this, you should be +able to debug the user program the same way you would the kernel, by +placing breakpoints, inspecting data, etc. Your actions apply to every +user program running in Pintos, not just to the one you want to debug, +so be careful in interpreting the results: GDB does not know +which process is currently active (because that is an abstraction +the Pintos kernel creates). Also, a name that appears in +both the kernel and the user program will actually refer to the kernel +name. (The latter problem can be avoided by giving the user executable +name on the GDB command line, instead of kernel.o, and then using +loadusersymbols to load kernel.o.) +loadusersymbols is implemented via GDB's add-symbol-file +command. +

+

+ +

+
+

+ + +


+ +

D.5.3 FAQ

+ +

+ +

+
+
GDB can't connect to Bochs. +

+ +If the target remote command fails, then make sure that both +GDB and pintos are running on the same machine by +running hostname in each terminal. If the names printed +differ, then you need to open a new terminal for GDB on the +machine running pintos. +

+

+ +

+
GDB doesn't recognize any of the macros. +

+ +If you start GDB with pintos-gdb, it should load the Pintos +macros automatically. If you start GDB some other way, then you must +issue the command source pintosdir/src/misc/gdb-macros, +where pintosdir is the root of your Pintos directory, before you +can use them. +

+

+ +

+
Can I debug Pintos with DDD? +

+ +Yes, you can. DDD invokes GDB as a subprocess, so you'll need to tell +it to invokes pintos-gdb instead: +
 
ddd --gdb --debugger pintos-gdb
+

+ +

+
Can I use GDB inside Emacs? +

+ +Yes, you can. Emacs has special support for running GDB as a +subprocess. Type M-x gdb and enter your pintos-gdb +command at the prompt. The Emacs manual has information on how to use +its debugging features in a section titled "Debuggers." +

+

+ +

+
GDB is doing something weird. +

+ +If you notice strange behavior while using GDB, there +are three possibilities: a bug in your +modified Pintos, a bug in Bochs's +interface to GDB or in GDB itself, or +a bug in the original Pintos code. The first and second +are quite likely, and you should seriously consider both. We hope +that the third is less likely, but it is also possible. +

+

+ + +


+ +

D.6 Triple Faults

+ +

+ +When a CPU exception handler, such as a page fault handler, cannot be +invoked because it is missing or defective, the CPU will try to invoke +the "double fault" handler. If the double fault handler is itself +missing or defective, that's called a "triple fault." A triple fault +causes an immediate CPU reset. +

+

+ +Thus, if you get yourself into a situation where the machine reboots in +a loop, that's probably a "triple fault." In a triple fault +situation, you might not be able to use printf() for debugging, +because the reboots might be happening even before everything needed for +printf() is initialized. +

+

+ +There are at least two ways to debug triple faults. First, you can run +Pintos in Bochs under GDB (see section D.5 GDB). If Bochs has been built +properly for Pintos, a triple fault under GDB will cause it to print the +message "Triple fault: stopping for gdb" on the console and break into +the debugger. (If Bochs is not running under GDB, a triple fault will +still cause it to reboot.) You can then inspect where Pintos stopped, +which is where the triple fault occurred. +

+

+ +Another option is what I call "debugging by infinite loop." +Pick a place in the Pintos code, insert the infinite loop +for (;;); there, and recompile and run. There are two likely +possibilities: +

+

+ +

+

+ +If you move around the infinite loop in a "binary search" fashion, you +can use this technique to pin down the exact spot that everything goes +wrong. It should only take a few minutes at most. +

+

+ + +


+ +

D.7 Modifying Bochs

+ +

+ +An advanced debugging technique is to modify and recompile the +simulator. This proves useful when the simulated hardware has more +information than it makes available to the OS. For example, page +faults have a long list of potential causes, but the hardware does not +report to the OS exactly which one is the particular cause. +Furthermore, a bug in the kernel's handling of page faults can easily +lead to recursive faults, but a "triple fault" will cause the CPU to +reset itself, which is hardly conducive to debugging. +

+

+ +In a case like this, you might appreciate being able to make Bochs +print out more debug information, such as the exact type of fault that +occurred. It's not very hard. You start by retrieving the source +code for Bochs 2.2.6 from http://bochs.sourceforge.net and +saving the file bochs-2.2.6.tar.gz into a directory. +The script pintos/src/misc/bochs-2.2.6-build.sh +applies a number of patches contained in pintos/src/misc +to the Bochs tree, then builds Bochs and installs it in a directory +of your choice. +Run this script without arguments to learn usage instructions. +To use your bochs binary with pintos, make sure +it is the one printed by which `bochs`; otherwise, modify +your PATH accordingly. +

+

+ +Of course, to get any good out of this you'll have to actually modify +Bochs. Instructions for doing this are firmly out of the scope of +this document. However, if you want to debug page faults as suggested +above, a good place to start adding printf()s is +BX_CPU_C::dtranslate_linear() in cpu/paging.cc. +

+

+ + +


+ +

D.8 Tips

+ +

+ +The page allocator in threads/palloc.c and the block allocator in +threads/malloc.c clear all the bytes in memory to +0xcc at time of free. Thus, if you see an attempt to +dereference a pointer like 0xcccccccc, or some other reference to +0xcc, there's a good chance you're trying to reuse a page that's +already been freed. Also, byte 0xcc is the CPU opcode for "invoke +interrupt 3," so if you see an error like Interrupt 0x03 (#BP +Breakpoint Exception), then Pintos tried to execute code in a freed page or +block. +

+

+ +An assertion failure on the expression sec_no < d->capacity +indicates that Pintos tried to access a file through an inode that has +been closed and freed. Freeing an inode clears its starting sector +number to 0xcccccccc, which is not a valid sector number for disks +smaller than about 1.6 TB. + +


+ + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_9.html b/doc/pintos_9.html new file mode 100644 index 0000000..b63a0ba --- /dev/null +++ b/doc/pintos_9.html @@ -0,0 +1,143 @@ + + + + + +Pintos Projects: Development Tools + + + + + + + + + + + + + + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+ +
+

E. Development Tools

+ +

+ +Here are some tools that you might find useful while developing code. +

+

+ + +


+ +

E.1 Tags

+ +

+ +Tags are an index to the functions and global variables declared in a +program. Many editors, including Emacs and vi, can use +them. The Makefile in pintos/src produces Emacs-style +tags with the command make TAGS or vi-style tags with +make tags. +

+

+ +In Emacs, use M-. to follow a tag in the current window, +C-x 4 . in a new window, or C-x 5 . in a new frame. If +your cursor is on a symbol name for any of those commands, it becomes +the default target. If a tag name has multiple definitions, M-0 +M-. jumps to the next one. To jump back to where you were before +you followed the last tag, use M-*. +

+

+ + +


+ +

E.2 cscope

+ +

+ +The cscope program also provides an index to functions and +variables declared in a program. It has some features that tag +facilities lack. Most notably, it can find all the points in a +program at which a given function is called. +

+

+ +The Makefile in pintos/src produces cscope +indexes when it is invoked as make cscope. Once the index has +been generated, run cscope from a shell command line; no +command-line arguments are normally necessary. Then use the arrow +keys to choose one of the search criteria listed near the bottom of +the terminal, type in an identifier, and hit Enter. +cscope will then display the matches in the upper part of +the terminal. You may use the arrow keys to choose a particular +match; if you then hit Enter, cscope will invoke the +default system editor(7) and position the +cursor on that match. To start a new search, type Tab. To exit +cscope, type Ctrl-d. +

+

+ +Emacs and some versions of vi have their own interfaces to +cscope. For information on how to use these interface, +visit http://cscope.sourceforge.net, the cscope home +page. +

+

+ + +


+ +

E.3 git

+ +

+ +git is a version-control system. That is, you can use it to keep +track of multiple versions of files. The idea is that you do some +work on your code and test it, then check it into the version-control +system. If you decide that the work you've done since your last +check-in is no good, you can easily revert to the last checked-in +version. Furthermore, you can retrieve any old version of your code +as of some given day and time. The version control logs tell you who +made changes and when. +

+

+ + +


+ + + + + + + +
[ << ][ >> ]           [Top][Contents][Index][ ? ]
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_abt.html b/doc/pintos_abt.html new file mode 100644 index 0000000..6683381 --- /dev/null +++ b/doc/pintos_abt.html @@ -0,0 +1,205 @@ + + + + + +Pintos Projects: About this document + + + + + + + + + + + + + + + + + +
[Top][Contents][Index][ ? ]
+

About this document

+This document was generated +by +using texi2html +

+The buttons in the navigation panels have the following meaning: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Button Name Go to From 1.2.3 go to
+ [ < ] +Back + +previous section in reading order + +1.2.2 +
+ [ > ] +Forward + +next section in reading order + +1.2.4 +
+ [ << ] +FastBack + +beginning of this chapter or previous chapter + +1 +
+ [ Up ] +Up + +up section + +1.2 +
+ [ >> ] +FastForward + +next chapter + +2 +
+ [Top] +Top + +cover (top) of document + +   +
+ [Contents] +Contents + +table of contents + +   +
+ [Index] +Index + +concept index + +   +
+ [ ? ] +About + +this page + +   +
+

+ where the Example assumes that the current position + is at Subsubsection One-Two-Three of a document of + the following structure:

+ + +
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_fot.html b/doc/pintos_fot.html new file mode 100644 index 0000000..820500e --- /dev/null +++ b/doc/pintos_fot.html @@ -0,0 +1,79 @@ + + + + + +Pintos Projects: Footnotes + + + + + + + + + + + + + + + + + +
[Top][Contents][Index][ ? ]
+

Footnotes

+

(1)

+

GDB might tell you that +schedule() doesn't exist, which is arguably a GDB bug. +You can work around this by setting the breakpoint by filename and +line number, e.g. break thread.c:ln where ln is +the line number of the first declaration in schedule(). +

(2)

+

We +will treat these terms as synonyms. There is no standard +distinction between them, although Intel processor manuals make +a minor distinction between them on 80x86. +

(3)

+

This is because switch_threads() takes +arguments on the stack and the 80x86 SVR4 calling convention +requires the caller, not the called function, to remove them when the +call is complete. See [ SysV-i386] chapter 3 for details. +

(4)

+

Actually, virtual to physical translation on the +80x86 architecture occurs via an intermediate "linear +address," but Pintos (and most modern 80x86 OSes) set up the CPU +so that linear and virtual addresses are one and the same. Thus, you +can effectively ignore this CPU feature. +

(5)

+

pintos-gdb is a wrapper around +gdb (80x86) or i386-elf-gdb (SPARC) that loads +the Pintos macros at startup. +

(6)

+

To be precise, GDB will stop +only when running under Bochs. When running under QEMU, you must +set a breakpoint in the page_fault function to stop execution +when a page fault occurs. In that case, the btpagefault macro is +unnecessary. +

(7)

+

This is typically vi. To +exit vi, type : q Enter. +


+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_ovr.html b/doc/pintos_ovr.html new file mode 100644 index 0000000..610d243 --- /dev/null +++ b/doc/pintos_ovr.html @@ -0,0 +1,69 @@ + + + + + +Pintos Projects: Short Table of Contents + + + + + + + + + + + + + + + + + +
[Top][Contents][Index][ ? ]
+

Short Table of Contents

+
+1. Introduction +
+2. Project 0: Introducing Pintos +
+3. Project 1: Threads +
+4. Project 2: Virtual Memory +
+A. Reference Guide +
+B. Coding Standards +
+C. Project Documentation +
+D. Debugging Tools +
+E. Development Tools +
+Bibliography +
+License +
+ +
+
+
+ +This document was generated +by on March, 6 2012 +using texi2html + + + + diff --git a/doc/pintos_tour.pdf b/doc/pintos_tour.pdf new file mode 100644 index 0000000..98ec371 Binary files /dev/null and b/doc/pintos_tour.pdf differ diff --git a/doc/sample.tmpl b/doc/sample.tmpl new file mode 100644 index 0000000..2d07635 --- /dev/null +++ b/doc/sample.tmpl @@ -0,0 +1,104 @@ + + +-----------------+ + | CS 140 | + | SAMPLE PROJECT | + | DESIGN DOCUMENT | + +-----------------+ + +---- GROUP ---- + +Ben Pfaff + +---- PRELIMINARIES ---- + +>> If you have any preliminary comments on your submission, notes for +>> the TAs, or extra credit, please give them here. + +(This is a sample design document.) + +>> Please cite any offline or online sources you consulted while +>> preparing your submission, other than the Pintos documentation, +>> course text, and lecture notes. + +None. + + JOIN + ==== + +---- DATA STRUCTURES ---- + +>> Copy here the declaration of each new or changed `struct' or `struct' +>> member, global or static variable, `typedef', or enumeration. +>> Identify the purpose of each in 25 words or less. + +A "latch" is a new synchronization primitive. Acquires block +until the first release. Afterward, all ongoing and future +acquires pass immediately. + + /* Latch. */ + struct latch + { + bool released; /* Released yet? */ + struct lock monitor_lock; /* Monitor lock. */ + struct condition rel_cond; /* Signaled when released. */ + }; + +Added to struct thread: + + /* Members for implementing thread_join(). */ + struct latch ready_to_die; /* Release when thread about to die. */ + struct semaphore can_die; /* Up when thread allowed to die. */ + struct list children; /* List of child threads. */ + list_elem children_elem; /* Element of `children' list. */ + +---- ALGORITHMS ---- + +>> Briefly describe your implementation of thread_join() and how it +>> interacts with thread termination. + +thread_join() finds the joined child on the thread's list of +children and waits for the child to exit by acquiring the child's +ready_to_die latch. When thread_exit() is called, the thread +releases its ready_to_die latch, allowing the parent to continue. + +---- SYNCHRONIZATION ---- + +>> Consider parent thread P with child thread C. How do you ensure +>> proper synchronization and avoid race conditions when P calls wait(C) +>> before C exits? After C exits? How do you ensure that all resources +>> are freed in each case? How about when P terminates without waiting, +>> before C exits? After C exits? Are there any special cases? + +C waits in thread_exit() for P to die before it finishes its own +exit, using the can_die semaphore "down"ed by C and "up"ed by P as +it exits. Regardless of whether whether C has terminated, there +is no race on wait(C), because C waits for P's permission before +it frees itself. + +Regardless of whether P waits for C, P still "up"s C's can_die +semaphore when P dies, so C will always be freed. (However, +freeing C's resources is delayed until P's death.) + +The initial thread is a special case because it has no parent to +wait for it or to "up" its can_die semaphore. Therefore, its +can_die semaphore is initialized to 1. + +---- RATIONALE ---- + +>> Critique your design, pointing out advantages and disadvantages in +>> your design choices. + +This design has the advantage of simplicity. Encapsulating most +of the synchronization logic into a new "latch" structure +abstracts what little complexity there is into a separate layer, +making the design easier to reason about. Also, all the new data +members are in `struct thread', with no need for any extra dynamic +allocation, etc., that would require extra management code. + +On the other hand, this design is wasteful in that a child thread +cannot free itself before its parent has terminated. A parent +thread that creates a large number of short-lived child threads +could unnecessarily exhaust kernel memory. This is probably +acceptable for implementing kernel threads, but it may be a bad +idea for use with user processes because of the larger number of +resources that user processes tend to own. diff --git a/doc/start.tmpl b/doc/start.tmpl new file mode 100644 index 0000000..83b17ad --- /dev/null +++ b/doc/start.tmpl @@ -0,0 +1,101 @@ + +--------------------+ + | OS Development | + | PROJECT 0: INTRO | + | DESIGN DOCUMENT | + +--------------------+ + +---- GROUP ---- + +>> Fill in the names and email addresses of your group members. + +FirstName LastName +FirstName LastName +FirstName LastName + +---- PRELIMINARIES ---- + +>> If you have any preliminary comments on your submission, notes for the +>> TAs, or extra credit, please give them here. + +>> Please cite any offline or online sources you consulted while +>> preparing your submission, other than the Pintos documentation, course +>> text, lecture notes, and course staff. + + ALARM CLOCK + =========== + +---- DATA STRUCTURES ---- + +>> A1: Copy here the declaration of each new or changed `struct' or +>> `struct' member, global or static variable, `typedef', or +>> enumeration. Identify the purpose of each in 25 words or less. + +---- ALGORITHMS ---- + +>> A2: Briefly describe what happens in a call to timer_sleep(), +>> including the effects of the timer interrupt handler. + +>> A3: What steps are taken to minimize the amount of time spent in +>> the timer interrupt handler? + +---- SYNCHRONIZATION ---- + +>> A4: How are race conditions avoided when multiple threads call +>> timer_sleep() simultaneously? + +>> A5: How are race conditions avoided when a timer interrupt occurs +>> during a call to timer_sleep()? + +---- RATIONALE ---- + +>> A6: Why did you choose this design? In what ways is it superior to +>> another design you considered? + + ARGUMENT PASSING + ================ + +---- DATA STRUCTURES ---- + +>> A1: Copy here the declaration of each new or changed `struct' or +>> `struct' member, global or static variable, `typedef', or +>> enumeration. Identify the purpose of each in 25 words or less. + +---- ALGORITHMS ---- + +>> A2: Briefly describe how you implemented argument parsing. How do +>> you arrange for the elements of argv[] to be in the right order? +>> How do you avoid overflowing the stack page? + +---- RATIONALE ---- + +>> A3: Why does Pintos implement strtok_r() but not strtok()? + +>> A4: In Pintos, the kernel separates commands into a executable name +>> and arguments. In Unix-like systems, the shell does this +>> separation. Identify at least two advantages of the Unix approach. + + + + SURVEY QUESTIONS + ================ + +Answering these questions is optional, but it will help us improve the +course in future quarters. Feel free to tell us anything you +want--these questions are just to spur your thoughts. You may also +choose to respond anonymously in the course evaluations at the end of +the quarter. + +>> In your opinion, was this assignment, or any one of the three problems +>> in it, too easy or too hard? Did it take too long or too little time? + +>> Did you find that working on a particular part of the assignment gave +>> you greater insight into some aspect of OS design? + +>> Is there some particular fact or hint we should give students in +>> future quarters to help them solve the problems? Conversely, did you +>> find any of our guidance to be misleading? + +>> Do you have any suggestions for the TAs to more effectively assist +>> students, either for future quarters or the remaining projects? + +>> Any other comments? diff --git a/doc/threads.tmpl b/doc/threads.tmpl new file mode 100644 index 0000000..c3df5fe --- /dev/null +++ b/doc/threads.tmpl @@ -0,0 +1,82 @@ + +--------------------+ + | CS 140 | + | PROJECT 1: THREADS | + | DESIGN DOCUMENT | + +--------------------+ + +---- GROUP ---- + +>> Fill in the names and email addresses of your group members. + +FirstName LastName +FirstName LastName +FirstName LastName + +---- PRELIMINARIES ---- + +>> If you have any preliminary comments on your submission, notes for the +>> TAs, or extra credit, please give them here. + +>> Please cite any offline or online sources you consulted while +>> preparing your submission, other than the Pintos documentation, course +>> text, lecture notes, and course staff. + + + PRIORITY SCHEDULING + =================== + +---- DATA STRUCTURES ---- + +>> B1: Copy here the declaration of each new or changed `struct' or +>> `struct' member, global or static variable, `typedef', or +>> enumeration. Identify the purpose of each in 25 words or less. + +>> B2: Explain the data structure used to track priority donation. +>> Use ASCII art to diagram a nested donation. (Alternately, submit a +>> .png file.) + +---- ALGORITHMS ---- + +>> B3: How do you ensure that the highest priority thread waiting for +>> a lock, semaphore, or condition variable wakes up first? + +>> B4: Describe the sequence of events when a call to lock_acquire() +>> causes a priority donation. How is nested donation handled? + +>> B5: Describe the sequence of events when lock_release() is called +>> on a lock that a higher-priority thread is waiting for. + +---- SYNCHRONIZATION ---- + +>> B6: Describe a potential race in thread_set_priority() and explain +>> how your implementation avoids it. Can you use a lock to avoid +>> this race? + +---- RATIONALE ---- + +>> B7: Why did you choose this design? In what ways is it superior to +>> another design you considered? + + SURVEY QUESTIONS + ================ + +Answering these questions is optional, but it will help us improve the +course in future quarters. Feel free to tell us anything you +want--these questions are just to spur your thoughts. You may also +choose to respond anonymously in the course evaluations at the end of +the quarter. + +>> In your opinion, was this assignment, or any one of the three problems +>> in it, too easy or too hard? Did it take too long or too little time? + +>> Did you find that working on a particular part of the assignment gave +>> you greater insight into some aspect of OS design? + +>> Is there some particular fact or hint we should give students in +>> future quarters to help them solve the problems? Conversely, did you +>> find any of our guidance to be misleading? + +>> Do you have any suggestions for the TAs to more effectively assist +>> students, either for future quarters or the remaining projects? + +>> Any other comments? diff --git a/doc/vm.tmpl b/doc/vm.tmpl new file mode 100644 index 0000000..82b0806 --- /dev/null +++ b/doc/vm.tmpl @@ -0,0 +1,81 @@ + +---------------------------+ + | CS 140 | + | PROJECT 3: VIRTUAL MEMORY | + | DESIGN DOCUMENT | + +---------------------------+ + +---- GROUP ---- + +>> Fill in the names and email addresses of your group members. + +FirstName LastName +FirstName LastName +FirstName LastName + +---- PRELIMINARIES ---- + +>> If you have any preliminary comments on your submission, notes for the +>> TAs, or extra credit, please give them here. + +>> Please cite any offline or online sources you consulted while +>> preparing your submission, other than the Pintos documentation, course +>> text, lecture notes, and course staff. + + PAGE TABLE MANAGEMENT + ===================== + +TODO + + STACK GROWTH + ============ +TODO + + MEMORY MAPPED FILES + =================== + +---- DATA STRUCTURES ---- + +>> C1: Copy here the declaration of each new or changed `struct' or +>> `struct' member, global or static variable, `typedef', or +>> enumeration. Identify the purpose of each in 25 words or less. + +---- ALGORITHMS ---- + +>> C2: Describe how memory mapped files integrate into your virtual +>> memory subsystem. + +>> C3: Explain how you determine whether a new file mapping overlaps +>> any existing segment. + +---- RATIONALE ---- + +>> C4: Mappings created with "mmap" have similar semantics to those of +>> data demand-paged from executables, except that "mmap" mappings are +>> written back to their original files, not to swap. This implies +>> that much of their implementation can be shared. Explain why your +>> implementation either does or does not share much of the code for +>> the two situations. + + SURVEY QUESTIONS + ================ + +Answering these questions is optional, but it will help us improve the +course in future quarters. Feel free to tell us anything you +want--these questions are just to spur your thoughts. You may also +choose to respond anonymously in the course evaluations at the end of +the quarter. + +>> In your opinion, was this assignment, or any one of the three problems +>> in it, too easy or too hard? Did it take too long or too little time? + +>> Did you find that working on a particular part of the assignment gave +>> you greater insight into some aspect of OS design? + +>> Is there some particular fact or hint we should give students in +>> future quarters to help them solve the problems? Conversely, did you +>> find any of our guidance to be misleading? + +>> Do you have any suggestions for the TAs to more effectively assist +>> students, either for future quarters or the remaining projects? + +>> Any other comments? diff --git a/env.sh b/env.sh new file mode 100644 index 0000000..99fb242 --- /dev/null +++ b/env.sh @@ -0,0 +1,3 @@ +#!/bin/sh +export PATH="$PATH:$(dirname "${BASH_SOURCE[0]}")/pintos-progos/utils" +export PATH="/opt/bochs/bin:$PATH" diff --git a/gdb-macros b/gdb-macros new file mode 100644 index 0000000..a0b68c3 --- /dev/null +++ b/gdb-macros @@ -0,0 +1,140 @@ +# +# A set of useful macros that can help debug Pintos. +# +# Include with "source" cmd in gdb. +# Use "help user-defined" for help. +# +# Author: Godmar Back , Feb 2006 +# +# $Id: gdb-macros,v 1.1 2006-04-07 18:29:34 blp Exp $ +# + +# for internal use +define offsetof + set $rc = (char*)&((struct $arg0 *)0)->$arg1 - (char*)0 +end + +define list_entry + offsetof $arg1 $arg2 + set $rc = ((struct $arg1 *) ((uint8_t *) ($arg0) - $rc)) +end + +# dump a Pintos list +define dumplist + set $list = $arg0 + set $e = $list->head.next + set $i = 0 + while $e != &(($arg0).tail) + list_entry $e $arg1 $arg2 + set $l = $rc + printf "pintos-debug: dumplist #%d: %p ", $i++, $l + output *$l + set $e = $e->next + printf "\n" + end +end + +document dumplist + Dump the content of a Pintos list, + invoke as dumplist name_of_list name_of_struct name_of_elem_in_list_struct +end + +# print a thread's backtrace, given a pointer to the struct thread * +define btthread + if $arg0 == ($esp - ((unsigned)$esp % 4096)) + bt + else + set $saveEIP = $eip + set $saveESP = $esp + set $saveEBP = $ebp + + set $esp = ((struct thread *)$arg0)->stack + set $ebp = ((void**)$esp)[2] + set $eip = ((void**)$esp)[4] + + bt + + set $eip = $saveEIP + set $esp = $saveESP + set $ebp = $saveEBP + end +end +document btthread + Show the backtrace of a thread, + invoke as btthread pointer_to_struct_thread +end + +# print backtraces associated with all threads in a list +define btthreadlist + set $list = $arg0 + set $e = $list->head.next + while $e != &(($arg0).tail) + list_entry $e thread $arg1 + printf "pintos-debug: dumping backtrace of thread '%s' @%p\n", \ + ((struct thread*)$rc)->name, $rc + btthread $rc + set $e = $e->next + printf "\n" + end +end +document btthreadlist + Given a list of threads, print each thread's backtrace + invoke as btthreadlist name_of_list name_of_elem_in_list_struct +end + +# print backtraces of all threads (based on 'all_list' all threads list) +define btthreadall + btthreadlist all_list allelem +end +document btthreadall + Print backtraces of all threads +end + +# print a correct backtrace by adjusting $eip +# this works best right at intr0e_stub +define btpagefault + set $saveeip = $eip + set $eip = ((void**)$esp)[1] + backtrace + set $eip = $saveeip +end +document btpagefault + Print a backtrace of the current thread after a pagefault +end + +# invoked whenever the program stops +define hook-stop + # stopped at stub #0E = #14 (page fault exception handler stub) + if ($eip == intr0e_stub) + set $savedeip = ((void**)$esp)[1] + # if this was in user mode, the OS should handle it + # either handle the page fault or terminate the process + if ($savedeip < 0xC0000000) + printf "pintos-debug: a page fault exception occurred in user mode\n" + printf "pintos-debug: hit 'c' to continue, or 's' to step to intr_handler\n" + else + # if this was in kernel mode, a stack trace might be useful + printf "pintos-debug: a page fault occurred in kernel mode\n" + btpagefault + end + end +end + +# load symbols for a Pintos user program +define loadusersymbols + shell objdump -h $arg0 | awk '/.text/ { print "add-symbol-file $arg0 0x"$4 }' > .loadsymbols + source .loadsymbols + shell rm -f .loadsymbols +end +document loadusersymbols + Load the symbols contained in a user program's executable. + Example: + loadusersymbols tests/userprog/exec-multiple +end + +define debugpintos + target remote localhost:1234 +end +document debugpintos + Attach debugger to pintos process +end diff --git a/pintos-progos/LICENSE b/pintos-progos/LICENSE new file mode 100644 index 0000000..8702541 --- /dev/null +++ b/pintos-progos/LICENSE @@ -0,0 +1,95 @@ +Pintos, including its documentation, is subject to the following +license: + + Copyright (C) 2004, 2005, 2006 Board of Trustees, Leland Stanford + Jr. University. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +A few individual files in Pintos were originally derived from other +projects, but they have been extensively modified for use in Pintos. +The original code falls under the original license, and modifications +for Pintos are additionally covered by the Pintos license above. + +In particular, code derived from Nachos is subject to the following +license: + +/* Copyright (c) 1992-1996 The Regents of the University of California. + All rights reserved. + + Permission to use, copy, modify, and distribute this software + and its documentation for any purpose, without fee, and + without written agreement is hereby granted, provided that the + above copyright notice and the following two paragraphs appear + in all copies of this software. + + IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO + ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE + AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA + HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" + BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO + PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR + MODIFICATIONS. +*/ + +Also, code derived from MIT's 6.828 course code is subject to the +following license: + +/* + * Copyright (C) 1997 Massachusetts Institute of Technology + * + * This software is being provided by the copyright holders under the + * following license. By obtaining, using and/or copying this software, + * you agree that you have read, understood, and will comply with the + * following terms and conditions: + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose and without fee or royalty is + * hereby granted, provided that the full text of this NOTICE appears on + * ALL copies of the software and documentation or portions thereof, + * including modifications, that you make. + * + * THIS SOFTWARE IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, + * BUT NOT LIMITATION, COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR + * WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR + * THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY + * THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT + * HOLDERS WILL BEAR NO LIABILITY FOR ANY USE OF THIS SOFTWARE OR + * DOCUMENTATION. + * + * The name and trademarks of copyright holders may NOT be used in + * advertising or publicity pertaining to the software without specific, + * written prior permission. Title to copyright in this software and any + * associated documentation will at all times remain with copyright + * holders. See the file AUTHORS which should have accompanied this software + * for a list of all copyright holders. + * + * This file may be derived from previously copyrighted software. This + * copyright applies only to those changes made by the copyright + * holders listed in the AUTHORS file. The rest of this file is covered by + * the copyright notices, if any, listed below. + */ diff --git a/pintos-progos/Make.config b/pintos-progos/Make.config new file mode 100644 index 0000000..f00e8fc --- /dev/null +++ b/pintos-progos/Make.config @@ -0,0 +1,52 @@ +# -*- makefile -*- + +SHELL = /bin/sh + +VPATH = $(SRCDIR) + +# Binary utilities. +# If the host appears to be x86, use the normal tools. +# If it's x86-64, use the compiler and linker in 32-bit mode. +# Otherwise assume cross-tools are installed as i386-elf-*. +X86 = i.86\|pentium.*\|[pk][56]\|nexgen\|viac3\|6x86\|athlon.*\|i86pc +X86_64 = x86_64 +ifneq (0, $(shell expr `uname -m` : '$(X86)')) + CC = gcc + LD = ld + OBJCOPY = objcopy +else + ifneq (0, $(shell expr `uname -m` : '$(X86_64)')) + CC = gcc -m32 + LD = ld -melf_i386 + OBJCOPY = objcopy + else + CC = i386-elf-gcc + LD = i386-elf-ld + OBJCOPY = i386-elf-objcopy + endif +endif + +ifeq ($(strip $(shell command -v $(CC) 2> /dev/null)),) +$(warning *** Compiler ($(CC)) not found. Did you set $$PATH properly? Please refer to the Getting Started section in the documentation for details. ***) +endif + +# Compiler and assembler invocation. +DEFINES = +WARNINGS = -Wall -W -Wstrict-prototypes -Wmissing-prototypes -Wsystem-headers +CFLAGS = -g -msoft-float -O +CPPFLAGS = -nostdinc -I$(SRCDIR) -I$(SRCDIR)/lib +ASFLAGS = -Wa,--gstabs +LDFLAGS = +DEPS = -MMD -MF $(@:.o=.d) + +# Turn off -fstack-protector, which we don't support. +CFLAGS += -fno-stack-protector + +# Turn off --build-id in the linker, which confuses the Pintos loader. +LDFLAGS += -Wl,--build-id=none + +%.o: %.c + $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) $(WARNINGS) $(DEFINES) $(DEPS) + +%.o: %.S + $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) $(DEPS) diff --git a/pintos-progos/Makefile b/pintos-progos/Makefile new file mode 100644 index 0000000..3ba9194 --- /dev/null +++ b/pintos-progos/Makefile @@ -0,0 +1,29 @@ +BUILD_SUBDIRS = threads userprog vm filesys intro + +all:: + @echo "Run 'make' in subdirectories: $(BUILD_SUBDIRS)." + @echo "This top-level make has only 'clean' targets." + +CLEAN_SUBDIRS = $(BUILD_SUBDIRS) examples utils + +clean:: + for d in $(CLEAN_SUBDIRS); do $(MAKE) -C $$d $@; done + rm -f TAGS tags + +distclean:: clean + find . -name '*~' -exec rm '{}' \; + +TAGS_SUBDIRS = $(BUILD_SUBDIRS) devices lib +TAGS_SOURCES = find $(TAGS_SUBDIRS) -name \*.[chS] -print + +TAGS:: + etags --members `$(TAGS_SOURCES)` + +tags:: + ctags -T --no-warn `$(TAGS_SOURCES)` + +cscope.files:: + $(TAGS_SOURCES) > cscope.files + +cscope:: cscope.files + cscope -b -q -k diff --git a/pintos-progos/Makefile.build b/pintos-progos/Makefile.build new file mode 100644 index 0000000..e997d27 --- /dev/null +++ b/pintos-progos/Makefile.build @@ -0,0 +1,109 @@ +# -*- makefile -*- + +SRCDIR = ../.. + +all: kernel.bin loader.bin + +include ../../Make.config +include ../Make.vars +include ../../tests/Make.tests + +# Compiler and assembler options. +kernel.bin: CPPFLAGS += -I$(SRCDIR)/lib/kernel + +# Core kernel. +threads_SRC = threads/start.S # Startup code. +threads_SRC += threads/init.c # Main program. +threads_SRC += threads/thread.c # Thread management core. +threads_SRC += threads/switch.S # Thread switch routine. +threads_SRC += threads/interrupt.c # Interrupt core. +threads_SRC += threads/intr-stubs.S # Interrupt stubs. +threads_SRC += threads/synch.c # Synchronization. +threads_SRC += threads/palloc.c # Page allocator. +threads_SRC += threads/malloc.c # Subpage allocator. + +# Device driver code. +devices_SRC = devices/pit.c # Programmable interrupt timer chip. +devices_SRC += devices/timer.c # Periodic timer device. +devices_SRC += devices/kbd.c # Keyboard device. +devices_SRC += devices/vga.c # Video device. +devices_SRC += devices/serial.c # Serial port device. +devices_SRC += devices/block.c # Block device abstraction layer. +devices_SRC += devices/partition.c # Partition block device. +devices_SRC += devices/ide.c # IDE disk block device. +devices_SRC += devices/input.c # Serial and keyboard input. +devices_SRC += devices/intq.c # Interrupt queue. +devices_SRC += devices/rtc.c # Real-time clock. +devices_SRC += devices/shutdown.c # Reboot and power off. +devices_SRC += devices/speaker.c # PC speaker. + +# Library code shared between kernel and user programs. +lib_SRC = lib/debug.c # Debug helpers. +lib_SRC += lib/random.c # Pseudo-random numbers. +lib_SRC += lib/stdio.c # I/O library. +lib_SRC += lib/stdlib.c # Utility functions. +lib_SRC += lib/string.c # String functions. +lib_SRC += lib/arithmetic.c # 64-bit arithmetic for GCC. +lib_SRC += lib/ustar.c # Unix standard tar format utilities. + +# Kernel-specific library code. +lib/kernel_SRC = lib/kernel/debug.c # Debug helpers. +lib/kernel_SRC += lib/kernel/list.c # Doubly-linked lists. +lib/kernel_SRC += lib/kernel/bitmap.c # Bitmaps. +lib/kernel_SRC += lib/kernel/hash.c # Hash tables. +lib/kernel_SRC += lib/kernel/console.c # printf(), putchar(). + +# User process code. +userprog_SRC = userprog/process.c # Process loading. +userprog_SRC += userprog/pagedir.c # Page directories. +userprog_SRC += userprog/exception.c # User exception handler. +userprog_SRC += userprog/syscall.c # System call handler. +userprog_SRC += userprog/gdt.c # GDT initialization. +userprog_SRC += userprog/tss.c # TSS management. + +# No virtual memory code yet. +#vm_SRC = vm/file.c # Some file. + +# Filesystem code. +filesys_SRC = filesys/filesys.c # Filesystem core. +filesys_SRC += filesys/free-map.c # Free sector bitmap. +filesys_SRC += filesys/file.c # Files. +filesys_SRC += filesys/directory.c # Directories. +filesys_SRC += filesys/inode.c # File headers. +filesys_SRC += filesys/fsutil.c # Utilities. + +SOURCES = $(foreach dir,$(KERNEL_SUBDIRS),$($(dir)_SRC)) +OBJECTS = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SOURCES))) +DEPENDS = $(patsubst %.o,%.d,$(OBJECTS)) + +threads/kernel.lds.s: CPPFLAGS += -P +threads/kernel.lds.s: threads/kernel.lds.S threads/loader.h + +kernel.o: threads/kernel.lds.s $(OBJECTS) + $(LD) -T $< -o $@ $(OBJECTS) + +kernel.bin: kernel.o + $(OBJCOPY) -R .note -R .comment -S $< $@ + +threads/loader.o: threads/loader.S + $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) + +loader.bin: threads/loader.o + $(LD) -N -e 0 -Ttext 0x7c00 --oformat binary -o $@ $< + +os.dsk: kernel.bin + cat $^ > $@ + +clean:: + rm -f $(OBJECTS) $(DEPENDS) + rm -f threads/loader.o threads/kernel.lds.s threads/loader.d + rm -f kernel.bin.tmp + rm -f kernel.o kernel.lds.s + rm -f kernel.bin loader.bin + rm -f bochsout.txt bochsrc.txt + rm -f results grade + +Makefile: $(SRCDIR)/Makefile.build + cp $< $@ + +-include $(DEPENDS) diff --git a/pintos-progos/Makefile.kernel b/pintos-progos/Makefile.kernel new file mode 100644 index 0000000..162a411 --- /dev/null +++ b/pintos-progos/Makefile.kernel @@ -0,0 +1,20 @@ +# -*- makefile -*- + +all: + +include Make.vars + +DIRS = $(sort $(addprefix build/,$(KERNEL_SUBDIRS) $(TEST_SUBDIRS) lib/user)) + +all grade check: $(DIRS) build/Makefile + cd build && $(MAKE) $@ +$(DIRS): + mkdir -p $@ +build/Makefile: ../Makefile.build + cp $< $@ + +build/%: $(DIRS) build/Makefile + cd build && $(MAKE) $* + +clean: + rm -rf build diff --git a/pintos-progos/Makefile.userprog b/pintos-progos/Makefile.userprog new file mode 100644 index 0000000..0df391a --- /dev/null +++ b/pintos-progos/Makefile.userprog @@ -0,0 +1,52 @@ +# -*- makefile -*- + +$(PROGS): CPPFLAGS += -I$(SRCDIR)/lib/user -I. + +# Linker flags. +$(PROGS): LDFLAGS += -nostdlib -static -Wl,-T,$(LDSCRIPT) +$(PROGS): LDSCRIPT = $(SRCDIR)/lib/user/user.lds + +# Library code shared between kernel and user programs. +lib_SRC = lib/debug.c # Debug code. +lib_SRC += lib/random.c # Pseudo-random numbers. +lib_SRC += lib/stdio.c # I/O library. +lib_SRC += lib/stdlib.c # Utility functions. +lib_SRC += lib/string.c # String functions. +lib_SRC += lib/arithmetic.c # 64-bit arithmetic for GCC. +lib_SRC += lib/ustar.c # Unix standard tar format utilities. + +# User level only library code. +lib/user_SRC = lib/user/debug.c # Debug helpers. +lib/user_SRC += lib/user/syscall.c # System calls. +lib/user_SRC += lib/user/console.c # Console code. + +LIB_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(lib_SRC) $(lib/user_SRC))) +LIB_DEP = $(patsubst %.o,%.d,$(LIB_OBJ)) +LIB = lib/user/entry.o libc.a + +PROGS_SRC = $(foreach prog,$(PROGS),$($(prog)_SRC)) +PROGS_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(PROGS_SRC))) +PROGS_DEP = $(patsubst %.o,%.d,$(PROGS_OBJ)) + +all: $(PROGS) + +define TEMPLATE +$(1)_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$($(1)_SRC))) +$(1): $$($(1)_OBJ) $$(LIB) $$(LDSCRIPT) + $$(CC) $$(LDFLAGS) $$($(1)_OBJ) $$(LIB) -o $$@ +endef + +$(foreach prog,$(PROGS),$(eval $(call TEMPLATE,$(prog)))) + +libc.a: $(LIB_OBJ) + rm -f $@ + ar r $@ $^ + ranlib $@ + +clean:: + rm -f $(PROGS) $(PROGS_OBJ) $(PROGS_DEP) + rm -f $(LIB_DEP) $(LIB_OBJ) lib/user/entry.[do] libc.a + +.PHONY: all clean + +-include $(LIB_DEP) $(PROGS_DEP) diff --git a/pintos-progos/devices/block.c b/pintos-progos/devices/block.c new file mode 100644 index 0000000..a3acec1 --- /dev/null +++ b/pintos-progos/devices/block.c @@ -0,0 +1,223 @@ +#include "devices/block.h" +#include +#include +#include +#include "devices/ide.h" +#include "threads/malloc.h" + +/* A block device. */ +struct block + { + struct list_elem list_elem; /* Element in all_blocks. */ + + char name[16]; /* Block device name. */ + enum block_type type; /* Type of block device. */ + block_sector_t size; /* Size in sectors. */ + + const struct block_operations *ops; /* Driver operations. */ + void *aux; /* Extra data owned by driver. */ + + unsigned long long read_cnt; /* Number of sectors read. */ + unsigned long long write_cnt; /* Number of sectors written. */ + }; + +/* List of all block devices. */ +static struct list all_blocks = LIST_INITIALIZER (all_blocks); + +/* The block block assigned to each Pintos role. */ +static struct block *block_by_role[BLOCK_ROLE_CNT]; + +static struct block *list_elem_to_block (struct list_elem *); + +/* Returns a human-readable name for the given block device + TYPE. */ +const char * +block_type_name (enum block_type type) +{ + static const char *block_type_names[BLOCK_CNT] = + { + "kernel", + "filesys", + "scratch", + "swap", + "raw", + "foreign", + }; + + ASSERT (type < BLOCK_CNT); + return block_type_names[type]; +} + +/* Returns the block device fulfilling the given ROLE, or a null + pointer if no block device has been assigned that role. */ +struct block * +block_get_role (enum block_type role) +{ + ASSERT (role < BLOCK_ROLE_CNT); + return block_by_role[role]; +} + +/* Assigns BLOCK the given ROLE. */ +void +block_set_role (enum block_type role, struct block *block) +{ + ASSERT (role < BLOCK_ROLE_CNT); + block_by_role[role] = block; +} + +/* Returns the first block device in kernel probe order, or a + null pointer if no block devices are registered. */ +struct block * +block_first (void) +{ + return list_elem_to_block (list_begin (&all_blocks)); +} + +/* Returns the block device following BLOCK in kernel probe + order, or a null pointer if BLOCK is the last block device. */ +struct block * +block_next (struct block *block) +{ + return list_elem_to_block (list_next (&block->list_elem)); +} + +/* Returns the block device with the given NAME, or a null + pointer if no block device has that name. */ +struct block * +block_get_by_name (const char *name) +{ + struct list_elem *e; + + for (e = list_begin (&all_blocks); e != list_end (&all_blocks); + e = list_next (e)) + { + struct block *block = list_entry (e, struct block, list_elem); + if (!strcmp (name, block->name)) + return block; + } + + return NULL; +} + +/* Verifies that SECTOR is a valid offset within BLOCK. + Panics if not. */ +static void +check_sector (struct block *block, block_sector_t sector) +{ + if (sector >= block->size) + { + /* We do not use ASSERT because we want to panic here + regardless of whether NDEBUG is defined. */ + PANIC ("Access past end of device %s (sector=%"PRDSNu", " + "size=%"PRDSNu")\n", block_name (block), sector, block->size); + } +} + +/* Reads sector SECTOR from BLOCK into BUFFER, which must + have room for BLOCK_SECTOR_SIZE bytes. + Internally synchronizes accesses to block devices, so external + per-block device locking is unneeded. */ +void +block_read (struct block *block, block_sector_t sector, void *buffer) +{ + check_sector (block, sector); + block->ops->read (block->aux, sector, buffer); + block->read_cnt++; +} + +/* Write sector SECTOR to BLOCK from BUFFER, which must contain + BLOCK_SECTOR_SIZE bytes. Returns after the block device has + acknowledged receiving the data. + Internally synchronizes accesses to block devices, so external + per-block device locking is unneeded. */ +void +block_write (struct block *block, block_sector_t sector, const void *buffer) +{ + check_sector (block, sector); + ASSERT (block->type != BLOCK_FOREIGN); + block->ops->write (block->aux, sector, buffer); + block->write_cnt++; +} + +/* Returns the number of sectors in BLOCK. */ +block_sector_t +block_size (struct block *block) +{ + return block->size; +} + +/* Returns BLOCK's name (e.g. "hda"). */ +const char * +block_name (struct block *block) +{ + return block->name; +} + +/* Returns BLOCK's type. */ +enum block_type +block_type (struct block *block) +{ + return block->type; +} + +/* Prints statistics for each block device used for a Pintos role. */ +void +block_print_stats (void) +{ + int i; + + for (i = 0; i < BLOCK_ROLE_CNT; i++) + { + struct block *block = block_by_role[i]; + if (block != NULL) + { + printf ("%s (%s): %llu reads, %llu writes\n", + block->name, block_type_name (block->type), + block->read_cnt, block->write_cnt); + } + } +} + +/* Registers a new block device with the given NAME. If + EXTRA_INFO is non-null, it is printed as part of a user + message. The block device's SIZE in sectors and its TYPE must + be provided, as well as the it operation functions OPS, which + will be passed AUX in each function call. */ +struct block * +block_register (const char *name, enum block_type type, + const char *extra_info, block_sector_t size, + const struct block_operations *ops, void *aux) +{ + struct block *block = malloc (sizeof *block); + if (block == NULL) + PANIC ("Failed to allocate memory for block device descriptor"); + + list_push_back (&all_blocks, &block->list_elem); + strlcpy (block->name, name, sizeof block->name); + block->type = type; + block->size = size; + block->ops = ops; + block->aux = aux; + block->read_cnt = 0; + block->write_cnt = 0; + + printf ("%s: %'"PRDSNu" sectors (", block->name, block->size); + print_human_readable_size ((uint64_t) block->size * BLOCK_SECTOR_SIZE); + printf (")"); + if (extra_info != NULL) + printf (", %s", extra_info); + printf ("\n"); + + return block; +} + +/* Returns the block device corresponding to LIST_ELEM, or a null + pointer if LIST_ELEM is the list end of all_blocks. */ +static struct block * +list_elem_to_block (struct list_elem *list_elem) +{ + return (list_elem != list_end (&all_blocks) + ? list_entry (list_elem, struct block, list_elem) + : NULL); +} + diff --git a/pintos-progos/devices/block.h b/pintos-progos/devices/block.h new file mode 100644 index 0000000..21732d6 --- /dev/null +++ b/pintos-progos/devices/block.h @@ -0,0 +1,74 @@ +#ifndef DEVICES_BLOCK_H +#define DEVICES_BLOCK_H + +#include +#include + +/* Size of a block device sector in bytes. + All IDE disks use this sector size, as do most USB and SCSI + disks. It's not worth it to try to cater to other sector + sizes in Pintos (yet). */ +#define BLOCK_SECTOR_SIZE 512 + +/* Index of a block device sector. + Good enough for devices up to 2 TB. */ +typedef uint32_t block_sector_t; + +/* Format specifier for printf(), e.g.: + printf ("sector=%"PRDSNu"\n", sector); */ +#define PRDSNu PRIu32 + +/* Higher-level interface for file systems, etc. */ + +struct block; + +/* Type of a block device. */ +enum block_type + { + /* Block device types that play a role in Pintos. */ + BLOCK_KERNEL, /* Pintos OS kernel. */ + BLOCK_FILESYS, /* File system. */ + BLOCK_SCRATCH, /* Scratch. */ + BLOCK_SWAP, /* Swap. */ + BLOCK_ROLE_CNT, + + /* Other kinds of block devices that Pintos may see but does + not interact with. */ + BLOCK_RAW = BLOCK_ROLE_CNT, /* "Raw" device with unidentified contents. */ + BLOCK_FOREIGN, /* Owned by non-Pintos operating system. */ + BLOCK_CNT /* Number of Pintos block types. */ + }; + +const char *block_type_name (enum block_type); + +/* Finding block devices. */ +struct block *block_get_role (enum block_type); +void block_set_role (enum block_type, struct block *); +struct block *block_get_by_name (const char *name); + +struct block *block_first (void); +struct block *block_next (struct block *); + +/* Block device operations. */ +block_sector_t block_size (struct block *); +void block_read (struct block *, block_sector_t, void *); +void block_write (struct block *, block_sector_t, const void *); +const char *block_name (struct block *); +enum block_type block_type (struct block *); + +/* Statistics. */ +void block_print_stats (void); + +/* Lower-level interface to block device drivers. */ + +struct block_operations + { + void (*read) (void *aux, block_sector_t, void *buffer); + void (*write) (void *aux, block_sector_t, const void *buffer); + }; + +struct block *block_register (const char *name, enum block_type, + const char *extra_info, block_sector_t size, + const struct block_operations *, void *aux); + +#endif /* devices/block.h */ diff --git a/pintos-progos/devices/ide.c b/pintos-progos/devices/ide.c new file mode 100644 index 0000000..2cc0292 --- /dev/null +++ b/pintos-progos/devices/ide.c @@ -0,0 +1,527 @@ +#include "devices/ide.h" +#include +#include +#include +#include +#include "devices/block.h" +#include "devices/partition.h" +#include "devices/timer.h" +#include "threads/io.h" +#include "threads/interrupt.h" +#include "threads/synch.h" + +/* The code in this file is an interface to an ATA (IDE) + controller. It attempts to comply to [ATA-3]. */ + +/* ATA command block port addresses. */ +#define reg_data(CHANNEL) ((CHANNEL)->reg_base + 0) /* Data. */ +#define reg_error(CHANNEL) ((CHANNEL)->reg_base + 1) /* Error. */ +#define reg_nsect(CHANNEL) ((CHANNEL)->reg_base + 2) /* Sector Count. */ +#define reg_lbal(CHANNEL) ((CHANNEL)->reg_base + 3) /* LBA 0:7. */ +#define reg_lbam(CHANNEL) ((CHANNEL)->reg_base + 4) /* LBA 15:8. */ +#define reg_lbah(CHANNEL) ((CHANNEL)->reg_base + 5) /* LBA 23:16. */ +#define reg_device(CHANNEL) ((CHANNEL)->reg_base + 6) /* Device/LBA 27:24. */ +#define reg_status(CHANNEL) ((CHANNEL)->reg_base + 7) /* Status (r/o). */ +#define reg_command(CHANNEL) reg_status (CHANNEL) /* Command (w/o). */ + +/* ATA control block port addresses. + (If we supported non-legacy ATA controllers this would not be + flexible enough, but it's fine for what we do.) */ +#define reg_ctl(CHANNEL) ((CHANNEL)->reg_base + 0x206) /* Control (w/o). */ +#define reg_alt_status(CHANNEL) reg_ctl (CHANNEL) /* Alt Status (r/o). */ + +/* Alternate Status Register bits. */ +#define STA_BSY 0x80 /* Busy. */ +#define STA_DRDY 0x40 /* Device Ready. */ +#define STA_DRQ 0x08 /* Data Request. */ + +/* Control Register bits. */ +#define CTL_SRST 0x04 /* Software Reset. */ + +/* Device Register bits. */ +#define DEV_MBS 0xa0 /* Must be set. */ +#define DEV_LBA 0x40 /* Linear based addressing. */ +#define DEV_DEV 0x10 /* Select device: 0=master, 1=slave. */ + +/* Commands. + Many more are defined but this is the small subset that we + use. */ +#define CMD_IDENTIFY_DEVICE 0xec /* IDENTIFY DEVICE. */ +#define CMD_READ_SECTOR_RETRY 0x20 /* READ SECTOR with retries. */ +#define CMD_WRITE_SECTOR_RETRY 0x30 /* WRITE SECTOR with retries. */ + +/* An ATA device. */ +struct ata_disk + { + char name[8]; /* Name, e.g. "hda". */ + struct channel *channel; /* Channel that disk is attached to. */ + int dev_no; /* Device 0 or 1 for master or slave. */ + bool is_ata; /* Is device an ATA disk? */ + }; + +/* An ATA channel (aka controller). + Each channel can control up to two disks. */ +struct channel + { + char name[8]; /* Name, e.g. "ide0". */ + uint16_t reg_base; /* Base I/O port. */ + uint8_t irq; /* Interrupt in use. */ + + struct lock lock; /* Must acquire to access the controller. */ + bool expecting_interrupt; /* True if an interrupt is expected, false if + any interrupt would be spurious. */ + struct semaphore completion_wait; /* Up'd by interrupt handler. */ + + struct ata_disk devices[2]; /* The devices on this channel. */ + }; + +/* We support the two "legacy" ATA channels found in a standard PC. */ +#define CHANNEL_CNT 2 +static struct channel channels[CHANNEL_CNT]; + +static struct block_operations ide_operations; + +static void reset_channel (struct channel *); +static bool check_device_type (struct ata_disk *); +static void identify_ata_device (struct ata_disk *); + +static void select_sector (struct ata_disk *, block_sector_t); +static void issue_pio_command (struct channel *, uint8_t command); +static void input_sector (struct channel *, void *); +static void output_sector (struct channel *, const void *); + +static void wait_until_idle (const struct ata_disk *); +static bool wait_while_busy (const struct ata_disk *); +static void select_device (const struct ata_disk *); +static void select_device_wait (const struct ata_disk *); + +static void interrupt_handler (struct intr_frame *); + +/* Initialize the disk subsystem and detect disks. */ +void +ide_init (void) +{ + size_t chan_no; + + for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++) + { + struct channel *c = &channels[chan_no]; + int dev_no; + + /* Initialize channel. */ + snprintf (c->name, sizeof c->name, "ide%zu", chan_no); + switch (chan_no) + { + case 0: + c->reg_base = 0x1f0; + c->irq = 14 + 0x20; + break; + case 1: + c->reg_base = 0x170; + c->irq = 15 + 0x20; + break; + default: + NOT_REACHED (); + } + lock_init (&c->lock); + c->expecting_interrupt = false; + sema_init (&c->completion_wait, 0); + + /* Initialize devices. */ + for (dev_no = 0; dev_no < 2; dev_no++) + { + struct ata_disk *d = &c->devices[dev_no]; + snprintf (d->name, sizeof d->name, + "hd%c", 'a' + chan_no * 2 + dev_no); + d->channel = c; + d->dev_no = dev_no; + d->is_ata = false; + } + + /* Register interrupt handler. */ + intr_register_ext (c->irq, interrupt_handler, c->name); + + /* Reset hardware. */ + reset_channel (c); + + /* Distinguish ATA hard disks from other devices. */ + if (check_device_type (&c->devices[0])) + check_device_type (&c->devices[1]); + + /* Read hard disk identity information. */ + for (dev_no = 0; dev_no < 2; dev_no++) + if (c->devices[dev_no].is_ata) + identify_ata_device (&c->devices[dev_no]); + } +} + +/* Disk detection and identification. */ + +static char *descramble_ata_string (char *, int size); + +/* Resets an ATA channel and waits for any devices present on it + to finish the reset. */ +static void +reset_channel (struct channel *c) +{ + bool present[2]; + int dev_no; + + /* The ATA reset sequence depends on which devices are present, + so we start by detecting device presence. */ + for (dev_no = 0; dev_no < 2; dev_no++) + { + struct ata_disk *d = &c->devices[dev_no]; + + select_device (d); + + outb (reg_nsect (c), 0x55); + outb (reg_lbal (c), 0xaa); + + outb (reg_nsect (c), 0xaa); + outb (reg_lbal (c), 0x55); + + outb (reg_nsect (c), 0x55); + outb (reg_lbal (c), 0xaa); + + present[dev_no] = (inb (reg_nsect (c)) == 0x55 + && inb (reg_lbal (c)) == 0xaa); + } + + /* Issue soft reset sequence, which selects device 0 as a side effect. + Also enable interrupts. */ + outb (reg_ctl (c), 0); + timer_usleep (10); + outb (reg_ctl (c), CTL_SRST); + timer_usleep (10); + outb (reg_ctl (c), 0); + + timer_msleep (150); + + /* Wait for device 0 to clear BSY. */ + if (present[0]) + { + select_device (&c->devices[0]); + wait_while_busy (&c->devices[0]); + } + + /* Wait for device 1 to clear BSY. */ + if (present[1]) + { + int i; + + select_device (&c->devices[1]); + for (i = 0; i < 3000; i++) + { + if (inb (reg_nsect (c)) == 1 && inb (reg_lbal (c)) == 1) + break; + timer_msleep (10); + } + wait_while_busy (&c->devices[1]); + } +} + +/* Checks whether device D is an ATA disk and sets D's is_ata + member appropriately. If D is device 0 (master), returns true + if it's possible that a slave (device 1) exists on this + channel. If D is device 1 (slave), the return value is not + meaningful. */ +static bool +check_device_type (struct ata_disk *d) +{ + struct channel *c = d->channel; + uint8_t error, lbam, lbah, status; + + select_device (d); + + error = inb (reg_error (c)); + lbam = inb (reg_lbam (c)); + lbah = inb (reg_lbah (c)); + status = inb (reg_status (c)); + + if ((error != 1 && (error != 0x81 || d->dev_no == 1)) + || (status & STA_DRDY) == 0 + || (status & STA_BSY) != 0) + { + d->is_ata = false; + return error != 0x81; + } + else + { + d->is_ata = (lbam == 0 && lbah == 0) || (lbam == 0x3c && lbah == 0xc3); + return true; + } +} + +/* Sends an IDENTIFY DEVICE command to disk D and reads the + response. Registers the disk with the block device + layer. */ +static void +identify_ata_device (struct ata_disk *d) +{ + struct channel *c = d->channel; + char id[BLOCK_SECTOR_SIZE]; + block_sector_t capacity; + char *model, *serial; + char extra_info[128]; + struct block *block; + + ASSERT (d->is_ata); + + /* Send the IDENTIFY DEVICE command, wait for an interrupt + indicating the device's response is ready, and read the data + into our buffer. */ + select_device_wait (d); + issue_pio_command (c, CMD_IDENTIFY_DEVICE); + sema_down (&c->completion_wait); + if (!wait_while_busy (d)) + { + d->is_ata = false; + return; + } + input_sector (c, id); + + /* Calculate capacity. + Read model name and serial number. */ + capacity = *(uint32_t *) &id[60 * 2]; + model = descramble_ata_string (&id[10 * 2], 20); + serial = descramble_ata_string (&id[27 * 2], 40); + snprintf (extra_info, sizeof extra_info, + "model \"%s\", serial \"%s\"", model, serial); + + /* Disable access to IDE disks over 1 GB, which are likely + physical IDE disks rather than virtual ones. If we don't + allow access to those, we're less likely to scribble on + someone's important data. You can disable this check by + hand if you really want to do so. */ + if (capacity >= 1024 * 1024 * 1024 / BLOCK_SECTOR_SIZE) + { + printf ("%s: ignoring ", d->name); + print_human_readable_size (capacity * 512); + printf ("disk for safety\n"); + d->is_ata = false; + return; + } + + /* Register. */ + block = block_register (d->name, BLOCK_RAW, extra_info, capacity, + &ide_operations, d); + partition_scan (block); +} + +/* Translates STRING, which consists of SIZE bytes in a funky + format, into a null-terminated string in-place. Drops + trailing whitespace and null bytes. Returns STRING. */ +static char * +descramble_ata_string (char *string, int size) +{ + int i; + + /* Swap all pairs of bytes. */ + for (i = 0; i + 1 < size; i += 2) + { + char tmp = string[i]; + string[i] = string[i + 1]; + string[i + 1] = tmp; + } + + /* Find the last non-white, non-null character. */ + for (size--; size > 0; size--) + { + int c = string[size - 1]; + if (c != '\0' && !isspace (c)) + break; + } + string[size] = '\0'; + + return string; +} + +/* Reads sector SEC_NO from disk D into BUFFER, which must have + room for BLOCK_SECTOR_SIZE bytes. + Internally synchronizes accesses to disks, so external + per-disk locking is unneeded. */ +static void +ide_read (void *d_, block_sector_t sec_no, void *buffer) +{ + struct ata_disk *d = d_; + struct channel *c = d->channel; + lock_acquire (&c->lock); + select_sector (d, sec_no); + issue_pio_command (c, CMD_READ_SECTOR_RETRY); + sema_down (&c->completion_wait); + if (!wait_while_busy (d)) + PANIC ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no); + input_sector (c, buffer); + lock_release (&c->lock); +} + +/* Write sector SEC_NO to disk D from BUFFER, which must contain + BLOCK_SECTOR_SIZE bytes. Returns after the disk has + acknowledged receiving the data. + Internally synchronizes accesses to disks, so external + per-disk locking is unneeded. */ +static void +ide_write (void *d_, block_sector_t sec_no, const void *buffer) +{ + struct ata_disk *d = d_; + struct channel *c = d->channel; + lock_acquire (&c->lock); + select_sector (d, sec_no); + issue_pio_command (c, CMD_WRITE_SECTOR_RETRY); + if (!wait_while_busy (d)) + PANIC ("%s: disk write failed, sector=%"PRDSNu, d->name, sec_no); + output_sector (c, buffer); + sema_down (&c->completion_wait); + lock_release (&c->lock); +} + +static struct block_operations ide_operations = + { + ide_read, + ide_write + }; + +/* Selects device D, waiting for it to become ready, and then + writes SEC_NO to the disk's sector selection registers. (We + use LBA mode.) */ +static void +select_sector (struct ata_disk *d, block_sector_t sec_no) +{ + struct channel *c = d->channel; + + ASSERT (sec_no < (1UL << 28)); + + select_device_wait (d); + outb (reg_nsect (c), 1); + outb (reg_lbal (c), sec_no); + outb (reg_lbam (c), sec_no >> 8); + outb (reg_lbah (c), (sec_no >> 16)); + outb (reg_device (c), + DEV_MBS | DEV_LBA | (d->dev_no == 1 ? DEV_DEV : 0) | (sec_no >> 24)); +} + +/* Writes COMMAND to channel C and prepares for receiving a + completion interrupt. */ +static void +issue_pio_command (struct channel *c, uint8_t command) +{ + /* Interrupts must be enabled or our semaphore will never be + up'd by the completion handler. */ + ASSERT (intr_get_level () == INTR_ON); + + c->expecting_interrupt = true; + outb (reg_command (c), command); +} + +/* Reads a sector from channel C's data register in PIO mode into + SECTOR, which must have room for BLOCK_SECTOR_SIZE bytes. */ +static void +input_sector (struct channel *c, void *sector) +{ + insw (reg_data (c), sector, BLOCK_SECTOR_SIZE / 2); +} + +/* Writes SECTOR to channel C's data register in PIO mode. + SECTOR must contain BLOCK_SECTOR_SIZE bytes. */ +static void +output_sector (struct channel *c, const void *sector) +{ + outsw (reg_data (c), sector, BLOCK_SECTOR_SIZE / 2); +} + +/* Low-level ATA primitives. */ + +/* Wait up to 10 seconds for the controller to become idle, that + is, for the BSY and DRQ bits to clear in the status register. + + As a side effect, reading the status register clears any + pending interrupt. */ +static void +wait_until_idle (const struct ata_disk *d) +{ + int i; + + for (i = 0; i < 1000; i++) + { + if ((inb (reg_status (d->channel)) & (STA_BSY | STA_DRQ)) == 0) + return; + timer_usleep (10); + } + + printf ("%s: idle timeout\n", d->name); +} + +/* Wait up to 30 seconds for disk D to clear BSY, + and then return the status of the DRQ bit. + The ATA standards say that a disk may take as long as that to + complete its reset. */ +static bool +wait_while_busy (const struct ata_disk *d) +{ + struct channel *c = d->channel; + int i; + + for (i = 0; i < 3000; i++) + { + if (i == 700) + printf ("%s: busy, waiting...", d->name); + if (!(inb (reg_alt_status (c)) & STA_BSY)) + { + if (i >= 700) + printf ("ok\n"); + return (inb (reg_alt_status (c)) & STA_DRQ) != 0; + } + timer_msleep (10); + } + + printf ("failed\n"); + return false; +} + +/* Program D's channel so that D is now the selected disk. */ +static void +select_device (const struct ata_disk *d) +{ + struct channel *c = d->channel; + uint8_t dev = DEV_MBS; + if (d->dev_no == 1) + dev |= DEV_DEV; + outb (reg_device (c), dev); + inb (reg_alt_status (c)); + timer_nsleep (400); +} + +/* Select disk D in its channel, as select_device(), but wait for + the channel to become idle before and after. */ +static void +select_device_wait (const struct ata_disk *d) +{ + wait_until_idle (d); + select_device (d); + wait_until_idle (d); +} + +/* ATA interrupt handler. */ +static void +interrupt_handler (struct intr_frame *f) +{ + struct channel *c; + + for (c = channels; c < channels + CHANNEL_CNT; c++) + if (f->vec_no == c->irq) + { + if (c->expecting_interrupt) + { + inb (reg_status (c)); /* Acknowledge interrupt. */ + sema_up (&c->completion_wait); /* Wake up waiter. */ + } + else + printf ("%s: unexpected interrupt\n", c->name); + return; + } + + NOT_REACHED (); +} + + diff --git a/pintos-progos/devices/ide.h b/pintos-progos/devices/ide.h new file mode 100644 index 0000000..b35da5e --- /dev/null +++ b/pintos-progos/devices/ide.h @@ -0,0 +1,6 @@ +#ifndef DEVICES_IDE_H +#define DEVICES_IDE_H + +void ide_init (void); + +#endif /* devices/ide.h */ diff --git a/pintos-progos/devices/input.c b/pintos-progos/devices/input.c new file mode 100644 index 0000000..4a12160 --- /dev/null +++ b/pintos-progos/devices/input.c @@ -0,0 +1,52 @@ +#include "devices/input.h" +#include +#include "devices/intq.h" +#include "devices/serial.h" + +/* Stores keys from the keyboard and serial port. */ +static struct intq buffer; + +/* Initializes the input buffer. */ +void +input_init (void) +{ + intq_init (&buffer); +} + +/* Adds a key to the input buffer. + Interrupts must be off and the buffer must not be full. */ +void +input_putc (uint8_t key) +{ + ASSERT (intr_get_level () == INTR_OFF); + ASSERT (!intq_full (&buffer)); + + intq_putc (&buffer, key); + serial_notify (); +} + +/* Retrieves a key from the input buffer. + If the buffer is empty, waits for a key to be pressed. */ +uint8_t +input_getc (void) +{ + enum intr_level old_level; + uint8_t key; + + old_level = intr_disable (); + key = intq_getc (&buffer); + serial_notify (); + intr_set_level (old_level); + + return key; +} + +/* Returns true if the input buffer is full, + false otherwise. + Interrupts must be off. */ +bool +input_full (void) +{ + ASSERT (intr_get_level () == INTR_OFF); + return intq_full (&buffer); +} diff --git a/pintos-progos/devices/input.h b/pintos-progos/devices/input.h new file mode 100644 index 0000000..a2f50e9 --- /dev/null +++ b/pintos-progos/devices/input.h @@ -0,0 +1,12 @@ +#ifndef DEVICES_INPUT_H +#define DEVICES_INPUT_H + +#include +#include + +void input_init (void); +void input_putc (uint8_t); +uint8_t input_getc (void); +bool input_full (void); + +#endif /* devices/input.h */ diff --git a/pintos-progos/devices/intq.c b/pintos-progos/devices/intq.c new file mode 100644 index 0000000..40b23ae --- /dev/null +++ b/pintos-progos/devices/intq.c @@ -0,0 +1,114 @@ +#include "devices/intq.h" +#include +#include "threads/thread.h" + +static int next (int pos); +static void wait (struct intq *q, struct thread **waiter); +static void signal (struct intq *q, struct thread **waiter); + +/* Initializes interrupt queue Q. */ +void +intq_init (struct intq *q) +{ + lock_init (&q->lock); + q->not_full = q->not_empty = NULL; + q->head = q->tail = 0; +} + +/* Returns true if Q is empty, false otherwise. */ +bool +intq_empty (const struct intq *q) +{ + ASSERT (intr_get_level () == INTR_OFF); + return q->head == q->tail; +} + +/* Returns true if Q is full, false otherwise. */ +bool +intq_full (const struct intq *q) +{ + ASSERT (intr_get_level () == INTR_OFF); + return next (q->head) == q->tail; +} + +/* Removes a byte from Q and returns it. + If Q is empty, sleeps until a byte is added. + When called from an interrupt handler, Q must not be empty. */ +uint8_t +intq_getc (struct intq *q) +{ + uint8_t byte; + + ASSERT (intr_get_level () == INTR_OFF); + while (intq_empty (q)) + { + ASSERT (!intr_context ()); + lock_acquire (&q->lock); + wait (q, &q->not_empty); + lock_release (&q->lock); + } + + byte = q->buf[q->tail]; + q->tail = next (q->tail); + signal (q, &q->not_full); + return byte; +} + +/* Adds BYTE to the end of Q. + If Q is full, sleeps until a byte is removed. + When called from an interrupt handler, Q must not be full. */ +void +intq_putc (struct intq *q, uint8_t byte) +{ + ASSERT (intr_get_level () == INTR_OFF); + while (intq_full (q)) + { + ASSERT (!intr_context ()); + lock_acquire (&q->lock); + wait (q, &q->not_full); + lock_release (&q->lock); + } + + q->buf[q->head] = byte; + q->head = next (q->head); + signal (q, &q->not_empty); +} + +/* Returns the position after POS within an intq. */ +static int +next (int pos) +{ + return (pos + 1) % INTQ_BUFSIZE; +} + +/* WAITER must be the address of Q's not_empty or not_full + member. Waits until the given condition is true. */ +static void +wait (struct intq *q UNUSED, struct thread **waiter) +{ + ASSERT (!intr_context ()); + ASSERT (intr_get_level () == INTR_OFF); + ASSERT ((waiter == &q->not_empty && intq_empty (q)) + || (waiter == &q->not_full && intq_full (q))); + + *waiter = thread_current (); + thread_block (); +} + +/* WAITER must be the address of Q's not_empty or not_full + member, and the associated condition must be true. If a + thread is waiting for the condition, wakes it up and resets + the waiting thread. */ +static void +signal (struct intq *q UNUSED, struct thread **waiter) +{ + ASSERT (intr_get_level () == INTR_OFF); + ASSERT ((waiter == &q->not_empty && !intq_empty (q)) + || (waiter == &q->not_full && !intq_full (q))); + + if (*waiter != NULL) + { + thread_unblock (*waiter); + *waiter = NULL; + } +} diff --git a/pintos-progos/devices/intq.h b/pintos-progos/devices/intq.h new file mode 100644 index 0000000..2312b12 --- /dev/null +++ b/pintos-progos/devices/intq.h @@ -0,0 +1,43 @@ +#ifndef DEVICES_INTQ_H +#define DEVICES_INTQ_H + +#include "threads/interrupt.h" +#include "threads/synch.h" + +/* An "interrupt queue", a circular buffer shared between + kernel threads and external interrupt handlers. + + Interrupt queue functions can be called from kernel threads or + from external interrupt handlers. Except for intq_init(), + interrupts must be off in either case. + + The interrupt queue has the structure of a "monitor". Locks + and condition variables from threads/synch.h cannot be used in + this case, as they normally would, because they can only + protect kernel threads from one another, not from interrupt + handlers. */ + +/* Queue buffer size, in bytes. */ +#define INTQ_BUFSIZE 64 + +/* A circular queue of bytes. */ +struct intq + { + /* Waiting threads. */ + struct lock lock; /* Only one thread may wait at once. */ + struct thread *not_full; /* Thread waiting for not-full condition. */ + struct thread *not_empty; /* Thread waiting for not-empty condition. */ + + /* Queue. */ + uint8_t buf[INTQ_BUFSIZE]; /* Buffer. */ + int head; /* New data is written here. */ + int tail; /* Old data is read here. */ + }; + +void intq_init (struct intq *); +bool intq_empty (const struct intq *); +bool intq_full (const struct intq *); +uint8_t intq_getc (struct intq *); +void intq_putc (struct intq *, uint8_t); + +#endif /* devices/intq.h */ diff --git a/pintos-progos/devices/kbd.c b/pintos-progos/devices/kbd.c new file mode 100644 index 0000000..fcc82be --- /dev/null +++ b/pintos-progos/devices/kbd.c @@ -0,0 +1,213 @@ +#include "devices/kbd.h" +#include +#include +#include +#include +#include "devices/input.h" +#include "devices/shutdown.h" +#include "threads/interrupt.h" +#include "threads/io.h" + +/* Keyboard data register port. */ +#define DATA_REG 0x60 + +/* Current state of shift keys. + True if depressed, false otherwise. */ +static bool left_shift, right_shift; /* Left and right Shift keys. */ +static bool left_alt, right_alt; /* Left and right Alt keys. */ +static bool left_ctrl, right_ctrl; /* Left and right Ctl keys. */ + +/* Status of Caps Lock. + True when on, false when off. */ +static bool caps_lock; + +/* Number of keys pressed. */ +static int64_t key_cnt; + +static intr_handler_func keyboard_interrupt; + +/* Initializes the keyboard. */ +void +kbd_init (void) +{ + intr_register_ext (0x21, keyboard_interrupt, "8042 Keyboard"); +} + +/* Prints keyboard statistics. */ +void +kbd_print_stats (void) +{ + printf ("Keyboard: %lld keys pressed\n", key_cnt); +} + +/* Maps a set of contiguous scancodes into characters. */ +struct keymap + { + uint8_t first_scancode; /* First scancode. */ + const char *chars; /* chars[0] has scancode first_scancode, + chars[1] has scancode first_scancode + 1, + and so on to the end of the string. */ + }; + +/* Keys that produce the same characters regardless of whether + the Shift keys are down. Case of letters is an exception + that we handle elsewhere. */ +static const struct keymap invariant_keymap[] = + { + {0x01, "\033"}, /* Escape. */ + {0x0e, "\b"}, + {0x0f, "\tQWERTYUIOP"}, + {0x1c, "\r"}, + {0x1e, "ASDFGHJKL"}, + {0x2c, "ZXCVBNM"}, + {0x37, "*"}, + {0x39, " "}, + {0x53, "\177"}, /* Delete. */ + {0, NULL}, + }; + +/* Characters for keys pressed without Shift, for those keys + where it matters. */ +static const struct keymap unshifted_keymap[] = + { + {0x02, "1234567890-="}, + {0x1a, "[]"}, + {0x27, ";'`"}, + {0x2b, "\\"}, + {0x33, ",./"}, + {0, NULL}, + }; + +/* Characters for keys pressed with Shift, for those keys where + it matters. */ +static const struct keymap shifted_keymap[] = + { + {0x02, "!@#$%^&*()_+"}, + {0x1a, "{}"}, + {0x27, ":\"~"}, + {0x2b, "|"}, + {0x33, "<>?"}, + {0, NULL}, + }; + +static bool map_key (const struct keymap[], unsigned scancode, uint8_t *); + +static void +keyboard_interrupt (struct intr_frame *args UNUSED) +{ + /* Status of shift keys. */ + bool shift = left_shift || right_shift; + bool alt = left_alt || right_alt; + bool ctrl = left_ctrl || right_ctrl; + + /* Keyboard scancode. */ + unsigned code; + + /* False if key pressed, true if key released. */ + bool release; + + /* Character that corresponds to `code'. */ + uint8_t c; + + /* Read scancode, including second byte if prefix code. */ + code = inb (DATA_REG); + if (code == 0xe0) + code = (code << 8) | inb (DATA_REG); + + /* Bit 0x80 distinguishes key press from key release + (even if there's a prefix). */ + release = (code & 0x80) != 0; + code &= ~0x80u; + + /* Interpret key. */ + if (code == 0x3a) + { + /* Caps Lock. */ + if (!release) + caps_lock = !caps_lock; + } + else if (map_key (invariant_keymap, code, &c) + || (!shift && map_key (unshifted_keymap, code, &c)) + || (shift && map_key (shifted_keymap, code, &c))) + { + /* Ordinary character. */ + if (!release) + { + /* Reboot if Ctrl+Alt+Del pressed. */ + if (c == 0177 && ctrl && alt) + shutdown_reboot (); + + /* Handle Ctrl, Shift. + Note that Ctrl overrides Shift. */ + if (ctrl && c >= 0x40 && c < 0x60) + { + /* A is 0x41, Ctrl+A is 0x01, etc. */ + c -= 0x40; + } + else if (shift == caps_lock) + c = tolower (c); + + /* Handle Alt by setting the high bit. + This 0x80 is unrelated to the one used to + distinguish key press from key release. */ + if (alt) + c += 0x80; + + /* Append to keyboard buffer. */ + if (!input_full ()) + { + key_cnt++; + input_putc (c); + } + } + } + else + { + /* Maps a keycode into a shift state variable. */ + struct shift_key + { + unsigned scancode; + bool *state_var; + }; + + /* Table of shift keys. */ + static const struct shift_key shift_keys[] = + { + { 0x2a, &left_shift}, + { 0x36, &right_shift}, + { 0x38, &left_alt}, + {0xe038, &right_alt}, + { 0x1d, &left_ctrl}, + {0xe01d, &right_ctrl}, + {0, NULL}, + }; + + const struct shift_key *key; + + /* Scan the table. */ + for (key = shift_keys; key->scancode != 0; key++) + if (key->scancode == code) + { + *key->state_var = !release; + break; + } + } +} + +/* Scans the array of keymaps K for SCANCODE. + If found, sets *C to the corresponding character and returns + true. + If not found, returns false and C is ignored. */ +static bool +map_key (const struct keymap k[], unsigned scancode, uint8_t *c) +{ + for (; k->first_scancode != 0; k++) + if (scancode >= k->first_scancode + && scancode < k->first_scancode + strlen (k->chars)) + { + *c = k->chars[scancode - k->first_scancode]; + return true; + } + + return false; +} diff --git a/pintos-progos/devices/kbd.h b/pintos-progos/devices/kbd.h new file mode 100644 index 0000000..ed9c06b --- /dev/null +++ b/pintos-progos/devices/kbd.h @@ -0,0 +1,9 @@ +#ifndef DEVICES_KBD_H +#define DEVICES_KBD_H + +#include + +void kbd_init (void); +void kbd_print_stats (void); + +#endif /* devices/kbd.h */ diff --git a/pintos-progos/devices/partition.c b/pintos-progos/devices/partition.c new file mode 100644 index 0000000..7e97332 --- /dev/null +++ b/pintos-progos/devices/partition.c @@ -0,0 +1,324 @@ +#include "devices/partition.h" +#include +#include +#include +#include +#include "devices/block.h" +#include "threads/malloc.h" + +/* A partition of a block device. */ +struct partition + { + struct block *block; /* Underlying block device. */ + block_sector_t start; /* First sector within device. */ + }; + +static struct block_operations partition_operations; + +static void read_partition_table (struct block *, block_sector_t sector, + block_sector_t primary_extended_sector, + int *part_nr); +static void found_partition (struct block *, uint8_t type, + block_sector_t start, block_sector_t size, + int part_nr); +static const char *partition_type_name (uint8_t); + +/* Scans BLOCK for partitions of interest to Pintos. */ +void +partition_scan (struct block *block) +{ + int part_nr = 0; + read_partition_table (block, 0, 0, &part_nr); + if (part_nr == 0) + printf ("%s: Device contains no partitions\n", block_name (block)); +} + +/* Reads the partition table in the given SECTOR of BLOCK and + scans it for partitions of interest to Pintos. + + If SECTOR is 0, so that this is the top-level partition table + on BLOCK, then PRIMARY_EXTENDED_SECTOR is not meaningful; + otherwise, it should designate the sector of the top-level + extended partition table that was traversed to arrive at + SECTOR, for use in finding logical partitions (see the large + comment below). + + PART_NR points to the number of non-empty primary or logical + partitions already encountered on BLOCK. It is incremented as + partitions are found. */ +static void +read_partition_table (struct block *block, block_sector_t sector, + block_sector_t primary_extended_sector, + int *part_nr) +{ + /* Format of a partition table entry. See [Partitions]. */ + struct partition_table_entry + { + uint8_t bootable; /* 0x00=not bootable, 0x80=bootable. */ + uint8_t start_chs[3]; /* Encoded starting cylinder, head, sector. */ + uint8_t type; /* Partition type (see partition_type_name). */ + uint8_t end_chs[3]; /* Encoded ending cylinder, head, sector. */ + uint32_t offset; /* Start sector offset from partition table. */ + uint32_t size; /* Number of sectors. */ + } + PACKED; + + /* Partition table sector. */ + struct partition_table + { + uint8_t loader[446]; /* Loader, in top-level partition table. */ + struct partition_table_entry partitions[4]; /* Table entries. */ + uint16_t signature; /* Should be 0xaa55. */ + } + PACKED; + + struct partition_table *pt; + size_t i; + + /* Check SECTOR validity. */ + if (sector >= block_size (block)) + { + printf ("%s: Partition table at sector %"PRDSNu" past end of device.\n", + block_name (block), sector); + return; + } + + /* Read sector. */ + ASSERT (sizeof *pt == BLOCK_SECTOR_SIZE); + pt = malloc (sizeof *pt); + if (pt == NULL) + PANIC ("Failed to allocate memory for partition table."); + block_read (block, 0, pt); + + /* Check signature. */ + if (pt->signature != 0xaa55) + { + if (primary_extended_sector == 0) + printf ("%s: Invalid partition table signature\n", block_name (block)); + else + printf ("%s: Invalid extended partition table in sector %"PRDSNu"\n", + block_name (block), sector); + free (pt); + return; + } + + /* Parse partitions. */ + for (i = 0; i < sizeof pt->partitions / sizeof *pt->partitions; i++) + { + struct partition_table_entry *e = &pt->partitions[i]; + + if (e->size == 0 || e->type == 0) + { + /* Ignore empty partition. */ + } + else if (e->type == 0x05 /* Extended partition. */ + || e->type == 0x0f /* Windows 98 extended partition. */ + || e->type == 0x85 /* Linux extended partition. */ + || e->type == 0xc5) /* DR-DOS extended partition. */ + { + printf ("%s: Extended partition in sector %"PRDSNu"\n", + block_name (block), sector); + + /* The interpretation of the offset field for extended + partitions is bizarre. When the extended partition + table entry is in the master boot record, that is, + the device's primary partition table in sector 0, then + the offset is an absolute sector number. Otherwise, + no matter how deep the partition table we're reading + is nested, the offset is relative to the start of + the extended partition that the MBR points to. */ + if (sector == 0) + read_partition_table (block, e->offset, e->offset, part_nr); + else + read_partition_table (block, e->offset + primary_extended_sector, + primary_extended_sector, part_nr); + } + else + { + ++*part_nr; + + found_partition (block, e->type, e->offset + sector, + e->size, *part_nr); + } + } + + free (pt); +} + +/* We have found a primary or logical partition of the given TYPE + on BLOCK, starting at sector START and continuing for SIZE + sectors, which we are giving the partition number PART_NR. + Check whether this is a partition of interest to Pintos, and + if so then add it to the proper element of partitions[]. */ +static void +found_partition (struct block *block, uint8_t part_type, + block_sector_t start, block_sector_t size, + int part_nr) +{ + if (start >= block_size (block)) + printf ("%s%d: Partition starts past end of device (sector %"PRDSNu")\n", + block_name (block), part_nr, start); + else if (start + size < start || start + size > block_size (block)) + printf ("%s%d: Partition end (%"PRDSNu") past end of device (%"PRDSNu")\n", + block_name (block), part_nr, start + size, block_size (block)); + else + { + enum block_type type = (part_type == 0x20 ? BLOCK_KERNEL + : part_type == 0x21 ? BLOCK_FILESYS + : part_type == 0x22 ? BLOCK_SCRATCH + : part_type == 0x23 ? BLOCK_SWAP + : BLOCK_FOREIGN); + struct partition *p; + char extra_info[128]; + char name[16]; + + p = malloc (sizeof *p); + if (p == NULL) + PANIC ("Failed to allocate memory for partition descriptor"); + p->block = block; + p->start = start; + + snprintf (name, sizeof name, "%s%d", block_name (block), part_nr); + snprintf (extra_info, sizeof extra_info, "%s (%02x)", + partition_type_name (part_type), part_type); + block_register (name, type, extra_info, size, &partition_operations, p); + } +} + +/* Returns a human-readable name for the given partition TYPE. */ +static const char * +partition_type_name (uint8_t type) +{ + /* Name of each known type of partition. + From util-linux-2.12r/fdisk/i386_sys_types.c. + This initializer makes use of a C99 feature that allows + array elements to be initialized by index. */ + static const char *type_names[256] = + { + [0x00] = "Empty", + [0x01] = "FAT12", + [0x02] = "XENIX root", + [0x03] = "XENIX usr", + [0x04] = "FAT16 <32M", + [0x05] = "Extended", + [0x06] = "FAT16", + [0x07] = "HPFS/NTFS", + [0x08] = "AIX", + [0x09] = "AIX bootable", + [0x0a] = "OS/2 Boot Manager", + [0x0b] = "W95 FAT32", + [0x0c] = "W95 FAT32 (LBA)", + [0x0e] = "W95 FAT16 (LBA)", + [0x0f] = "W95 Ext'd (LBA)", + [0x10] = "OPUS", + [0x11] = "Hidden FAT12", + [0x12] = "Compaq diagnostics", + [0x14] = "Hidden FAT16 <32M", + [0x16] = "Hidden FAT16", + [0x17] = "Hidden HPFS/NTFS", + [0x18] = "AST SmartSleep", + [0x1b] = "Hidden W95 FAT32", + [0x1c] = "Hidden W95 FAT32 (LBA)", + [0x1e] = "Hidden W95 FAT16 (LBA)", + [0x20] = "Pintos OS kernel", + [0x21] = "Pintos file system", + [0x22] = "Pintos scratch", + [0x23] = "Pintos swap", + [0x24] = "NEC DOS", + [0x39] = "Plan 9", + [0x3c] = "PartitionMagic recovery", + [0x40] = "Venix 80286", + [0x41] = "PPC PReP Boot", + [0x42] = "SFS", + [0x4d] = "QNX4.x", + [0x4e] = "QNX4.x 2nd part", + [0x4f] = "QNX4.x 3rd part", + [0x50] = "OnTrack DM", + [0x51] = "OnTrack DM6 Aux1", + [0x52] = "CP/M", + [0x53] = "OnTrack DM6 Aux3", + [0x54] = "OnTrackDM6", + [0x55] = "EZ-Drive", + [0x56] = "Golden Bow", + [0x5c] = "Priam Edisk", + [0x61] = "SpeedStor", + [0x63] = "GNU HURD or SysV", + [0x64] = "Novell Netware 286", + [0x65] = "Novell Netware 386", + [0x70] = "DiskSecure Multi-Boot", + [0x75] = "PC/IX", + [0x80] = "Old Minix", + [0x81] = "Minix / old Linux", + [0x82] = "Linux swap / Solaris", + [0x83] = "Linux", + [0x84] = "OS/2 hidden C: drive", + [0x85] = "Linux extended", + [0x86] = "NTFS volume set", + [0x87] = "NTFS volume set", + [0x88] = "Linux plaintext", + [0x8e] = "Linux LVM", + [0x93] = "Amoeba", + [0x94] = "Amoeba BBT", + [0x9f] = "BSD/OS", + [0xa0] = "IBM Thinkpad hibernation", + [0xa5] = "FreeBSD", + [0xa6] = "OpenBSD", + [0xa7] = "NeXTSTEP", + [0xa8] = "Darwin UFS", + [0xa9] = "NetBSD", + [0xab] = "Darwin boot", + [0xb7] = "BSDI fs", + [0xb8] = "BSDI swap", + [0xbb] = "Boot Wizard hidden", + [0xbe] = "Solaris boot", + [0xbf] = "Solaris", + [0xc1] = "DRDOS/sec (FAT-12)", + [0xc4] = "DRDOS/sec (FAT-16 < 32M)", + [0xc6] = "DRDOS/sec (FAT-16)", + [0xc7] = "Syrinx", + [0xda] = "Non-FS data", + [0xdb] = "CP/M / CTOS / ...", + [0xde] = "Dell Utility", + [0xdf] = "BootIt", + [0xe1] = "DOS access", + [0xe3] = "DOS R/O", + [0xe4] = "SpeedStor", + [0xeb] = "BeOS fs", + [0xee] = "EFI GPT", + [0xef] = "EFI (FAT-12/16/32)", + [0xf0] = "Linux/PA-RISC boot", + [0xf1] = "SpeedStor", + [0xf4] = "SpeedStor", + [0xf2] = "DOS secondary", + [0xfd] = "Linux raid autodetect", + [0xfe] = "LANstep", + [0xff] = "BBT", + }; + + return type_names[type] != NULL ? type_names[type] : "Unknown"; +} + +/* Reads sector SECTOR from partition P into BUFFER, which must + have room for BLOCK_SECTOR_SIZE bytes. */ +static void +partition_read (void *p_, block_sector_t sector, void *buffer) +{ + struct partition *p = p_; + block_read (p->block, p->start + sector, buffer); +} + +/* Write sector SECTOR to partition P from BUFFER, which must + contain BLOCK_SECTOR_SIZE bytes. Returns after the block has + acknowledged receiving the data. */ +static void +partition_write (void *p_, block_sector_t sector, const void *buffer) +{ + struct partition *p = p_; + block_write (p->block, p->start + sector, buffer); +} + +static struct block_operations partition_operations = + { + partition_read, + partition_write + }; diff --git a/pintos-progos/devices/partition.h b/pintos-progos/devices/partition.h new file mode 100644 index 0000000..47fea4d --- /dev/null +++ b/pintos-progos/devices/partition.h @@ -0,0 +1,8 @@ +#ifndef DEVICES_PARTITION_H +#define DEVICES_PARTITION_H + +struct block; + +void partition_scan (struct block *); + +#endif /* devices/partition.h */ diff --git a/pintos-progos/devices/pit.c b/pintos-progos/devices/pit.c new file mode 100644 index 0000000..bfb1889 --- /dev/null +++ b/pintos-progos/devices/pit.c @@ -0,0 +1,83 @@ +#include "devices/pit.h" +#include +#include +#include "threads/interrupt.h" +#include "threads/io.h" + +/* Interface to 8254 Programmable Interrupt Timer (PIT). + Refer to [8254] for details. */ + +/* 8254 registers. */ +#define PIT_PORT_CONTROL 0x43 /* Control port. */ +#define PIT_PORT_COUNTER(CHANNEL) (0x40 + (CHANNEL)) /* Counter port. */ + +/* PIT cycles per second. */ +#define PIT_HZ 1193180 + +/* Configure the given CHANNEL in the PIT. In a PC, the PIT's + three output channels are hooked up like this: + + - Channel 0 is connected to interrupt line 0, so that it can + be used as a periodic timer interrupt, as implemented in + Pintos in devices/timer.c. + + - Channel 1 is used for dynamic RAM refresh (in older PCs). + No good can come of messing with this. + + - Channel 2 is connected to the PC speaker, so that it can + be used to play a tone, as implemented in Pintos in + devices/speaker.c. + + MODE specifies the form of output: + + - Mode 2 is a periodic pulse: the channel's output is 1 for + most of the period, but drops to 0 briefly toward the end + of the period. This is useful for hooking up to an + interrupt controller to generate a periodic interrupt. + + - Mode 3 is a square wave: for the first half of the period + it is 1, for the second half it is 0. This is useful for + generating a tone on a speaker. + + - Other modes are less useful. + + FREQUENCY is the number of periods per second, in Hz. */ +void +pit_configure_channel (int channel, int mode, int frequency) +{ + uint16_t count; + enum intr_level old_level; + + ASSERT (channel == 0 || channel == 2); + ASSERT (mode == 2 || mode == 3); + + /* Convert FREQUENCY to a PIT counter value. The PIT has a + clock that runs at PIT_HZ cycles per second. We must + translate FREQUENCY into a number of these cycles. */ + if (frequency < 19) + { + /* Frequency is too low: the quotient would overflow the + 16-bit counter. Force it to 0, which the PIT treats as + 65536, the highest possible count. This yields a 18.2 + Hz timer, approximately. */ + count = 0; + } + else if (frequency > PIT_HZ) + { + /* Frequency is too high: the quotient would underflow to + 0, which the PIT would interpret as 65536. A count of 1 + is illegal in mode 2, so we force it to 2, which yields + a 596.590 kHz timer, approximately. (This timer rate is + probably too fast to be useful anyhow.) */ + count = 2; + } + else + count = (PIT_HZ + frequency / 2) / frequency; + + /* Configure the PIT mode and load its counters. */ + old_level = intr_disable (); + outb (PIT_PORT_CONTROL, (channel << 6) | 0x30 | (mode << 1)); + outb (PIT_PORT_COUNTER (channel), count); + outb (PIT_PORT_COUNTER (channel), count >> 8); + intr_set_level (old_level); +} diff --git a/pintos-progos/devices/pit.h b/pintos-progos/devices/pit.h new file mode 100644 index 0000000..dff36ae --- /dev/null +++ b/pintos-progos/devices/pit.h @@ -0,0 +1,8 @@ +#ifndef DEVICES_PIT_H +#define DEVICES_PIT_H + +#include + +void pit_configure_channel (int channel, int mode, int frequency); + +#endif /* devices/pit.h */ diff --git a/pintos-progos/devices/rtc.c b/pintos-progos/devices/rtc.c new file mode 100644 index 0000000..d99eb46 --- /dev/null +++ b/pintos-progos/devices/rtc.c @@ -0,0 +1,112 @@ +#include "devices/rtc.h" +#include +#include "threads/io.h" + +/* This code is an interface to the MC146818A-compatible real + time clock found on PC motherboards. See [MC146818A] for + hardware details. */ + +/* I/O register addresses. */ +#define CMOS_REG_SET 0x70 /* Selects CMOS register exposed by REG_IO. */ +#define CMOS_REG_IO 0x71 /* Contains the selected data byte. */ + +/* Indexes of CMOS registers with real-time clock functions. + Note that all of these registers are in BCD format, + so that 0x59 means 59, not 89. */ +#define RTC_REG_SEC 0 /* Second: 0x00...0x59. */ +#define RTC_REG_MIN 2 /* Minute: 0x00...0x59. */ +#define RTC_REG_HOUR 4 /* Hour: 0x00...0x23. */ +#define RTC_REG_MDAY 7 /* Day of the month: 0x01...0x31. */ +#define RTC_REG_MON 8 /* Month: 0x01...0x12. */ +#define RTC_REG_YEAR 9 /* Year: 0x00...0x99. */ + +/* Indexes of CMOS control registers. */ +#define RTC_REG_A 0x0a /* Register A: update-in-progress. */ +#define RTC_REG_B 0x0b /* Register B: 24/12 hour time, irq enables. */ +#define RTC_REG_C 0x0c /* Register C: pending interrupts. */ +#define RTC_REG_D 0x0d /* Register D: valid time? */ + +/* Register A. */ +#define RTCSA_UIP 0x80 /* Set while time update in progress. */ + +/* Register B. */ +#define RTCSB_SET 0x80 /* Disables update to let time be set. */ +#define RTCSB_DM 0x04 /* 0 = BCD time format, 1 = binary format. */ +#define RTCSB_24HR 0x02 /* 0 = 12-hour format, 1 = 24-hour format. */ + +static int bcd_to_bin (uint8_t); +static uint8_t cmos_read (uint8_t index); + +/* Returns number of seconds since Unix epoch of January 1, + 1970. */ +time_t +rtc_get_time (void) +{ + static const int days_per_month[12] = + { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + int sec, min, hour, mday, mon, year; + time_t time; + int i; + + /* Get time components. + + We repeatedly read the time until it is stable from one read + to another, in case we start our initial read in the middle + of an update. This strategy is not recommended by the + MC146818A datasheet, but it is simpler than any of their + suggestions and, furthermore, it is also used by Linux. + + The MC146818A can be configured for BCD or binary format, + but for historical reasons everyone always uses BCD format + except on obscure non-PC platforms, so we don't bother + trying to detect the format in use. */ + do + { + sec = bcd_to_bin (cmos_read (RTC_REG_SEC)); + min = bcd_to_bin (cmos_read (RTC_REG_MIN)); + hour = bcd_to_bin (cmos_read (RTC_REG_HOUR)); + mday = bcd_to_bin (cmos_read (RTC_REG_MDAY)); + mon = bcd_to_bin (cmos_read (RTC_REG_MON)); + year = bcd_to_bin (cmos_read (RTC_REG_YEAR)); + } + while (sec != bcd_to_bin (cmos_read (RTC_REG_SEC))); + + /* Translate years-since-1900 into years-since-1970. + If it's before the epoch, assume that it has passed 2000. + This will break at 2070, but that's long after our 31-bit + time_t breaks in 2038. */ + if (year < 70) + year += 100; + year -= 70; + + /* Break down all components into seconds. */ + time = (year * 365 + (year - 1) / 4) * 24 * 60 * 60; + for (i = 1; i <= mon; i++) + time += days_per_month[i - 1] * 24 * 60 * 60; + if (mon > 2 && year % 4 == 0) + time += 24 * 60 * 60; + time += (mday - 1) * 24 * 60 * 60; + time += hour * 60 * 60; + time += min * 60; + time += sec; + + return time; +} + +/* Returns the integer value of the given BCD byte. */ +static int +bcd_to_bin (uint8_t x) +{ + return (x & 0x0f) + ((x >> 4) * 10); +} + +/* Reads a byte from the CMOS register with the given INDEX and + returns the byte read. */ +static uint8_t +cmos_read (uint8_t index) +{ + outb (CMOS_REG_SET, index); + return inb (CMOS_REG_IO); +} diff --git a/pintos-progos/devices/rtc.h b/pintos-progos/devices/rtc.h new file mode 100644 index 0000000..96a822f --- /dev/null +++ b/pintos-progos/devices/rtc.h @@ -0,0 +1,8 @@ +#ifndef RTC_H +#define RTC_H + +typedef unsigned long time_t; + +time_t rtc_get_time (void); + +#endif diff --git a/pintos-progos/devices/serial.c b/pintos-progos/devices/serial.c new file mode 100644 index 0000000..df770a7 --- /dev/null +++ b/pintos-progos/devices/serial.c @@ -0,0 +1,228 @@ +#include "devices/serial.h" +#include +#include "devices/input.h" +#include "devices/intq.h" +#include "devices/timer.h" +#include "threads/io.h" +#include "threads/interrupt.h" +#include "threads/synch.h" +#include "threads/thread.h" + +/* Register definitions for the 16550A UART used in PCs. + The 16550A has a lot more going on than shown here, but this + is all we need. + + Refer to [PC16650D] for hardware information. */ + +/* I/O port base address for the first serial port. */ +#define IO_BASE 0x3f8 + +/* DLAB=0 registers. */ +#define RBR_REG (IO_BASE + 0) /* Receiver Buffer Reg. (read-only). */ +#define THR_REG (IO_BASE + 0) /* Transmitter Holding Reg. (write-only). */ +#define IER_REG (IO_BASE + 1) /* Interrupt Enable Reg.. */ + +/* DLAB=1 registers. */ +#define LS_REG (IO_BASE + 0) /* Divisor Latch (LSB). */ +#define MS_REG (IO_BASE + 1) /* Divisor Latch (MSB). */ + +/* DLAB-insensitive registers. */ +#define IIR_REG (IO_BASE + 2) /* Interrupt Identification Reg. (read-only) */ +#define FCR_REG (IO_BASE + 2) /* FIFO Control Reg. (write-only). */ +#define LCR_REG (IO_BASE + 3) /* Line Control Register. */ +#define MCR_REG (IO_BASE + 4) /* MODEM Control Register. */ +#define LSR_REG (IO_BASE + 5) /* Line Status Register (read-only). */ + +/* Interrupt Enable Register bits. */ +#define IER_RECV 0x01 /* Interrupt when data received. */ +#define IER_XMIT 0x02 /* Interrupt when transmit finishes. */ + +/* Line Control Register bits. */ +#define LCR_N81 0x03 /* No parity, 8 data bits, 1 stop bit. */ +#define LCR_DLAB 0x80 /* Divisor Latch Access Bit (DLAB). */ + +/* MODEM Control Register. */ +#define MCR_OUT2 0x08 /* Output line 2. */ + +/* Line Status Register. */ +#define LSR_DR 0x01 /* Data Ready: received data byte is in RBR. */ +#define LSR_THRE 0x20 /* THR Empty. */ + +/* Transmission mode. */ +static enum { UNINIT, POLL, QUEUE } mode; + +/* Data to be transmitted. */ +static struct intq txq; + +static void set_serial (int bps); +static void putc_poll (uint8_t); +static void write_ier (void); +static intr_handler_func serial_interrupt; + +/* Initializes the serial port device for polling mode. + Polling mode busy-waits for the serial port to become free + before writing to it. It's slow, but until interrupts have + been initialized it's all we can do. */ +static void +init_poll (void) +{ + ASSERT (mode == UNINIT); + outb (IER_REG, 0); /* Turn off all interrupts. */ + outb (FCR_REG, 0); /* Disable FIFO. */ + set_serial (9600); /* 9.6 kbps, N-8-1. */ + outb (MCR_REG, MCR_OUT2); /* Required to enable interrupts. */ + intq_init (&txq); + mode = POLL; +} + +/* Initializes the serial port device for queued interrupt-driven + I/O. With interrupt-driven I/O we don't waste CPU time + waiting for the serial device to become ready. */ +void +serial_init_queue (void) +{ + enum intr_level old_level; + + if (mode == UNINIT) + init_poll (); + ASSERT (mode == POLL); + + intr_register_ext (0x20 + 4, serial_interrupt, "serial"); + mode = QUEUE; + old_level = intr_disable (); + write_ier (); + intr_set_level (old_level); +} + +/* Sends BYTE to the serial port. */ +void +serial_putc (uint8_t byte) +{ + enum intr_level old_level = intr_disable (); + + if (mode != QUEUE) + { + /* If we're not set up for interrupt-driven I/O yet, + use dumb polling to transmit a byte. */ + if (mode == UNINIT) + init_poll (); + putc_poll (byte); + } + else + { + /* Otherwise, queue a byte and update the interrupt enable + register. */ + if (old_level == INTR_OFF && intq_full (&txq)) + { + /* Interrupts are off and the transmit queue is full. + If we wanted to wait for the queue to empty, + we'd have to reenable interrupts. + That's impolite, so we'll send a character via + polling instead. */ + putc_poll (intq_getc (&txq)); + } + + intq_putc (&txq, byte); + write_ier (); + } + + intr_set_level (old_level); +} + +/* Flushes anything in the serial buffer out the port in polling + mode. */ +void +serial_flush (void) +{ + enum intr_level old_level = intr_disable (); + while (!intq_empty (&txq)) + putc_poll (intq_getc (&txq)); + intr_set_level (old_level); +} + +/* The fullness of the input buffer may have changed. Reassess + whether we should block receive interrupts. + Called by the input buffer routines when characters are added + to or removed from the buffer. */ +void +serial_notify (void) +{ + ASSERT (intr_get_level () == INTR_OFF); + if (mode == QUEUE) + write_ier (); +} + +/* Configures the serial port for BPS bits per second. */ +static void +set_serial (int bps) +{ + int base_rate = 1843200 / 16; /* Base rate of 16550A, in Hz. */ + uint16_t divisor = base_rate / bps; /* Clock rate divisor. */ + + ASSERT (bps >= 300 && bps <= 115200); + + /* Enable DLAB. */ + outb (LCR_REG, LCR_N81 | LCR_DLAB); + + /* Set data rate. */ + outb (LS_REG, divisor & 0xff); + outb (MS_REG, divisor >> 8); + + /* Reset DLAB. */ + outb (LCR_REG, LCR_N81); +} + +/* Update interrupt enable register. */ +static void +write_ier (void) +{ + uint8_t ier = 0; + + ASSERT (intr_get_level () == INTR_OFF); + + /* Enable transmit interrupt if we have any characters to + transmit. */ + if (!intq_empty (&txq)) + ier |= IER_XMIT; + + /* Enable receive interrupt if we have room to store any + characters we receive. */ + if (!input_full ()) + ier |= IER_RECV; + + outb (IER_REG, ier); +} + +/* Polls the serial port until it's ready, + and then transmits BYTE. */ +static void +putc_poll (uint8_t byte) +{ + ASSERT (intr_get_level () == INTR_OFF); + + while ((inb (LSR_REG) & LSR_THRE) == 0) + continue; + outb (THR_REG, byte); +} + +/* Serial interrupt handler. */ +static void +serial_interrupt (struct intr_frame *f UNUSED) +{ + /* Inquire about interrupt in UART. Without this, we can + occasionally miss an interrupt running under QEMU. */ + inb (IIR_REG); + + /* As long as we have room to receive a byte, and the hardware + has a byte for us, receive a byte. */ + while (!input_full () && (inb (LSR_REG) & LSR_DR) != 0) + input_putc (inb (RBR_REG)); + + /* As long as we have a byte to transmit, and the hardware is + ready to accept a byte for transmission, transmit a byte. */ + while (!intq_empty (&txq) && (inb (LSR_REG) & LSR_THRE) != 0) + outb (THR_REG, intq_getc (&txq)); + + /* Update interrupt enable register based on queue status. */ + write_ier (); +} diff --git a/pintos-progos/devices/serial.h b/pintos-progos/devices/serial.h new file mode 100644 index 0000000..6e04778 --- /dev/null +++ b/pintos-progos/devices/serial.h @@ -0,0 +1,11 @@ +#ifndef DEVICES_SERIAL_H +#define DEVICES_SERIAL_H + +#include + +void serial_init_queue (void); +void serial_putc (uint8_t); +void serial_flush (void); +void serial_notify (void); + +#endif /* devices/serial.h */ diff --git a/pintos-progos/devices/shutdown.c b/pintos-progos/devices/shutdown.c new file mode 100644 index 0000000..7ff9a95 --- /dev/null +++ b/pintos-progos/devices/shutdown.c @@ -0,0 +1,131 @@ +#include "devices/shutdown.h" +#include +#include +#include "devices/kbd.h" +#include "devices/serial.h" +#include "devices/timer.h" +#include "threads/io.h" +#include "threads/thread.h" +#ifdef USERPROG +#include "userprog/exception.h" +#endif +#ifdef FILESYS +#include "devices/block.h" +#include "filesys/filesys.h" +#endif + +/* Keyboard control register port. */ +#define CONTROL_REG 0x64 + +/* How to shut down when shutdown() is called. */ +static enum shutdown_type how = SHUTDOWN_NONE; + +static void print_stats (void); + +/* Shuts down the machine in the way configured by + shutdown_configure(). If the shutdown type is SHUTDOWN_NONE + (which is the default), returns without doing anything. */ +void +shutdown (void) +{ + switch (how) + { + case SHUTDOWN_POWER_OFF: + shutdown_power_off (); + break; + + case SHUTDOWN_REBOOT: + shutdown_reboot (); + break; + + default: + /* Nothing to do. */ + break; + } +} + +/* Sets TYPE as the way that machine will shut down when Pintos + execution is complete. */ +void +shutdown_configure (enum shutdown_type type) +{ + how = type; +} + +/* Reboots the machine via the keyboard controller. */ +void +shutdown_reboot (void) +{ + printf ("Rebooting...\n"); + + /* See [kbd] for details on how to program the keyboard + * controller. */ + for (;;) + { + int i; + + /* Poll keyboard controller's status byte until + * 'input buffer empty' is reported. */ + for (i = 0; i < 0x10000; i++) + { + if ((inb (CONTROL_REG) & 0x02) == 0) + break; + timer_udelay (2); + } + + timer_udelay (50); + + /* Pulse bit 0 of the output port P2 of the keyboard controller. + * This will reset the CPU. */ + outb (CONTROL_REG, 0xfe); + timer_udelay (50); + } +} + +/* Powers down the machine we're running on, + as long as we're running on Bochs or QEMU. */ +void +shutdown_power_off (void) +{ + const char s[] = "Shutdown"; + const char *p; + +#ifdef FILESYS + filesys_done (); +#endif + + print_stats (); + + printf ("Powering off...\n"); + serial_flush (); + + /* This is a special power-off sequence supported by Bochs and + QEMU, but not by physical hardware. */ + for (p = s; *p != '\0'; p++) + outb (0x8900, *p); + + /* This will power off a VMware VM if "gui.exitOnCLIHLT = TRUE" + is set in its configuration file. (The "pintos" script does + that automatically.) */ + asm volatile ("cli; hlt" : : : "memory"); + + /* None of those worked. */ + printf ("still running...\n"); + for (;;); +} + +/* Print statistics about Pintos execution. */ +static void +print_stats (void) +{ + timer_print_stats (); + thread_print_stats (); +#ifdef FILESYS + block_print_stats (); +#endif + console_print_stats (); + kbd_print_stats (); +#ifdef USERPROG + exception_print_stats (); +#endif +} diff --git a/pintos-progos/devices/shutdown.h b/pintos-progos/devices/shutdown.h new file mode 100644 index 0000000..dc4f942 --- /dev/null +++ b/pintos-progos/devices/shutdown.h @@ -0,0 +1,19 @@ +#ifndef DEVICES_SHUTDOWN_H +#define DEVICES_SHUTDOWN_H + +#include + +/* How to shut down when Pintos has nothing left to do. */ +enum shutdown_type + { + SHUTDOWN_NONE, /* Loop forever. */ + SHUTDOWN_POWER_OFF, /* Power off the machine (if possible). */ + SHUTDOWN_REBOOT, /* Reboot the machine (if possible). */ + }; + +void shutdown (void); +void shutdown_configure (enum shutdown_type); +void shutdown_reboot (void) NO_RETURN; +void shutdown_power_off (void) NO_RETURN; + +#endif /* devices/shutdown.h */ diff --git a/pintos-progos/devices/speaker.c b/pintos-progos/devices/speaker.c new file mode 100644 index 0000000..5052005 --- /dev/null +++ b/pintos-progos/devices/speaker.c @@ -0,0 +1,68 @@ +#include "devices/speaker.h" +#include "devices/pit.h" +#include "threads/io.h" +#include "threads/interrupt.h" +#include "devices/timer.h" + +/* Speaker port enable I/O register. */ +#define SPEAKER_PORT_GATE 0x61 + +/* Speaker port enable bits. */ +#define SPEAKER_GATE_ENABLE 0x03 + +/* Sets the PC speaker to emit a tone at the given FREQUENCY, in + Hz. */ +void +speaker_on (int frequency) +{ + if (frequency >= 20 && frequency <= 20000) + { + /* Set the timer channel that's connected to the speaker to + output a square wave at the given FREQUENCY, then + connect the timer channel output to the speaker. */ + enum intr_level old_level = intr_disable (); + pit_configure_channel (2, 3, frequency); + outb (SPEAKER_PORT_GATE, inb (SPEAKER_PORT_GATE) | SPEAKER_GATE_ENABLE); + intr_set_level (old_level); + } + else + { + /* FREQUENCY is outside the range of normal human hearing. + Just turn off the speaker. */ + speaker_off (); + } +} + +/* Turn off the PC speaker, by disconnecting the timer channel's + output from the speaker. */ +void +speaker_off (void) +{ + enum intr_level old_level = intr_disable (); + outb (SPEAKER_PORT_GATE, inb (SPEAKER_PORT_GATE) & ~SPEAKER_GATE_ENABLE); + intr_set_level (old_level); +} + +/* Briefly beep the PC speaker. */ +void +speaker_beep (void) +{ + /* Only attempt to beep the speaker if interrupts are enabled, + because we don't want to freeze the machine during the beep. + We could add a hook to the timer interrupt to avoid that + problem, but then we'd risk failing to ever stop the beep if + Pintos crashes for some unrelated reason. There's nothing + more annoying than a machine whose beeping you can't stop + without a power cycle. + + We can't just enable interrupts while we sleep. For one + thing, we get called (indirectly) from printf, which should + always work, even during boot before we're ready to enable + interrupts. */ + if (intr_get_level () == INTR_ON) + { + speaker_on (440); + timer_msleep (250); + speaker_off (); + } +} diff --git a/pintos-progos/devices/speaker.h b/pintos-progos/devices/speaker.h new file mode 100644 index 0000000..98cef7b --- /dev/null +++ b/pintos-progos/devices/speaker.h @@ -0,0 +1,8 @@ +#ifndef DEVICES_SPEAKER_H +#define DEVICES_SPEAKER_H + +void speaker_on (int frequency); +void speaker_off (void); +void speaker_beep (void); + +#endif /* devices/speaker.h */ diff --git a/pintos-progos/devices/timer.c b/pintos-progos/devices/timer.c new file mode 100644 index 0000000..befaaae --- /dev/null +++ b/pintos-progos/devices/timer.c @@ -0,0 +1,246 @@ +#include "devices/timer.h" +#include +#include +#include +#include +#include "devices/pit.h" +#include "threads/interrupt.h" +#include "threads/synch.h" +#include "threads/thread.h" + +/* See [8254] for hardware details of the 8254 timer chip. */ + +#if TIMER_FREQ < 19 +#error 8254 timer requires TIMER_FREQ >= 19 +#endif +#if TIMER_FREQ > 1000 +#error TIMER_FREQ <= 1000 recommended +#endif + +/* Number of timer ticks since OS booted. */ +static int64_t ticks; + +/* Number of loops per timer tick. + Initialized by timer_calibrate(). */ +static unsigned loops_per_tick; + +static intr_handler_func timer_interrupt; +static bool too_many_loops (unsigned loops); +static void busy_wait (int64_t loops); +static void real_time_sleep (int64_t num, int32_t denom); +static void real_time_delay (int64_t num, int32_t denom); + +/* Sets up the timer to interrupt TIMER_FREQ times per second, + and registers the corresponding interrupt. */ +void +timer_init (void) +{ + pit_configure_channel (0, 2, TIMER_FREQ); + intr_register_ext (0x20, timer_interrupt, "8254 Timer"); +} + +/* Calibrates loops_per_tick, used to implement brief delays. */ +void +timer_calibrate (void) +{ + unsigned high_bit, test_bit; + + ASSERT (intr_get_level () == INTR_ON); + printf ("Calibrating timer... "); + + /* Approximate loops_per_tick as the largest power-of-two + still less than one timer tick. */ + loops_per_tick = 1u << 10; + while (!too_many_loops (loops_per_tick << 1)) + { + loops_per_tick <<= 1; + ASSERT (loops_per_tick != 0); + } + + /* Refine the next 8 bits of loops_per_tick. */ + high_bit = loops_per_tick; + for (test_bit = high_bit >> 1; test_bit != high_bit >> 10; test_bit >>= 1) + if (!too_many_loops (high_bit | test_bit)) + loops_per_tick |= test_bit; + + printf ("%'"PRIu64" loops/s.\n", (uint64_t) loops_per_tick * TIMER_FREQ); +} + +/* Returns the number of timer ticks since the OS booted. */ +int64_t +timer_ticks (void) +{ + enum intr_level old_level = intr_disable (); + int64_t t = ticks; + intr_set_level (old_level); + return t; +} + +/* Returns the number of timer ticks elapsed since THEN, which + should be a value once returned by timer_ticks(). */ +int64_t +timer_elapsed (int64_t then) +{ + return timer_ticks () - then; +} + +/* Sleeps for approximately TICKS timer ticks. Interrupts must + be turned on. */ +void +timer_sleep (int64_t ticks) +{ + int64_t start = timer_ticks (); + + ASSERT (intr_get_level () == INTR_ON); + while (timer_elapsed (start) < ticks) + thread_yield (); +} + +/* Sleeps for approximately MS milliseconds. Interrupts must be + turned on. */ +void +timer_msleep (int64_t ms) +{ + real_time_sleep (ms, 1000); +} + +/* Sleeps for approximately US microseconds. Interrupts must be + turned on. */ +void +timer_usleep (int64_t us) +{ + real_time_sleep (us, 1000 * 1000); +} + +/* Sleeps for approximately NS nanoseconds. Interrupts must be + turned on. */ +void +timer_nsleep (int64_t ns) +{ + real_time_sleep (ns, 1000 * 1000 * 1000); +} + +/* Busy-waits for approximately MS milliseconds. Interrupts need + not be turned on. + + Busy waiting wastes CPU cycles, and busy waiting with + interrupts off for the interval between timer ticks or longer + will cause timer ticks to be lost. Thus, use timer_msleep() + instead if interrupts are enabled. */ +void +timer_mdelay (int64_t ms) +{ + real_time_delay (ms, 1000); +} + +/* Sleeps for approximately US microseconds. Interrupts need not + be turned on. + + Busy waiting wastes CPU cycles, and busy waiting with + interrupts off for the interval between timer ticks or longer + will cause timer ticks to be lost. Thus, use timer_usleep() + instead if interrupts are enabled. */ +void +timer_udelay (int64_t us) +{ + real_time_delay (us, 1000 * 1000); +} + +/* Sleeps execution for approximately NS nanoseconds. Interrupts + need not be turned on. + + Busy waiting wastes CPU cycles, and busy waiting with + interrupts off for the interval between timer ticks or longer + will cause timer ticks to be lost. Thus, use timer_nsleep() + instead if interrupts are enabled.*/ +void +timer_ndelay (int64_t ns) +{ + real_time_delay (ns, 1000 * 1000 * 1000); +} + +/* Prints timer statistics. */ +void +timer_print_stats (void) +{ + printf ("Timer: %"PRId64" ticks\n", timer_ticks ()); +} + +/* Timer interrupt handler. */ +static void +timer_interrupt (struct intr_frame *args UNUSED) +{ + ticks++; + thread_tick (); +} + +/* Returns true if LOOPS iterations waits for more than one timer + tick, otherwise false. */ +static bool +too_many_loops (unsigned loops) +{ + /* Wait for a timer tick. */ + int64_t start = ticks; + while (ticks == start) + barrier (); + + /* Run LOOPS loops. */ + start = ticks; + busy_wait (loops); + + /* If the tick count changed, we iterated too long. */ + barrier (); + return start != ticks; +} + +/* Iterates through a simple loop LOOPS times, for implementing + brief delays. + + Marked NO_INLINE because code alignment can significantly + affect timings, so that if this function was inlined + differently in different places the results would be difficult + to predict. */ +static void NO_INLINE +busy_wait (int64_t loops) +{ + while (loops-- > 0) + barrier (); +} + +/* Sleep for approximately NUM/DENOM seconds. */ +static void +real_time_sleep (int64_t num, int32_t denom) +{ + /* Convert NUM/DENOM seconds into timer ticks, rounding down. + + (NUM / DENOM) s + ---------------------- = NUM * TIMER_FREQ / DENOM ticks. + 1 s / TIMER_FREQ ticks + */ + int64_t ticks = num * TIMER_FREQ / denom; + + ASSERT (intr_get_level () == INTR_ON); + if (ticks > 0) + { + /* We're waiting for at least one full timer tick. Use + timer_sleep() because it will yield the CPU to other + processes. */ + timer_sleep (ticks); + } + else + { + /* Otherwise, use a busy-wait loop for more accurate + sub-tick timing. */ + real_time_delay (num, denom); + } +} + +/* Busy-wait for approximately NUM/DENOM seconds. */ +static void +real_time_delay (int64_t num, int32_t denom) +{ + /* Scale the numerator and denominator down by 1000 to avoid + the possibility of overflow. */ + ASSERT (denom % 1000 == 0); + busy_wait (loops_per_tick * num / 1000 * TIMER_FREQ / (denom / 1000)); +} diff --git a/pintos-progos/devices/timer.h b/pintos-progos/devices/timer.h new file mode 100644 index 0000000..cd3d6bb --- /dev/null +++ b/pintos-progos/devices/timer.h @@ -0,0 +1,29 @@ +#ifndef DEVICES_TIMER_H +#define DEVICES_TIMER_H + +#include +#include + +/* Number of timer interrupts per second. */ +#define TIMER_FREQ 100 + +void timer_init (void); +void timer_calibrate (void); + +int64_t timer_ticks (void); +int64_t timer_elapsed (int64_t); + +/* Sleep and yield the CPU to other threads. */ +void timer_sleep (int64_t ticks); +void timer_msleep (int64_t milliseconds); +void timer_usleep (int64_t microseconds); +void timer_nsleep (int64_t nanoseconds); + +/* Busy waits. */ +void timer_mdelay (int64_t milliseconds); +void timer_udelay (int64_t microseconds); +void timer_ndelay (int64_t nanoseconds); + +void timer_print_stats (void); + +#endif /* devices/timer.h */ diff --git a/pintos-progos/devices/vga.c b/pintos-progos/devices/vga.c new file mode 100644 index 0000000..f421b61 --- /dev/null +++ b/pintos-progos/devices/vga.c @@ -0,0 +1,172 @@ +#include "devices/vga.h" +#include +#include +#include +#include +#include "devices/speaker.h" +#include "threads/io.h" +#include "threads/interrupt.h" +#include "threads/vaddr.h" + +/* VGA text screen support. See [FREEVGA] for more information. */ + +/* Number of columns and rows on the text display. */ +#define COL_CNT 80 +#define ROW_CNT 25 + +/* Current cursor position. (0,0) is in the upper left corner of + the display. */ +static size_t cx, cy; + +/* Attribute value for gray text on a black background. */ +#define GRAY_ON_BLACK 0x07 + +/* Framebuffer. See [FREEVGA] under "VGA Text Mode Operation". + The character at (x,y) is fb[y][x][0]. + The attribute at (x,y) is fb[y][x][1]. */ +static uint8_t (*fb)[COL_CNT][2]; + +static void clear_row (size_t y); +static void cls (void); +static void newline (void); +static void move_cursor (void); +static void find_cursor (size_t *x, size_t *y); + +/* Initializes the VGA text display. */ +static void +init (void) +{ + /* Already initialized? */ + static bool inited; + if (!inited) + { + fb = ptov (0xb8000); + find_cursor (&cx, &cy); + inited = true; + } +} + +/* Writes C to the VGA text display, interpreting control + characters in the conventional ways. */ +void +vga_putc (int c) +{ + /* Disable interrupts to lock out interrupt handlers + that might write to the console. */ + enum intr_level old_level = intr_disable (); + + init (); + + switch (c) + { + case '\n': + newline (); + break; + + case '\f': + cls (); + break; + + case '\b': + if (cx > 0) + cx--; + break; + + case '\r': + cx = 0; + break; + + case '\t': + cx = ROUND_UP (cx + 1, 8); + if (cx >= COL_CNT) + newline (); + break; + + case '\a': + intr_set_level (old_level); + speaker_beep (); + intr_disable (); + break; + + default: + fb[cy][cx][0] = c; + fb[cy][cx][1] = GRAY_ON_BLACK; + if (++cx >= COL_CNT) + newline (); + break; + } + + /* Update cursor position. */ + move_cursor (); + + intr_set_level (old_level); +} + +/* Clears the screen and moves the cursor to the upper left. */ +static void +cls (void) +{ + size_t y; + + for (y = 0; y < ROW_CNT; y++) + clear_row (y); + + cx = cy = 0; + move_cursor (); +} + +/* Clears row Y to spaces. */ +static void +clear_row (size_t y) +{ + size_t x; + + for (x = 0; x < COL_CNT; x++) + { + fb[y][x][0] = ' '; + fb[y][x][1] = GRAY_ON_BLACK; + } +} + +/* Advances the cursor to the first column in the next line on + the screen. If the cursor is already on the last line on the + screen, scrolls the screen upward one line. */ +static void +newline (void) +{ + cx = 0; + cy++; + if (cy >= ROW_CNT) + { + cy = ROW_CNT - 1; + memmove (&fb[0], &fb[1], sizeof fb[0] * (ROW_CNT - 1)); + clear_row (ROW_CNT - 1); + } +} + +/* Moves the hardware cursor to (cx,cy). */ +static void +move_cursor (void) +{ + /* See [FREEVGA] under "Manipulating the Text-mode Cursor". */ + uint16_t cp = cx + COL_CNT * cy; + outw (0x3d4, 0x0e | (cp & 0xff00)); + outw (0x3d4, 0x0f | (cp << 8)); +} + +/* Reads the current hardware cursor position into (*X,*Y). */ +static void +find_cursor (size_t *x, size_t *y) +{ + /* See [FREEVGA] under "Manipulating the Text-mode Cursor". */ + uint16_t cp; + + outb (0x3d4, 0x0e); + cp = inb (0x3d5) << 8; + + outb (0x3d4, 0x0f); + cp |= inb (0x3d5); + + *x = cp % COL_CNT; + *y = cp / COL_CNT; +} diff --git a/pintos-progos/devices/vga.h b/pintos-progos/devices/vga.h new file mode 100644 index 0000000..59690fb --- /dev/null +++ b/pintos-progos/devices/vga.h @@ -0,0 +1,6 @@ +#ifndef DEVICES_VGA_H +#define DEVICES_VGA_H + +void vga_putc (int); + +#endif /* devices/vga.h */ diff --git a/pintos-progos/examples/.gitignore b/pintos-progos/examples/.gitignore new file mode 100644 index 0000000..a9e09d7 --- /dev/null +++ b/pintos-progos/examples/.gitignore @@ -0,0 +1,19 @@ +cat +cmp +cp +echo +halt +hex-dump +ls +mcat +mcp +mkdir +pwd +rm +shell +bubsort +insult +lineup +matmult +recursor +*.d diff --git a/pintos-progos/examples/Makefile b/pintos-progos/examples/Makefile new file mode 100644 index 0000000..f773950 --- /dev/null +++ b/pintos-progos/examples/Makefile @@ -0,0 +1,36 @@ +SRCDIR = .. + +# Test programs to compile, and a list of sources for each. +# To add a new test, put its name on the PROGS list +# and then add a name_SRC line that lists its source files. +PROGS = cat cmp cp echo halt hello hex-dump ls mcat mcp mkdir pwd rm shell \ + bubsort insult lineup matmult recursor test + +# Should work from project 2 onward. +cat_SRC = cat.c +cmp_SRC = cmp.c +cp_SRC = cp.c +echo_SRC = echo.c +halt_SRC = halt.c +hello_SRC = hello.c +hex-dump_SRC = hex-dump.c +insult_SRC = insult.c +lineup_SRC = lineup.c +ls_SRC = ls.c +recursor_SRC = recursor.c +rm_SRC = rm.c +test_SRC = test.c + +# Should work in project 3; also in project 4 if VM is included. +bubsort_SRC = bubsort.c +matmult_SRC = matmult.c +mcat_SRC = mcat.c +mcp_SRC = mcp.c + +# Should work in project 4. +mkdir_SRC = mkdir.c +pwd_SRC = pwd.c +shell_SRC = shell.c + +include $(SRCDIR)/Make.config +include $(SRCDIR)/Makefile.userprog diff --git a/pintos-progos/examples/bubsort.c b/pintos-progos/examples/bubsort.c new file mode 100644 index 0000000..343219e --- /dev/null +++ b/pintos-progos/examples/bubsort.c @@ -0,0 +1,38 @@ +/* sort.c + + Test program to sort a large number of integers. + + Intention is to stress virtual memory system. + + Ideally, we could read the unsorted array off of the file + system, and store the result back to the file system! */ +#include + +/* Size of array to sort. */ +#define SORT_SIZE 128 + +int +main (void) +{ + /* Array to sort. Static to reduce stack usage. */ + static int array[SORT_SIZE]; + + int i, j, tmp; + + /* First initialize the array in descending order. */ + for (i = 0; i < SORT_SIZE; i++) + array[i] = SORT_SIZE - i - 1; + + /* Then sort in ascending order. */ + for (i = 0; i < SORT_SIZE - 1; i++) + for (j = 0; j < SORT_SIZE - 1 - i; j++) + if (array[j] > array[j + 1]) + { + tmp = array[j]; + array[j] = array[j + 1]; + array[j + 1] = tmp; + } + + printf ("sort exiting with code %d\n", array[0]); + return array[0]; +} diff --git a/pintos-progos/examples/cat.c b/pintos-progos/examples/cat.c new file mode 100644 index 0000000..c8d229d --- /dev/null +++ b/pintos-progos/examples/cat.c @@ -0,0 +1,34 @@ +/* cat.c + + Prints files specified on command line to the console. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + bool success = true; + int i; + + for (i = 1; i < argc; i++) + { + int fd = open (argv[i]); + if (fd < 0) + { + printf ("%s: open failed\n", argv[i]); + success = false; + continue; + } + for (;;) + { + char buffer[1024]; + int bytes_read = read (fd, buffer, sizeof buffer); + if (bytes_read == 0) + break; + write (STDOUT_FILENO, buffer, bytes_read); + } + close (fd); + } + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/pintos-progos/examples/cmp.c b/pintos-progos/examples/cmp.c new file mode 100644 index 0000000..94b406d --- /dev/null +++ b/pintos-progos/examples/cmp.c @@ -0,0 +1,68 @@ +/* cat.c + + Compares two files. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + int fd[2]; + + if (argc != 3) + { + printf ("usage: cmp A B\n"); + return EXIT_FAILURE; + } + + /* Open files. */ + fd[0] = open (argv[1]); + if (fd[0] < 0) + { + printf ("%s: open failed\n", argv[1]); + return EXIT_FAILURE; + } + fd[1] = open (argv[2]); + if (fd[1] < 0) + { + printf ("%s: open failed\n", argv[1]); + return EXIT_FAILURE; + } + + /* Compare data. */ + for (;;) + { + int pos; + char buffer[2][1024]; + int bytes_read[2]; + int min_read; + int i; + + pos = tell (fd[0]); + bytes_read[0] = read (fd[0], buffer[0], sizeof buffer[0]); + bytes_read[1] = read (fd[1], buffer[1], sizeof buffer[1]); + min_read = bytes_read[0] < bytes_read[1] ? bytes_read[0] : bytes_read[1]; + if (min_read == 0) + break; + + for (i = 0; i < min_read; i++) + if (buffer[0][i] != buffer[1][i]) + { + printf ("Byte %d is %02hhx ('%c') in %s but %02hhx ('%c') in %s\n", + pos + i, + buffer[0][i], buffer[0][i], argv[1], + buffer[1][i], buffer[1][i], argv[2]); + return EXIT_FAILURE; + } + + if (min_read < bytes_read[1]) + printf ("%s is shorter than %s\n", argv[1], argv[2]); + else if (min_read < bytes_read[0]) + printf ("%s is shorter than %s\n", argv[2], argv[1]); + } + + printf ("%s and %s are identical\n", argv[1], argv[2]); + + return EXIT_SUCCESS; +} diff --git a/pintos-progos/examples/cp.c b/pintos-progos/examples/cp.c new file mode 100644 index 0000000..86a5cd7 --- /dev/null +++ b/pintos-progos/examples/cp.c @@ -0,0 +1,55 @@ +/* cat.c + +Copies one file to another. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + int in_fd, out_fd; + + if (argc != 3) + { + printf ("usage: cp OLD NEW\n"); + return EXIT_FAILURE; + } + + /* Open input file. */ + in_fd = open (argv[1]); + if (in_fd < 0) + { + printf ("%s: open failed\n", argv[1]); + return EXIT_FAILURE; + } + + /* Create and open output file. */ + if (!create (argv[2], filesize (in_fd))) + { + printf ("%s: create failed\n", argv[2]); + return EXIT_FAILURE; + } + out_fd = open (argv[2]); + if (out_fd < 0) + { + printf ("%s: open failed\n", argv[2]); + return EXIT_FAILURE; + } + + /* Copy data. */ + for (;;) + { + char buffer[1024]; + int bytes_read = read (in_fd, buffer, sizeof buffer); + if (bytes_read == 0) + break; + if (write (out_fd, buffer, bytes_read) != bytes_read) + { + printf ("%s: write failed\n", argv[2]); + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} diff --git a/pintos-progos/examples/echo.c b/pintos-progos/examples/echo.c new file mode 100644 index 0000000..1b136f2 --- /dev/null +++ b/pintos-progos/examples/echo.c @@ -0,0 +1,14 @@ +#include +#include + +int +main (int argc, char **argv) +{ + int i; + + for (i = 0; i < argc; i++) + printf ("%s ", argv[i]); + printf ("\n"); + + return EXIT_SUCCESS; +} diff --git a/pintos-progos/examples/halt.c b/pintos-progos/examples/halt.c new file mode 100644 index 0000000..bad7250 --- /dev/null +++ b/pintos-progos/examples/halt.c @@ -0,0 +1,14 @@ +/* halt.c + + Simple program to test whether running a user program works. + + Just invokes a system call that shuts down the OS. */ + +#include + +int +main (void) +{ + halt (); + /* not reached */ +} diff --git a/pintos-progos/examples/hello.c b/pintos-progos/examples/hello.c new file mode 100644 index 0000000..a12fd94 --- /dev/null +++ b/pintos-progos/examples/hello.c @@ -0,0 +1,9 @@ +#include +#include + +int +main (int argc, char **argv) +{ + printf ("Hello World :)\n"); + return EXIT_SUCCESS; +} diff --git a/pintos-progos/examples/hex-dump.c b/pintos-progos/examples/hex-dump.c new file mode 100644 index 0000000..ee313f2 --- /dev/null +++ b/pintos-progos/examples/hex-dump.c @@ -0,0 +1,35 @@ +/* hex-dump.c + + Prints files specified on command line to the console in hex. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + bool success = true; + int i; + + for (i = 1; i < argc; i++) + { + int fd = open (argv[i]); + if (fd < 0) + { + printf ("%s: open failed\n", argv[i]); + success = false; + continue; + } + for (;;) + { + char buffer[1024]; + int pos = tell (fd); + int bytes_read = read (fd, buffer, sizeof buffer); + if (bytes_read == 0) + break; + hex_dump (pos, buffer, bytes_read, true); + } + close (fd); + } + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/pintos-progos/examples/insult.c b/pintos-progos/examples/insult.c new file mode 100644 index 0000000..98c4e6a --- /dev/null +++ b/pintos-progos/examples/insult.c @@ -0,0 +1,369 @@ +/* Insult.c + + This is a version of the famous CS 107 random sentence + generator. I wrote a program that reads a grammar definition + file and writes a C file containing that grammar as hard code + static C strings. Thus the majority of the code below in + machine generated and totally unreadable. The arrays created + are specially designed to make generating the sentences as + easy as possible. + + Originally by Greg Hutchins, March 1998. + Modified by Ben Pfaff for Pintos, Sept 2004. */ +char *start[] = + { "You", "1", "5", ".", "May", "13", ".", "With", "the", "19", "of", "18", +",", "may", "13", "." +}; +char startLoc[] = { 3, 0, 4, 7, 16 }; +char *adj[] = { "3", "4", "2", ",", "1" }; +char adjLoc[] = { 3, 0, 1, 2, 5 }; +char *adj3[] = { "3", "4" }; +char adj3Loc[] = { 2, 0, 1, 2 }; +char *adj1[] = + { "lame", "dried", "up", "par-broiled", "bloated", "half-baked", "spiteful", +"egotistical", "ungrateful", "stupid", "moronic", "fat", "ugly", "puny", "pitiful", +"insignificant", "blithering", "repulsive", "worthless", "blundering", "retarded", +"useless", "obnoxious", "low-budget", "assinine", "neurotic", "subhuman", "crochety", +"indescribable", "contemptible", "unspeakable", "sick", "lazy", "good-for-nothing", +"slutty", "mentally-deficient", "creepy", "sloppy", "dismal", "pompous", "pathetic", +"friendless", "revolting", "slovenly", "cantankerous", "uncultured", "insufferable", +"gross", "unkempt", "defective", "crumby" +}; +char adj1Loc[] = + { 50, 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, +21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, +43, 44, 45, 46, 47, 48, 49, 50, 51 }; +char *adj2[] = + { "putrefied", "festering", "funky", "moldy", "leprous", "curdled", "fetid", +"slimy", "crusty", "sweaty", "damp", "deranged", "smelly", "stenchy", "malignant", +"noxious", "grimy", "reeky", "nasty", "mutilated", "sloppy", "gruesome", "grisly", +"sloshy", "wormy", "mealy", "spoiled", "contaminated", "rancid", "musty", +"fly-covered", "moth-eaten", "decaying", "decomposed", "freeze-dried", "defective", +"petrified", "rotting", "scabrous", "hirsute" +}; +char adj2Loc[] = + { 40, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, +20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 }; +char *name[] = + { "10", ",", "bad", "excuse", "for", "6", ",", "6", "for", "brains", ",", +"4", "11", "8", "for", "brains", "offspring", "of", "a", "motherless", "10", "7", "6", +"7", "4", "11", "8" +}; +char nameLoc[] = { 7, 0, 1, 6, 10, 16, 21, 23, 27 }; +char *stuff[] = + { "shit", "toe", "jam", "filth", "puss", "earwax", "leaf", "clippings", +"bat", "guano", "mucus", "fungus", "mung", "refuse", "earwax", "spittoon", "spittle", +"phlegm" +}; +char stuffLoc[] = { 14, 0, 1, 3, 4, 5, 6, 8, 10, 11, 12, 13, 14, 15, 17, 18 }; +char *noun_and_prep[] = + { "bit", "of", "piece", "of", "vat", "of", "lump", "of", "crock", "of", +"ball", "of", "tub", "of", "load", "of", "bucket", "of", "mound", "of", "glob", "of", "bag", +"of", "heap", "of", "mountain", "of", "load", "of", "barrel", "of", "sack", "of", "blob", "of", +"pile", "of", "truckload", "of", "vat", "of" +}; +char noun_and_prepLoc[] = + { 21, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, +38, 40, 42 }; +char *organics[] = + { "droppings", "mung", "zits", "puckies", "tumors", "cysts", "tumors", +"livers", "froth", "parts", "scabs", "guts", "entrails", "blubber", "carcuses", "gizards", +"9" +}; +char organicsLoc[] = + { 17, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }; +char *body_parts[] = + { "kidneys", "genitals", "buttocks", "earlobes", "innards", "feet" +}; +char body_partsLoc[] = { 6, 0, 1, 2, 3, 4, 5, 6 }; +char *noun[] = + { "pop", "tart", "warthog", "twinkie", "barnacle", "fondue", "pot", +"cretin", "fuckwad", "moron", "ass", "neanderthal", "nincompoop", "simpleton", "11" +}; +char nounLoc[] = { 13, 0, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; +char *animal[] = + { "donkey", "llama", "dingo", "lizard", "gekko", "lemur", "moose", "camel", +"goat", "eel" +}; +char animalLoc[] = { 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; +char *good_verb[] = + { "love", "cuddle", "fondle", "adore", "smooch", "hug", "caress", "worship", +"look", "at", "touch" +}; +char good_verbLoc[] = { 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11 }; +char *curse[] = + { "14", "20", "23", "14", "17", "20", "23", "14", "find", "your", "9", +"suddenly", "delectable", "14", "and", "14", "seek", "a", "battleground", "23" +}; +char curseLoc[] = { 4, 0, 3, 7, 13, 20 }; +char *afflictors[] = + { "15", "21", "15", "21", "15", "21", "15", "21", "a", "22", "Rush", +"Limbaugh", "the", "hosts", "of", "Hades" +}; +char afflictorsLoc[] = { 6, 0, 2, 4, 6, 8, 12, 16 }; +char *quantity[] = + { "a", "4", "hoard", "of", "a", "4", "pack", "of", "a", "truckload", "of", +"a", "swarm", "of", "many", "an", "army", "of", "a", "4", "heard", "of", "a", "4", +"platoon", "of", "a", "4", "and", "4", "group", "of", "16" +}; +char quantityLoc[] = { 10, 0, 4, 8, 11, 14, 15, 18, 22, 26, 32, 33 }; +char *numbers[] = + { "a", "thousand", "three", "million", "ninty-nine", "nine-hundred,", +"ninty-nine", "forty-two", "a", "gazillion", "sixty-eight", "times", "thirty-three" +}; +char numbersLoc[] = { 7, 0, 2, 4, 5, 7, 8, 10, 13 }; +char *adv[] = + { "viciously", "manicly", "merrily", "happily", ",", "with", "the", "19", +"of", "18", ",", "gleefully", ",", "with", "much", "ritualistic", "celebration", ",", +"franticly" +}; +char advLoc[] = { 8, 0, 1, 2, 3, 4, 11, 12, 18, 19 }; +char *metaphor[] = + { "an", "irate", "manticore", "Thor's", "belch", "Alah's", "fist", "16", +"titans", "a", "particularly", "vicious", "she-bear", "in", "the", "midst", "of", "her", +"menstrual", "cycle", "a", "pissed-off", "Jabberwock" +}; +char metaphorLoc[] = { 6, 0, 3, 5, 7, 9, 20, 23 }; +char *force[] = { "force", "fury", "power", "rage" }; +char forceLoc[] = { 4, 0, 1, 2, 3, 4 }; +char *bad_action[] = + { "spit", "shimmy", "slobber", "find", "refuge", "find", "shelter", "dance", +"retch", "vomit", "defecate", "erect", "a", "strip", "mall", "build", "a", "26", "have", "a", +"religious", "experience", "discharge", "bodily", "waste", "fart", "dance", "drool", +"lambada", "spill", "16", "rusty", "tacks", "bite", "you", "sneeze", "sing", "16", +"campfire", "songs", "smite", "you", "16", "times", "construct", "a", "new", "home", "throw", +"a", "party", "procreate" +}; +char bad_actionLoc[] = + { 25, 0, 1, 2, 3, 5, 7, 8, 9, 10, 11, 15, 18, 22, 25, 26, 27, 28, 29, 33, +35, 36, 40, 44, 48, 51, 52 }; +char *beasties[] = + { "yaks", "22", "maggots", "22", "cockroaches", "stinging", "scorpions", +"fleas", "22", "weasels", "22", "gnats", "South", "American", "killer", "bees", "spiders", +"4", "monkeys", "22", "wiener-dogs", "22", "rats", "22", "wolverines", "4", ",", "22", +"pit-fiends" +}; +char beastiesLoc[] = + { 14, 0, 1, 3, 5, 7, 8, 10, 12, 16, 17, 19, 21, 23, 25, 29 }; +char *condition[] = + { "frothing", "manic", "crazed", "plague-ridden", "disease-carrying", +"biting", "rabid", "blood-thirsty", "ravaging", "slavering" +}; +char conditionLoc[] = { 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; +char *place[] = + { "in", "24", "25", "upon", "your", "mother's", "grave", "on", "24", "best", +"rug", "in", "the", "26", "you", "call", "home", "upon", "your", "heinie" +}; +char placeLoc[] = { 5, 0, 3, 7, 11, 17, 20 }; +char *relation[] = + { "your", "your", "your", "your", "father's", "your", "mother's", "your", +"grandma's" +}; +char relationLoc[] = { 6, 0, 1, 2, 3, 5, 7, 9 }; +char *in_something[] = + { "entrails", "anal", "cavity", "shoes", "house", "pantry", "general", +"direction", "pants", "bed" +}; +char in_somethingLoc[] = { 8, 0, 1, 3, 4, 5, 6, 8, 9, 10 }; +char *bad_place[] = + { "rat", "hole", "sewer", "toxic", "dump", "oil", "refinery", "landfill", +"porto-pottie" +}; +char bad_placeLoc[] = { 6, 0, 2, 3, 5, 7, 8, 9 }; +char **daGrammar[27]; +char *daGLoc[27]; + +static void +init_grammar (void) +{ + daGrammar[0] = start; + daGLoc[0] = startLoc; + daGrammar[1] = adj; + daGLoc[1] = adjLoc; + daGrammar[2] = adj3; + daGLoc[2] = adj3Loc; + daGrammar[3] = adj1; + daGLoc[3] = adj1Loc; + daGrammar[4] = adj2; + daGLoc[4] = adj2Loc; + daGrammar[5] = name; + daGLoc[5] = nameLoc; + daGrammar[6] = stuff; + daGLoc[6] = stuffLoc; + daGrammar[7] = noun_and_prep; + daGLoc[7] = noun_and_prepLoc; + daGrammar[8] = organics; + daGLoc[8] = organicsLoc; + daGrammar[9] = body_parts; + daGLoc[9] = body_partsLoc; + daGrammar[10] = noun; + daGLoc[10] = nounLoc; + daGrammar[11] = animal; + daGLoc[11] = animalLoc; + daGrammar[12] = good_verb; + daGLoc[12] = good_verbLoc; + daGrammar[13] = curse; + daGLoc[13] = curseLoc; + daGrammar[14] = afflictors; + daGLoc[14] = afflictorsLoc; + daGrammar[15] = quantity; + daGLoc[15] = quantityLoc; + daGrammar[16] = numbers; + daGLoc[16] = numbersLoc; + daGrammar[17] = adv; + daGLoc[17] = advLoc; + daGrammar[18] = metaphor; + daGLoc[18] = metaphorLoc; + daGrammar[19] = force; + daGLoc[19] = forceLoc; + daGrammar[20] = bad_action; + daGLoc[20] = bad_actionLoc; + daGrammar[21] = beasties; + daGLoc[21] = beastiesLoc; + daGrammar[22] = condition; + daGLoc[22] = conditionLoc; + daGrammar[23] = place; + daGLoc[23] = placeLoc; + daGrammar[24] = relation; + daGLoc[24] = relationLoc; + daGrammar[25] = in_something; + daGLoc[25] = in_somethingLoc; + daGrammar[26] = bad_place; + daGLoc[26] = bad_placeLoc; +} + +#include +#include +#include +#include +#include +#include +#include + +void expand (int num, char **grammar[], char *location[], int handle); + +static void +usage (int ret_code, const char *message, ...) PRINTF_FORMAT (2, 3); + +static void +usage (int ret_code, const char *message, ...) +{ + va_list args; + + if (message != NULL) + { + va_start (args, message); + vprintf (message, args); + va_end (args); + } + + printf ("\n" + "Usage: insult [OPTION]...\n" + "Prints random insults to screen.\n\n" + " -h: this help message\n" + " -s : set the random seed (default 4951)\n" + " -n : choose number of insults (default 4)\n" + " -f : redirect output to \n"); + + exit (ret_code); +} + +int +main (int argc, char *argv[]) +{ + int sentence_cnt, new_seed, i, file_flag, sent_flag, seed_flag; + int handle; + + new_seed = 4951; + sentence_cnt = 4; + file_flag = 0; + seed_flag = 0; + sent_flag = 0; + handle = STDOUT_FILENO; + + for (i = 1; i < argc; i++) + { + if (strcmp (argv[1], "-h") == 0) + usage (0, NULL); + else if (strcmp (argv[i], "-s") == 0) + { + if (seed_flag++) + usage (-1, "Can't have more than one seed"); + if (++i >= argc) + usage (-1, "Missing value for -s"); + new_seed = atoi (argv[i]); + } + else if (strcmp (argv[i], "-n") == 0) + { + if (sent_flag++) + usage (-1, "Can't have more than one sentence option"); + if (++i >= argc) + usage (-1, "Missing value for -n"); + sentence_cnt = atoi (argv[i]); + if (sentence_cnt < 1) + usage (-1, "Must have at least one sentence"); + } + else if (strcmp (argv[i], "-f") == 0) + { + if (file_flag++) + usage (-1, "Can't have more than one output file"); + if (++i >= argc) + usage (-1, "Missing value for -f"); + + /* Because files have fixed length in the basic Pintos + file system, the 0 argument means that this option + will not be useful until project 4 is + implemented. */ + create (argv[i], 0); + handle = open (argv[i]); + if (handle < 0) + { + printf ("%s: open failed\n", argv[i]); + return EXIT_FAILURE; + } + } + else + usage (-1, "Unrecognized flag"); + } + + init_grammar (); + + random_init (new_seed); + hprintf (handle, "\n"); + + for (i = 0; i < sentence_cnt; i++) + { + hprintf (handle, "\n"); + expand (0, daGrammar, daGLoc, handle); + hprintf (handle, "\n\n"); + } + + if (file_flag) + close (handle); + + return EXIT_SUCCESS; +} + +void +expand (int num, char **grammar[], char *location[], int handle) +{ + char *word; + int i, which, listStart, listEnd; + + which = random_ulong () % location[num][0] + 1; + listStart = location[num][which]; + listEnd = location[num][which + 1]; + for (i = listStart; i < listEnd; i++) + { + word = grammar[num][i]; + if (!isdigit (*word)) + { + if (!ispunct (*word)) + hprintf (handle, " "); + hprintf (handle, "%s", word); + } + else + expand (atoi (word), grammar, location, handle); + } + +} diff --git a/pintos-progos/examples/lib/.gitignore b/pintos-progos/examples/lib/.gitignore new file mode 100644 index 0000000..a438335 --- /dev/null +++ b/pintos-progos/examples/lib/.gitignore @@ -0,0 +1 @@ +*.d diff --git a/pintos-progos/examples/lib/user/.dummy b/pintos-progos/examples/lib/user/.dummy new file mode 100644 index 0000000..e69de29 diff --git a/pintos-progos/examples/lib/user/.gitignore b/pintos-progos/examples/lib/user/.gitignore new file mode 100644 index 0000000..a438335 --- /dev/null +++ b/pintos-progos/examples/lib/user/.gitignore @@ -0,0 +1 @@ +*.d diff --git a/pintos-progos/examples/lineup.c b/pintos-progos/examples/lineup.c new file mode 100644 index 0000000..60402d0 --- /dev/null +++ b/pintos-progos/examples/lineup.c @@ -0,0 +1,46 @@ +/* lineup.c + + Converts a file to uppercase in-place. + + Incidentally, another way to do this while avoiding the seeks + would be to open the input file, then remove() it and reopen + it under another handle. Because of Unix deletion semantics + this works fine. */ + +#include +#include +#include + +int +main (int argc, char *argv[]) +{ + char buf[1024]; + int handle; + + if (argc != 2) + exit (1); + + handle = open (argv[1]); + if (handle < 0) + exit (2); + + for (;;) + { + int n, i; + + n = read (handle, buf, sizeof buf); + if (n <= 0) + break; + + for (i = 0; i < n; i++) + buf[i] = toupper ((unsigned char) buf[i]); + + seek (handle, tell (handle) - n); + if (write (handle, buf, n) != n) + printf ("write failed\n"); + } + + close (handle); + + return EXIT_SUCCESS; +} diff --git a/pintos-progos/examples/ls.c b/pintos-progos/examples/ls.c new file mode 100644 index 0000000..fbe27a1 --- /dev/null +++ b/pintos-progos/examples/ls.c @@ -0,0 +1,90 @@ +/* ls.c + + Lists the contents of the directory or directories named on + the command line, or of the current directory if none are + named. + + By default, only the name of each file is printed. If "-l" is + given as the first argument, the type, size, and inumber of + each file is also printed. This won't work until project 4. */ + +#include +#include +#include + +static bool +list_dir (const char *dir, bool verbose) +{ + int dir_fd = open (dir); + if (dir_fd == -1) + { + printf ("%s: not found\n", dir); + return false; + } + + if (isdir (dir_fd)) + { + char name[READDIR_MAX_LEN]; + + printf ("%s", dir); + if (verbose) + printf (" (inumber %d)", inumber (dir_fd)); + printf (":\n"); + + while (readdir (dir_fd, name)) + { + printf ("%s", name); + if (verbose) + { + char full_name[128]; + int entry_fd; + + snprintf (full_name, sizeof full_name, "%s/%s", dir, name); + entry_fd = open (full_name); + + printf (": "); + if (entry_fd != -1) + { + if (isdir (entry_fd)) + printf ("directory"); + else + printf ("%d-byte file", filesize (entry_fd)); + printf (", inumber %d", inumber (entry_fd)); + } + else + printf ("open failed"); + close (entry_fd); + } + printf ("\n"); + } + } + else + printf ("%s: not a directory\n", dir); + close (dir_fd); + return true; +} + +int +main (int argc, char *argv[]) +{ + bool success = true; + bool verbose = false; + + if (argc > 1 && !strcmp (argv[1], "-l")) + { + verbose = true; + argv++; + argc--; + } + + if (argc <= 1) + success = list_dir (".", verbose); + else + { + int i; + for (i = 1; i < argc; i++) + if (!list_dir (argv[i], verbose)) + success = false; + } + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/pintos-progos/examples/matmult.c b/pintos-progos/examples/matmult.c new file mode 100644 index 0000000..4f0615f --- /dev/null +++ b/pintos-progos/examples/matmult.c @@ -0,0 +1,57 @@ +/* matmult.c + + Test program to do matrix multiplication on large arrays. + + Intended to stress virtual memory system. + + Ideally, we could read the matrices off of the file system, + and store the result back to the file system! + */ + +#include +#include + +/* You should define DIM to be large enough that the arrays + don't fit in physical memory. + + Dim Memory + ------ -------- + 16 3 kB + 64 48 kB + 128 192 kB + 256 768 kB + 512 3,072 kB + 1,024 12,288 kB + 2,048 49,152 kB + 4,096 196,608 kB + 8,192 786,432 kB + 16,384 3,145,728 kB */ +#define DIM 128 + +int A[DIM][DIM]; +int B[DIM][DIM]; +int C[DIM][DIM]; + +int +main (void) +{ + int i, j, k; + + /* Initialize the matrices. */ + for (i = 0; i < DIM; i++) + for (j = 0; j < DIM; j++) + { + A[i][j] = i; + B[i][j] = j; + C[i][j] = 0; + } + + /* Multiply matrices. */ + for (i = 0; i < DIM; i++) + for (j = 0; j < DIM; j++) + for (k = 0; k < DIM; k++) + C[i][j] += A[i][k] * B[k][j]; + + /* Done. */ + exit (C[DIM - 1][DIM - 1]); +} diff --git a/pintos-progos/examples/mcat.c b/pintos-progos/examples/mcat.c new file mode 100644 index 0000000..7b39760 --- /dev/null +++ b/pintos-progos/examples/mcat.c @@ -0,0 +1,45 @@ +/* mcat.c + + Prints files specified on command line to the console, using + mmap. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + int i; + + for (i = 1; i < argc; i++) + { + int fd; + mapid_t map; + void *data = (void *) 0x10000000; + int size; + + /* Open input file. */ + fd = open (argv[i]); + if (fd < 0) + { + printf ("%s: open failed\n", argv[i]); + return EXIT_FAILURE; + } + size = filesize (fd); + + /* Map files. */ + map = mmap (fd, data); + if (map == MAP_FAILED) + { + printf ("%s: mmap failed\n", argv[i]); + return EXIT_FAILURE; + } + + /* Write file to console. */ + write (STDOUT_FILENO, data, size); + + /* Unmap files (optional). */ + munmap (map); + } + return EXIT_SUCCESS; +} diff --git a/pintos-progos/examples/mcp.c b/pintos-progos/examples/mcp.c new file mode 100644 index 0000000..6091dc8 --- /dev/null +++ b/pintos-progos/examples/mcp.c @@ -0,0 +1,68 @@ +/* mcp.c + + Copies one file to another, using mmap. */ + +#include +#include +#include + +int +main (int argc, char *argv[]) +{ + int in_fd, out_fd; + mapid_t in_map, out_map; + void *in_data = (void *) 0x10000000; + void *out_data = (void *) 0x20000000; + int size; + + if (argc != 3) + { + printf ("usage: cp OLD NEW\n"); + return EXIT_FAILURE; + } + + /* Open input file. */ + in_fd = open (argv[1]); + if (in_fd < 0) + { + printf ("%s: open failed\n", argv[1]); + return EXIT_FAILURE; + } + size = filesize (in_fd); + + /* Create and open output file. */ + if (!create (argv[2], size)) + { + printf ("%s: create failed\n", argv[2]); + return EXIT_FAILURE; + } + out_fd = open (argv[2]); + if (out_fd < 0) + { + printf ("%s: open failed\n", argv[2]); + return EXIT_FAILURE; + } + + /* Map files. */ + in_map = mmap (in_fd, in_data); + if (in_map == MAP_FAILED) + { + printf ("%s: mmap failed\n", argv[1]); + return EXIT_FAILURE; + } + out_map = mmap (out_fd, out_data); + if (out_map == MAP_FAILED) + { + printf ("%s: mmap failed\n", argv[2]); + return EXIT_FAILURE; + } + + /* Copy files. */ + memcpy (out_data, in_data, size); + + /* Unmap files (optional). */ + munmap (in_map); + munmap (out_map); + + return EXIT_SUCCESS; +} diff --git a/pintos-progos/examples/mkdir.c b/pintos-progos/examples/mkdir.c new file mode 100644 index 0000000..7ddbc3f --- /dev/null +++ b/pintos-progos/examples/mkdir.c @@ -0,0 +1,24 @@ +/* mkdir.c + + Creates a directory. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + if (argc != 2) + { + printf ("usage: %s DIRECTORY\n", argv[0]); + return EXIT_FAILURE; + } + + if (!mkdir (argv[1])) + { + printf ("%s: mkdir failed\n", argv[1]); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/pintos-progos/examples/pwd.c b/pintos-progos/examples/pwd.c new file mode 100644 index 0000000..d2305cf --- /dev/null +++ b/pintos-progos/examples/pwd.c @@ -0,0 +1,152 @@ +/* pwd.c + + Prints the absolute name of the present working directory. */ + +#include +#include +#include +#include + +static bool getcwd (char *cwd, size_t cwd_size); + +int +main (void) +{ + char cwd[128]; + if (getcwd (cwd, sizeof cwd)) + { + printf ("%s\n", cwd); + return EXIT_SUCCESS; + } + else + { + printf ("error\n"); + return EXIT_FAILURE; + } +} + +/* Stores the inode number for FILE_NAME in *INUM. + Returns true if successful, false if the file could not be + opened. */ +static bool +get_inumber (const char *file_name, int *inum) +{ + int fd = open (file_name); + if (fd >= 0) + { + *inum = inumber (fd); + close (fd); + return true; + } + else + return false; +} + +/* Prepends PREFIX to the characters stored in the final *DST_LEN + bytes of the DST_SIZE-byte buffer that starts at DST. + Returns true if successful, false if adding that many + characters, plus a null terminator, would overflow the buffer. + (No null terminator is actually added or depended upon, but + its space is accounted for.) */ +static bool +prepend (const char *prefix, + char *dst, size_t *dst_len, size_t dst_size) +{ + size_t prefix_len = strlen (prefix); + if (prefix_len + *dst_len + 1 <= dst_size) + { + *dst_len += prefix_len; + memcpy ((dst + dst_size) - *dst_len, prefix, prefix_len); + return true; + } + else + return false; +} + +/* Stores the current working directory, as a null-terminated + string, in the CWD_SIZE bytes in CWD. + Returns true if successful, false on error. Errors include + system errors, directory trees deeper than MAX_LEVEL levels, + and insufficient space in CWD. */ +static bool +getcwd (char *cwd, size_t cwd_size) +{ + size_t cwd_len = 0; + +#define MAX_LEVEL 20 + char name[MAX_LEVEL * 3 + 1 + READDIR_MAX_LEN + 1]; + char *namep; + + int child_inum; + + /* Make sure there's enough space for at least "/". */ + if (cwd_size < 2) + return false; + + /* Get inumber for current directory. */ + if (!get_inumber (".", &child_inum)) + return false; + + namep = name; + for (;;) + { + int parent_inum, parent_fd; + + /* Compose "../../../..", etc., in NAME. */ + if ((namep - name) > MAX_LEVEL * 3) + return false; + *namep++ = '.'; + *namep++ = '.'; + *namep = '\0'; + + /* Open directory. */ + parent_fd = open (name); + if (parent_fd < 0) + return false; + *namep++ = '/'; + + /* If parent and child have the same inumber, + then we've arrived at the root. */ + parent_inum = inumber (parent_fd); + if (parent_inum == child_inum) + break; + + /* Find name of file in parent directory with the child's + inumber. */ + for (;;) + { + int test_inum; + if (!readdir (parent_fd, namep) || !get_inumber (name, &test_inum)) + { + close (parent_fd); + return false; + } + if (test_inum == child_inum) + break; + } + close (parent_fd); + + /* Prepend "/name" to CWD. */ + if (!prepend (namep - 1, cwd, &cwd_len, cwd_size)) + return false; + + /* Move up. */ + child_inum = parent_inum; + } + + /* Finalize CWD. */ + if (cwd_len > 0) + { + /* Move the string to the beginning of CWD, + and null-terminate it. */ + memmove (cwd, (cwd + cwd_size) - cwd_len, cwd_len); + cwd[cwd_len] = '\0'; + } + else + { + /* Special case for the root. */ + strlcpy (cwd, "/", cwd_size); + } + + return true; +} diff --git a/pintos-progos/examples/recursor.c b/pintos-progos/examples/recursor.c new file mode 100644 index 0000000..79c784a --- /dev/null +++ b/pintos-progos/examples/recursor.c @@ -0,0 +1,34 @@ +#include +#include +#include + +int +main (int argc, char *argv[]) +{ + char buffer[128]; + pid_t pid; + int retval = 0; + + if (argc != 4) + { + printf ("usage: recursor \n"); + exit (1); + } + + /* Print args. */ + printf ("%s %s %s %s\n", argv[0], argv[1], argv[2], argv[3]); + + /* Execute child and wait for it to finish if requested. */ + if (atoi (argv[2]) != 0) + { + snprintf (buffer, sizeof buffer, + "recursor %s %d %s", argv[1], atoi (argv[2]) - 1, argv[3]); + pid = exec (buffer); + if (atoi (argv[3])) + retval = wait (pid); + } + + /* Done. */ + printf ("%s %s: dying, retval=%d\n", argv[1], argv[2], retval); + exit (retval); +} diff --git a/pintos-progos/examples/rm.c b/pintos-progos/examples/rm.c new file mode 100644 index 0000000..0db7f7b --- /dev/null +++ b/pintos-progos/examples/rm.c @@ -0,0 +1,21 @@ +/* rm.c + + Removes files specified on command line. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + bool success = true; + int i; + + for (i = 1; i < argc; i++) + if (!remove (argv[i])) + { + printf ("%s: remove failed\n", argv[i]); + success = false; + } + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/pintos-progos/examples/shell.c b/pintos-progos/examples/shell.c new file mode 100644 index 0000000..93641b4 --- /dev/null +++ b/pintos-progos/examples/shell.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include + +static void read_line (char line[], size_t); +static bool backspace (char **pos, char line[]); + +int +main (void) +{ + printf ("Shell starting...\n"); + for (;;) + { + char command[80]; + + /* Read command. */ + printf ("--"); + read_line (command, sizeof command); + + /* Execute command. */ + if (!strcmp (command, "exit")) + break; + else if (!memcmp (command, "cd ", 3)) + { + if (!chdir (command + 3)) + printf ("\"%s\": chdir failed\n", command + 3); + } + else if (command[0] == '\0') + { + /* Empty command. */ + } + else + { + pid_t pid = exec (command); + if (pid != PID_ERROR) + printf ("\"%s\": exit code %d\n", command, wait (pid)); + else + printf ("exec failed\n"); + } + } + + printf ("Shell exiting."); + return EXIT_SUCCESS; +} + +/* Reads a line of input from the user into LINE, which has room + for SIZE bytes. Handles backspace and Ctrl+U in the ways + expected by Unix users. On return, LINE will always be + null-terminated and will not end in a new-line character. */ +static void +read_line (char line[], size_t size) +{ + char *pos = line; + for (;;) + { + char c; + read (STDIN_FILENO, &c, 1); + + switch (c) + { + case '\r': + *pos = '\0'; + putchar ('\n'); + return; + + case '\b': + backspace (&pos, line); + break; + + case ('U' - 'A') + 1: /* Ctrl+U. */ + while (backspace (&pos, line)) + continue; + break; + + default: + /* Add character to line. */ + if (pos < line + size - 1) + { + putchar (c); + *pos++ = c; + } + break; + } + } +} + +/* If *POS is past the beginning of LINE, backs up one character + position. Returns true if successful, false if nothing was + done. */ +static bool +backspace (char **pos, char line[]) +{ + if (*pos > line) + { + /* Back up cursor, overwrite character, back up + again. */ + printf ("\b \b"); + (*pos)--; + return true; + } + else + return false; +} diff --git a/pintos-progos/examples/test.c b/pintos-progos/examples/test.c new file mode 100644 index 0000000..44dc307 --- /dev/null +++ b/pintos-progos/examples/test.c @@ -0,0 +1,101 @@ +/* test.c + + Experiments with syscalls + argc < 2 Print Hello World + argv[1][0] == 'p' print argv[2] + == 'e' Exec Test + == 'f' File test + == 'F' File descriptor stress test + == 'h' Halt + == '0' Null-Pointer Access +*/ + +#include +#include + +#define LARGE_BUF_SIZE 4150 +char large_buf[LARGE_BUF_SIZE]; + +#define NUM_EXEC_CHILDS 7 +char *execs[NUM_EXEC_CHILDS] = { "test", "test p FOO", "test p BAR", "test f", "test 0", &large_buf[0], "test^" }; + +#define MAX_FD 4097 + +static void init_args(void); +static void init_args() +{ + int i = 0; + char *t = ""; + while(i < LARGE_BUF_SIZE-1) { + if(!*t) t = "test "; + large_buf[i++] = *t++; + } + large_buf[LARGE_BUF_SIZE-1]='\0'; +} + +int +main (int argc, char** argv) +{ + if(argc < 2) { + printf("Hello World!\n"); + exit(0); + } + init_args(); + if(argv[1][0] == 'e') { + int r = 0; + int i; + int tid[NUM_EXEC_CHILDS]; + + for(i = 0; i < NUM_EXEC_CHILDS; i++) { + tid[i] = exec(execs[i]); + } + for(i = 0; i < NUM_EXEC_CHILDS; i++) { + if (tid[i] >= 0) { + r = wait(tid[i]); + printf("P child %d exited with exit code %d\n",i, r); + } else { + printf("P child %d failed to start\n", i); + } + } + } else if(argv[1][0] == 'f') { + char buf[10]; + int r; + create ("test.txt", 10); + int handle = open ("test.txt"); + if (handle < 2) + printf ("open(test.txt) returned %d", handle); + if ((r=write(handle,"987654321",10)) != 10) { + printf("write failed: %d not %d\n",r,10); + exit(1); + } + seek(handle,0); + if ((r=read(handle, buf, 10)) != 10) { + printf("read failed: %d not %d\n",r,10); + exit(1); + } + printf("test.txt: %s\n", buf); + } else if(argv[1][0] == 'F') { + int j,i; + create ("foo.txt", 10); + for (j = 0; j < 5; j++) { + for (i = 2; i <= MAX_FD; i++) { + if (open ("foo.txt") < 0) { + printf("Opening the %d's file failed\n",i-2); + break; + } + } + while(--i >= 2) { + close (i); + } + } + } else if(argv[1][0] == '0') { + printf("Null pointer value is: %d\n",*((int*)NULL)); + } else if(argv[1][0] == 'h') { + halt(); + } else if(argv[1][0] == 'p' && argc >= 3) { + printf("%s\n", argv[2]); + } else { + printf("ARGV[1] is %s\n", argv[1]); + } + return 0; +} diff --git a/pintos-progos/filesys/.gitignore b/pintos-progos/filesys/.gitignore new file mode 100644 index 0000000..6d5357c --- /dev/null +++ b/pintos-progos/filesys/.gitignore @@ -0,0 +1,3 @@ +build +bochsrc.txt +bochsout.txt diff --git a/pintos-progos/filesys/Make.vars b/pintos-progos/filesys/Make.vars new file mode 100644 index 0000000..b3aa005 --- /dev/null +++ b/pintos-progos/filesys/Make.vars @@ -0,0 +1,13 @@ +# -*- makefile -*- + +kernel.bin: DEFINES = -DUSERPROG -DFILESYS +KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys +TEST_SUBDIRS = tests/userprog tests/filesys/base tests/filesys/extended +GRADING_FILE = $(SRCDIR)/tests/filesys/Grading.no-vm +SIMULATOR = --qemu + +# Uncomment the lines below to enable VM. +#kernel.bin: DEFINES += -DVM +#KERNEL_SUBDIRS += vm +#TEST_SUBDIRS += tests/vm +#GRADING_FILE = $(SRCDIR)/tests/filesys/Grading.with-vm diff --git a/pintos-progos/filesys/Makefile b/pintos-progos/filesys/Makefile new file mode 100644 index 0000000..34c10aa --- /dev/null +++ b/pintos-progos/filesys/Makefile @@ -0,0 +1 @@ +include ../Makefile.kernel diff --git a/pintos-progos/filesys/directory.c b/pintos-progos/filesys/directory.c new file mode 100644 index 0000000..030c1c9 --- /dev/null +++ b/pintos-progos/filesys/directory.c @@ -0,0 +1,236 @@ +#include "filesys/directory.h" +#include +#include +#include +#include "filesys/filesys.h" +#include "filesys/inode.h" +#include "threads/malloc.h" + +/* A directory. */ +struct dir + { + struct inode *inode; /* Backing store. */ + off_t pos; /* Current position. */ + }; + +/* A single directory entry. */ +struct dir_entry + { + block_sector_t inode_sector; /* Sector number of header. */ + char name[NAME_MAX + 1]; /* Null terminated file name. */ + bool in_use; /* In use or free? */ + }; + +/* Creates a directory with space for ENTRY_CNT entries in the + given SECTOR. Returns true if successful, false on failure. */ +bool +dir_create (block_sector_t sector, size_t entry_cnt) +{ + return inode_create (sector, entry_cnt * sizeof (struct dir_entry)); +} + +/* Opens and returns the directory for the given INODE, of which + it takes ownership. Returns a null pointer on failure. */ +struct dir * +dir_open (struct inode *inode) +{ + struct dir *dir = calloc (1, sizeof *dir); + if (inode != NULL && dir != NULL) + { + dir->inode = inode; + dir->pos = 0; + return dir; + } + else + { + inode_close (inode); + free (dir); + return NULL; + } +} + +/* Opens the root directory and returns a directory for it. + Return true if successful, false on failure. */ +struct dir * +dir_open_root (void) +{ + return dir_open (inode_open (ROOT_DIR_SECTOR)); +} + +/* Opens and returns a new directory for the same inode as DIR. + Returns a null pointer on failure. */ +struct dir * +dir_reopen (struct dir *dir) +{ + return dir_open (inode_reopen (dir->inode)); +} + +/* Destroys DIR and frees associated resources. */ +void +dir_close (struct dir *dir) +{ + if (dir != NULL) + { + inode_close (dir->inode); + free (dir); + } +} + +/* Returns the inode encapsulated by DIR. */ +struct inode * +dir_get_inode (struct dir *dir) +{ + return dir->inode; +} + +/* Searches DIR for a file with the given NAME. + If successful, returns true, sets *EP to the directory entry + if EP is non-null, and sets *OFSP to the byte offset of the + directory entry if OFSP is non-null. + otherwise, returns false and ignores EP and OFSP. */ +static bool +lookup (const struct dir *dir, const char *name, + struct dir_entry *ep, off_t *ofsp) +{ + struct dir_entry e; + size_t ofs; + + ASSERT (dir != NULL); + ASSERT (name != NULL); + + for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e; + ofs += sizeof e) + if (e.in_use && !strcmp (name, e.name)) + { + if (ep != NULL) + *ep = e; + if (ofsp != NULL) + *ofsp = ofs; + return true; + } + return false; +} + +/* Searches DIR for a file with the given NAME + and returns true if one exists, false otherwise. + On success, sets *INODE to an inode for the file, otherwise to + a null pointer. The caller must close *INODE. */ +bool +dir_lookup (const struct dir *dir, const char *name, + struct inode **inode) +{ + struct dir_entry e; + + ASSERT (dir != NULL); + ASSERT (name != NULL); + + if (lookup (dir, name, &e, NULL)) + *inode = inode_open (e.inode_sector); + else + *inode = NULL; + + return *inode != NULL; +} + +/* Adds a file named NAME to DIR, which must not already contain a + file by that name. The file's inode is in sector + INODE_SECTOR. + Returns true if successful, false on failure. + Fails if NAME is invalid (i.e. too long) or a disk or memory + error occurs. */ +bool +dir_add (struct dir *dir, const char *name, block_sector_t inode_sector) +{ + struct dir_entry e; + off_t ofs; + bool success = false; + + ASSERT (dir != NULL); + ASSERT (name != NULL); + + /* Check NAME for validity. */ + if (*name == '\0' || strlen (name) > NAME_MAX) + return false; + + /* Check that NAME is not in use. */ + if (lookup (dir, name, NULL, NULL)) + goto done; + + /* Set OFS to offset of free slot. + If there are no free slots, then it will be set to the + current end-of-file. + + inode_read_at() will only return a short read at end of file. + Otherwise, we'd need to verify that we didn't get a short + read due to something intermittent such as low memory. */ + for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e; + ofs += sizeof e) + if (!e.in_use) + break; + + /* Write slot. */ + e.in_use = true; + strlcpy (e.name, name, sizeof e.name); + e.inode_sector = inode_sector; + success = inode_write_at (dir->inode, &e, sizeof e, ofs) == sizeof e; + + done: + return success; +} + +/* Removes any entry for NAME in DIR. + Returns true if successful, false on failure, + which occurs only if there is no file with the given NAME. */ +bool +dir_remove (struct dir *dir, const char *name) +{ + struct dir_entry e; + struct inode *inode = NULL; + bool success = false; + off_t ofs; + + ASSERT (dir != NULL); + ASSERT (name != NULL); + + /* Find directory entry. */ + if (!lookup (dir, name, &e, &ofs)) + goto done; + + /* Open inode. */ + inode = inode_open (e.inode_sector); + if (inode == NULL) + goto done; + + /* Erase directory entry. */ + e.in_use = false; + if (inode_write_at (dir->inode, &e, sizeof e, ofs) != sizeof e) + goto done; + + /* Remove inode. */ + inode_remove (inode); + success = true; + + done: + inode_close (inode); + return success; +} + +/* Reads the next directory entry in DIR and stores the name in + NAME. Returns true if successful, false if the directory + contains no more entries. */ +bool +dir_readdir (struct dir *dir, char name[NAME_MAX + 1]) +{ + struct dir_entry e; + + while (inode_read_at (dir->inode, &e, sizeof e, dir->pos) == sizeof e) + { + dir->pos += sizeof e; + if (e.in_use) + { + strlcpy (name, e.name, NAME_MAX + 1); + return true; + } + } + return false; +} diff --git a/pintos-progos/filesys/directory.h b/pintos-progos/filesys/directory.h new file mode 100644 index 0000000..930acf9 --- /dev/null +++ b/pintos-progos/filesys/directory.h @@ -0,0 +1,30 @@ +#ifndef FILESYS_DIRECTORY_H +#define FILESYS_DIRECTORY_H + +#include +#include +#include "devices/block.h" + +/* Maximum length of a file name component. + This is the traditional UNIX maximum length. + After directories are implemented, this maximum length may be + retained, but much longer full path names must be allowed. */ +#define NAME_MAX 14 + +struct inode; + +/* Opening and closing directories. */ +bool dir_create (block_sector_t sector, size_t entry_cnt); +struct dir *dir_open (struct inode *); +struct dir *dir_open_root (void); +struct dir *dir_reopen (struct dir *); +void dir_close (struct dir *); +struct inode *dir_get_inode (struct dir *); + +/* Reading and writing. */ +bool dir_lookup (const struct dir *, const char *name, struct inode **); +bool dir_add (struct dir *, const char *name, block_sector_t); +bool dir_remove (struct dir *, const char *name); +bool dir_readdir (struct dir *, char name[NAME_MAX + 1]); + +#endif /* filesys/directory.h */ diff --git a/pintos-progos/filesys/file.c b/pintos-progos/filesys/file.c new file mode 100644 index 0000000..d5fc10d --- /dev/null +++ b/pintos-progos/filesys/file.c @@ -0,0 +1,168 @@ +#include "filesys/file.h" +#include +#include "filesys/inode.h" +#include "threads/malloc.h" + +/* An open file. */ +struct file + { + struct inode *inode; /* File's inode. */ + off_t pos; /* Current position. */ + bool deny_write; /* Has file_deny_write() been called? */ + }; + +/* Opens a file for the given INODE, of which it takes ownership, + and returns the new file. Returns a null pointer if an + allocation fails or if INODE is null. */ +struct file * +file_open (struct inode *inode) +{ + struct file *file = calloc (1, sizeof *file); + if (inode != NULL && file != NULL) + { + file->inode = inode; + file->pos = 0; + file->deny_write = false; + return file; + } + else + { + inode_close (inode); + free (file); + return NULL; + } +} + +/* Opens and returns a new file for the same inode as FILE. + Returns a null pointer if unsuccessful. */ +struct file * +file_reopen (struct file *file) +{ + return file_open (inode_reopen (file->inode)); +} + +/* Closes FILE. */ +void +file_close (struct file *file) +{ + if (file != NULL) + { + file_allow_write (file); + inode_close (file->inode); + free (file); + } +} + +/* Returns the inode encapsulated by FILE. */ +struct inode * +file_get_inode (struct file *file) +{ + return file->inode; +} + +/* Reads SIZE bytes from FILE into BUFFER, + starting at the file's current position. + Returns the number of bytes actually read, + which may be less than SIZE if end of file is reached. + Advances FILE's position by the number of bytes read. */ +off_t +file_read (struct file *file, void *buffer, off_t size) +{ + off_t bytes_read = inode_read_at (file->inode, buffer, size, file->pos); + file->pos += bytes_read; + return bytes_read; +} + +/* Reads SIZE bytes from FILE into BUFFER, + starting at offset FILE_OFS in the file. + Returns the number of bytes actually read, + which may be less than SIZE if end of file is reached. + The file's current position is unaffected. */ +off_t +file_read_at (struct file *file, void *buffer, off_t size, off_t file_ofs) +{ + return inode_read_at (file->inode, buffer, size, file_ofs); +} + +/* Writes SIZE bytes from BUFFER into FILE, + starting at the file's current position. + Returns the number of bytes actually written, + which may be less than SIZE if end of file is reached. + (Normally we'd grow the file in that case, but file growth is + not yet implemented.) + Advances FILE's position by the number of bytes read. */ +off_t +file_write (struct file *file, const void *buffer, off_t size) +{ + off_t bytes_written = inode_write_at (file->inode, buffer, size, file->pos); + file->pos += bytes_written; + return bytes_written; +} + +/* Writes SIZE bytes from BUFFER into FILE, + starting at offset FILE_OFS in the file. + Returns the number of bytes actually written, + which may be less than SIZE if end of file is reached. + (Normally we'd grow the file in that case, but file growth is + not yet implemented.) + The file's current position is unaffected. */ +off_t +file_write_at (struct file *file, const void *buffer, off_t size, + off_t file_ofs) +{ + return inode_write_at (file->inode, buffer, size, file_ofs); +} + +/* Prevents write operations on FILE's underlying inode + until file_allow_write() is called or FILE is closed. */ +void +file_deny_write (struct file *file) +{ + ASSERT (file != NULL); + if (!file->deny_write) + { + file->deny_write = true; + inode_deny_write (file->inode); + } +} + +/* Re-enables write operations on FILE's underlying inode. + (Writes might still be denied by some other file that has the + same inode open.) */ +void +file_allow_write (struct file *file) +{ + ASSERT (file != NULL); + if (file->deny_write) + { + file->deny_write = false; + inode_allow_write (file->inode); + } +} + +/* Returns the size of FILE in bytes. */ +off_t +file_length (struct file *file) +{ + ASSERT (file != NULL); + return inode_length (file->inode); +} + +/* Sets the current position in FILE to NEW_POS bytes from the + start of the file. */ +void +file_seek (struct file *file, off_t new_pos) +{ + ASSERT (file != NULL); + ASSERT (new_pos >= 0); + file->pos = new_pos; +} + +/* Returns the current position in FILE as a byte offset from the + start of the file. */ +off_t +file_tell (struct file *file) +{ + ASSERT (file != NULL); + return file->pos; +} diff --git a/pintos-progos/filesys/file.h b/pintos-progos/filesys/file.h new file mode 100644 index 0000000..a33c5af --- /dev/null +++ b/pintos-progos/filesys/file.h @@ -0,0 +1,29 @@ +#ifndef FILESYS_FILE_H +#define FILESYS_FILE_H + +#include "filesys/off_t.h" + +struct inode; + +/* Opening and closing files. */ +struct file *file_open (struct inode *); +struct file *file_reopen (struct file *); +void file_close (struct file *); +struct inode *file_get_inode (struct file *); + +/* Reading and writing. */ +off_t file_read (struct file *, void *, off_t); +off_t file_read_at (struct file *, void *, off_t size, off_t start); +off_t file_write (struct file *, const void *, off_t); +off_t file_write_at (struct file *, const void *, off_t size, off_t start); + +/* Preventing writes. */ +void file_deny_write (struct file *); +void file_allow_write (struct file *); + +/* File position. */ +void file_seek (struct file *, off_t); +off_t file_tell (struct file *); +off_t file_length (struct file *); + +#endif /* filesys/file.h */ diff --git a/pintos-progos/filesys/filesys.c b/pintos-progos/filesys/filesys.c new file mode 100644 index 0000000..7a53f5f --- /dev/null +++ b/pintos-progos/filesys/filesys.c @@ -0,0 +1,103 @@ +#include "filesys/filesys.h" +#include +#include +#include +#include "filesys/file.h" +#include "filesys/free-map.h" +#include "filesys/inode.h" +#include "filesys/directory.h" + +/* Partition that contains the file system. */ +struct block *fs_device; + +static void do_format (void); + +/* Initializes the file system module. + If FORMAT is true, reformats the file system. */ +void +filesys_init (bool format) +{ + fs_device = block_get_role (BLOCK_FILESYS); + if (fs_device == NULL) + PANIC ("No file system device found, can't initialize file system."); + + inode_init (); + free_map_init (); + + if (format) + do_format (); + + free_map_open (); +} + +/* Shuts down the file system module, writing any unwritten data + to disk. */ +void +filesys_done (void) +{ + free_map_close (); +} + +/* Creates a file named NAME with the given INITIAL_SIZE. + Returns true if successful, false otherwise. + Fails if a file named NAME already exists, + or if internal memory allocation fails. */ +bool +filesys_create (const char *name, off_t initial_size) +{ + block_sector_t inode_sector = 0; + struct dir *dir = dir_open_root (); + bool success = (dir != NULL + && free_map_allocate (1, &inode_sector) + && inode_create (inode_sector, initial_size) + && dir_add (dir, name, inode_sector)); + if (!success && inode_sector != 0) + free_map_release (inode_sector, 1); + dir_close (dir); + + return success; +} + +/* Opens the file with the given NAME. + Returns the new file if successful or a null pointer + otherwise. + Fails if no file named NAME exists, + or if an internal memory allocation fails. */ +struct file * +filesys_open (const char *name) +{ + struct dir *dir = dir_open_root (); + struct inode *inode = NULL; + + if (dir != NULL) + dir_lookup (dir, name, &inode); + dir_close (dir); + + return file_open (inode); +} + +/* Deletes the file named NAME. + Returns true if successful, false on failure. + Fails if no file named NAME exists, + or if an internal memory allocation fails. */ +bool +filesys_remove (const char *name) +{ + struct dir *dir = dir_open_root (); + bool success = dir != NULL && dir_remove (dir, name); + dir_close (dir); + + return success; +} + +/* Formats the file system. */ +static void +do_format (void) +{ + printf ("Formatting file system..."); + free_map_create (); + if (!dir_create (ROOT_DIR_SECTOR, 16)) + PANIC ("root directory creation failed"); + free_map_close (); + printf ("done.\n"); +} diff --git a/pintos-progos/filesys/filesys.h b/pintos-progos/filesys/filesys.h new file mode 100644 index 0000000..c1cda84 --- /dev/null +++ b/pintos-progos/filesys/filesys.h @@ -0,0 +1,20 @@ +#ifndef FILESYS_FILESYS_H +#define FILESYS_FILESYS_H + +#include +#include "filesys/off_t.h" + +/* Sectors of system file inodes. */ +#define FREE_MAP_SECTOR 0 /* Free map file inode sector. */ +#define ROOT_DIR_SECTOR 1 /* Root directory file inode sector. */ + +/* Block device that contains the file system. */ +struct block *fs_device; + +void filesys_init (bool format); +void filesys_done (void); +bool filesys_create (const char *name, off_t initial_size); +struct file *filesys_open (const char *name); +bool filesys_remove (const char *name); + +#endif /* filesys/filesys.h */ diff --git a/pintos-progos/filesys/free-map.c b/pintos-progos/filesys/free-map.c new file mode 100644 index 0000000..29ea4df --- /dev/null +++ b/pintos-progos/filesys/free-map.c @@ -0,0 +1,85 @@ +#include "filesys/free-map.h" +#include +#include +#include "filesys/file.h" +#include "filesys/filesys.h" +#include "filesys/inode.h" + +static struct file *free_map_file; /* Free map file. */ +static struct bitmap *free_map; /* Free map, one bit per sector. */ + +/* Initializes the free map. */ +void +free_map_init (void) +{ + free_map = bitmap_create (block_size (fs_device)); + if (free_map == NULL) + PANIC ("bitmap creation failed--file system device is too large"); + bitmap_mark (free_map, FREE_MAP_SECTOR); + bitmap_mark (free_map, ROOT_DIR_SECTOR); +} + +/* Allocates CNT consecutive sectors from the free map and stores + the first into *SECTORP. + Returns true if successful, false if not enough consecutive + sectors were available or if the free_map file could not be + written. */ +bool +free_map_allocate (size_t cnt, block_sector_t *sectorp) +{ + block_sector_t sector = bitmap_scan_and_flip (free_map, 0, cnt, false); + if (sector != BITMAP_ERROR + && free_map_file != NULL + && !bitmap_write (free_map, free_map_file)) + { + bitmap_set_multiple (free_map, sector, cnt, false); + sector = BITMAP_ERROR; + } + if (sector != BITMAP_ERROR) + *sectorp = sector; + return sector != BITMAP_ERROR; +} + +/* Makes CNT sectors starting at SECTOR available for use. */ +void +free_map_release (block_sector_t sector, size_t cnt) +{ + ASSERT (bitmap_all (free_map, sector, cnt)); + bitmap_set_multiple (free_map, sector, cnt, false); + bitmap_write (free_map, free_map_file); +} + +/* Opens the free map file and reads it from disk. */ +void +free_map_open (void) +{ + free_map_file = file_open (inode_open (FREE_MAP_SECTOR)); + if (free_map_file == NULL) + PANIC ("can't open free map"); + if (!bitmap_read (free_map, free_map_file)) + PANIC ("can't read free map"); +} + +/* Writes the free map to disk and closes the free map file. */ +void +free_map_close (void) +{ + file_close (free_map_file); +} + +/* Creates a new free map file on disk and writes the free map to + it. */ +void +free_map_create (void) +{ + /* Create inode. */ + if (!inode_create (FREE_MAP_SECTOR, bitmap_file_size (free_map))) + PANIC ("free map creation failed"); + + /* Write bitmap to file. */ + free_map_file = file_open (inode_open (FREE_MAP_SECTOR)); + if (free_map_file == NULL) + PANIC ("can't open free map"); + if (!bitmap_write (free_map, free_map_file)) + PANIC ("can't write free map"); +} diff --git a/pintos-progos/filesys/free-map.h b/pintos-progos/filesys/free-map.h new file mode 100644 index 0000000..316cd1c --- /dev/null +++ b/pintos-progos/filesys/free-map.h @@ -0,0 +1,17 @@ +#ifndef FILESYS_FREE_MAP_H +#define FILESYS_FREE_MAP_H + +#include +#include +#include "devices/block.h" + +void free_map_init (void); +void free_map_read (void); +void free_map_create (void); +void free_map_open (void); +void free_map_close (void); + +bool free_map_allocate (size_t, block_sector_t *); +void free_map_release (block_sector_t, size_t); + +#endif /* filesys/free-map.h */ diff --git a/pintos-progos/filesys/fsutil.c b/pintos-progos/filesys/fsutil.c new file mode 100644 index 0000000..447f291 --- /dev/null +++ b/pintos-progos/filesys/fsutil.c @@ -0,0 +1,222 @@ +#include "filesys/fsutil.h" +#include +#include +#include +#include +#include +#include "filesys/directory.h" +#include "filesys/file.h" +#include "filesys/filesys.h" +#include "threads/malloc.h" +#include "threads/palloc.h" +#include "threads/vaddr.h" + +/* List files in the root directory. */ +void +fsutil_ls (char **argv UNUSED) +{ + struct dir *dir; + char name[NAME_MAX + 1]; + + printf ("Files in the root directory:\n"); + dir = dir_open_root (); + if (dir == NULL) + PANIC ("root dir open failed"); + while (dir_readdir (dir, name)) + printf ("%s\n", name); + printf ("End of listing.\n"); +} + +/* Prints the contents of file ARGV[1] to the system console as + hex and ASCII. */ +void +fsutil_cat (char **argv) +{ + const char *file_name = argv[1]; + + struct file *file; + char *buffer; + + printf ("Printing '%s' to the console...\n", file_name); + file = filesys_open (file_name); + if (file == NULL) + PANIC ("%s: open failed", file_name); + buffer = palloc_get_page (PAL_ASSERT); + for (;;) + { + off_t pos = file_tell (file); + off_t n = file_read (file, buffer, PGSIZE); + if (n == 0) + break; + + hex_dump (pos, buffer, n, true); + } + palloc_free_page (buffer); + file_close (file); +} + +/* Deletes file ARGV[1]. */ +void +fsutil_rm (char **argv) +{ + const char *file_name = argv[1]; + + printf ("Deleting '%s'...\n", file_name); + if (!filesys_remove (file_name)) + PANIC ("%s: delete failed\n", file_name); +} + +/* Extracts a ustar-format tar archive from the scratch block + device into the Pintos file system. */ +void +fsutil_extract (char **argv UNUSED) +{ + static block_sector_t sector = 0; + + struct block *src; + void *header, *data; + + /* Allocate buffers. */ + header = malloc (BLOCK_SECTOR_SIZE); + data = malloc (BLOCK_SECTOR_SIZE); + if (header == NULL || data == NULL) + PANIC ("couldn't allocate buffers"); + + /* Open source block device. */ + src = block_get_role (BLOCK_SCRATCH); + if (src == NULL) + PANIC ("couldn't open scratch device"); + + printf ("Extracting ustar archive from scratch device " + "into file system...\n"); + + for (;;) + { + const char *file_name; + const char *error; + enum ustar_type type; + int size; + + /* Read and parse ustar header. */ + block_read (src, sector++, header); + error = ustar_parse_header (header, &file_name, &type, &size); + if (error != NULL) + PANIC ("bad ustar header in sector %"PRDSNu" (%s)", sector - 1, error); + + if (type == USTAR_EOF) + { + /* End of archive. */ + break; + } + else if (type == USTAR_DIRECTORY) + printf ("ignoring directory %s\n", file_name); + else if (type == USTAR_REGULAR) + { + struct file *dst; + + printf ("Putting '%s' into the file system...\n", file_name); + + /* Create destination file. */ + if (!filesys_create (file_name, size)) + PANIC ("%s: create failed", file_name); + dst = filesys_open (file_name); + if (dst == NULL) + PANIC ("%s: open failed", file_name); + + /* Do copy. */ + while (size > 0) + { + int chunk_size = (size > BLOCK_SECTOR_SIZE + ? BLOCK_SECTOR_SIZE + : size); + block_read (src, sector++, data); + if (file_write (dst, data, chunk_size) != chunk_size) + PANIC ("%s: write failed with %d bytes unwritten", + file_name, size); + size -= chunk_size; + } + + /* Finish up. */ + file_close (dst); + } + } + + /* Erase the ustar header from the start of the block device, + so that the extraction operation is idempotent. We erase + two blocks because two blocks of zeros are the ustar + end-of-archive marker. */ + printf ("Erasing ustar archive...\n"); + memset (header, 0, BLOCK_SECTOR_SIZE); + block_write (src, 0, header); + block_write (src, 1, header); + + free (data); + free (header); +} + +/* Copies file FILE_NAME from the file system to the scratch + device, in ustar format. + + The first call to this function will write starting at the + beginning of the scratch device. Later calls advance across + the device. This position is independent of that used for + fsutil_extract(), so `extract' should precede all + `append's. */ +void +fsutil_append (char **argv) +{ + static block_sector_t sector = 0; + + const char *file_name = argv[1]; + void *buffer; + struct file *src; + struct block *dst; + off_t size; + + printf ("Appending '%s' to ustar archive on scratch device...\n", file_name); + + /* Allocate buffer. */ + buffer = malloc (BLOCK_SECTOR_SIZE); + if (buffer == NULL) + PANIC ("couldn't allocate buffer"); + + /* Open source file. */ + src = filesys_open (file_name); + if (src == NULL) + PANIC ("%s: open failed", file_name); + size = file_length (src); + + /* Open target block device. */ + dst = block_get_role (BLOCK_SCRATCH); + if (dst == NULL) + PANIC ("couldn't open scratch device"); + + /* Write ustar header to first sector. */ + if (!ustar_make_header (file_name, USTAR_REGULAR, size, buffer)) + PANIC ("%s: name too long for ustar format", file_name); + block_write (dst, sector++, buffer); + + /* Do copy. */ + while (size > 0) + { + int chunk_size = size > BLOCK_SECTOR_SIZE ? BLOCK_SECTOR_SIZE : size; + if (sector >= block_size (dst)) + PANIC ("%s: out of space on scratch device", file_name); + if (file_read (src, buffer, chunk_size) != chunk_size) + PANIC ("%s: read failed with %"PROTd" bytes unread", file_name, size); + memset (buffer + chunk_size, 0, BLOCK_SECTOR_SIZE - chunk_size); + block_write (dst, sector++, buffer); + size -= chunk_size; + } + + /* Write ustar end-of-archive marker, which is two consecutive + sectors full of zeros. Don't advance our position past + them, though, in case we have more files to append. */ + memset (buffer, 0, BLOCK_SECTOR_SIZE); + block_write (dst, sector, buffer); + block_write (dst, sector, buffer + 1); + + /* Finish up. */ + file_close (src); + free (buffer); +} diff --git a/pintos-progos/filesys/fsutil.h b/pintos-progos/filesys/fsutil.h new file mode 100644 index 0000000..cc73705 --- /dev/null +++ b/pintos-progos/filesys/fsutil.h @@ -0,0 +1,10 @@ +#ifndef FILESYS_FSUTIL_H +#define FILESYS_FSUTIL_H + +void fsutil_ls (char **argv); +void fsutil_cat (char **argv); +void fsutil_rm (char **argv); +void fsutil_extract (char **argv); +void fsutil_append (char **argv); + +#endif /* filesys/fsutil.h */ diff --git a/pintos-progos/filesys/inode.c b/pintos-progos/filesys/inode.c new file mode 100644 index 0000000..3463563 --- /dev/null +++ b/pintos-progos/filesys/inode.c @@ -0,0 +1,345 @@ +#include "filesys/inode.h" +#include +#include +#include +#include +#include "filesys/filesys.h" +#include "filesys/free-map.h" +#include "threads/malloc.h" + +/* Identifies an inode. */ +#define INODE_MAGIC 0x494e4f44 + +/* On-disk inode. + Must be exactly BLOCK_SECTOR_SIZE bytes long. */ +struct inode_disk + { + block_sector_t start; /* First data sector. */ + off_t length; /* File size in bytes. */ + unsigned magic; /* Magic number. */ + uint32_t unused[125]; /* Not used. */ + }; + +/* Returns the number of sectors to allocate for an inode SIZE + bytes long. */ +static inline size_t +bytes_to_sectors (off_t size) +{ + return DIV_ROUND_UP (size, BLOCK_SECTOR_SIZE); +} + +/* In-memory inode. */ +struct inode + { + struct list_elem elem; /* Element in inode list. */ + block_sector_t sector; /* Sector number of disk location. */ + int open_cnt; /* Number of openers. */ + bool removed; /* True if deleted, false otherwise. */ + int deny_write_cnt; /* 0: writes ok, >0: deny writes. */ + struct inode_disk data; /* Inode content. */ + }; + +/* Returns the block device sector that contains byte offset POS + within INODE. + Returns -1 if INODE does not contain data for a byte at offset + POS. */ +static block_sector_t +byte_to_sector (const struct inode *inode, off_t pos) +{ + ASSERT (inode != NULL); + if (pos < inode->data.length) + return inode->data.start + pos / BLOCK_SECTOR_SIZE; + else + return -1; +} + +/* List of open inodes, so that opening a single inode twice + returns the same `struct inode'. */ +static struct list open_inodes; + +/* Initializes the inode module. */ +void +inode_init (void) +{ + list_init (&open_inodes); +} + +/* Initializes an inode with LENGTH bytes of data and + writes the new inode to sector SECTOR on the file system + device. + Returns true if successful. + Returns false if memory or disk allocation fails. */ +bool +inode_create (block_sector_t sector, off_t length) +{ + struct inode_disk *disk_inode = NULL; + bool success = false; + + ASSERT (length >= 0); + + /* If this assertion fails, the inode structure is not exactly + one sector in size, and you should fix that. */ + ASSERT (sizeof *disk_inode == BLOCK_SECTOR_SIZE); + + disk_inode = calloc (1, sizeof *disk_inode); + if (disk_inode != NULL) + { + size_t sectors = bytes_to_sectors (length); + disk_inode->length = length; + disk_inode->magic = INODE_MAGIC; + if (free_map_allocate (sectors, &disk_inode->start)) + { + block_write (fs_device, sector, disk_inode); + if (sectors > 0) + { + static char zeros[BLOCK_SECTOR_SIZE]; + size_t i; + + for (i = 0; i < sectors; i++) + block_write (fs_device, disk_inode->start + i, zeros); + } + success = true; + } + free (disk_inode); + } + return success; +} + +/* Reads an inode from SECTOR + and returns a `struct inode' that contains it. + Returns a null pointer if memory allocation fails. */ +struct inode * +inode_open (block_sector_t sector) +{ + struct list_elem *e; + struct inode *inode; + + /* Check whether this inode is already open. */ + for (e = list_begin (&open_inodes); e != list_end (&open_inodes); + e = list_next (e)) + { + inode = list_entry (e, struct inode, elem); + if (inode->sector == sector) + { + inode_reopen (inode); + return inode; + } + } + + /* Allocate memory. */ + inode = malloc (sizeof *inode); + if (inode == NULL) + return NULL; + + /* Initialize. */ + list_push_front (&open_inodes, &inode->elem); + inode->sector = sector; + inode->open_cnt = 1; + inode->deny_write_cnt = 0; + inode->removed = false; + block_read (fs_device, inode->sector, &inode->data); + return inode; +} + +/* Reopens and returns INODE. */ +struct inode * +inode_reopen (struct inode *inode) +{ + if (inode != NULL) + inode->open_cnt++; + return inode; +} + +/* Returns INODE's inode number. */ +block_sector_t +inode_get_inumber (const struct inode *inode) +{ + return inode->sector; +} + +/* Closes INODE and writes it to disk. + If this was the last reference to INODE, frees its memory. + If INODE was also a removed inode, frees its blocks. */ +void +inode_close (struct inode *inode) +{ + /* Ignore null pointer. */ + if (inode == NULL) + return; + + /* Release resources if this was the last opener. */ + if (--inode->open_cnt == 0) + { + /* Remove from inode list and release lock. */ + list_remove (&inode->elem); + + /* Deallocate blocks if removed. */ + if (inode->removed) + { + free_map_release (inode->sector, 1); + free_map_release (inode->data.start, + bytes_to_sectors (inode->data.length)); + } + + free (inode); + } +} + +/* Marks INODE to be deleted when it is closed by the last caller who + has it open. */ +void +inode_remove (struct inode *inode) +{ + ASSERT (inode != NULL); + inode->removed = true; +} + +/* Reads SIZE bytes from INODE into BUFFER, starting at position OFFSET. + Returns the number of bytes actually read, which may be less + than SIZE if an error occurs or end of file is reached. */ +off_t +inode_read_at (struct inode *inode, void *buffer_, off_t size, off_t offset) +{ + uint8_t *buffer = buffer_; + off_t bytes_read = 0; + uint8_t *bounce = NULL; + + while (size > 0) + { + /* Disk sector to read, starting byte offset within sector. */ + block_sector_t sector_idx = byte_to_sector (inode, offset); + int sector_ofs = offset % BLOCK_SECTOR_SIZE; + + /* Bytes left in inode, bytes left in sector, lesser of the two. */ + off_t inode_left = inode_length (inode) - offset; + int sector_left = BLOCK_SECTOR_SIZE - sector_ofs; + int min_left = inode_left < sector_left ? inode_left : sector_left; + + /* Number of bytes to actually copy out of this sector. */ + int chunk_size = size < min_left ? size : min_left; + if (chunk_size <= 0) + break; + + if (sector_ofs == 0 && chunk_size == BLOCK_SECTOR_SIZE) + { + /* Read full sector directly into caller's buffer. */ + block_read (fs_device, sector_idx, buffer + bytes_read); + } + else + { + /* Read sector into bounce buffer, then partially copy + into caller's buffer. */ + if (bounce == NULL) + { + bounce = malloc (BLOCK_SECTOR_SIZE); + if (bounce == NULL) + break; + } + block_read (fs_device, sector_idx, bounce); + memcpy (buffer + bytes_read, bounce + sector_ofs, chunk_size); + } + + /* Advance. */ + size -= chunk_size; + offset += chunk_size; + bytes_read += chunk_size; + } + free (bounce); + + return bytes_read; +} + +/* Writes SIZE bytes from BUFFER into INODE, starting at OFFSET. + Returns the number of bytes actually written, which may be + less than SIZE if end of file is reached or an error occurs. + (Normally a write at end of file would extend the inode, but + growth is not yet implemented.) */ +off_t +inode_write_at (struct inode *inode, const void *buffer_, off_t size, + off_t offset) +{ + const uint8_t *buffer = buffer_; + off_t bytes_written = 0; + uint8_t *bounce = NULL; + + if (inode->deny_write_cnt) + return 0; + + while (size > 0) + { + /* Sector to write, starting byte offset within sector. */ + block_sector_t sector_idx = byte_to_sector (inode, offset); + int sector_ofs = offset % BLOCK_SECTOR_SIZE; + + /* Bytes left in inode, bytes left in sector, lesser of the two. */ + off_t inode_left = inode_length (inode) - offset; + int sector_left = BLOCK_SECTOR_SIZE - sector_ofs; + int min_left = inode_left < sector_left ? inode_left : sector_left; + + /* Number of bytes to actually write into this sector. */ + int chunk_size = size < min_left ? size : min_left; + if (chunk_size <= 0) + break; + + if (sector_ofs == 0 && chunk_size == BLOCK_SECTOR_SIZE) + { + /* Write full sector directly to disk. */ + block_write (fs_device, sector_idx, buffer + bytes_written); + } + else + { + /* We need a bounce buffer. */ + if (bounce == NULL) + { + bounce = malloc (BLOCK_SECTOR_SIZE); + if (bounce == NULL) + break; + } + + /* If the sector contains data before or after the chunk + we're writing, then we need to read in the sector + first. Otherwise we start with a sector of all zeros. */ + if (sector_ofs > 0 || chunk_size < sector_left) + block_read (fs_device, sector_idx, bounce); + else + memset (bounce, 0, BLOCK_SECTOR_SIZE); + memcpy (bounce + sector_ofs, buffer + bytes_written, chunk_size); + block_write (fs_device, sector_idx, bounce); + } + + /* Advance. */ + size -= chunk_size; + offset += chunk_size; + bytes_written += chunk_size; + } + free (bounce); + + return bytes_written; +} + +/* Disables writes to INODE. + May be called at most once per inode opener. */ +void +inode_deny_write (struct inode *inode) +{ + inode->deny_write_cnt++; + ASSERT (inode->deny_write_cnt <= inode->open_cnt); +} + +/* Re-enables writes to INODE. + Must be called once by each inode opener who has called + inode_deny_write() on the inode, before closing the inode. */ +void +inode_allow_write (struct inode *inode) +{ + ASSERT (inode->deny_write_cnt > 0); + ASSERT (inode->deny_write_cnt <= inode->open_cnt); + inode->deny_write_cnt--; +} + +/* Returns the length, in bytes, of INODE's data. */ +off_t +inode_length (const struct inode *inode) +{ + return inode->data.length; +} diff --git a/pintos-progos/filesys/inode.h b/pintos-progos/filesys/inode.h new file mode 100644 index 0000000..cb42310 --- /dev/null +++ b/pintos-progos/filesys/inode.h @@ -0,0 +1,23 @@ +#ifndef FILESYS_INODE_H +#define FILESYS_INODE_H + +#include +#include "filesys/off_t.h" +#include "devices/block.h" + +struct bitmap; + +void inode_init (void); +bool inode_create (block_sector_t, off_t); +struct inode *inode_open (block_sector_t); +struct inode *inode_reopen (struct inode *); +block_sector_t inode_get_inumber (const struct inode *); +void inode_close (struct inode *); +void inode_remove (struct inode *); +off_t inode_read_at (struct inode *, void *, off_t size, off_t offset); +off_t inode_write_at (struct inode *, const void *, off_t size, off_t offset); +void inode_deny_write (struct inode *); +void inode_allow_write (struct inode *); +off_t inode_length (const struct inode *); + +#endif /* filesys/inode.h */ diff --git a/pintos-progos/filesys/off_t.h b/pintos-progos/filesys/off_t.h new file mode 100644 index 0000000..9caff4d --- /dev/null +++ b/pintos-progos/filesys/off_t.h @@ -0,0 +1,15 @@ +#ifndef FILESYS_OFF_T_H +#define FILESYS_OFF_T_H + +#include + +/* An offset within a file. + This is a separate header because multiple headers want this + definition but not any others. */ +typedef int32_t off_t; + +/* Format specifier for printf(), e.g.: + printf ("offset=%"PROTd"\n", offset); */ +#define PROTd PRId32 + +#endif /* filesys/off_t.h */ diff --git a/pintos-progos/intro/Make.vars b/pintos-progos/intro/Make.vars new file mode 100644 index 0000000..c612275 --- /dev/null +++ b/pintos-progos/intro/Make.vars @@ -0,0 +1,7 @@ +# -*- makefile -*- + +kernel.bin: DEFINES = -DUSERPROG -DFILESYS +KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys $(KERNEL_TESTS) +KERNEL_TESTS = tests/intro/alarm-clock +TEST_SUBDIRS = tests/intro/alarm-clock tests/intro/userprog-args +GRADING_FILE = $(SRCDIR)/tests/intro/Grading diff --git a/pintos-progos/intro/Makefile b/pintos-progos/intro/Makefile new file mode 100644 index 0000000..34c10aa --- /dev/null +++ b/pintos-progos/intro/Makefile @@ -0,0 +1 @@ +include ../Makefile.kernel diff --git a/pintos-progos/lib/arithmetic.c b/pintos-progos/lib/arithmetic.c new file mode 100644 index 0000000..bfc9b5a --- /dev/null +++ b/pintos-progos/lib/arithmetic.c @@ -0,0 +1,189 @@ +#include + +/* On x86, division of one 64-bit integer by another cannot be + done with a single instruction or a short sequence. Thus, GCC + implements 64-bit division and remainder operations through + function calls. These functions are normally obtained from + libgcc, which is automatically included by GCC in any link + that it does. + + Some x86-64 machines, however, have a compiler and utilities + that can generate 32-bit x86 code without having any of the + necessary libraries, including libgcc. Thus, we can make + Pintos work on these machines by simply implementing our own + 64-bit division routines, which are the only routines from + libgcc that Pintos requires. + + Completeness is another reason to include these routines. If + Pintos is completely self-contained, then that makes it that + much less mysterious. */ + +/* Uses x86 DIVL instruction to divide 64-bit N by 32-bit D to + yield a 32-bit quotient. Returns the quotient. + Traps with a divide error (#DE) if the quotient does not fit + in 32 bits. */ +static inline uint32_t +divl (uint64_t n, uint32_t d) +{ + uint32_t n1 = n >> 32; + uint32_t n0 = n; + uint32_t q, r; + + asm ("divl %4" + : "=d" (r), "=a" (q) + : "0" (n1), "1" (n0), "rm" (d)); + + return q; +} + +/* Returns the number of leading zero bits in X, + which must be nonzero. */ +static int +nlz (uint32_t x) +{ + /* This technique is portable, but there are better ways to do + it on particular systems. With sufficiently new enough GCC, + you can use __builtin_clz() to take advantage of GCC's + knowledge of how to do it. Or you can use the x86 BSR + instruction directly. */ + int n = 0; + if (x <= 0x0000FFFF) + { + n += 16; + x <<= 16; + } + if (x <= 0x00FFFFFF) + { + n += 8; + x <<= 8; + } + if (x <= 0x0FFFFFFF) + { + n += 4; + x <<= 4; + } + if (x <= 0x3FFFFFFF) + { + n += 2; + x <<= 2; + } + if (x <= 0x7FFFFFFF) + n++; + return n; +} + +/* Divides unsigned 64-bit N by unsigned 64-bit D and returns the + quotient. */ +static uint64_t +udiv64 (uint64_t n, uint64_t d) +{ + if ((d >> 32) == 0) + { + /* Proof of correctness: + + Let n, d, b, n1, and n0 be defined as in this function. + Let [x] be the "floor" of x. Let T = b[n1/d]. Assume d + nonzero. Then: + [n/d] = [n/d] - T + T + = [n/d - T] + T by (1) below + = [(b*n1 + n0)/d - T] + T by definition of n + = [(b*n1 + n0)/d - dT/d] + T + = [(b(n1 - d[n1/d]) + n0)/d] + T + = [(b[n1 % d] + n0)/d] + T, by definition of % + which is the expression calculated below. + + (1) Note that for any real x, integer i: [x] + i = [x + i]. + + To prevent divl() from trapping, [(b[n1 % d] + n0)/d] must + be less than b. Assume that [n1 % d] and n0 take their + respective maximum values of d - 1 and b - 1: + [(b(d - 1) + (b - 1))/d] < b + <=> [(bd - 1)/d] < b + <=> [b - 1/d] < b + which is a tautology. + + Therefore, this code is correct and will not trap. */ + uint64_t b = 1ULL << 32; + uint32_t n1 = n >> 32; + uint32_t n0 = n; + uint32_t d0 = d; + + return divl (b * (n1 % d0) + n0, d0) + b * (n1 / d0); + } + else + { + /* Based on the algorithm and proof available from + http://www.hackersdelight.org/revisions.pdf. */ + if (n < d) + return 0; + else + { + uint32_t d1 = d >> 32; + int s = nlz (d1); + uint64_t q = divl (n >> 1, (d << s) >> 32) >> (31 - s); + return n - (q - 1) * d < d ? q - 1 : q; + } + } +} + +/* Divides unsigned 64-bit N by unsigned 64-bit D and returns the + remainder. */ +static uint32_t +umod64 (uint64_t n, uint64_t d) +{ + return n - d * udiv64 (n, d); +} + +/* Divides signed 64-bit N by signed 64-bit D and returns the + quotient. */ +static int64_t +sdiv64 (int64_t n, int64_t d) +{ + uint64_t n_abs = n >= 0 ? (uint64_t) n : -(uint64_t) n; + uint64_t d_abs = d >= 0 ? (uint64_t) d : -(uint64_t) d; + uint64_t q_abs = udiv64 (n_abs, d_abs); + return (n < 0) == (d < 0) ? (int64_t) q_abs : -(int64_t) q_abs; +} + +/* Divides signed 64-bit N by signed 64-bit D and returns the + remainder. */ +static int32_t +smod64 (int64_t n, int64_t d) +{ + return n - d * sdiv64 (n, d); +} + +/* These are the routines that GCC calls. */ + +long long __divdi3 (long long n, long long d); +long long __moddi3 (long long n, long long d); +unsigned long long __udivdi3 (unsigned long long n, unsigned long long d); +unsigned long long __umoddi3 (unsigned long long n, unsigned long long d); + +/* Signed 64-bit division. */ +long long +__divdi3 (long long n, long long d) +{ + return sdiv64 (n, d); +} + +/* Signed 64-bit remainder. */ +long long +__moddi3 (long long n, long long d) +{ + return smod64 (n, d); +} + +/* Unsigned 64-bit division. */ +unsigned long long +__udivdi3 (unsigned long long n, unsigned long long d) +{ + return udiv64 (n, d); +} + +/* Unsigned 64-bit remainder. */ +unsigned long long +__umoddi3 (unsigned long long n, unsigned long long d) +{ + return umod64 (n, d); +} diff --git a/pintos-progos/lib/ctype.h b/pintos-progos/lib/ctype.h new file mode 100644 index 0000000..9096aca --- /dev/null +++ b/pintos-progos/lib/ctype.h @@ -0,0 +1,28 @@ +#ifndef __LIB_CTYPE_H +#define __LIB_CTYPE_H + +static inline int islower (int c) { return c >= 'a' && c <= 'z'; } +static inline int isupper (int c) { return c >= 'A' && c <= 'Z'; } +static inline int isalpha (int c) { return islower (c) || isupper (c); } +static inline int isdigit (int c) { return c >= '0' && c <= '9'; } +static inline int isalnum (int c) { return isalpha (c) || isdigit (c); } +static inline int isxdigit (int c) { + return isdigit (c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} +static inline int isspace (int c) { + return (c == ' ' || c == '\f' || c == '\n' + || c == '\r' || c == '\t' || c == '\v'); +} +static inline int isblank (int c) { return c == ' ' || c == '\t'; } +static inline int isgraph (int c) { return c > 32 && c < 127; } +static inline int isprint (int c) { return c >= 32 && c < 127; } +static inline int iscntrl (int c) { return (c >= 0 && c < 32) || c == 127; } +static inline int isascii (int c) { return c >= 0 && c < 128; } +static inline int ispunct (int c) { + return isprint (c) && !isalnum (c) && !isspace (c); +} + +static inline int tolower (int c) { return isupper (c) ? c - 'A' + 'a' : c; } +static inline int toupper (int c) { return islower (c) ? c - 'a' + 'A' : c; } + +#endif /* lib/ctype.h */ diff --git a/pintos-progos/lib/debug.c b/pintos-progos/lib/debug.c new file mode 100644 index 0000000..b4f8c2d --- /dev/null +++ b/pintos-progos/lib/debug.c @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include +#include + +/* Prints the call stack, that is, a list of addresses, one in + each of the functions we are nested within. gdb or addr2line + may be applied to kernel.o to translate these into file names, + line numbers, and function names. */ +void +debug_backtrace (void) +{ + static bool explained; + void **frame; + + printf ("Call stack: %p", __builtin_return_address (0)); + for (frame = __builtin_frame_address (1); + (uintptr_t) frame >= 0x1000 && frame[0] != NULL; + frame = frame[0]) + printf (" %p", frame[1]); + printf (".\n"); + + if (!explained) + { + explained = true; + printf ("The `backtrace' program can make call stacks useful.\n" + "Read \"Backtraces\" in the \"Debugging Tools\" chapter\n" + "of the Pintos documentation for more information.\n"); + } +} diff --git a/pintos-progos/lib/debug.h b/pintos-progos/lib/debug.h new file mode 100644 index 0000000..888ab7b --- /dev/null +++ b/pintos-progos/lib/debug.h @@ -0,0 +1,39 @@ +#ifndef __LIB_DEBUG_H +#define __LIB_DEBUG_H + +/* GCC lets us add "attributes" to functions, function + parameters, etc. to indicate their properties. + See the GCC manual for details. */ +#define UNUSED __attribute__ ((unused)) +#define NO_RETURN __attribute__ ((noreturn)) +#define NO_INLINE __attribute__ ((noinline)) +#define PRINTF_FORMAT(FMT, FIRST) __attribute__ ((format (printf, FMT, FIRST))) + +/* Halts the OS, printing the source file name, line number, and + function name, plus a user-specific message. */ +#define PANIC(...) debug_panic (__FILE__, __LINE__, __func__, __VA_ARGS__) + +void debug_panic (const char *file, int line, const char *function, + const char *message, ...) PRINTF_FORMAT (4, 5) NO_RETURN; +void debug_backtrace (void); +void debug_backtrace_all (void); + +#endif + + + +/* This is outside the header guard so that debug.h may be + included multiple times with different settings of NDEBUG. */ +#undef ASSERT +#undef NOT_REACHED + +#ifndef NDEBUG +#define ASSERT(CONDITION) \ + if (CONDITION) { } else { \ + PANIC ("assertion `%s' failed.", #CONDITION); \ + } +#define NOT_REACHED() PANIC ("executed an unreachable statement"); +#else +#define ASSERT(CONDITION) ((void) 0) +#define NOT_REACHED() for (;;) +#endif /* lib/debug.h */ diff --git a/pintos-progos/lib/inttypes.h b/pintos-progos/lib/inttypes.h new file mode 100644 index 0000000..f703725 --- /dev/null +++ b/pintos-progos/lib/inttypes.h @@ -0,0 +1,48 @@ +#ifndef __LIB_INTTYPES_H +#define __LIB_INTTYPES_H + +#include + +#define PRId8 "hhd" +#define PRIi8 "hhi" +#define PRIo8 "hho" +#define PRIu8 "hhu" +#define PRIx8 "hhx" +#define PRIX8 "hhX" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" + +#define PRId32 "d" +#define PRIi32 "i" +#define PRIo32 "o" +#define PRIu32 "u" +#define PRIx32 "x" +#define PRIX32 "X" + +#define PRId64 "lld" +#define PRIi64 "lli" +#define PRIo64 "llo" +#define PRIu64 "llu" +#define PRIx64 "llx" +#define PRIX64 "llX" + +#define PRIdMAX "jd" +#define PRIiMAX "ji" +#define PRIoMAX "jo" +#define PRIuMAX "ju" +#define PRIxMAX "jx" +#define PRIXMAX "jX" + +#define PRIdPTR "td" +#define PRIiPTR "ti" +#define PRIoPTR "to" +#define PRIuPTR "tu" +#define PRIxPTR "tx" +#define PRIXPTR "tX" + +#endif /* lib/inttypes.h */ diff --git a/pintos-progos/lib/kernel/bitmap.c b/pintos-progos/lib/kernel/bitmap.c new file mode 100644 index 0000000..d14a98c --- /dev/null +++ b/pintos-progos/lib/kernel/bitmap.c @@ -0,0 +1,371 @@ +#include "bitmap.h" +#include +#include +#include +#include +#include "threads/malloc.h" +#ifdef FILESYS +#include "filesys/file.h" +#endif + +/* Element type. + + This must be an unsigned integer type at least as wide as int. + + Each bit represents one bit in the bitmap. + If bit 0 in an element represents bit K in the bitmap, + then bit 1 in the element represents bit K+1 in the bitmap, + and so on. */ +typedef unsigned long elem_type; + +/* Number of bits in an element. */ +#define ELEM_BITS (sizeof (elem_type) * CHAR_BIT) + +/* From the outside, a bitmap is an array of bits. From the + inside, it's an array of elem_type (defined above) that + simulates an array of bits. */ +struct bitmap + { + size_t bit_cnt; /* Number of bits. */ + elem_type *bits; /* Elements that represent bits. */ + }; + +/* Returns the index of the element that contains the bit + numbered BIT_IDX. */ +static inline size_t +elem_idx (size_t bit_idx) +{ + return bit_idx / ELEM_BITS; +} + +/* Returns an elem_type where only the bit corresponding to + BIT_IDX is turned on. */ +static inline elem_type +bit_mask (size_t bit_idx) +{ + return (elem_type) 1 << (bit_idx % ELEM_BITS); +} + +/* Returns the number of elements required for BIT_CNT bits. */ +static inline size_t +elem_cnt (size_t bit_cnt) +{ + return DIV_ROUND_UP (bit_cnt, ELEM_BITS); +} + +/* Returns the number of bytes required for BIT_CNT bits. */ +static inline size_t +byte_cnt (size_t bit_cnt) +{ + return sizeof (elem_type) * elem_cnt (bit_cnt); +} + +/* Returns a bit mask in which the bits actually used in the last + element of B's bits are set to 1 and the rest are set to 0. */ +static inline elem_type +last_mask (const struct bitmap *b) +{ + int last_bits = b->bit_cnt % ELEM_BITS; + return last_bits ? ((elem_type) 1 << last_bits) - 1 : (elem_type) -1; +} + +/* Creation and destruction. */ + +/* Creates and returns a pointer to a newly allocated bitmap with room for + BIT_CNT (or more) bits. Returns a null pointer if memory allocation fails. + The caller is responsible for freeing the bitmap, with bitmap_destroy(), + when it is no longer needed. */ +struct bitmap * +bitmap_create (size_t bit_cnt) +{ + struct bitmap *b = malloc (sizeof *b); + if (b != NULL) + { + b->bit_cnt = bit_cnt; + b->bits = malloc (byte_cnt (bit_cnt)); + if (b->bits != NULL || bit_cnt == 0) + { + bitmap_set_all (b, false); + return b; + } + free (b); + } + return NULL; +} + +/* Creates and returns a bitmap with BIT_CNT bits in the + BLOCK_SIZE bytes of storage preallocated at BLOCK. + BLOCK_SIZE must be at least bitmap_needed_bytes(BIT_CNT). */ +struct bitmap * +bitmap_create_in_buf (size_t bit_cnt, void *block, size_t block_size UNUSED) +{ + struct bitmap *b = block; + + ASSERT (block_size >= bitmap_buf_size (bit_cnt)); + + b->bit_cnt = bit_cnt; + b->bits = (elem_type *) (b + 1); + bitmap_set_all (b, false); + return b; +} + +/* Returns the number of bytes required to accomodate a bitmap + with BIT_CNT bits (for use with bitmap_create_in_buf()). */ +size_t +bitmap_buf_size (size_t bit_cnt) +{ + return sizeof (struct bitmap) + byte_cnt (bit_cnt); +} + +/* Destroys bitmap B, freeing its storage. + Not for use on bitmaps created by bitmap_create_in_buf(). */ +void +bitmap_destroy (struct bitmap *b) +{ + if (b != NULL) + { + free (b->bits); + free (b); + } +} + +/* Bitmap size. */ + +/* Returns the number of bits in B. */ +size_t +bitmap_size (const struct bitmap *b) +{ + return b->bit_cnt; +} + +/* Setting and testing single bits. */ + +/* Atomically sets the bit numbered IDX in B to VALUE. */ +void +bitmap_set (struct bitmap *b, size_t idx, bool value) +{ + ASSERT (b != NULL); + ASSERT (idx < b->bit_cnt); + if (value) + bitmap_mark (b, idx); + else + bitmap_reset (b, idx); +} + +/* Atomically sets the bit numbered BIT_IDX in B to true. */ +void +bitmap_mark (struct bitmap *b, size_t bit_idx) +{ + size_t idx = elem_idx (bit_idx); + elem_type mask = bit_mask (bit_idx); + + /* This is equivalent to `b->bits[idx] |= mask' except that it + is guaranteed to be atomic on a uniprocessor machine. See + the description of the OR instruction in [IA32-v2b]. */ + asm ("orl %1, %0" : "=m" (b->bits[idx]) : "r" (mask) : "cc"); +} + +/* Atomically sets the bit numbered BIT_IDX in B to false. */ +void +bitmap_reset (struct bitmap *b, size_t bit_idx) +{ + size_t idx = elem_idx (bit_idx); + elem_type mask = bit_mask (bit_idx); + + /* This is equivalent to `b->bits[idx] &= ~mask' except that it + is guaranteed to be atomic on a uniprocessor machine. See + the description of the AND instruction in [IA32-v2a]. */ + asm ("andl %1, %0" : "=m" (b->bits[idx]) : "r" (~mask) : "cc"); +} + +/* Atomically toggles the bit numbered IDX in B; + that is, if it is true, makes it false, + and if it is false, makes it true. */ +void +bitmap_flip (struct bitmap *b, size_t bit_idx) +{ + size_t idx = elem_idx (bit_idx); + elem_type mask = bit_mask (bit_idx); + + /* This is equivalent to `b->bits[idx] ^= mask' except that it + is guaranteed to be atomic on a uniprocessor machine. See + the description of the XOR instruction in [IA32-v2b]. */ + asm ("xorl %1, %0" : "=m" (b->bits[idx]) : "r" (mask) : "cc"); +} + +/* Returns the value of the bit numbered IDX in B. */ +bool +bitmap_test (const struct bitmap *b, size_t idx) +{ + ASSERT (b != NULL); + ASSERT (idx < b->bit_cnt); + return (b->bits[elem_idx (idx)] & bit_mask (idx)) != 0; +} + +/* Setting and testing multiple bits. */ + +/* Sets all bits in B to VALUE. */ +void +bitmap_set_all (struct bitmap *b, bool value) +{ + ASSERT (b != NULL); + + bitmap_set_multiple (b, 0, bitmap_size (b), value); +} + +/* Sets the CNT bits starting at START in B to VALUE. */ +void +bitmap_set_multiple (struct bitmap *b, size_t start, size_t cnt, bool value) +{ + size_t i; + + ASSERT (b != NULL); + ASSERT (start <= b->bit_cnt); + ASSERT (start + cnt <= b->bit_cnt); + + for (i = 0; i < cnt; i++) + bitmap_set (b, start + i, value); +} + +/* Returns the number of bits in B between START and START + CNT, + exclusive, that are set to VALUE. */ +size_t +bitmap_count (const struct bitmap *b, size_t start, size_t cnt, bool value) +{ + size_t i, value_cnt; + + ASSERT (b != NULL); + ASSERT (start <= b->bit_cnt); + ASSERT (start + cnt <= b->bit_cnt); + + value_cnt = 0; + for (i = 0; i < cnt; i++) + if (bitmap_test (b, start + i) == value) + value_cnt++; + return value_cnt; +} + +/* Returns true if any bits in B between START and START + CNT, + exclusive, are set to VALUE, and false otherwise. */ +bool +bitmap_contains (const struct bitmap *b, size_t start, size_t cnt, bool value) +{ + size_t i; + + ASSERT (b != NULL); + ASSERT (start <= b->bit_cnt); + ASSERT (start + cnt <= b->bit_cnt); + + for (i = 0; i < cnt; i++) + if (bitmap_test (b, start + i) == value) + return true; + return false; +} + +/* Returns true if any bits in B between START and START + CNT, + exclusive, are set to true, and false otherwise.*/ +bool +bitmap_any (const struct bitmap *b, size_t start, size_t cnt) +{ + return bitmap_contains (b, start, cnt, true); +} + +/* Returns true if no bits in B between START and START + CNT, + exclusive, are set to true, and false otherwise.*/ +bool +bitmap_none (const struct bitmap *b, size_t start, size_t cnt) +{ + return !bitmap_contains (b, start, cnt, true); +} + +/* Returns true if every bit in B between START and START + CNT, + exclusive, is set to true, and false otherwise. */ +bool +bitmap_all (const struct bitmap *b, size_t start, size_t cnt) +{ + return !bitmap_contains (b, start, cnt, false); +} + +/* Finding set or unset bits. */ + +/* Finds and returns the starting index of the first group of CNT + consecutive bits in B at or after START that are all set to + VALUE. + If there is no such group, returns BITMAP_ERROR. */ +size_t +bitmap_scan (const struct bitmap *b, size_t start, size_t cnt, bool value) +{ + ASSERT (b != NULL); + ASSERT (start <= b->bit_cnt); + + if (cnt <= b->bit_cnt) + { + size_t last = b->bit_cnt - cnt; + size_t i; + for (i = start; i <= last; i++) + if (!bitmap_contains (b, i, cnt, !value)) + return i; + } + return BITMAP_ERROR; +} + +/* Finds the first group of CNT consecutive bits in B at or after + START that are all set to VALUE, flips them all to !VALUE, + and returns the index of the first bit in the group. + If there is no such group, returns BITMAP_ERROR. + If CNT is zero, returns 0. + Bits are set atomically, but testing bits is not atomic with + setting them. */ +size_t +bitmap_scan_and_flip (struct bitmap *b, size_t start, size_t cnt, bool value) +{ + size_t idx = bitmap_scan (b, start, cnt, value); + if (idx != BITMAP_ERROR) + bitmap_set_multiple (b, idx, cnt, !value); + return idx; +} + +/* File input and output. */ + +#ifdef FILESYS +/* Returns the number of bytes needed to store B in a file. */ +size_t +bitmap_file_size (const struct bitmap *b) +{ + return byte_cnt (b->bit_cnt); +} + +/* Reads B from FILE. Returns true if successful, false + otherwise. */ +bool +bitmap_read (struct bitmap *b, struct file *file) +{ + bool success = true; + if (b->bit_cnt > 0) + { + off_t size = byte_cnt (b->bit_cnt); + success = file_read_at (file, b->bits, size, 0) == size; + b->bits[elem_cnt (b->bit_cnt) - 1] &= last_mask (b); + } + return success; +} + +/* Writes B to FILE. Return true if successful, false + otherwise. */ +bool +bitmap_write (const struct bitmap *b, struct file *file) +{ + off_t size = byte_cnt (b->bit_cnt); + return file_write_at (file, b->bits, size, 0) == size; +} +#endif /* FILESYS */ + +/* Debugging. */ + +/* Dumps the contents of B to the console as hexadecimal. */ +void +bitmap_dump (const struct bitmap *b) +{ + hex_dump (0, b->bits, byte_cnt (b->bit_cnt), false); +} + diff --git a/pintos-progos/lib/kernel/bitmap.h b/pintos-progos/lib/kernel/bitmap.h new file mode 100644 index 0000000..a50593c --- /dev/null +++ b/pintos-progos/lib/kernel/bitmap.h @@ -0,0 +1,51 @@ +#ifndef __LIB_KERNEL_BITMAP_H +#define __LIB_KERNEL_BITMAP_H + +#include +#include +#include + +/* Bitmap abstract data type. */ + +/* Creation and destruction. */ +struct bitmap *bitmap_create (size_t bit_cnt); +struct bitmap *bitmap_create_in_buf (size_t bit_cnt, void *, size_t byte_cnt); +size_t bitmap_buf_size (size_t bit_cnt); +void bitmap_destroy (struct bitmap *); + +/* Bitmap size. */ +size_t bitmap_size (const struct bitmap *); + +/* Setting and testing single bits. */ +void bitmap_set (struct bitmap *, size_t idx, bool); +void bitmap_mark (struct bitmap *, size_t idx); +void bitmap_reset (struct bitmap *, size_t idx); +void bitmap_flip (struct bitmap *, size_t idx); +bool bitmap_test (const struct bitmap *, size_t idx); + +/* Setting and testing multiple bits. */ +void bitmap_set_all (struct bitmap *, bool); +void bitmap_set_multiple (struct bitmap *, size_t start, size_t cnt, bool); +size_t bitmap_count (const struct bitmap *, size_t start, size_t cnt, bool); +bool bitmap_contains (const struct bitmap *, size_t start, size_t cnt, bool); +bool bitmap_any (const struct bitmap *, size_t start, size_t cnt); +bool bitmap_none (const struct bitmap *, size_t start, size_t cnt); +bool bitmap_all (const struct bitmap *, size_t start, size_t cnt); + +/* Finding set or unset bits. */ +#define BITMAP_ERROR SIZE_MAX +size_t bitmap_scan (const struct bitmap *, size_t start, size_t cnt, bool); +size_t bitmap_scan_and_flip (struct bitmap *, size_t start, size_t cnt, bool); + +/* File input and output. */ +#ifdef FILESYS +struct file; +size_t bitmap_file_size (const struct bitmap *); +bool bitmap_read (struct bitmap *, struct file *); +bool bitmap_write (const struct bitmap *, struct file *); +#endif + +/* Debugging. */ +void bitmap_dump (const struct bitmap *); + +#endif /* lib/kernel/bitmap.h */ diff --git a/pintos-progos/lib/kernel/console.c b/pintos-progos/lib/kernel/console.c new file mode 100644 index 0000000..844b184 --- /dev/null +++ b/pintos-progos/lib/kernel/console.c @@ -0,0 +1,191 @@ +#include +#include +#include +#include "devices/serial.h" +#include "devices/vga.h" +#include "threads/init.h" +#include "threads/interrupt.h" +#include "threads/synch.h" + +static void vprintf_helper (char, void *); +static void putchar_have_lock (uint8_t c); + +/* The console lock. + Both the vga and serial layers do their own locking, so it's + safe to call them at any time. + But this lock is useful to prevent simultaneous printf() calls + from mixing their output, which looks confusing. */ +static struct lock console_lock; + +/* True in ordinary circumstances: we want to use the console + lock to avoid mixing output between threads, as explained + above. + + False in early boot before the point that locks are functional + or the console lock has been initialized, or after a kernel + panics. In the former case, taking the lock would cause an + assertion failure, which in turn would cause a panic, turning + it into the latter case. In the latter case, if it is a buggy + lock_acquire() implementation that caused the panic, we'll + likely just recurse. */ +static bool use_console_lock; + +/* It's possible, if you add enough debug output to Pintos, to + try to recursively grab console_lock from a single thread. As + a real example, I added a printf() call to palloc_free(). + Here's a real backtrace that resulted: + + lock_console() + vprintf() + printf() - palloc() tries to grab the lock again + palloc_free() + thread_schedule_tail() - another thread dying as we switch threads + schedule() + thread_yield() + intr_handler() - timer interrupt + intr_set_level() + serial_putc() + putchar_have_lock() + putbuf() + sys_write() - one process writing to the console + syscall_handler() + intr_handler() + + This kind of thing is very difficult to debug, so we avoid the + problem by simulating a recursive lock with a depth + counter. */ +static int console_lock_depth; + +/* Number of characters written to console. */ +static int64_t write_cnt; + +/* Enable console locking. */ +void +console_init (void) +{ + lock_init (&console_lock); + use_console_lock = true; +} + +/* Notifies the console that a kernel panic is underway, + which warns it to avoid trying to take the console lock from + now on. */ +void +console_panic (void) +{ + use_console_lock = false; +} + +/* Prints console statistics. */ +void +console_print_stats (void) +{ + printf ("Console: %lld characters output\n", write_cnt); +} + +/* Acquires the console lock. */ +static void +acquire_console (void) +{ + if (!intr_context () && use_console_lock) + { + if (lock_held_by_current_thread (&console_lock)) + console_lock_depth++; + else + lock_acquire (&console_lock); + } +} + +/* Releases the console lock. */ +static void +release_console (void) +{ + if (!intr_context () && use_console_lock) + { + if (console_lock_depth > 0) + console_lock_depth--; + else + lock_release (&console_lock); + } +} + +/* Returns true if the current thread has the console lock, + false otherwise. */ +static bool +console_locked_by_current_thread (void) +{ + return (intr_context () + || !use_console_lock + || lock_held_by_current_thread (&console_lock)); +} + +/* The standard vprintf() function, + which is like printf() but uses a va_list. + Writes its output to both vga display and serial port. */ +int +vprintf (const char *format, va_list args) +{ + int char_cnt = 0; + + acquire_console (); + __vprintf (format, args, vprintf_helper, &char_cnt); + release_console (); + + return char_cnt; +} + +/* Writes string S to the console, followed by a new-line + character. */ +int +puts (const char *s) +{ + acquire_console (); + while (*s != '\0') + putchar_have_lock (*s++); + putchar_have_lock ('\n'); + release_console (); + + return 0; +} + +/* Writes the N characters in BUFFER to the console. */ +void +putbuf (const char *buffer, size_t n) +{ + acquire_console (); + while (n-- > 0) + putchar_have_lock (*buffer++); + release_console (); +} + +/* Writes C to the vga display and serial port. */ +int +putchar (int c) +{ + acquire_console (); + putchar_have_lock (c); + release_console (); + + return c; +} + +/* Helper function for vprintf(). */ +static void +vprintf_helper (char c, void *char_cnt_) +{ + int *char_cnt = char_cnt_; + (*char_cnt)++; + putchar_have_lock (c); +} + +/* Writes C to the vga display and serial port. + The caller has already acquired the console lock if + appropriate. */ +static void +putchar_have_lock (uint8_t c) +{ + ASSERT (console_locked_by_current_thread ()); + write_cnt++; + serial_putc (c); + vga_putc (c); +} diff --git a/pintos-progos/lib/kernel/console.h b/pintos-progos/lib/kernel/console.h new file mode 100644 index 0000000..ab99249 --- /dev/null +++ b/pintos-progos/lib/kernel/console.h @@ -0,0 +1,8 @@ +#ifndef __LIB_KERNEL_CONSOLE_H +#define __LIB_KERNEL_CONSOLE_H + +void console_init (void); +void console_panic (void); +void console_print_stats (void); + +#endif /* lib/kernel/console.h */ diff --git a/pintos-progos/lib/kernel/debug.c b/pintos-progos/lib/kernel/debug.c new file mode 100644 index 0000000..b12f4f9 --- /dev/null +++ b/pintos-progos/lib/kernel/debug.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include +#include "threads/init.h" +#include "threads/interrupt.h" +#include "threads/thread.h" +#include "threads/switch.h" +#include "threads/vaddr.h" +#include "devices/serial.h" +#include "devices/shutdown.h" + +/* Halts the OS, printing the source file name, line number, and + function name, plus a user-specific message. */ +void +debug_panic (const char *file, int line, const char *function, + const char *message, ...) +{ + static int level; + va_list args; + + intr_disable (); + console_panic (); + + level++; + if (level == 1) + { + printf ("Kernel PANIC at %s:%d in %s(): ", file, line, function); + + va_start (args, message); + vprintf (message, args); + printf ("\n"); + va_end (args); + + debug_backtrace (); + } + else if (level == 2) + printf ("Kernel PANIC recursion at %s:%d in %s().\n", + file, line, function); + else + { + /* Don't print anything: that's probably why we recursed. */ + } + + serial_flush (); + shutdown (); + for (;;); +} + +/* Print call stack of a thread. + The thread may be running, ready, or blocked. */ +static void +print_stacktrace(struct thread *t, void *aux UNUSED) +{ + void *retaddr = NULL, **frame = NULL; + const char *status = "UNKNOWN"; + + switch (t->status) { + case THREAD_RUNNING: + status = "RUNNING"; + break; + + case THREAD_READY: + status = "READY"; + break; + + case THREAD_BLOCKED: + status = "BLOCKED"; + break; + + default: + break; + } + + printf ("Call stack of thread `%s' (status %s):", t->name, status); + + if (t == thread_current()) + { + frame = __builtin_frame_address (1); + retaddr = __builtin_return_address (0); + } + else + { + /* Retrieve the values of the base and instruction pointers + as they were saved when this thread called switch_threads. */ + struct switch_threads_frame * saved_frame; + + saved_frame = (struct switch_threads_frame *)t->stack; + + /* Skip threads if they have been added to the all threads + list, but have never been scheduled. + We can identify because their `stack' member either points + at the top of their kernel stack page, or the + switch_threads_frame's 'eip' member points at switch_entry. + See also threads.c. */ + if (t->stack == (uint8_t *)t + PGSIZE || saved_frame->eip == switch_entry) + { + printf (" thread was never scheduled.\n"); + return; + } + + frame = (void **) saved_frame->ebp; + retaddr = (void *) saved_frame->eip; + } + + printf (" %p", retaddr); + for (; (uintptr_t) frame >= 0x1000 && frame[0] != NULL; frame = frame[0]) + printf (" %p", frame[1]); + printf (".\n"); +} + +/* Prints call stack of all threads. */ +void +debug_backtrace_all (void) +{ + enum intr_level oldlevel = intr_disable (); + + thread_foreach (print_stacktrace, 0); + intr_set_level (oldlevel); +} diff --git a/pintos-progos/lib/kernel/hash.c b/pintos-progos/lib/kernel/hash.c new file mode 100644 index 0000000..57eed45 --- /dev/null +++ b/pintos-progos/lib/kernel/hash.c @@ -0,0 +1,430 @@ +/* Hash table. + + This data structure is thoroughly documented in the Tour of + Pintos for Project 3. + + See hash.h for basic information. */ + +#include "hash.h" +#include "../debug.h" +#include "threads/malloc.h" + +#define list_elem_to_hash_elem(LIST_ELEM) \ + list_entry(LIST_ELEM, struct hash_elem, list_elem) + +static struct list *find_bucket (struct hash *, struct hash_elem *); +static struct hash_elem *find_elem (struct hash *, struct list *, + struct hash_elem *); +static void insert_elem (struct hash *, struct list *, struct hash_elem *); +static void remove_elem (struct hash *, struct hash_elem *); +static void rehash (struct hash *); + +/* Initializes hash table H to compute hash values using HASH and + compare hash elements using LESS, given auxiliary data AUX. */ +bool +hash_init (struct hash *h, + hash_hash_func *hash, hash_less_func *less, void *aux) +{ + h->elem_cnt = 0; + h->bucket_cnt = 4; + h->buckets = malloc (sizeof *h->buckets * h->bucket_cnt); + h->hash = hash; + h->less = less; + h->aux = aux; + + if (h->buckets != NULL) + { + hash_clear (h, NULL); + return true; + } + else + return false; +} + +/* Removes all the elements from H. + + If DESTRUCTOR is non-null, then it is called for each element + in the hash. DESTRUCTOR may, if appropriate, deallocate the + memory used by the hash element. However, modifying hash + table H while hash_clear() is running, using any of the + functions hash_clear(), hash_destroy(), hash_insert(), + hash_replace(), or hash_delete(), yields undefined behavior, + whether done in DESTRUCTOR or elsewhere. */ +void +hash_clear (struct hash *h, hash_action_func *destructor) +{ + size_t i; + + for (i = 0; i < h->bucket_cnt; i++) + { + struct list *bucket = &h->buckets[i]; + + if (destructor != NULL) + while (!list_empty (bucket)) + { + struct list_elem *list_elem = list_pop_front (bucket); + struct hash_elem *hash_elem = list_elem_to_hash_elem (list_elem); + destructor (hash_elem, h->aux); + } + + list_init (bucket); + } + + h->elem_cnt = 0; +} + +/* Destroys hash table H. + + If DESTRUCTOR is non-null, then it is first called for each + element in the hash. DESTRUCTOR may, if appropriate, + deallocate the memory used by the hash element. However, + modifying hash table H while hash_clear() is running, using + any of the functions hash_clear(), hash_destroy(), + hash_insert(), hash_replace(), or hash_delete(), yields + undefined behavior, whether done in DESTRUCTOR or + elsewhere. */ +void +hash_destroy (struct hash *h, hash_action_func *destructor) +{ + if (destructor != NULL) + hash_clear (h, destructor); + free (h->buckets); +} + +/* Inserts NEW into hash table H and returns a null pointer, if + no equal element is already in the table. + If an equal element is already in the table, returns it + without inserting NEW. */ +struct hash_elem * +hash_insert (struct hash *h, struct hash_elem *new) +{ + struct list *bucket = find_bucket (h, new); + struct hash_elem *old = find_elem (h, bucket, new); + + if (old == NULL) + insert_elem (h, bucket, new); + + rehash (h); + + return old; +} + +/* Inserts NEW into hash table H, replacing any equal element + already in the table, which is returned. */ +struct hash_elem * +hash_replace (struct hash *h, struct hash_elem *new) +{ + struct list *bucket = find_bucket (h, new); + struct hash_elem *old = find_elem (h, bucket, new); + + if (old != NULL) + remove_elem (h, old); + insert_elem (h, bucket, new); + + rehash (h); + + return old; +} + +/* Finds and returns an element equal to E in hash table H, or a + null pointer if no equal element exists in the table. */ +struct hash_elem * +hash_find (struct hash *h, struct hash_elem *e) +{ + return find_elem (h, find_bucket (h, e), e); +} + +/* Finds, removes, and returns an element equal to E in hash + table H. Returns a null pointer if no equal element existed + in the table. + + If the elements of the hash table are dynamically allocated, + or own resources that are, then it is the caller's + responsibility to deallocate them. */ +struct hash_elem * +hash_delete (struct hash *h, struct hash_elem *e) +{ + struct hash_elem *found = find_elem (h, find_bucket (h, e), e); + if (found != NULL) + { + remove_elem (h, found); + rehash (h); + } + return found; +} + +/* Calls ACTION for each element in hash table H in arbitrary + order. + Modifying hash table H while hash_apply() is running, using + any of the functions hash_clear(), hash_destroy(), + hash_insert(), hash_replace(), or hash_delete(), yields + undefined behavior, whether done from ACTION or elsewhere. */ +void +hash_apply (struct hash *h, hash_action_func *action) +{ + size_t i; + + ASSERT (action != NULL); + + for (i = 0; i < h->bucket_cnt; i++) + { + struct list *bucket = &h->buckets[i]; + struct list_elem *elem, *next; + + for (elem = list_begin (bucket); elem != list_end (bucket); elem = next) + { + next = list_next (elem); + action (list_elem_to_hash_elem (elem), h->aux); + } + } +} + +/* Initializes I for iterating hash table H. + + Iteration idiom: + + struct hash_iterator i; + + hash_first (&i, h); + while (hash_next (&i)) + { + struct foo *f = hash_entry (hash_cur (&i), struct foo, elem); + ...do something with f... + } + + Modifying hash table H during iteration, using any of the + functions hash_clear(), hash_destroy(), hash_insert(), + hash_replace(), or hash_delete(), invalidates all + iterators. */ +void +hash_first (struct hash_iterator *i, struct hash *h) +{ + ASSERT (i != NULL); + ASSERT (h != NULL); + + i->hash = h; + i->bucket = i->hash->buckets; + i->elem = list_elem_to_hash_elem (list_head (i->bucket)); +} + +/* Advances I to the next element in the hash table and returns + it. Returns a null pointer if no elements are left. Elements + are returned in arbitrary order. + + Modifying a hash table H during iteration, using any of the + functions hash_clear(), hash_destroy(), hash_insert(), + hash_replace(), or hash_delete(), invalidates all + iterators. */ +struct hash_elem * +hash_next (struct hash_iterator *i) +{ + ASSERT (i != NULL); + + i->elem = list_elem_to_hash_elem (list_next (&i->elem->list_elem)); + while (i->elem == list_elem_to_hash_elem (list_end (i->bucket))) + { + if (++i->bucket >= i->hash->buckets + i->hash->bucket_cnt) + { + i->elem = NULL; + break; + } + i->elem = list_elem_to_hash_elem (list_begin (i->bucket)); + } + + return i->elem; +} + +/* Returns the current element in the hash table iteration, or a + null pointer at the end of the table. Undefined behavior + after calling hash_first() but before hash_next(). */ +struct hash_elem * +hash_cur (struct hash_iterator *i) +{ + return i->elem; +} + +/* Returns the number of elements in H. */ +size_t +hash_size (struct hash *h) +{ + return h->elem_cnt; +} + +/* Returns true if H contains no elements, false otherwise. */ +bool +hash_empty (struct hash *h) +{ + return h->elem_cnt == 0; +} + +/* Fowler-Noll-Vo hash constants, for 32-bit word sizes. */ +#define FNV_32_PRIME 16777619u +#define FNV_32_BASIS 2166136261u + +/* Returns a hash of the SIZE bytes in BUF. */ +unsigned +hash_bytes (const void *buf_, size_t size) +{ + /* Fowler-Noll-Vo 32-bit hash, for bytes. */ + const unsigned char *buf = buf_; + unsigned hash; + + ASSERT (buf != NULL); + + hash = FNV_32_BASIS; + while (size-- > 0) + hash = (hash * FNV_32_PRIME) ^ *buf++; + + return hash; +} + +/* Returns a hash of string S. */ +unsigned +hash_string (const char *s_) +{ + const unsigned char *s = (const unsigned char *) s_; + unsigned hash; + + ASSERT (s != NULL); + + hash = FNV_32_BASIS; + while (*s != '\0') + hash = (hash * FNV_32_PRIME) ^ *s++; + + return hash; +} + +/* Returns a hash of integer I. */ +unsigned +hash_int (int i) +{ + return hash_bytes (&i, sizeof i); +} + +/* Returns the bucket in H that E belongs in. */ +static struct list * +find_bucket (struct hash *h, struct hash_elem *e) +{ + size_t bucket_idx = h->hash (e, h->aux) & (h->bucket_cnt - 1); + return &h->buckets[bucket_idx]; +} + +/* Searches BUCKET in H for a hash element equal to E. Returns + it if found or a null pointer otherwise. */ +static struct hash_elem * +find_elem (struct hash *h, struct list *bucket, struct hash_elem *e) +{ + struct list_elem *i; + + for (i = list_begin (bucket); i != list_end (bucket); i = list_next (i)) + { + struct hash_elem *hi = list_elem_to_hash_elem (i); + if (!h->less (hi, e, h->aux) && !h->less (e, hi, h->aux)) + return hi; + } + return NULL; +} + +/* Returns X with its lowest-order bit set to 1 turned off. */ +static inline size_t +turn_off_least_1bit (size_t x) +{ + return x & (x - 1); +} + +/* Returns true if X is a power of 2, otherwise false. */ +static inline size_t +is_power_of_2 (size_t x) +{ + return x != 0 && turn_off_least_1bit (x) == 0; +} + +/* Element per bucket ratios. */ +#define MIN_ELEMS_PER_BUCKET 1 /* Elems/bucket < 1: reduce # of buckets. */ +#define BEST_ELEMS_PER_BUCKET 2 /* Ideal elems/bucket. */ +#define MAX_ELEMS_PER_BUCKET 4 /* Elems/bucket > 4: increase # of buckets. */ + +/* Changes the number of buckets in hash table H to match the + ideal. This function can fail because of an out-of-memory + condition, but that'll just make hash accesses less efficient; + we can still continue. */ +static void +rehash (struct hash *h) +{ + size_t old_bucket_cnt, new_bucket_cnt; + struct list *new_buckets, *old_buckets; + size_t i; + + ASSERT (h != NULL); + + /* Save old bucket info for later use. */ + old_buckets = h->buckets; + old_bucket_cnt = h->bucket_cnt; + + /* Calculate the number of buckets to use now. + We want one bucket for about every BEST_ELEMS_PER_BUCKET. + We must have at least four buckets, and the number of + buckets must be a power of 2. */ + new_bucket_cnt = h->elem_cnt / BEST_ELEMS_PER_BUCKET; + if (new_bucket_cnt < 4) + new_bucket_cnt = 4; + while (!is_power_of_2 (new_bucket_cnt)) + new_bucket_cnt = turn_off_least_1bit (new_bucket_cnt); + + /* Don't do anything if the bucket count wouldn't change. */ + if (new_bucket_cnt == old_bucket_cnt) + return; + + /* Allocate new buckets and initialize them as empty. */ + new_buckets = malloc (sizeof *new_buckets * new_bucket_cnt); + if (new_buckets == NULL) + { + /* Allocation failed. This means that use of the hash table will + be less efficient. However, it is still usable, so + there's no reason for it to be an error. */ + return; + } + for (i = 0; i < new_bucket_cnt; i++) + list_init (&new_buckets[i]); + + /* Install new bucket info. */ + h->buckets = new_buckets; + h->bucket_cnt = new_bucket_cnt; + + /* Move each old element into the appropriate new bucket. */ + for (i = 0; i < old_bucket_cnt; i++) + { + struct list *old_bucket; + struct list_elem *elem, *next; + + old_bucket = &old_buckets[i]; + for (elem = list_begin (old_bucket); + elem != list_end (old_bucket); elem = next) + { + struct list *new_bucket + = find_bucket (h, list_elem_to_hash_elem (elem)); + next = list_next (elem); + list_remove (elem); + list_push_front (new_bucket, elem); + } + } + + free (old_buckets); +} + +/* Inserts E into BUCKET (in hash table H). */ +static void +insert_elem (struct hash *h, struct list *bucket, struct hash_elem *e) +{ + h->elem_cnt++; + list_push_front (bucket, &e->list_elem); +} + +/* Removes E from hash table H. */ +static void +remove_elem (struct hash *h, struct hash_elem *e) +{ + h->elem_cnt--; + list_remove (&e->list_elem); +} + diff --git a/pintos-progos/lib/kernel/hash.h b/pintos-progos/lib/kernel/hash.h new file mode 100644 index 0000000..db9f674 --- /dev/null +++ b/pintos-progos/lib/kernel/hash.h @@ -0,0 +1,103 @@ +#ifndef __LIB_KERNEL_HASH_H +#define __LIB_KERNEL_HASH_H + +/* Hash table. + + This data structure is thoroughly documented in the Tour of + Pintos for Project 3. + + This is a standard hash table with chaining. To locate an + element in the table, we compute a hash function over the + element's data and use that as an index into an array of + doubly linked lists, then linearly search the list. + + The chain lists do not use dynamic allocation. Instead, each + structure that can potentially be in a hash must embed a + struct hash_elem member. All of the hash functions operate on + these `struct hash_elem's. The hash_entry macro allows + conversion from a struct hash_elem back to a structure object + that contains it. This is the same technique used in the + linked list implementation. Refer to lib/kernel/list.h for a + detailed explanation. */ + +#include +#include +#include +#include "list.h" + +/* Hash element. */ +struct hash_elem + { + struct list_elem list_elem; + }; + +/* Converts pointer to hash element HASH_ELEM into a pointer to + the structure that HASH_ELEM is embedded inside. Supply the + name of the outer structure STRUCT and the member name MEMBER + of the hash element. See the big comment at the top of the + file for an example. */ +#define hash_entry(HASH_ELEM, STRUCT, MEMBER) \ + ((STRUCT *) ((uint8_t *) &(HASH_ELEM)->list_elem \ + - offsetof (STRUCT, MEMBER.list_elem))) + +/* Computes and returns the hash value for hash element E, given + auxiliary data AUX. */ +typedef unsigned hash_hash_func (const struct hash_elem *e, void *aux); + +/* Compares the value of two hash elements A and B, given + auxiliary data AUX. Returns true if A is less than B, or + false if A is greater than or equal to B. */ +typedef bool hash_less_func (const struct hash_elem *a, + const struct hash_elem *b, + void *aux); + +/* Performs some operation on hash element E, given auxiliary + data AUX. */ +typedef void hash_action_func (struct hash_elem *e, void *aux); + +/* Hash table. */ +struct hash + { + size_t elem_cnt; /* Number of elements in table. */ + size_t bucket_cnt; /* Number of buckets, a power of 2. */ + struct list *buckets; /* Array of `bucket_cnt' lists. */ + hash_hash_func *hash; /* Hash function. */ + hash_less_func *less; /* Comparison function. */ + void *aux; /* Auxiliary data for `hash' and `less'. */ + }; + +/* A hash table iterator. */ +struct hash_iterator + { + struct hash *hash; /* The hash table. */ + struct list *bucket; /* Current bucket. */ + struct hash_elem *elem; /* Current hash element in current bucket. */ + }; + +/* Basic life cycle. */ +bool hash_init (struct hash *, hash_hash_func *, hash_less_func *, void *aux); +void hash_clear (struct hash *, hash_action_func *); +void hash_destroy (struct hash *, hash_action_func *); + +/* Search, insertion, deletion. */ +struct hash_elem *hash_insert (struct hash *, struct hash_elem *); +struct hash_elem *hash_replace (struct hash *, struct hash_elem *); +struct hash_elem *hash_find (struct hash *, struct hash_elem *); +struct hash_elem *hash_delete (struct hash *, struct hash_elem *); + +/* Iteration. */ +void hash_apply (struct hash *, hash_action_func *); +void hash_first (struct hash_iterator *, struct hash *); +struct hash_elem *hash_next (struct hash_iterator *); +struct hash_elem *hash_cur (struct hash_iterator *); + +/* Information. */ +size_t hash_size (struct hash *); +bool hash_empty (struct hash *); + +/* Sample hash functions. */ +unsigned hash_bytes (const void *, size_t); +unsigned hash_string (const char *); +unsigned hash_int (int); + +#endif /* lib/kernel/hash.h */ diff --git a/pintos-progos/lib/kernel/list.c b/pintos-progos/lib/kernel/list.c new file mode 100644 index 0000000..316d9ef --- /dev/null +++ b/pintos-progos/lib/kernel/list.c @@ -0,0 +1,524 @@ +#include "list.h" +#include "../debug.h" + +/* Our doubly linked lists have two header elements: the "head" + just before the first element and the "tail" just after the + last element. The `prev' link of the front header is null, as + is the `next' link of the back header. Their other two links + point toward each other via the interior elements of the list. + + An empty list looks like this: + + +------+ +------+ + <---| head |<--->| tail |---> + +------+ +------+ + + A list with two elements in it looks like this: + + +------+ +-------+ +-------+ +------+ + <---| head |<--->| 1 |<--->| 2 |<--->| tail |<---> + +------+ +-------+ +-------+ +------+ + + The symmetry of this arrangement eliminates lots of special + cases in list processing. For example, take a look at + list_remove(): it takes only two pointer assignments and no + conditionals. That's a lot simpler than the code would be + without header elements. + + (Because only one of the pointers in each header element is used, + we could in fact combine them into a single header element + without sacrificing this simplicity. But using two separate + elements allows us to do a little bit of checking on some + operations, which can be valuable.) */ + +static bool is_sorted (struct list_elem *a, struct list_elem *b, + list_less_func *less, void *aux) UNUSED; + +/* Returns true if ELEM is a head, false otherwise. */ +static inline bool +is_head (struct list_elem *elem) +{ + return elem != NULL && elem->prev == NULL && elem->next != NULL; +} + +/* Returns true if ELEM is an interior element, + false otherwise. */ +static inline bool +is_interior (struct list_elem *elem) +{ + return elem != NULL && elem->prev != NULL && elem->next != NULL; +} + +/* Returns true if ELEM is a tail, false otherwise. */ +static inline bool +is_tail (struct list_elem *elem) +{ + return elem != NULL && elem->prev != NULL && elem->next == NULL; +} + +/* Initializes LIST as an empty list. */ +void +list_init (struct list *list) +{ + ASSERT (list != NULL); + list->head.prev = NULL; + list->head.next = &list->tail; + list->tail.prev = &list->head; + list->tail.next = NULL; +} + +/* Returns the beginning of LIST. */ +struct list_elem * +list_begin (struct list *list) +{ + ASSERT (list != NULL); + return list->head.next; +} + +/* Returns the element after ELEM in its list. If ELEM is the + last element in its list, returns the list tail. Results are + undefined if ELEM is itself a list tail. */ +struct list_elem * +list_next (struct list_elem *elem) +{ + ASSERT (is_head (elem) || is_interior (elem)); + return elem->next; +} + +/* Returns LIST's tail. + + list_end() is often used in iterating through a list from + front to back. See the big comment at the top of list.h for + an example. */ +struct list_elem * +list_end (struct list *list) +{ + ASSERT (list != NULL); + return &list->tail; +} + +/* Returns the LIST's reverse beginning, for iterating through + LIST in reverse order, from back to front. */ +struct list_elem * +list_rbegin (struct list *list) +{ + ASSERT (list != NULL); + return list->tail.prev; +} + +/* Returns the element before ELEM in its list. If ELEM is the + first element in its list, returns the list head. Results are + undefined if ELEM is itself a list head. */ +struct list_elem * +list_prev (struct list_elem *elem) +{ + ASSERT (is_interior (elem) || is_tail (elem)); + return elem->prev; +} + +/* Returns LIST's head. + + list_rend() is often used in iterating through a list in + reverse order, from back to front. Here's typical usage, + following the example from the top of list.h: + + for (e = list_rbegin (&foo_list); e != list_rend (&foo_list); + e = list_prev (e)) + { + struct foo *f = list_entry (e, struct foo, elem); + ...do something with f... + } +*/ +struct list_elem * +list_rend (struct list *list) +{ + ASSERT (list != NULL); + return &list->head; +} + +/* Return's LIST's head. + + list_head() can be used for an alternate style of iterating + through a list, e.g.: + + e = list_head (&list); + while ((e = list_next (e)) != list_end (&list)) + { + ... + } +*/ +struct list_elem * +list_head (struct list *list) +{ + ASSERT (list != NULL); + return &list->head; +} + +/* Return's LIST's tail. */ +struct list_elem * +list_tail (struct list *list) +{ + ASSERT (list != NULL); + return &list->tail; +} + +/* Inserts ELEM just before BEFORE, which may be either an + interior element or a tail. The latter case is equivalent to + list_push_back(). */ +void +list_insert (struct list_elem *before, struct list_elem *elem) +{ + ASSERT (is_interior (before) || is_tail (before)); + ASSERT (elem != NULL); + + elem->prev = before->prev; + elem->next = before; + before->prev->next = elem; + before->prev = elem; +} + +/* Removes elements FIRST though LAST (exclusive) from their + current list, then inserts them just before BEFORE, which may + be either an interior element or a tail. */ +void +list_splice (struct list_elem *before, + struct list_elem *first, struct list_elem *last) +{ + ASSERT (is_interior (before) || is_tail (before)); + if (first == last) + return; + last = list_prev (last); + + ASSERT (is_interior (first)); + ASSERT (is_interior (last)); + + /* Cleanly remove FIRST...LAST from its current list. */ + first->prev->next = last->next; + last->next->prev = first->prev; + + /* Splice FIRST...LAST into new list. */ + first->prev = before->prev; + last->next = before; + before->prev->next = first; + before->prev = last; +} + +/* Inserts ELEM at the beginning of LIST, so that it becomes the + front in LIST. */ +void +list_push_front (struct list *list, struct list_elem *elem) +{ + list_insert (list_begin (list), elem); +} + +/* Inserts ELEM at the end of LIST, so that it becomes the + back in LIST. */ +void +list_push_back (struct list *list, struct list_elem *elem) +{ + list_insert (list_end (list), elem); +} + +/* Removes ELEM from its list and returns the element that + followed it. Undefined behavior if ELEM is not in a list. + + A list element must be treated very carefully after removing + it from its list. Calling list_next() or list_prev() on ELEM + will return the item that was previously before or after ELEM, + but, e.g., list_prev(list_next(ELEM)) is no longer ELEM! + + The list_remove() return value provides a convenient way to + iterate and remove elements from a list: + + for (e = list_begin (&list); e != list_end (&list); e = list_remove (e)) + { + ...do something with e... + } + + If you need to free() elements of the list then you need to be + more conservative. Here's an alternate strategy that works + even in that case: + + while (!list_empty (&list)) + { + struct list_elem *e = list_pop_front (&list); + ...do something with e... + } +*/ +struct list_elem * +list_remove (struct list_elem *elem) +{ + ASSERT (is_interior (elem)); + elem->prev->next = elem->next; + elem->next->prev = elem->prev; + return elem->next; +} + +/* Removes the front element from LIST and returns it. + Undefined behavior if LIST is empty before removal. */ +struct list_elem * +list_pop_front (struct list *list) +{ + struct list_elem *front = list_front (list); + list_remove (front); + return front; +} + +/* Removes the back element from LIST and returns it. + Undefined behavior if LIST is empty before removal. */ +struct list_elem * +list_pop_back (struct list *list) +{ + struct list_elem *back = list_back (list); + list_remove (back); + return back; +} + +/* Returns the front element in LIST. + Undefined behavior if LIST is empty. */ +struct list_elem * +list_front (struct list *list) +{ + ASSERT (!list_empty (list)); + return list->head.next; +} + +/* Returns the back element in LIST. + Undefined behavior if LIST is empty. */ +struct list_elem * +list_back (struct list *list) +{ + ASSERT (!list_empty (list)); + return list->tail.prev; +} + +/* Returns the number of elements in LIST. + Runs in O(n) in the number of elements. */ +size_t +list_size (struct list *list) +{ + struct list_elem *e; + size_t cnt = 0; + + for (e = list_begin (list); e != list_end (list); e = list_next (e)) + cnt++; + return cnt; +} + +/* Returns true if LIST is empty, false otherwise. */ +bool +list_empty (struct list *list) +{ + return list_begin (list) == list_end (list); +} + +/* Swaps the `struct list_elem *'s that A and B point to. */ +static void +swap (struct list_elem **a, struct list_elem **b) +{ + struct list_elem *t = *a; + *a = *b; + *b = t; +} + +/* Reverses the order of LIST. */ +void +list_reverse (struct list *list) +{ + if (!list_empty (list)) + { + struct list_elem *e; + + for (e = list_begin (list); e != list_end (list); e = e->prev) + swap (&e->prev, &e->next); + swap (&list->head.next, &list->tail.prev); + swap (&list->head.next->prev, &list->tail.prev->next); + } +} + +/* Returns true only if the list elements A through B (exclusive) + are in order according to LESS given auxiliary data AUX. */ +static bool +is_sorted (struct list_elem *a, struct list_elem *b, + list_less_func *less, void *aux) +{ + if (a != b) + while ((a = list_next (a)) != b) + if (less (a, list_prev (a), aux)) + return false; + return true; +} + +/* Finds a run, starting at A and ending not after B, of list + elements that are in nondecreasing order according to LESS + given auxiliary data AUX. Returns the (exclusive) end of the + run. + A through B (exclusive) must form a non-empty range. */ +static struct list_elem * +find_end_of_run (struct list_elem *a, struct list_elem *b, + list_less_func *less, void *aux) +{ + ASSERT (a != NULL); + ASSERT (b != NULL); + ASSERT (less != NULL); + ASSERT (a != b); + + do + { + a = list_next (a); + } + while (a != b && !less (a, list_prev (a), aux)); + return a; +} + +/* Merges A0 through A1B0 (exclusive) with A1B0 through B1 + (exclusive) to form a combined range also ending at B1 + (exclusive). Both input ranges must be nonempty and sorted in + nondecreasing order according to LESS given auxiliary data + AUX. The output range will be sorted the same way. */ +static void +inplace_merge (struct list_elem *a0, struct list_elem *a1b0, + struct list_elem *b1, + list_less_func *less, void *aux) +{ + ASSERT (a0 != NULL); + ASSERT (a1b0 != NULL); + ASSERT (b1 != NULL); + ASSERT (less != NULL); + ASSERT (is_sorted (a0, a1b0, less, aux)); + ASSERT (is_sorted (a1b0, b1, less, aux)); + + while (a0 != a1b0 && a1b0 != b1) + if (!less (a1b0, a0, aux)) + a0 = list_next (a0); + else + { + a1b0 = list_next (a1b0); + list_splice (a0, list_prev (a1b0), a1b0); + } +} + +/* Sorts LIST according to LESS given auxiliary data AUX, using a + natural iterative merge sort that runs in O(n lg n) time and + O(1) space in the number of elements in LIST. */ +void +list_sort (struct list *list, list_less_func *less, void *aux) +{ + size_t output_run_cnt; /* Number of runs output in current pass. */ + + ASSERT (list != NULL); + ASSERT (less != NULL); + + /* Pass over the list repeatedly, merging adjacent runs of + nondecreasing elements, until only one run is left. */ + do + { + struct list_elem *a0; /* Start of first run. */ + struct list_elem *a1b0; /* End of first run, start of second. */ + struct list_elem *b1; /* End of second run. */ + + output_run_cnt = 0; + for (a0 = list_begin (list); a0 != list_end (list); a0 = b1) + { + /* Each iteration produces one output run. */ + output_run_cnt++; + + /* Locate two adjacent runs of nondecreasing elements + A0...A1B0 and A1B0...B1. */ + a1b0 = find_end_of_run (a0, list_end (list), less, aux); + if (a1b0 == list_end (list)) + break; + b1 = find_end_of_run (a1b0, list_end (list), less, aux); + + /* Merge the runs. */ + inplace_merge (a0, a1b0, b1, less, aux); + } + } + while (output_run_cnt > 1); + + ASSERT (is_sorted (list_begin (list), list_end (list), less, aux)); +} + +/* Inserts ELEM in the proper position in LIST, which must be + sorted according to LESS given auxiliary data AUX. + Runs in O(n) average case in the number of elements in LIST. */ +void +list_insert_ordered (struct list *list, struct list_elem *elem, + list_less_func *less, void *aux) +{ + struct list_elem *e; + + ASSERT (list != NULL); + ASSERT (elem != NULL); + ASSERT (less != NULL); + + for (e = list_begin (list); e != list_end (list); e = list_next (e)) + if (less (elem, e, aux)) + break; + return list_insert (e, elem); +} + +/* Iterates through LIST and removes all but the first in each + set of adjacent elements that are equal according to LESS + given auxiliary data AUX. If DUPLICATES is non-null, then the + elements from LIST are appended to DUPLICATES. */ +void +list_unique (struct list *list, struct list *duplicates, + list_less_func *less, void *aux) +{ + struct list_elem *elem, *next; + + ASSERT (list != NULL); + ASSERT (less != NULL); + if (list_empty (list)) + return; + + elem = list_begin (list); + while ((next = list_next (elem)) != list_end (list)) + if (!less (elem, next, aux) && !less (next, elem, aux)) + { + list_remove (next); + if (duplicates != NULL) + list_push_back (duplicates, next); + } + else + elem = next; +} + +/* Returns the element in LIST with the largest value according + to LESS given auxiliary data AUX. If there is more than one + maximum, returns the one that appears earlier in the list. If + the list is empty, returns its tail. */ +struct list_elem * +list_max (struct list *list, list_less_func *less, void *aux) +{ + struct list_elem *max = list_begin (list); + if (max != list_end (list)) + { + struct list_elem *e; + + for (e = list_next (max); e != list_end (list); e = list_next (e)) + if (less (max, e, aux)) + max = e; + } + return max; +} + +/* Returns the element in LIST with the smallest value according + to LESS given auxiliary data AUX. If there is more than one + minimum, returns the one that appears earlier in the list. If + the list is empty, returns its tail. */ +struct list_elem * +list_min (struct list *list, list_less_func *less, void *aux) +{ + struct list_elem *min = list_begin (list); + if (min != list_end (list)) + { + struct list_elem *e; + + for (e = list_next (min); e != list_end (list); e = list_next (e)) + if (less (e, min, aux)) + min = e; + } + return min; +} diff --git a/pintos-progos/lib/kernel/list.h b/pintos-progos/lib/kernel/list.h new file mode 100644 index 0000000..82efbb5 --- /dev/null +++ b/pintos-progos/lib/kernel/list.h @@ -0,0 +1,181 @@ +#ifndef __LIB_KERNEL_LIST_H +#define __LIB_KERNEL_LIST_H + +/* Doubly linked list. + + This implementation of a doubly linked list does not require + use of dynamically allocated memory. Instead, each structure + that is a potential list element must embed a struct list_elem + member. All of the list functions operate on these `struct + list_elem's. The list_entry macro allows conversion from a + struct list_elem back to a structure object that contains it. + + For example, suppose there is a needed for a list of `struct + foo'. `struct foo' should contain a `struct list_elem' + member, like so: + + struct foo + { + struct list_elem elem; + int bar; + ...other members... + }; + + Then a list of `struct foo' can be be declared and initialized + like so: + + struct list foo_list; + + list_init (&foo_list); + + Iteration is a typical situation where it is necessary to + convert from a struct list_elem back to its enclosing + structure. Here's an example using foo_list: + + struct list_elem *e; + + for (e = list_begin (&foo_list); e != list_end (&foo_list); + e = list_next (e)) + { + struct foo *f = list_entry (e, struct foo, elem); + ...do something with f... + } + + You can find real examples of list usage throughout the + source; for example, malloc.c, palloc.c, and thread.c in the + threads directory all use lists. + + The interface for this list is inspired by the list<> template + in the C++ STL. If you're familiar with list<>, you should + find this easy to use. However, it should be emphasized that + these lists do *no* type checking and can't do much other + correctness checking. If you screw up, it will bite you. + + Glossary of list terms: + + - "front": The first element in a list. Undefined in an + empty list. Returned by list_front(). + + - "back": The last element in a list. Undefined in an empty + list. Returned by list_back(). + + - "tail": The element figuratively just after the last + element of a list. Well defined even in an empty list. + Returned by list_end(). Used as the end sentinel for an + iteration from front to back. + + - "beginning": In a non-empty list, the front. In an empty + list, the tail. Returned by list_begin(). Used as the + starting point for an iteration from front to back. + + - "head": The element figuratively just before the first + element of a list. Well defined even in an empty list. + Returned by list_rend(). Used as the end sentinel for an + iteration from back to front. + + - "reverse beginning": In a non-empty list, the back. In an + empty list, the head. Returned by list_rbegin(). Used as + the starting point for an iteration from back to front. + + - "interior element": An element that is not the head or + tail, that is, a real list element. An empty list does + not have any interior elements. +*/ + +#include +#include +#include + +/* List element. */ +struct list_elem + { + struct list_elem *prev; /* Previous list element. */ + struct list_elem *next; /* Next list element. */ + }; + +/* List. */ +struct list + { + struct list_elem head; /* List head. */ + struct list_elem tail; /* List tail. */ + }; + +/* Converts pointer to list element LIST_ELEM into a pointer to + the structure that LIST_ELEM is embedded inside. Supply the + name of the outer structure STRUCT and the member name MEMBER + of the list element. See the big comment at the top of the + file for an example. */ +#define list_entry(LIST_ELEM, STRUCT, MEMBER) \ + ((STRUCT *) ((uint8_t *) &(LIST_ELEM)->next \ + - offsetof (STRUCT, MEMBER.next))) + +/* List initialization. + + A list may be initialized by calling list_init(): + + struct list my_list; + list_init (&my_list); + + or with an initializer using LIST_INITIALIZER: + + struct list my_list = LIST_INITIALIZER (my_list); */ +#define LIST_INITIALIZER(NAME) { { NULL, &(NAME).tail }, \ + { &(NAME).head, NULL } } + +void list_init (struct list *); + +/* List traversal. */ +struct list_elem *list_begin (struct list *); +struct list_elem *list_next (struct list_elem *); +struct list_elem *list_end (struct list *); + +struct list_elem *list_rbegin (struct list *); +struct list_elem *list_prev (struct list_elem *); +struct list_elem *list_rend (struct list *); + +struct list_elem *list_head (struct list *); +struct list_elem *list_tail (struct list *); + +/* List insertion. */ +void list_insert (struct list_elem *, struct list_elem *); +void list_splice (struct list_elem *before, + struct list_elem *first, struct list_elem *last); +void list_push_front (struct list *, struct list_elem *); +void list_push_back (struct list *, struct list_elem *); + +/* List removal. */ +struct list_elem *list_remove (struct list_elem *); +struct list_elem *list_pop_front (struct list *); +struct list_elem *list_pop_back (struct list *); + +/* List elements. */ +struct list_elem *list_front (struct list *); +struct list_elem *list_back (struct list *); + +/* List properties. */ +size_t list_size (struct list *); +bool list_empty (struct list *); + +/* Miscellaneous. */ +void list_reverse (struct list *); + +/* Compares the value of two list elements A and B, given + auxiliary data AUX. Returns true if A is less than B, or + false if A is greater than or equal to B. */ +typedef bool list_less_func (const struct list_elem *a, + const struct list_elem *b, + void *aux); + +/* Operations on lists with ordered elements. */ +void list_sort (struct list *, + list_less_func *, void *aux); +void list_insert_ordered (struct list *, struct list_elem *, + list_less_func *, void *aux); +void list_unique (struct list *, struct list *duplicates, + list_less_func *, void *aux); + +/* Max and min. */ +struct list_elem *list_max (struct list *, list_less_func *, void *aux); +struct list_elem *list_min (struct list *, list_less_func *, void *aux); + +#endif /* lib/kernel/list.h */ diff --git a/pintos-progos/lib/kernel/stdio.h b/pintos-progos/lib/kernel/stdio.h new file mode 100644 index 0000000..3e5bae9 --- /dev/null +++ b/pintos-progos/lib/kernel/stdio.h @@ -0,0 +1,6 @@ +#ifndef __LIB_KERNEL_STDIO_H +#define __LIB_KERNEL_STDIO_H + +void putbuf (const char *, size_t); + +#endif /* lib/kernel/stdio.h */ diff --git a/pintos-progos/lib/limits.h b/pintos-progos/lib/limits.h new file mode 100644 index 0000000..c957ec4 --- /dev/null +++ b/pintos-progos/lib/limits.h @@ -0,0 +1,34 @@ +#ifndef __LIB_LIMITS_H +#define __LIB_LIMITS_H + +#define CHAR_BIT 8 + +#define SCHAR_MAX 127 +#define SCHAR_MIN (-SCHAR_MAX - 1) +#define UCHAR_MAX 255 + +#ifdef __CHAR_UNSIGNED__ +#define CHAR_MIN 0 +#define CHAR_MAX UCHAR_MAX +#else +#define CHAR_MIN SCHAR_MIN +#define CHAR_MAX SCHAR_MAX +#endif + +#define SHRT_MAX 32767 +#define SHRT_MIN (-SHRT_MAX - 1) +#define USHRT_MAX 65535 + +#define INT_MAX 2147483647 +#define INT_MIN (-INT_MAX - 1) +#define UINT_MAX 4294967295U + +#define LONG_MAX 2147483647L +#define LONG_MIN (-LONG_MAX - 1) +#define ULONG_MAX 4294967295UL + +#define LLONG_MAX 9223372036854775807LL +#define LLONG_MIN (-LLONG_MAX - 1) +#define ULLONG_MAX 18446744073709551615ULL + +#endif /* lib/limits.h */ diff --git a/pintos-progos/lib/packed.h b/pintos-progos/lib/packed.h new file mode 100644 index 0000000..9a9b6e2 --- /dev/null +++ b/pintos-progos/lib/packed.h @@ -0,0 +1,10 @@ +#ifndef __LIB_PACKED_H +#define __LIB_PACKED_H + +/* The "packed" attribute, when applied to a structure, prevents + GCC from inserting padding bytes between or after structure + members. It must be specified at the time of the structure's + definition, normally just after the closing brace. */ +#define PACKED __attribute__ ((packed)) + +#endif /* lib/packed.h */ diff --git a/pintos-progos/lib/random.c b/pintos-progos/lib/random.c new file mode 100644 index 0000000..a4761b6 --- /dev/null +++ b/pintos-progos/lib/random.c @@ -0,0 +1,83 @@ +#include "random.h" +#include +#include +#include "debug.h" + +/* RC4-based pseudo-random number generator (PRNG). + + RC4 is a stream cipher. We're not using it here for its + cryptographic properties, but because it is easy to implement + and its output is plenty random for non-cryptographic + purposes. + + See http://en.wikipedia.org/wiki/RC4_(cipher) for information + on RC4.*/ + +/* RC4 state. */ +static uint8_t s[256]; /* S[]. */ +static uint8_t s_i, s_j; /* i, j. */ + +/* Already initialized? */ +static bool inited; + +/* Swaps the bytes pointed to by A and B. */ +static inline void +swap_byte (uint8_t *a, uint8_t *b) +{ + uint8_t t = *a; + *a = *b; + *b = t; +} + +/* Initializes or reinitializes the PRNG with the given SEED. */ +void +random_init (unsigned seed) +{ + uint8_t *seedp = (uint8_t *) &seed; + int i; + uint8_t j; + + for (i = 0; i < 256; i++) + s[i] = i; + for (i = j = 0; i < 256; i++) + { + j += s[i] + seedp[i % sizeof seed]; + swap_byte (s + i, s + j); + } + + s_i = s_j = 0; + inited = true; +} + +/* Writes SIZE random bytes into BUF. */ +void +random_bytes (void *buf_, size_t size) +{ + uint8_t *buf; + + if (!inited) + random_init (0); + + for (buf = buf_; size-- > 0; buf++) + { + uint8_t s_k; + + s_i++; + s_j += s[s_i]; + swap_byte (s + s_i, s + s_j); + + s_k = s[s_i] + s[s_j]; + *buf = s[s_k]; + } +} + +/* Returns a pseudo-random unsigned long. + Use random_ulong() % n to obtain a random number in the range + 0...n (exclusive). */ +unsigned long +random_ulong (void) +{ + unsigned long ul; + random_bytes (&ul, sizeof ul); + return ul; +} diff --git a/pintos-progos/lib/random.h b/pintos-progos/lib/random.h new file mode 100644 index 0000000..0950ae2 --- /dev/null +++ b/pintos-progos/lib/random.h @@ -0,0 +1,10 @@ +#ifndef __LIB_RANDOM_H +#define __LIB_RANDOM_H + +#include + +void random_init (unsigned seed); +void random_bytes (void *, size_t); +unsigned long random_ulong (void); + +#endif /* lib/random.h */ diff --git a/pintos-progos/lib/round.h b/pintos-progos/lib/round.h new file mode 100644 index 0000000..3aa6642 --- /dev/null +++ b/pintos-progos/lib/round.h @@ -0,0 +1,18 @@ +#ifndef __LIB_ROUND_H +#define __LIB_ROUND_H + +/* Yields X rounded up to the nearest multiple of STEP. + For X >= 0, STEP >= 1 only. */ +#define ROUND_UP(X, STEP) (((X) + (STEP) - 1) / (STEP) * (STEP)) + +/* Yields X divided by STEP, rounded up. + For X >= 0, STEP >= 1 only. */ +#define DIV_ROUND_UP(X, STEP) (((X) + (STEP) - 1) / (STEP)) + +/* Yields X rounded down to the nearest multiple of STEP. + For X >= 0, STEP >= 1 only. */ +#define ROUND_DOWN(X, STEP) ((X) / (STEP) * (STEP)) + +/* There is no DIV_ROUND_DOWN. It would be simply X / STEP. */ + +#endif /* lib/round.h */ diff --git a/pintos-progos/lib/stdarg.h b/pintos-progos/lib/stdarg.h new file mode 100644 index 0000000..32622b5 --- /dev/null +++ b/pintos-progos/lib/stdarg.h @@ -0,0 +1,14 @@ +#ifndef __LIB_STDARG_H +#define __LIB_STDARG_H + +/* GCC has functionality as built-ins, + so all we need is to use it. */ + +typedef __builtin_va_list va_list; + +#define va_start(LIST, ARG) __builtin_va_start (LIST, ARG) +#define va_end(LIST) __builtin_va_end (LIST) +#define va_arg(LIST, TYPE) __builtin_va_arg (LIST, TYPE) +#define va_copy(DST, SRC) __builtin_va_copy (DST, SRC) + +#endif /* lib/stdarg.h */ diff --git a/pintos-progos/lib/stdbool.h b/pintos-progos/lib/stdbool.h new file mode 100644 index 0000000..f173a91 --- /dev/null +++ b/pintos-progos/lib/stdbool.h @@ -0,0 +1,9 @@ +#ifndef __LIB_STDBOOL_H +#define __LIB_STDBOOL_H + +#define bool _Bool +#define true 1 +#define false 0 +#define __bool_true_false_are_defined 1 + +#endif /* lib/stdbool.h */ diff --git a/pintos-progos/lib/stddef.h b/pintos-progos/lib/stddef.h new file mode 100644 index 0000000..4e74fa6 --- /dev/null +++ b/pintos-progos/lib/stddef.h @@ -0,0 +1,12 @@ +#ifndef __LIB_STDDEF_H +#define __LIB_STDDEF_H + +#define NULL ((void *) 0) +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *) 0)->MEMBER) + +/* GCC predefines the types we need for ptrdiff_t and size_t, + so that we don't have to guess. */ +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef __SIZE_TYPE__ size_t; + +#endif /* lib/stddef.h */ diff --git a/pintos-progos/lib/stdint.h b/pintos-progos/lib/stdint.h new file mode 100644 index 0000000..ef5f214 --- /dev/null +++ b/pintos-progos/lib/stdint.h @@ -0,0 +1,51 @@ +#ifndef __LIB_STDINT_H +#define __LIB_STDINT_H + +typedef signed char int8_t; +#define INT8_MAX 127 +#define INT8_MIN (-INT8_MAX - 1) + +typedef signed short int int16_t; +#define INT16_MAX 32767 +#define INT16_MIN (-INT16_MAX - 1) + +typedef signed int int32_t; +#define INT32_MAX 2147483647 +#define INT32_MIN (-INT32_MAX - 1) + +typedef signed long long int int64_t; +#define INT64_MAX 9223372036854775807LL +#define INT64_MIN (-INT64_MAX - 1) + +typedef unsigned char uint8_t; +#define UINT8_MAX 255 + +typedef unsigned short int uint16_t; +#define UINT16_MAX 65535 + +typedef unsigned int uint32_t; +#define UINT32_MAX 4294967295U + +typedef unsigned long long int uint64_t; +#define UINT64_MAX 18446744073709551615ULL + +typedef int32_t intptr_t; +#define INTPTR_MIN INT32_MIN +#define INTPTR_MAX INT32_MAX + +typedef uint32_t uintptr_t; +#define UINTPTR_MAX UINT32_MAX + +typedef int64_t intmax_t; +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX + +typedef uint64_t uintmax_t; +#define UINTMAX_MAX UINT64_MAX + +#define PTRDIFF_MIN INT32_MIN +#define PTRDIFF_MAX INT32_MAX + +#define SIZE_MAX UINT32_MAX + +#endif /* lib/stdint.h */ diff --git a/pintos-progos/lib/stdio.c b/pintos-progos/lib/stdio.c new file mode 100644 index 0000000..8927c50 --- /dev/null +++ b/pintos-progos/lib/stdio.c @@ -0,0 +1,655 @@ +#include +#include +#include +#include +#include +#include + +/* Auxiliary data for vsnprintf_helper(). */ +struct vsnprintf_aux + { + char *p; /* Current output position. */ + int length; /* Length of output string. */ + int max_length; /* Max length of output string. */ + }; + +static void vsnprintf_helper (char, void *); + +/* Like vprintf(), except that output is stored into BUFFER, + which must have space for BUF_SIZE characters. Writes at most + BUF_SIZE - 1 characters to BUFFER, followed by a null + terminator. BUFFER will always be null-terminated unless + BUF_SIZE is zero. Returns the number of characters that would + have been written to BUFFER, not including a null terminator, + had there been enough room. */ +int +vsnprintf (char *buffer, size_t buf_size, const char *format, va_list args) +{ + /* Set up aux data for vsnprintf_helper(). */ + struct vsnprintf_aux aux; + aux.p = buffer; + aux.length = 0; + aux.max_length = buf_size > 0 ? buf_size - 1 : 0; + + /* Do most of the work. */ + __vprintf (format, args, vsnprintf_helper, &aux); + + /* Add null terminator. */ + if (buf_size > 0) + *aux.p = '\0'; + + return aux.length; +} + +/* Helper function for vsnprintf(). */ +static void +vsnprintf_helper (char ch, void *aux_) +{ + struct vsnprintf_aux *aux = aux_; + + if (aux->length++ < aux->max_length) + *aux->p++ = ch; +} + +/* Like printf(), except that output is stored into BUFFER, + which must have space for BUF_SIZE characters. Writes at most + BUF_SIZE - 1 characters to BUFFER, followed by a null + terminator. BUFFER will always be null-terminated unless + BUF_SIZE is zero. Returns the number of characters that would + have been written to BUFFER, not including a null terminator, + had there been enough room. */ +int +snprintf (char *buffer, size_t buf_size, const char *format, ...) +{ + va_list args; + int retval; + + va_start (args, format); + retval = vsnprintf (buffer, buf_size, format, args); + va_end (args); + + return retval; +} + +/* Writes formatted output to the console. + In the kernel, the console is both the video display and first + serial port. + In userspace, the console is file descriptor 1. */ +int +printf (const char *format, ...) +{ + va_list args; + int retval; + + va_start (args, format); + retval = vprintf (format, args); + va_end (args); + + return retval; +} + +/* printf() formatting internals. */ + +/* A printf() conversion. */ +struct printf_conversion + { + /* Flags. */ + enum + { + MINUS = 1 << 0, /* '-' */ + PLUS = 1 << 1, /* '+' */ + SPACE = 1 << 2, /* ' ' */ + POUND = 1 << 3, /* '#' */ + ZERO = 1 << 4, /* '0' */ + GROUP = 1 << 5 /* '\'' */ + } + flags; + + /* Minimum field width. */ + int width; + + /* Numeric precision. + -1 indicates no precision was specified. */ + int precision; + + /* Type of argument to format. */ + enum + { + CHAR = 1, /* hh */ + SHORT = 2, /* h */ + INT = 3, /* (none) */ + INTMAX = 4, /* j */ + LONG = 5, /* l */ + LONGLONG = 6, /* ll */ + PTRDIFFT = 7, /* t */ + SIZET = 8 /* z */ + } + type; + }; + +struct integer_base + { + int base; /* Base. */ + const char *digits; /* Collection of digits. */ + int x; /* `x' character to use, for base 16 only. */ + int group; /* Number of digits to group with ' flag. */ + }; + +static const struct integer_base base_d = {10, "0123456789", 0, 3}; +static const struct integer_base base_o = {8, "01234567", 0, 3}; +static const struct integer_base base_x = {16, "0123456789abcdef", 'x', 4}; +static const struct integer_base base_X = {16, "0123456789ABCDEF", 'X', 4}; + +static const char *parse_conversion (const char *format, + struct printf_conversion *, + va_list *); +static void format_integer (uintmax_t value, bool is_signed, bool negative, + const struct integer_base *, + const struct printf_conversion *, + void (*output) (char, void *), void *aux); +static void output_dup (char ch, size_t cnt, + void (*output) (char, void *), void *aux); +static void format_string (const char *string, int length, + struct printf_conversion *, + void (*output) (char, void *), void *aux); + +void +__vprintf (const char *format, va_list args, + void (*output) (char, void *), void *aux) +{ + for (; *format != '\0'; format++) + { + struct printf_conversion c; + + /* Literally copy non-conversions to output. */ + if (*format != '%') + { + output (*format, aux); + continue; + } + format++; + + /* %% => %. */ + if (*format == '%') + { + output ('%', aux); + continue; + } + + /* Parse conversion specifiers. */ + format = parse_conversion (format, &c, &args); + + /* Do conversion. */ + switch (*format) + { + case 'd': + case 'i': + { + /* Signed integer conversions. */ + intmax_t value; + + switch (c.type) + { + case CHAR: + value = (signed char) va_arg (args, int); + break; + case SHORT: + value = (short) va_arg (args, int); + break; + case INT: + value = va_arg (args, int); + break; + case INTMAX: + value = va_arg (args, intmax_t); + break; + case LONG: + value = va_arg (args, long); + break; + case LONGLONG: + value = va_arg (args, long long); + break; + case PTRDIFFT: + value = va_arg (args, ptrdiff_t); + break; + case SIZET: + value = va_arg (args, size_t); + if (value > SIZE_MAX / 2) + value = value - SIZE_MAX - 1; + break; + default: + NOT_REACHED (); + } + + format_integer (value < 0 ? -value : value, + true, value < 0, &base_d, &c, output, aux); + } + break; + + case 'o': + case 'u': + case 'x': + case 'X': + { + /* Unsigned integer conversions. */ + uintmax_t value; + const struct integer_base *b; + + switch (c.type) + { + case CHAR: + value = (unsigned char) va_arg (args, unsigned); + break; + case SHORT: + value = (unsigned short) va_arg (args, unsigned); + break; + case INT: + value = va_arg (args, unsigned); + break; + case INTMAX: + value = va_arg (args, uintmax_t); + break; + case LONG: + value = va_arg (args, unsigned long); + break; + case LONGLONG: + value = va_arg (args, unsigned long long); + break; + case PTRDIFFT: + value = va_arg (args, ptrdiff_t); +#if UINTMAX_MAX != PTRDIFF_MAX + value &= ((uintmax_t) PTRDIFF_MAX << 1) | 1; +#endif + break; + case SIZET: + value = va_arg (args, size_t); + break; + default: + NOT_REACHED (); + } + + switch (*format) + { + case 'o': b = &base_o; break; + case 'u': b = &base_d; break; + case 'x': b = &base_x; break; + case 'X': b = &base_X; break; + default: NOT_REACHED (); + } + + format_integer (value, false, false, b, &c, output, aux); + } + break; + + case 'c': + { + /* Treat character as single-character string. */ + char ch = va_arg (args, int); + format_string (&ch, 1, &c, output, aux); + } + break; + + case 's': + { + /* String conversion. */ + const char *s = va_arg (args, char *); + if (s == NULL) + s = "(null)"; + + /* Limit string length according to precision. + Note: if c.precision == -1 then strnlen() will get + SIZE_MAX for MAXLEN, which is just what we want. */ + format_string (s, strnlen (s, c.precision), &c, output, aux); + } + break; + + case 'p': + { + /* Pointer conversion. + Format pointers as %#x. */ + void *p = va_arg (args, void *); + + c.flags = POUND; + format_integer ((uintptr_t) p, false, false, + &base_x, &c, output, aux); + } + break; + + case 'f': + case 'e': + case 'E': + case 'g': + case 'G': + case 'n': + /* We don't support floating-point arithmetic, + and %n can be part of a security hole. */ + __printf ("<>", output, aux, *format); + break; + + default: + __printf ("<>", output, aux, *format); + break; + } + } +} + +/* Parses conversion option characters starting at FORMAT and + initializes C appropriately. Returns the character in FORMAT + that indicates the conversion (e.g. the `d' in `%d'). Uses + *ARGS for `*' field widths and precisions. */ +static const char * +parse_conversion (const char *format, struct printf_conversion *c, + va_list *args) +{ + /* Parse flag characters. */ + c->flags = 0; + for (;;) + { + switch (*format++) + { + case '-': + c->flags |= MINUS; + break; + case '+': + c->flags |= PLUS; + break; + case ' ': + c->flags |= SPACE; + break; + case '#': + c->flags |= POUND; + break; + case '0': + c->flags |= ZERO; + break; + case '\'': + c->flags |= GROUP; + break; + default: + format--; + goto not_a_flag; + } + } + not_a_flag: + if (c->flags & MINUS) + c->flags &= ~ZERO; + if (c->flags & PLUS) + c->flags &= ~SPACE; + + /* Parse field width. */ + c->width = 0; + if (*format == '*') + { + format++; + c->width = va_arg (*args, int); + } + else + { + for (; isdigit (*format); format++) + c->width = c->width * 10 + *format - '0'; + } + if (c->width < 0) + { + c->width = -c->width; + c->flags |= MINUS; + } + + /* Parse precision. */ + c->precision = -1; + if (*format == '.') + { + format++; + if (*format == '*') + { + format++; + c->precision = va_arg (*args, int); + } + else + { + c->precision = 0; + for (; isdigit (*format); format++) + c->precision = c->precision * 10 + *format - '0'; + } + if (c->precision < 0) + c->precision = -1; + } + if (c->precision >= 0) + c->flags &= ~ZERO; + + /* Parse type. */ + c->type = INT; + switch (*format++) + { + case 'h': + if (*format == 'h') + { + format++; + c->type = CHAR; + } + else + c->type = SHORT; + break; + + case 'j': + c->type = INTMAX; + break; + + case 'l': + if (*format == 'l') + { + format++; + c->type = LONGLONG; + } + else + c->type = LONG; + break; + + case 't': + c->type = PTRDIFFT; + break; + + case 'z': + c->type = SIZET; + break; + + default: + format--; + break; + } + + return format; +} + +/* Performs an integer conversion, writing output to OUTPUT with + auxiliary data AUX. The integer converted has absolute value + VALUE. If IS_SIGNED is true, does a signed conversion with + NEGATIVE indicating a negative value; otherwise does an + unsigned conversion and ignores NEGATIVE. The output is done + according to the provided base B. Details of the conversion + are in C. */ +static void +format_integer (uintmax_t value, bool is_signed, bool negative, + const struct integer_base *b, + const struct printf_conversion *c, + void (*output) (char, void *), void *aux) +{ + char buf[64], *cp; /* Buffer and current position. */ + int x; /* `x' character to use or 0 if none. */ + int sign; /* Sign character or 0 if none. */ + int precision; /* Rendered precision. */ + int pad_cnt; /* # of pad characters to fill field width. */ + int digit_cnt; /* # of digits output so far. */ + + /* Determine sign character, if any. + An unsigned conversion will never have a sign character, + even if one of the flags requests one. */ + sign = 0; + if (is_signed) + { + if (c->flags & PLUS) + sign = negative ? '-' : '+'; + else if (c->flags & SPACE) + sign = negative ? '-' : ' '; + else if (negative) + sign = '-'; + } + + /* Determine whether to include `0x' or `0X'. + It will only be included with a hexadecimal conversion of a + nonzero value with the # flag. */ + x = (c->flags & POUND) && value ? b->x : 0; + + /* Accumulate digits into buffer. + This algorithm produces digits in reverse order, so later we + will output the buffer's content in reverse. */ + cp = buf; + digit_cnt = 0; + while (value > 0) + { + if ((c->flags & GROUP) && digit_cnt > 0 && digit_cnt % b->group == 0) + *cp++ = ','; + *cp++ = b->digits[value % b->base]; + value /= b->base; + digit_cnt++; + } + + /* Append enough zeros to match precision. + If requested precision is 0, then a value of zero is + rendered as a null string, otherwise as "0". + If the # flag is used with base 8, the result must always + begin with a zero. */ + precision = c->precision < 0 ? 1 : c->precision; + while (cp - buf < precision && cp < buf + sizeof buf - 1) + *cp++ = '0'; + if ((c->flags & POUND) && b->base == 8 && (cp == buf || cp[-1] != '0')) + *cp++ = '0'; + + /* Calculate number of pad characters to fill field width. */ + pad_cnt = c->width - (cp - buf) - (x ? 2 : 0) - (sign != 0); + if (pad_cnt < 0) + pad_cnt = 0; + + /* Do output. */ + if ((c->flags & (MINUS | ZERO)) == 0) + output_dup (' ', pad_cnt, output, aux); + if (sign) + output (sign, aux); + if (x) + { + output ('0', aux); + output (x, aux); + } + if (c->flags & ZERO) + output_dup ('0', pad_cnt, output, aux); + while (cp > buf) + output (*--cp, aux); + if (c->flags & MINUS) + output_dup (' ', pad_cnt, output, aux); +} + +/* Writes CH to OUTPUT with auxiliary data AUX, CNT times. */ +static void +output_dup (char ch, size_t cnt, void (*output) (char, void *), void *aux) +{ + while (cnt-- > 0) + output (ch, aux); +} + +/* Formats the LENGTH characters starting at STRING according to + the conversion specified in C. Writes output to OUTPUT with + auxiliary data AUX. */ +static void +format_string (const char *string, int length, + struct printf_conversion *c, + void (*output) (char, void *), void *aux) +{ + int i; + if (c->width > length && (c->flags & MINUS) == 0) + output_dup (' ', c->width - length, output, aux); + for (i = 0; i < length; i++) + output (string[i], aux); + if (c->width > length && (c->flags & MINUS) != 0) + output_dup (' ', c->width - length, output, aux); +} + +/* Wrapper for __vprintf() that converts varargs into a + va_list. */ +void +__printf (const char *format, + void (*output) (char, void *), void *aux, ...) +{ + va_list args; + + va_start (args, aux); + __vprintf (format, args, output, aux); + va_end (args); +} + +/* Dumps the SIZE bytes in BUF to the console as hex bytes + arranged 16 per line. Numeric offsets are also included, + starting at OFS for the first byte in BUF. If ASCII is true + then the corresponding ASCII characters are also rendered + alongside. */ +void +hex_dump (uintptr_t ofs, const void *buf_, size_t size, bool ascii) +{ + const uint8_t *buf = buf_; + const size_t per_line = 16; /* Maximum bytes per line. */ + + while (size > 0) + { + size_t start, end, n; + size_t i; + + /* Number of bytes on this line. */ + start = ofs % per_line; + end = per_line; + if (end - start > size) + end = start + size; + n = end - start; + + /* Print line. */ + printf ("%08jx ", (uintmax_t) ROUND_DOWN (ofs, per_line)); + for (i = 0; i < start; i++) + printf (" "); + for (; i < end; i++) + printf ("%02hhx%c", + buf[i - start], i == per_line / 2 - 1? '-' : ' '); + if (ascii) + { + for (; i < per_line; i++) + printf (" "); + printf ("|"); + for (i = 0; i < start; i++) + printf (" "); + for (; i < end; i++) + printf ("%c", + isprint (buf[i - start]) ? buf[i - start] : '.'); + for (; i < per_line; i++) + printf (" "); + printf ("|"); + } + printf ("\n"); + + ofs += n; + buf += n; + size -= n; + } +} + +/* Prints SIZE, which represents a number of bytes, in a + human-readable format, e.g. "256 kB". */ +void +print_human_readable_size (uint64_t size) +{ + if (size == 1) + printf ("1 byte"); + else + { + static const char *factors[] = {"bytes", "kB", "MB", "GB", "TB", NULL}; + const char **fp; + + for (fp = factors; size >= 1024 && fp[1] != NULL; fp++) + size /= 1024; + printf ("%"PRIu64" %s", size, *fp); + } +} diff --git a/pintos-progos/lib/stdio.h b/pintos-progos/lib/stdio.h new file mode 100644 index 0000000..2739c0a --- /dev/null +++ b/pintos-progos/lib/stdio.h @@ -0,0 +1,40 @@ +#ifndef __LIB_STDIO_H +#define __LIB_STDIO_H + +#include +#include +#include +#include +#include + +/* Include lib/user/stdio.h or lib/kernel/stdio.h, as + appropriate. */ +#include_next + +/* Predefined file handles. */ +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 + +/* Standard functions. */ +int printf (const char *, ...) PRINTF_FORMAT (1, 2); +int snprintf (char *, size_t, const char *, ...) PRINTF_FORMAT (3, 4); +int vprintf (const char *, va_list) PRINTF_FORMAT (1, 0); +int vsnprintf (char *, size_t, const char *, va_list) PRINTF_FORMAT (3, 0); +int putchar (int); +int puts (const char *); + +/* Nonstandard functions. */ +void hex_dump (uintptr_t ofs, const void *, size_t size, bool ascii); +void print_human_readable_size (uint64_t sz); + +/* Internal functions. */ +void __vprintf (const char *format, va_list args, + void (*output) (char, void *), void *aux); +void __printf (const char *format, + void (*output) (char, void *), void *aux, ...); + +/* Try to be helpful. */ +#define sprintf dont_use_sprintf_use_snprintf +#define vsprintf dont_use_vsprintf_use_vsnprintf + +#endif /* lib/stdio.h */ diff --git a/pintos-progos/lib/stdlib.c b/pintos-progos/lib/stdlib.c new file mode 100644 index 0000000..84c7f61 --- /dev/null +++ b/pintos-progos/lib/stdlib.c @@ -0,0 +1,208 @@ +#include +#include +#include +#include +#include + +/* Converts a string representation of a signed decimal integer + in S into an `int', which is returned. */ +int +atoi (const char *s) +{ + bool negative; + int value; + + ASSERT (s != NULL); + + /* Skip white space. */ + while (isspace ((unsigned char) *s)) + s++; + + /* Parse sign. */ + negative = false; + if (*s == '+') + s++; + else if (*s == '-') + { + negative = true; + s++; + } + + /* Parse digits. We always initially parse the value as + negative, and then make it positive later, because the + negative range of an int is bigger than the positive range + on a 2's complement system. */ + for (value = 0; isdigit (*s); s++) + value = value * 10 - (*s - '0'); + if (!negative) + value = -value; + + return value; +} + +/* Compares A and B by calling the AUX function. */ +static int +compare_thunk (const void *a, const void *b, void *aux) +{ + int (**compare) (const void *, const void *) = aux; + return (*compare) (a, b); +} + +/* Sorts ARRAY, which contains CNT elements of SIZE bytes each, + using COMPARE. When COMPARE is passed a pair of elements A + and B, respectively, it must return a strcmp()-type result, + i.e. less than zero if A < B, zero if A == B, greater than + zero if A > B. Runs in O(n lg n) time and O(1) space in + CNT. */ +void +qsort (void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *)) +{ + sort (array, cnt, size, compare_thunk, &compare); +} + +/* Swaps elements with 1-based indexes A_IDX and B_IDX in ARRAY + with elements of SIZE bytes each. */ +static void +do_swap (unsigned char *array, size_t a_idx, size_t b_idx, size_t size) +{ + unsigned char *a = array + (a_idx - 1) * size; + unsigned char *b = array + (b_idx - 1) * size; + size_t i; + + for (i = 0; i < size; i++) + { + unsigned char t = a[i]; + a[i] = b[i]; + b[i] = t; + } +} + +/* Compares elements with 1-based indexes A_IDX and B_IDX in + ARRAY with elements of SIZE bytes each, using COMPARE to + compare elements, passing AUX as auxiliary data, and returns a + strcmp()-type result. */ +static int +do_compare (unsigned char *array, size_t a_idx, size_t b_idx, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux) +{ + return compare (array + (a_idx - 1) * size, array + (b_idx - 1) * size, aux); +} + +/* "Float down" the element with 1-based index I in ARRAY of CNT + elements of SIZE bytes each, using COMPARE to compare + elements, passing AUX as auxiliary data. */ +static void +heapify (unsigned char *array, size_t i, size_t cnt, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux) +{ + for (;;) + { + /* Set `max' to the index of the largest element among I + and its children (if any). */ + size_t left = 2 * i; + size_t right = 2 * i + 1; + size_t max = i; + if (left <= cnt && do_compare (array, left, max, size, compare, aux) > 0) + max = left; + if (right <= cnt + && do_compare (array, right, max, size, compare, aux) > 0) + max = right; + + /* If the maximum value is already in element I, we're + done. */ + if (max == i) + break; + + /* Swap and continue down the heap. */ + do_swap (array, i, max, size); + i = max; + } +} + +/* Sorts ARRAY, which contains CNT elements of SIZE bytes each, + using COMPARE to compare elements, passing AUX as auxiliary + data. When COMPARE is passed a pair of elements A and B, + respectively, it must return a strcmp()-type result, i.e. less + than zero if A < B, zero if A == B, greater than zero if A > + B. Runs in O(n lg n) time and O(1) space in CNT. */ +void +sort (void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux) +{ + size_t i; + + ASSERT (array != NULL || cnt == 0); + ASSERT (compare != NULL); + ASSERT (size > 0); + + /* Build a heap. */ + for (i = cnt / 2; i > 0; i--) + heapify (array, i, cnt, size, compare, aux); + + /* Sort the heap. */ + for (i = cnt; i > 1; i--) + { + do_swap (array, 1, i, size); + heapify (array, 1, i - 1, size, compare, aux); + } +} + +/* Searches ARRAY, which contains CNT elements of SIZE bytes + each, for the given KEY. Returns a match is found, otherwise + a null pointer. If there are multiple matches, returns an + arbitrary one of them. + + ARRAY must be sorted in order according to COMPARE. + + Uses COMPARE to compare elements. When COMPARE is passed a + pair of elements A and B, respectively, it must return a + strcmp()-type result, i.e. less than zero if A < B, zero if A + == B, greater than zero if A > B. */ +void * +bsearch (const void *key, const void *array, size_t cnt, + size_t size, int (*compare) (const void *, const void *)) +{ + return binary_search (key, array, cnt, size, compare_thunk, &compare); +} + +/* Searches ARRAY, which contains CNT elements of SIZE bytes + each, for the given KEY. Returns a match is found, otherwise + a null pointer. If there are multiple matches, returns an + arbitrary one of them. + + ARRAY must be sorted in order according to COMPARE. + + Uses COMPARE to compare elements, passing AUX as auxiliary + data. When COMPARE is passed a pair of elements A and B, + respectively, it must return a strcmp()-type result, i.e. less + than zero if A < B, zero if A == B, greater than zero if A > + B. */ +void * +binary_search (const void *key, const void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux) +{ + const unsigned char *first = array; + const unsigned char *last = array + size * cnt; + + while (first < last) + { + size_t range = (last - first) / size; + const unsigned char *middle = first + (range / 2) * size; + int cmp = compare (key, middle, aux); + + if (cmp < 0) + last = middle; + else if (cmp > 0) + first = middle + size; + else + return (void *) middle; + } + + return NULL; +} + diff --git a/pintos-progos/lib/stdlib.h b/pintos-progos/lib/stdlib.h new file mode 100644 index 0000000..d14afa3 --- /dev/null +++ b/pintos-progos/lib/stdlib.h @@ -0,0 +1,22 @@ +#ifndef __LIB_STDLIB_H +#define __LIB_STDLIB_H + +#include + +/* Standard functions. */ +int atoi (const char *); +void qsort (void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *)); +void *bsearch (const void *key, const void *array, size_t cnt, + size_t size, int (*compare) (const void *, const void *)); + +/* Nonstandard functions. */ +void sort (void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux); +void *binary_search (const void *key, const void *array, size_t cnt, + size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux); + +#endif /* lib/stdlib.h */ diff --git a/pintos-progos/lib/string.c b/pintos-progos/lib/string.c new file mode 100644 index 0000000..d223c89 --- /dev/null +++ b/pintos-progos/lib/string.c @@ -0,0 +1,375 @@ +#include +#include + +/* Copies SIZE bytes from SRC to DST, which must not overlap. + Returns DST. */ +void * +memcpy (void *dst_, const void *src_, size_t size) +{ + unsigned char *dst = dst_; + const unsigned char *src = src_; + + ASSERT (dst != NULL || size == 0); + ASSERT (src != NULL || size == 0); + + while (size-- > 0) + *dst++ = *src++; + + return dst_; +} + +/* Copies SIZE bytes from SRC to DST, which are allowed to + overlap. Returns DST. */ +void * +memmove (void *dst_, const void *src_, size_t size) +{ + unsigned char *dst = dst_; + const unsigned char *src = src_; + + ASSERT (dst != NULL || size == 0); + ASSERT (src != NULL || size == 0); + + if (dst < src) + { + while (size-- > 0) + *dst++ = *src++; + } + else + { + dst += size; + src += size; + while (size-- > 0) + *--dst = *--src; + } + + return dst; +} + +/* Find the first differing byte in the two blocks of SIZE bytes + at A and B. Returns a positive value if the byte in A is + greater, a negative value if the byte in B is greater, or zero + if blocks A and B are equal. */ +int +memcmp (const void *a_, const void *b_, size_t size) +{ + const unsigned char *a = a_; + const unsigned char *b = b_; + + ASSERT (a != NULL || size == 0); + ASSERT (b != NULL || size == 0); + + for (; size-- > 0; a++, b++) + if (*a != *b) + return *a > *b ? +1 : -1; + return 0; +} + +/* Finds the first differing characters in strings A and B. + Returns a positive value if the character in A (as an unsigned + char) is greater, a negative value if the character in B (as + an unsigned char) is greater, or zero if strings A and B are + equal. */ +int +strcmp (const char *a_, const char *b_) +{ + const unsigned char *a = (const unsigned char *) a_; + const unsigned char *b = (const unsigned char *) b_; + + ASSERT (a != NULL); + ASSERT (b != NULL); + + while (*a != '\0' && *a == *b) + { + a++; + b++; + } + + return *a < *b ? -1 : *a > *b; +} + +/* Returns a pointer to the first occurrence of CH in the first + SIZE bytes starting at BLOCK. Returns a null pointer if CH + does not occur in BLOCK. */ +void * +memchr (const void *block_, int ch_, size_t size) +{ + const unsigned char *block = block_; + unsigned char ch = ch_; + + ASSERT (block != NULL || size == 0); + + for (; size-- > 0; block++) + if (*block == ch) + return (void *) block; + + return NULL; +} + +/* Finds and returns the first occurrence of C in STRING, or a + null pointer if C does not appear in STRING. If C == '\0' + then returns a pointer to the null terminator at the end of + STRING. */ +char * +strchr (const char *string, int c_) +{ + char c = c_; + + ASSERT (string != NULL); + + for (;;) + if (*string == c) + return (char *) string; + else if (*string == '\0') + return NULL; + else + string++; +} + +/* Returns the length of the initial substring of STRING that + consists of characters that are not in STOP. */ +size_t +strcspn (const char *string, const char *stop) +{ + size_t length; + + for (length = 0; string[length] != '\0'; length++) + if (strchr (stop, string[length]) != NULL) + break; + return length; +} + +/* Returns a pointer to the first character in STRING that is + also in STOP. If no character in STRING is in STOP, returns a + null pointer. */ +char * +strpbrk (const char *string, const char *stop) +{ + for (; *string != '\0'; string++) + if (strchr (stop, *string) != NULL) + return (char *) string; + return NULL; +} + +/* Returns a pointer to the last occurrence of C in STRING. + Returns a null pointer if C does not occur in STRING. */ +char * +strrchr (const char *string, int c_) +{ + char c = c_; + const char *p = NULL; + + for (; *string != '\0'; string++) + if (*string == c) + p = string; + return (char *) p; +} + +/* Returns the length of the initial substring of STRING that + consists of characters in SKIP. */ +size_t +strspn (const char *string, const char *skip) +{ + size_t length; + + for (length = 0; string[length] != '\0'; length++) + if (strchr (skip, string[length]) == NULL) + break; + return length; +} + +/* Returns a pointer to the first occurrence of NEEDLE within + HAYSTACK. Returns a null pointer if NEEDLE does not exist + within HAYSTACK. */ +char * +strstr (const char *haystack, const char *needle) +{ + size_t haystack_len = strlen (haystack); + size_t needle_len = strlen (needle); + + if (haystack_len >= needle_len) + { + size_t i; + + for (i = 0; i <= haystack_len - needle_len; i++) + if (!memcmp (haystack + i, needle, needle_len)) + return (char *) haystack + i; + } + + return NULL; +} + +/* Breaks a string into tokens separated by DELIMITERS. The + first time this function is called, S should be the string to + tokenize, and in subsequent calls it must be a null pointer. + SAVE_PTR is the address of a `char *' variable used to keep + track of the tokenizer's position. The return value each time + is the next token in the string, or a null pointer if no + tokens remain. + + This function treats multiple adjacent delimiters as a single + delimiter. The returned tokens will never be length 0. + DELIMITERS may change from one call to the next within a + single string. + + strtok_r() modifies the string S, changing delimiters to null + bytes. Thus, S must be a modifiable string. String literals, + in particular, are *not* modifiable in C, even though for + backward compatibility they are not `const'. + + Example usage: + + char s[] = " String to tokenize. "; + char *token, *save_ptr; + + for (token = strtok_r (s, " ", &save_ptr); token != NULL; + token = strtok_r (NULL, " ", &save_ptr)) + printf ("'%s'\n", token); + + outputs: + + 'String' + 'to' + 'tokenize.' +*/ +char * +strtok_r (char *s, const char *delimiters, char **save_ptr) +{ + char *token; + + ASSERT (delimiters != NULL); + ASSERT (save_ptr != NULL); + + /* If S is nonnull, start from it. + If S is null, start from saved position. */ + if (s == NULL) + s = *save_ptr; + ASSERT (s != NULL); + + /* Skip any DELIMITERS at our current position. */ + while (strchr (delimiters, *s) != NULL) + { + /* strchr() will always return nonnull if we're searching + for a null byte, because every string contains a null + byte (at the end). */ + if (*s == '\0') + { + *save_ptr = s; + return NULL; + } + + s++; + } + + /* Skip any non-DELIMITERS up to the end of the string. */ + token = s; + while (strchr (delimiters, *s) == NULL) + s++; + if (*s != '\0') + { + *s = '\0'; + *save_ptr = s + 1; + } + else + *save_ptr = s; + return token; +} + +/* Sets the SIZE bytes in DST to VALUE. */ +void * +memset (void *dst_, int value, size_t size) +{ + unsigned char *dst = dst_; + + ASSERT (dst != NULL || size == 0); + + while (size-- > 0) + *dst++ = value; + + return dst_; +} + +/* Returns the length of STRING. */ +size_t +strlen (const char *string) +{ + const char *p; + + ASSERT (string != NULL); + + for (p = string; *p != '\0'; p++) + continue; + return p - string; +} + +/* If STRING is less than MAXLEN characters in length, returns + its actual length. Otherwise, returns MAXLEN. */ +size_t +strnlen (const char *string, size_t maxlen) +{ + size_t length; + + for (length = 0; string[length] != '\0' && length < maxlen; length++) + continue; + return length; +} + +/* Copies string SRC to DST. If SRC is longer than SIZE - 1 + characters, only SIZE - 1 characters are copied. A null + terminator is always written to DST, unless SIZE is 0. + Returns the length of SRC, not including the null terminator. + + strlcpy() is not in the standard C library, but it is an + increasingly popular extension. See + http://www.courtesan.com/todd/papers/strlcpy.html for + information on strlcpy(). */ +size_t +strlcpy (char *dst, const char *src, size_t size) +{ + size_t src_len; + + ASSERT (dst != NULL); + ASSERT (src != NULL); + + src_len = strlen (src); + if (size > 0) + { + size_t dst_len = size - 1; + if (src_len < dst_len) + dst_len = src_len; + memcpy (dst, src, dst_len); + dst[dst_len] = '\0'; + } + return src_len; +} + +/* Concatenates string SRC to DST. The concatenated string is + limited to SIZE - 1 characters. A null terminator is always + written to DST, unless SIZE is 0. Returns the length that the + concatenated string would have assuming that there was + sufficient space, not including a null terminator. + + strlcat() is not in the standard C library, but it is an + increasingly popular extension. See + http://www.courtesan.com/todd/papers/strlcpy.html for + information on strlcpy(). */ +size_t +strlcat (char *dst, const char *src, size_t size) +{ + size_t src_len, dst_len; + + ASSERT (dst != NULL); + ASSERT (src != NULL); + + src_len = strlen (src); + dst_len = strlen (dst); + if (size > 0 && dst_len < size) + { + size_t copy_cnt = size - dst_len - 1; + if (src_len < copy_cnt) + copy_cnt = src_len; + memcpy (dst + dst_len, src, copy_cnt); + dst[dst_len + copy_cnt] = '\0'; + } + return src_len + dst_len; +} + diff --git a/pintos-progos/lib/string.h b/pintos-progos/lib/string.h new file mode 100644 index 0000000..1fff82a --- /dev/null +++ b/pintos-progos/lib/string.h @@ -0,0 +1,35 @@ +#ifndef __LIB_STRING_H +#define __LIB_STRING_H + +#include + +/* Standard. */ +void *memcpy (void *, const void *, size_t); +void *memmove (void *, const void *, size_t); +char *strncat (char *, const char *, size_t); +int memcmp (const void *, const void *, size_t); +int strcmp (const char *, const char *); +void *memchr (const void *, int, size_t); +char *strchr (const char *, int); +size_t strcspn (const char *, const char *); +char *strpbrk (const char *, const char *); +char *strrchr (const char *, int); +size_t strspn (const char *, const char *); +char *strstr (const char *, const char *); +void *memset (void *, int, size_t); +size_t strlen (const char *); + +/* Extensions. */ +size_t strlcpy (char *, const char *, size_t); +size_t strlcat (char *, const char *, size_t); +char *strtok_r (char *, const char *, char **); +size_t strnlen (const char *, size_t); + +/* Try to be helpful. */ +#define strcpy dont_use_strcpy_use_strlcpy +#define strncpy dont_use_strncpy_use_strlcpy +#define strcat dont_use_strcat_use_strlcat +#define strncat dont_use_strncat_use_strlcat +#define strtok dont_use_strtok_use_strtok_r + +#endif /* lib/string.h */ diff --git a/pintos-progos/lib/syscall-nr.h b/pintos-progos/lib/syscall-nr.h new file mode 100644 index 0000000..21a7af9 --- /dev/null +++ b/pintos-progos/lib/syscall-nr.h @@ -0,0 +1,34 @@ +#ifndef __LIB_SYSCALL_NR_H +#define __LIB_SYSCALL_NR_H + +/* System call numbers. */ +enum + { + /* Projects 2 and later. */ + SYS_HALT, /* Halt the operating system. */ + SYS_EXIT, /* Terminate this process. */ + SYS_EXEC, /* Start another process. */ + SYS_WAIT, /* Wait for a child process to die. */ + SYS_CREATE, /* Create a file. */ + SYS_REMOVE, /* Delete a file. */ + SYS_OPEN, /* Open a file. */ + SYS_FILESIZE, /* Obtain a file's size. */ + SYS_READ, /* Read from a file. */ + SYS_WRITE, /* Write to a file. */ + SYS_SEEK, /* Change position in a file. */ + SYS_TELL, /* Report current position in a file. */ + SYS_CLOSE, /* Close a file. */ + + /* Project 3 and optionally project 4. */ + SYS_MMAP, /* Map a file into memory. */ + SYS_MUNMAP, /* Remove a memory mapping. */ + + /* Project 4 only. */ + SYS_CHDIR, /* Change the current directory. */ + SYS_MKDIR, /* Create a directory. */ + SYS_READDIR, /* Reads a directory entry. */ + SYS_ISDIR, /* Tests if a fd represents a directory. */ + SYS_INUMBER /* Returns the inode number for a fd. */ + }; + +#endif /* lib/syscall-nr.h */ diff --git a/pintos-progos/lib/user/console.c b/pintos-progos/lib/user/console.c new file mode 100644 index 0000000..22bdc8c --- /dev/null +++ b/pintos-progos/lib/user/console.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include + +/* The standard vprintf() function, + which is like printf() but uses a va_list. */ +int +vprintf (const char *format, va_list args) +{ + return vhprintf (STDOUT_FILENO, format, args); +} + +/* Like printf(), but writes output to the given HANDLE. */ +int +hprintf (int handle, const char *format, ...) +{ + va_list args; + int retval; + + va_start (args, format); + retval = vhprintf (handle, format, args); + va_end (args); + + return retval; +} + +/* Writes string S to the console, followed by a new-line + character. */ +int +puts (const char *s) +{ + write (STDOUT_FILENO, s, strlen (s)); + putchar ('\n'); + + return 0; +} + +/* Writes C to the console. */ +int +putchar (int c) +{ + char c2 = c; + write (STDOUT_FILENO, &c2, 1); + return c; +} + +/* Auxiliary data for vhprintf_helper(). */ +struct vhprintf_aux + { + char buf[64]; /* Character buffer. */ + char *p; /* Current position in buffer. */ + int char_cnt; /* Total characters written so far. */ + int handle; /* Output file handle. */ + }; + +static void add_char (char, void *); +static void flush (struct vhprintf_aux *); + +/* Formats the printf() format specification FORMAT with + arguments given in ARGS and writes the output to the given + HANDLE. */ +int +vhprintf (int handle, const char *format, va_list args) +{ + struct vhprintf_aux aux; + aux.p = aux.buf; + aux.char_cnt = 0; + aux.handle = handle; + __vprintf (format, args, add_char, &aux); + flush (&aux); + return aux.char_cnt; +} + +/* Adds C to the buffer in AUX, flushing it if the buffer fills + up. */ +static void +add_char (char c, void *aux_) +{ + struct vhprintf_aux *aux = aux_; + *aux->p++ = c; + if (aux->p >= aux->buf + sizeof aux->buf) + flush (aux); + aux->char_cnt++; +} + +/* Flushes the buffer in AUX. */ +static void +flush (struct vhprintf_aux *aux) +{ + if (aux->p > aux->buf) + write (aux->handle, aux->buf, aux->p - aux->buf); + aux->p = aux->buf; +} diff --git a/pintos-progos/lib/user/debug.c b/pintos-progos/lib/user/debug.c new file mode 100644 index 0000000..f49b874 --- /dev/null +++ b/pintos-progos/lib/user/debug.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include + +/* Aborts the user program, printing the source file name, line + number, and function name, plus a user-specific message. */ +void +debug_panic (const char *file, int line, const char *function, + const char *message, ...) +{ + va_list args; + + printf ("User process ABORT at %s:%d in %s(): ", file, line, function); + + va_start (args, message); + vprintf (message, args); + printf ("\n"); + va_end (args); + + debug_backtrace (); + + exit (1); +} diff --git a/pintos-progos/lib/user/entry.c b/pintos-progos/lib/user/entry.c new file mode 100644 index 0000000..a707c70 --- /dev/null +++ b/pintos-progos/lib/user/entry.c @@ -0,0 +1,10 @@ +#include + +int main (int, char *[]); +void _start (int argc, char *argv[]); + +void +_start (int argc, char *argv[]) +{ + exit (main (argc, argv)); +} diff --git a/pintos-progos/lib/user/stdio.h b/pintos-progos/lib/user/stdio.h new file mode 100644 index 0000000..b9f3cc6 --- /dev/null +++ b/pintos-progos/lib/user/stdio.h @@ -0,0 +1,7 @@ +#ifndef __LIB_USER_STDIO_H +#define __LIB_USER_STDIO_H + +int hprintf (int, const char *, ...) PRINTF_FORMAT (2, 3); +int vhprintf (int, const char *, va_list) PRINTF_FORMAT (2, 0); + +#endif /* lib/user/stdio.h */ diff --git a/pintos-progos/lib/user/syscall.c b/pintos-progos/lib/user/syscall.c new file mode 100644 index 0000000..0a467f8 --- /dev/null +++ b/pintos-progos/lib/user/syscall.c @@ -0,0 +1,184 @@ +#include +#include "../syscall-nr.h" + +/* Invokes syscall NUMBER, passing no arguments, and returns the + return value as an `int'. */ +#define syscall0(NUMBER) \ + ({ \ + int retval; \ + asm volatile \ + ("pushl %[number]; int $0x30; addl $4, %%esp" \ + : "=a" (retval) \ + : [number] "i" (NUMBER) \ + : "memory"); \ + retval; \ + }) + +/* Invokes syscall NUMBER, passing argument ARG0, and returns the + return value as an `int'. */ +#define syscall1(NUMBER, ARG0) \ + ({ \ + int retval; \ + asm volatile \ + ("pushl %[arg0]; pushl %[number]; int $0x30; addl $8, %%esp" \ + : "=a" (retval) \ + : [number] "i" (NUMBER), \ + [arg0] "g" (ARG0) \ + : "memory"); \ + retval; \ + }) + +/* Invokes syscall NUMBER, passing arguments ARG0 and ARG1, and + returns the return value as an `int'. */ +#define syscall2(NUMBER, ARG0, ARG1) \ + ({ \ + int retval; \ + asm volatile \ + ("pushl %[arg1]; pushl %[arg0]; " \ + "pushl %[number]; int $0x30; addl $12, %%esp" \ + : "=a" (retval) \ + : [number] "i" (NUMBER), \ + [arg0] "r" (ARG0), \ + [arg1] "r" (ARG1) \ + : "memory"); \ + retval; \ + }) + +/* Invokes syscall NUMBER, passing arguments ARG0, ARG1, and + ARG2, and returns the return value as an `int'. */ +#define syscall3(NUMBER, ARG0, ARG1, ARG2) \ + ({ \ + int retval; \ + asm volatile \ + ("pushl %[arg2]; pushl %[arg1]; pushl %[arg0]; " \ + "pushl %[number]; int $0x30; addl $16, %%esp" \ + : "=a" (retval) \ + : [number] "i" (NUMBER), \ + [arg0] "r" (ARG0), \ + [arg1] "r" (ARG1), \ + [arg2] "r" (ARG2) \ + : "memory"); \ + retval; \ + }) + +void +halt (void) +{ + syscall0 (SYS_HALT); + NOT_REACHED (); +} + +void +exit (int status) +{ + syscall1 (SYS_EXIT, status); + NOT_REACHED (); +} + +pid_t +exec (const char *cmd_line) +{ + return (pid_t) syscall1 (SYS_EXEC, cmd_line); +} + +int +wait (pid_t pid) +{ + return syscall1 (SYS_WAIT, pid); +} + +bool +create (const char *file, unsigned initial_size) +{ + return syscall2 (SYS_CREATE, file, initial_size); +} + +bool +remove (const char *file) +{ + return syscall1 (SYS_REMOVE, file); +} + +int +open (const char *file) +{ + return syscall1 (SYS_OPEN, file); +} + +int +filesize (int fd) +{ + return syscall1 (SYS_FILESIZE, fd); +} + +int +read (int fd, void *buffer, unsigned size) +{ + return syscall3 (SYS_READ, fd, buffer, size); +} + +int +write (int fd, const void *buffer, unsigned size) +{ + return syscall3 (SYS_WRITE, fd, buffer, size); +} + +void +seek (int fd, unsigned position) +{ + syscall2 (SYS_SEEK, fd, position); +} + +unsigned +tell (int fd) +{ + return syscall1 (SYS_TELL, fd); +} + +void +close (int fd) +{ + syscall1 (SYS_CLOSE, fd); +} + +mapid_t +mmap (int fd, void *addr) +{ + return syscall2 (SYS_MMAP, fd, addr); +} + +void +munmap (mapid_t mapid) +{ + syscall1 (SYS_MUNMAP, mapid); +} + +bool +chdir (const char *dir) +{ + return syscall1 (SYS_CHDIR, dir); +} + +bool +mkdir (const char *dir) +{ + return syscall1 (SYS_MKDIR, dir); +} + +bool +readdir (int fd, char name[READDIR_MAX_LEN + 1]) +{ + return syscall2 (SYS_READDIR, fd, name); +} + +bool +isdir (int fd) +{ + return syscall1 (SYS_ISDIR, fd); +} + +int +inumber (int fd) +{ + return syscall1 (SYS_INUMBER, fd); +} diff --git a/pintos-progos/lib/user/syscall.h b/pintos-progos/lib/user/syscall.h new file mode 100644 index 0000000..a1bcf0b --- /dev/null +++ b/pintos-progos/lib/user/syscall.h @@ -0,0 +1,48 @@ +#ifndef __LIB_USER_SYSCALL_H +#define __LIB_USER_SYSCALL_H + +#include +#include + +/* Process identifier. */ +typedef int pid_t; +#define PID_ERROR ((pid_t) -1) + +/* Map region identifier. */ +typedef int mapid_t; +#define MAP_FAILED ((mapid_t) -1) + +/* Maximum characters in a filename written by readdir(). */ +#define READDIR_MAX_LEN 14 + +/* Typical return values from main() and arguments to exit(). */ +#define EXIT_SUCCESS 0 /* Successful execution. */ +#define EXIT_FAILURE 1 /* Unsuccessful execution. */ + +/* Projects 2 and later. */ +void halt (void) NO_RETURN; +void exit (int status) NO_RETURN; +pid_t exec (const char *cmd_line); +int wait (pid_t); +bool create (const char *file, unsigned initial_size); +bool remove (const char *file); +int open (const char *file); +int filesize (int fd); +int read (int fd, void *buffer, unsigned length); +int write (int fd, const void *buffer, unsigned length); +void seek (int fd, unsigned position); +unsigned tell (int fd); +void close (int fd); + +/* Project 3 and optionally project 4. */ +mapid_t mmap (int fd, void *addr); +void munmap (mapid_t); + +/* Project 4 only. */ +bool chdir (const char *dir); +bool mkdir (const char *dir); +bool readdir (int fd, char name[READDIR_MAX_LEN + 1]); +bool isdir (int fd); +int inumber (int fd); + +#endif /* lib/user/syscall.h */ diff --git a/pintos-progos/lib/user/user.lds b/pintos-progos/lib/user/user.lds new file mode 100644 index 0000000..cc6d6c0 --- /dev/null +++ b/pintos-progos/lib/user/user.lds @@ -0,0 +1,57 @@ +OUTPUT_FORMAT("elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) + +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + __executable_start = 0x08048000 + SIZEOF_HEADERS; + . = 0x08048000 + SIZEOF_HEADERS; + .text : { *(.text) } = 0x90 + .rodata : { *(.rodata) } + + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = ALIGN (0x1000) - ((0x1000 - .) & (0x1000 - 1)); + . = DATA_SEGMENT_ALIGN (0x1000, 0x1000); + + .data : { *(.data) } + .bss : { *(.bss) } + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /DISCARD/ : { *(.note.GNU-stack) } + /DISCARD/ : { *(.eh_frame) } +} diff --git a/pintos-progos/lib/ustar.c b/pintos-progos/lib/ustar.c new file mode 100644 index 0000000..49af69a --- /dev/null +++ b/pintos-progos/lib/ustar.c @@ -0,0 +1,228 @@ +#include +#include +#include +#include +#include + +/* Header for ustar-format tar archive. See the documentation of + the "pax" utility in [SUSv3] for the the "ustar" format + specification. */ +struct ustar_header + { + char name[100]; /* File name. Null-terminated if room. */ + char mode[8]; /* Permissions as octal string. */ + char uid[8]; /* User ID as octal string. */ + char gid[8]; /* Group ID as octal string. */ + char size[12]; /* File size in bytes as octal string. */ + char mtime[12]; /* Modification time in seconds + from Jan 1, 1970, as octal string. */ + char chksum[8]; /* Sum of octets in header as octal string. */ + char typeflag; /* An enum ustar_type value. */ + char linkname[100]; /* Name of link target. + Null-terminated if room. */ + char magic[6]; /* "ustar\0" */ + char version[2]; /* "00" */ + char uname[32]; /* User name, always null-terminated. */ + char gname[32]; /* Group name, always null-terminated. */ + char devmajor[8]; /* Device major number as octal string. */ + char devminor[8]; /* Device minor number as octal string. */ + char prefix[155]; /* Prefix to file name. + Null-terminated if room. */ + char padding[12]; /* Pad to 512 bytes. */ + } +PACKED; + +/* Returns the checksum for the given ustar format HEADER. */ +static unsigned int +calculate_chksum (const struct ustar_header *h) +{ + const uint8_t *header = (const uint8_t *) h; + unsigned int chksum; + size_t i; + + chksum = 0; + for (i = 0; i < USTAR_HEADER_SIZE; i++) + { + /* The ustar checksum is calculated as if the chksum field + were all spaces. */ + const size_t chksum_start = offsetof (struct ustar_header, chksum); + const size_t chksum_end = chksum_start + sizeof h->chksum; + bool in_chksum_field = i >= chksum_start && i < chksum_end; + chksum += in_chksum_field ? ' ' : header[i]; + } + return chksum; +} + +/* Drop possibly dangerous prefixes from FILE_NAME and return the + stripped name. An archive with file names that start with "/" + or "../" could cause a naive tar extractor to write to + arbitrary parts of the file system, not just the destination + directory. We don't want to create such archives or be such a + naive extractor. + + The return value can be a suffix of FILE_NAME or a string + literal. */ +static const char * +strip_antisocial_prefixes (const char *file_name) +{ + while (*file_name == '/' + || !memcmp (file_name, "./", 2) + || !memcmp (file_name, "../", 3)) + file_name = strchr (file_name, '/') + 1; + return *file_name == '\0' || !strcmp (file_name, "..") ? "." : file_name; +} + +/* Composes HEADER as a USTAR_HEADER_SIZE (512)-byte archive + header in ustar format for a SIZE-byte file named FILE_NAME of + the given TYPE. The caller is responsible for writing the + header to a file or device. + + If successful, returns true. On failure (due to an + excessively long file name), returns false. */ +bool +ustar_make_header (const char *file_name, enum ustar_type type, + int size, char header[USTAR_HEADER_SIZE]) +{ + struct ustar_header *h = (struct ustar_header *) header; + + ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE); + ASSERT (type == USTAR_REGULAR || type == USTAR_DIRECTORY); + + /* Check file name. */ + file_name = strip_antisocial_prefixes (file_name); + if (strlen (file_name) > 99) + { + printf ("%s: file name too long\n", file_name); + return false; + } + + /* Fill in header except for final checksum. */ + memset (h, 0, sizeof *h); + strlcpy (h->name, file_name, sizeof h->name); + snprintf (h->mode, sizeof h->mode, "%07o", + type == USTAR_REGULAR ? 0644 : 0755); + strlcpy (h->uid, "0000000", sizeof h->uid); + strlcpy (h->gid, "0000000", sizeof h->gid); + snprintf (h->size, sizeof h->size, "%011o", size); + snprintf (h->mtime, sizeof h->size, "%011o", 1136102400); + h->typeflag = type; + strlcpy (h->magic, "ustar", sizeof h->magic); + h->version[0] = h->version[1] = '0'; + strlcpy (h->gname, "root", sizeof h->gname); + strlcpy (h->uname, "root", sizeof h->uname); + + /* Compute and fill in final checksum. */ + snprintf (h->chksum, sizeof h->chksum, "%07o", calculate_chksum (h)); + + return true; +} + +/* Parses a SIZE-byte octal field in S in the format used by + ustar format. If successful, stores the field's value in + *VALUE and returns true; on failure, returns false. + + ustar octal fields consist of a sequence of octal digits + terminated by a space or a null byte. The ustar specification + seems ambiguous as to whether these fields must be padded on + the left with '0's, so we accept any field that fits in the + available space, regardless of whether it fills the space. */ +static bool +parse_octal_field (const char *s, size_t size, unsigned long int *value) +{ + size_t ofs; + + *value = 0; + for (ofs = 0; ofs < size; ofs++) + { + char c = s[ofs]; + if (c >= '0' && c <= '7') + { + if (*value > ULONG_MAX / 8) + { + /* Overflow. */ + return false; + } + *value = c - '0' + *value * 8; + } + else if (c == ' ' || c == '\0') + { + /* End of field, but disallow completely empty + fields. */ + return ofs > 0; + } + else + { + /* Bad character. */ + return false; + } + } + + /* Field did not end in space or null byte. */ + return false; +} + +/* Returns true if the CNT bytes starting at BLOCK are all zero, + false otherwise. */ +static bool +is_all_zeros (const char *block, size_t cnt) +{ + while (cnt-- > 0) + if (*block++ != 0) + return false; + return true; +} + +/* Parses HEADER as a ustar-format archive header for a regular + file or directory. If successful, stores the archived file's + name in *FILE_NAME (as a pointer into HEADER or a string + literal), its type in *TYPE, and its size in bytes in *SIZE, + and returns a null pointer. On failure, returns a + human-readable error message. */ +const char * +ustar_parse_header (const char header[USTAR_HEADER_SIZE], + const char **file_name, enum ustar_type *type, int *size) +{ + const struct ustar_header *h = (const struct ustar_header *) header; + unsigned long int chksum, size_ul; + + ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE); + + /* Detect end of archive. */ + if (is_all_zeros (header, USTAR_HEADER_SIZE)) + { + *file_name = NULL; + *type = USTAR_EOF; + *size = 0; + return NULL; + } + + /* Validate ustar header. */ + if (memcmp (h->magic, "ustar", 6)) + return "not a ustar archive"; + else if (h->version[0] != '0' || h->version[1] != '0') + return "invalid ustar version"; + else if (!parse_octal_field (h->chksum, sizeof h->chksum, &chksum)) + return "corrupt chksum field"; + else if (chksum != calculate_chksum (h)) + return "checksum mismatch"; + else if (h->name[sizeof h->name - 1] != '\0' || h->prefix[0] != '\0') + return "file name too long"; + else if (h->typeflag != USTAR_REGULAR && h->typeflag != USTAR_DIRECTORY) + return "unimplemented file type"; + if (h->typeflag == USTAR_REGULAR) + { + if (!parse_octal_field (h->size, sizeof h->size, &size_ul)) + return "corrupt file size field"; + else if (size_ul > INT_MAX) + return "file too large"; + } + else + size_ul = 0; + + /* Success. */ + *file_name = strip_antisocial_prefixes (h->name); + *type = h->typeflag; + *size = size_ul; + return NULL; +} + diff --git a/pintos-progos/lib/ustar.h b/pintos-progos/lib/ustar.h new file mode 100644 index 0000000..43a5513 --- /dev/null +++ b/pintos-progos/lib/ustar.h @@ -0,0 +1,29 @@ +#ifndef __LIB_USTAR_H +#define __LIB_USTAR_H + +/* Support for the standard Posix "ustar" format. See the + documentation of the "pax" utility in [SUSv3] for the the + "ustar" format specification. */ + +#include + +/* Type of a file entry in an archive. + The values here are the bytes that appear in the file format. + Only types of interest to Pintos are listed here. */ +enum ustar_type + { + USTAR_REGULAR = '0', /* Ordinary file. */ + USTAR_DIRECTORY = '5', /* Directory. */ + USTAR_EOF = -1 /* End of archive (not an official value). */ + }; + +/* Size of a ustar archive header, in bytes. */ +#define USTAR_HEADER_SIZE 512 + +bool ustar_make_header (const char *file_name, enum ustar_type, + int size, char header[USTAR_HEADER_SIZE]); +const char *ustar_parse_header (const char header[USTAR_HEADER_SIZE], + const char **file_name, + enum ustar_type *, int *size); + +#endif /* lib/ustar.h */ diff --git a/pintos-progos/misc/0001-bochs-2.3.7-jitter.patch b/pintos-progos/misc/0001-bochs-2.3.7-jitter.patch new file mode 100644 index 0000000..44190e3 --- /dev/null +++ b/pintos-progos/misc/0001-bochs-2.3.7-jitter.patch @@ -0,0 +1,78 @@ +From 5e6cfa27ba6de331ecc142e7f65b4d1c2112b4e2 Mon Sep 17 00:00:00 2001 +From: Alex Busenius +Date: Mon, 27 Apr 2009 15:33:37 +0200 +Subject: bochs-2.3.7 jitter + +--- + bochs.h | 2 ++ + iodev/pit82c54.cc | 9 ++++++++- + main.cc | 8 ++++++++ + 3 files changed, 18 insertions(+), 1 deletions(-) + +diff --git a/bochs.h b/bochs.h +index 2a643cd..75bcd96 100644 +--- a/bochs.h ++++ b/bochs.h +@@ -630,4 +630,6 @@ void bx_center_print(FILE *file, const char *line, unsigned maxwidth); + + #endif + ++extern int jitter; ++ + #endif /* BX_BOCHS_H */ +diff --git a/iodev/pit82c54.cc b/iodev/pit82c54.cc +index 0d65768..31ac041 100644 +--- a/iodev/pit82c54.cc ++++ b/iodev/pit82c54.cc +@@ -28,6 +28,7 @@ + + #include "iodev.h" + #include "pit82c54.h" ++#include + #define LOG_THIS this-> + + +@@ -399,7 +400,13 @@ pit_82C54::clock(Bit8u cnum) + case 2: + if (thisctr.count_written) { + if (thisctr.triggerGATE || thisctr.first_pass) { +- set_count(thisctr, thisctr.inlatch); ++ unsigned n = thisctr.inlatch; ++ if (jitter && n > 5) { ++ n *= (double) rand() / RAND_MAX; ++ if (n < 5) ++ n = 5; ++ } ++ set_count(thisctr, n); + thisctr.next_change_time=(thisctr.count_binary-1) & 0xFFFF; + thisctr.null_count=0; + if (thisctr.inlatch==1) { +diff --git a/main.cc b/main.cc +index ebdf258..09cf661 100644 +--- a/main.cc ++++ b/main.cc +@@ -112,6 +112,7 @@ BOCHSAPI BX_MEM_C bx_mem; + #endif + + char *bochsrc_filename = NULL; ++int jitter = 0; + + void bx_print_header () + { +@@ -541,6 +542,13 @@ int bx_init_main(int argc, char *argv[]) + else if (!strcmp("-q", argv[arg])) { + SIM->get_param_enum(BXPN_BOCHS_START)->set(BX_QUICK_START); + } ++ else if (!strcmp ("-j", argv[arg])) { ++ if (++arg >= argc) BX_PANIC(("-j must be followed by a number")); ++ else { ++ jitter = 1; ++ srand(atoi(argv[arg])); ++ } ++ } + else if (!strcmp("-f", argv[arg])) { + if (++arg >= argc) BX_PANIC(("-f must be followed by a filename")); + else bochsrc_filename = argv[arg]; +-- +1.6.2.3 + diff --git a/pintos-progos/misc/0002-bochs-2.3.7-triple-fault.patch b/pintos-progos/misc/0002-bochs-2.3.7-triple-fault.patch new file mode 100644 index 0000000..c8698bd --- /dev/null +++ b/pintos-progos/misc/0002-bochs-2.3.7-triple-fault.patch @@ -0,0 +1,87 @@ +From 356b7e781c815c70c992d58360caa42f1776d06b Mon Sep 17 00:00:00 2001 +From: Alex Busenius +Date: Mon, 27 Apr 2009 17:09:27 +0200 +Subject: bochs-2.3.7 triple fault + +--- + cpu/cpu.h | 4 ++++ + cpu/exception.cc | 7 +++++++ + gdbstub.cc | 11 ++++++++--- + 3 files changed, 19 insertions(+), 3 deletions(-) + +diff --git a/cpu/cpu.h b/cpu/cpu.h +index 7c7b11b..c47133a 100644 +--- a/cpu/cpu.h ++++ b/cpu/cpu.h +@@ -903,6 +903,10 @@ public: // for now... + #endif + Bit8u trace; + ++#if BX_GDBSTUB ++ Bit8u ispanic; ++#endif ++ + // for paging + struct { + bx_TLB_entry entry[BX_TLB_SIZE] BX_CPP_AlignN(16); +diff --git a/cpu/exception.cc b/cpu/exception.cc +index c3e3777..fb3abfc 100644 +--- a/cpu/exception.cc ++++ b/cpu/exception.cc +@@ -856,6 +856,13 @@ void BX_CPU_C::exception(unsigned vector, Bit16u error_code, bx_bool trap) + // trap into debugger (similar as done when PANIC occured) + bx_debug_break(); + #endif ++#if BX_GDBSTUB ++ if (bx_dbg.gdbstub_enabled) { ++ fprintf(stderr, "Triple fault: stopping for gdb\n"); ++ BX_CPU_THIS_PTR ispanic = 1; ++ longjmp(BX_CPU_THIS_PTR jmp_buf_env, 1); ++ } ++#endif + if (SIM->get_param_bool(BXPN_RESET_ON_TRIPLE_FAULT)->get()) { + BX_ERROR(("exception(): 3rd (%d) exception with no resolution, shutdown status is %02xh, resetting", vector, DEV_cmos_get_reg(0x0f))); + bx_pc_system.Reset(BX_RESET_SOFTWARE); +diff --git a/gdbstub.cc b/gdbstub.cc +index f58f866..bc5ed61 100644 +--- a/gdbstub.cc ++++ b/gdbstub.cc +@@ -471,7 +471,12 @@ static void debug_loop(void) + } + + stub_trace_flag = 0; ++ bx_cpu.ispanic = 0; + bx_cpu.cpu_loop(0); ++ if (bx_cpu.ispanic) ++ { ++ last_stop_reason = GDBSTUB_EXECUTION_BREAKPOINT; ++ } + + DEV_vga_refresh(); + +@@ -502,19 +507,19 @@ static void debug_loop(void) + + BX_INFO(("stepping")); + stub_trace_flag = 1; ++ bx_cpu.ispanic = 0; + bx_cpu.cpu_loop(0); + DEV_vga_refresh(); + stub_trace_flag = 0; + BX_INFO(("stopped with %x", last_stop_reason)); + buf[0] = 'S'; +- if (last_stop_reason == GDBSTUB_EXECUTION_BREAKPOINT || +- last_stop_reason == GDBSTUB_TRACE) ++ if (last_stop_reason == GDBSTUB_TRACE && !bx_cpu.ispanic) + { + write_signal(&buf[1], SIGTRAP); + } + else + { +- write_signal(&buf[1], SIGTRAP); ++ write_signal(&buf[1], SIGSEGV); + } + put_reply(buf); + break; +-- +1.6.2.3 + diff --git a/pintos-progos/misc/0003-bochs-2.3.7-page-fault-segv.patch b/pintos-progos/misc/0003-bochs-2.3.7-page-fault-segv.patch new file mode 100644 index 0000000..8b6e090 --- /dev/null +++ b/pintos-progos/misc/0003-bochs-2.3.7-page-fault-segv.patch @@ -0,0 +1,93 @@ +From 314833401978558db06bbb4f4f76e4dc7b603744 Mon Sep 17 00:00:00 2001 +From: Alex Busenius +Date: Mon, 27 Apr 2009 16:33:54 +0200 +Subject: bochs-2.3.7 page fault segv + +--- + bochs.h | 1 + + cpu/exception.cc | 4 ++++ + gdbstub.cc | 17 ++++++++++++++++- + 3 files changed, 21 insertions(+), 1 deletions(-) + +diff --git a/bochs.h b/bochs.h +index 75bcd96..657c7b8 100644 +--- a/bochs.h ++++ b/bochs.h +@@ -433,6 +433,7 @@ BOCHSAPI extern logfunc_t *genlog; + void bx_gdbstub_init(void); + void bx_gdbstub_break(void); + int bx_gdbstub_check(unsigned int eip); ++void bx_gdbstub_exception(unsigned int nr); + #define GDBSTUB_STOP_NO_REASON (0xac0) + + #if BX_SUPPORT_SMP +diff --git a/cpu/exception.cc b/cpu/exception.cc +index fb3abfc..8dac5ca 100644 +--- a/cpu/exception.cc ++++ b/cpu/exception.cc +@@ -1046,6 +1046,10 @@ void BX_CPU_C::exception(unsigned vector, Bit16u error_code, bx_bool trap) + + BX_CPU_THIS_PTR errorno++; + ++#if BX_GDBSTUB ++ bx_gdbstub_exception(vector); ++#endif ++ + if (real_mode()) { + // not INT, no error code pushed + BX_CPU_THIS_PTR interrupt(vector, 0, 0, 0); +diff --git a/gdbstub.cc b/gdbstub.cc +index bc5ed61..ad59373 100644 +--- a/gdbstub.cc ++++ b/gdbstub.cc +@@ -47,6 +47,7 @@ static int last_stop_reason = GDBSTUB_STOP_NO_REASON; + #define GDBSTUB_EXECUTION_BREAKPOINT (0xac1) + #define GDBSTUB_TRACE (0xac2) + #define GDBSTUB_USER_BREAK (0xac3) ++#define GDBSTUB_EXCEPTION_0E (0xac4) + + static bx_list_c *gdbstub_list; + static int listen_socket_fd; +@@ -323,6 +324,12 @@ int bx_gdbstub_check(unsigned int eip) + return GDBSTUB_STOP_NO_REASON; + } + ++void bx_gdbstub_exception(unsigned int nr) ++{ ++ if (nr == 0x0e) ++ last_stop_reason = GDBSTUB_EXCEPTION_0E; ++} ++ + static int remove_breakpoint(unsigned int addr, int len) + { + unsigned int i; +@@ -493,6 +500,10 @@ static void debug_loop(void) + { + write_signal(&buf[1], SIGTRAP); + } ++ else if (last_stop_reason == GDBSTUB_EXCEPTION_0E) ++ { ++ write_signal(&buf[1], SIGSEGV); ++ } + else + { + write_signal(&buf[1], 0); +@@ -517,10 +528,14 @@ static void debug_loop(void) + { + write_signal(&buf[1], SIGTRAP); + } +- else ++ else if (last_stop_reason == GDBSTUB_EXCEPTION_0E) + { + write_signal(&buf[1], SIGSEGV); + } ++ else ++ { ++ write_signal(&buf[1], 0); ++ } + put_reply(buf); + break; + } +-- +1.6.2.3 + diff --git a/pintos-progos/misc/bochs-2.3.7-build.sh b/pintos-progos/misc/bochs-2.3.7-build.sh new file mode 100755 index 0000000..57e35d1 --- /dev/null +++ b/pintos-progos/misc/bochs-2.3.7-build.sh @@ -0,0 +1,42 @@ +#! /bin/sh -e + +if test -z "$SRCDIR" || test -z "$PINTOSDIR" || test -z "$DSTDIR"; then + echo "usage: env SRCDIR= PINTOSDIR= DSTDIR= sh $0" + echo " where contains bochs-2.3.7.tar.gz" + echo " and is the root of the pintos source tree" + echo " and is the installation prefix (e.g. /usr/local)" + exit 1 +fi + +cd /tmp +mkdir bochs-pintos-$$ +cd bochs-pintos-$$ +mkdir bochs-2.3.7 +tar xzf $SRCDIR/bochs-2.3.7.tar.gz +cd bochs-2.3.7 +cat $PINTOSDIR/src/misc/0001-bochs-2.3.7-jitter.patch | patch -p1 +cat $PINTOSDIR/src/misc/0002-bochs-2.3.7-triple-fault.patch | patch -p1 +cat $PINTOSDIR/src/misc/0003-bochs-2.3.7-page-fault-segv.patch | patch -p1 +cat $PINTOSDIR/src/misc/bochs-2.3.7-gcc43.patch | patch -p1 +cat $PINTOSDIR/src/misc/bochs-2.3.7-typos.patch | patch -p1 +cat $PINTOSDIR/src/misc/bochs-2.3.7-linux3x.patch | patch -p1 +autoconf + +CFGOPTIONAL="--enable-large-pages --enable-mmx --enable-usb --enable-pci --enable-pcidev --enable-acpi --enable-global-pages --enable-show-ips" +CFGOPTIMIZE="--enable-all-optimizations --enable-guest2host-tlb --enable-repeat-speedups --enable-trace-cache --enable-icache --enable-fast-function-calls --enable-idle-hack " +CFGOPTS="--prefix=$DSTDIR --enable-ignore-bad-msr --enable-disasm --enable-logging --enable-fpu --enable-alignment-check --enable-plugins --enable-cpu-level=6 --enable-readline --without-sdl --without-svga --without-wx --with-x --with-x11 --with-term --with-nogui $CFGOPTIONAL" +mkdir plain && + cd plain && + ../configure $CFGOPTS --enable-gdb-stub && +# make -j3 && + make && echo "done building plain" && + sudo make install && + cd .. && +mkdir with-dbg && + cd with-dbg && + ../configure --enable-debugger $CFGOPTS && + # make -j3 && + make && echo "done building with-dbg" && + sudo cp -v bochs $DSTDIR/bin/bochs-dbg && + cd .. && + echo "SUCCESS" diff --git a/pintos-progos/misc/bochs-2.3.7-gcc43.patch b/pintos-progos/misc/bochs-2.3.7-gcc43.patch new file mode 100644 index 0000000..4646edf --- /dev/null +++ b/pintos-progos/misc/bochs-2.3.7-gcc43.patch @@ -0,0 +1,12 @@ +--- bochs-2.3.7.orig/bx_debug/symbols.cc 2008/03/30 14:32:14 1.11 ++++ bochs-2.3.7/bx_debug/symbols.cc 2008/06/16 17:09:52 1.12 +@@ -95,6 +95,9 @@ + #endif + + using namespace std; ++#ifdef __GNUC__ ++using namespace __gnu_cxx; ++#endif + + struct symbol_entry_t + { diff --git a/pintos-progos/misc/bochs-2.3.7-linux3x.patch b/pintos-progos/misc/bochs-2.3.7-linux3x.patch new file mode 100644 index 0000000..1c84060 --- /dev/null +++ b/pintos-progos/misc/bochs-2.3.7-linux3x.patch @@ -0,0 +1,11 @@ +--- a/configure.in 2012-01-03 11:12:22.104612131 +0100 ++++ b/configure.in 2012-01-03 11:13:05.507941106 +0100 +@@ -715,7 +715,7 @@ AC_ARG_ENABLE(pcidev, + PCIDEV_MODULE_MAKE_ALL="all-kernel24" + KERNEL_MODULE_SUFFIX="o" + ;; +- 2.6*) ++ 2.6*|3*) + PCIDEV_MODULE_MAKE_ALL="all-kernel26" + KERNEL_MODULE_SUFFIX="ko" + ;; diff --git a/pintos-progos/misc/bochs-2.3.7-typos.patch b/pintos-progos/misc/bochs-2.3.7-typos.patch new file mode 100644 index 0000000..c9fb168 --- /dev/null +++ b/pintos-progos/misc/bochs-2.3.7-typos.patch @@ -0,0 +1,24 @@ +diff -NaurwB bochs-2.3.7.orig/cpu/ia_opcodes.h bochs-2.3.7/cpu/ia_opcodes.h +--- bochs-2.3.7.orig/cpu/ia_opcodes.h 2008-05-30 22:35:08.000000000 +0200 ++++ bochs-2.3.7/cpu/ia_opcodes.h 2008-06-04 14:56:46.000000000 +0200 +@@ -891,7 +891,7 @@ + bx_define_opcode(BX_IA_PF2ID_PqQq, BX_CPU_C::PF2ID_PqQq) + bx_define_opcode(BX_IA_PF2IW_PqQq, BX_CPU_C::PF2IW_PqQq) + bx_define_opcode(BX_IA_PFACC_PqQq, BX_CPU_C::PFACC_PqQq) +-bx_define_opcode(BX_IA_PFADD_PqQq, BX_CPU_C::BX_PFADD_PqQq) ++bx_define_opcode(BX_IA_PFADD_PqQq, BX_CPU_C::PFADD_PqQq) + bx_define_opcode(BX_IA_PFCMPEQ_PqQq, BX_CPU_C::PFCMPEQ_PqQq) + bx_define_opcode(BX_IA_PFCMPGE_PqQq, BX_CPU_C::PFCMPGE_PqQq) + bx_define_opcode(BX_IA_PFCMPGT_PqQq, BX_CPU_C::PFCMPGT_PqQq) +diff -NaurwB bochs-2.3.7.orig/iodev/iodebug.h bochs-2.3.7/iodev/iodebug.h +--- bochs-2.3.7.orig/iodev/iodebug.h 2008-05-01 22:46:58.000000000 +0200 ++++ bochs-2.3.7/iodev/iodebug.h 2008-06-04 14:45:50.000000000 +0200 +@@ -18,7 +18,7 @@ + virtual void init(void); + virtual void reset (unsigned type) {} + static void mem_write(BX_CPU_C *cpu, bx_phy_address addr, unsigned len, void *data); +- static void mem_read(BX_CPU_C *cpu, bx_phy_addressu addr, unsigned len, void *data); ++ static void mem_read(BX_CPU_C *cpu, bx_phy_address addr, unsigned len, void *data); + + private: + static Bit32u read_handler(void *this_ptr, Bit32u address, unsigned io_len); diff --git a/pintos-progos/misc/gcc-3.3.6-cross-howto b/pintos-progos/misc/gcc-3.3.6-cross-howto new file mode 100644 index 0000000..ad25173 --- /dev/null +++ b/pintos-progos/misc/gcc-3.3.6-cross-howto @@ -0,0 +1,39 @@ +Here are the commands we used to build and install the SPARC +cross-compiler: + +PINTOSROOT=$HOME/private/pintos + +PREFIX=/usr/class/cs140/`uname -m` +PATH=$PATH:$PREFIX/bin +TMP=`pwd` + +wget ftp://ftp.gnu.org/pub/gnu/binutils/binutils-2.15.tar.bz2 +wget ftp://sources.redhat.com/pub/newlib/newlib-1.13.0.tar.gz +wget ftp://ftp.gnu.org/pub/gnu/gcc/gcc-3.3.6/gcc-core-3.3.6.tar.bz2 +wget ftp://ftp.gnu.org/pub/gnu/gdb/gdb-6.3.tar.bz2 + +bzcat binutils-2.15.tar.bz2 | tar x +tar xzf newlib-1.13.0.tar.gz +bzcat gcc-core-3.3.6.tar.bz2 | tar x +bzcat gdb-6.3.tar.bz2 | tar x + +cd $TMP/binutils-2.15 +mkdir i386 +cd i386 +../configure --target=i386-elf --prefix=$PREFIX +make LDFLAGS=-lintl +make install + +cd $TMP/gcc-3.3.6 +mkdir i386 +cd i386 +../configure --target=i386-elf --prefix=$PREFIX --with-gnu-as --with-as=$PREFIX/bin/i386-elf-as --with-gnu-ld --with-ld=$PREFIX/bin/i386-elf-ld --with-headers=$TMP/newlib-1.13.0/newlib/libc/include --with-newlib +make +make install + +cd $TMP/gdb-6.3 +mkdir i386 +cd i386 +../configure --target=i386-elf --prefix=$PREFIX --disable-tui +make LDFLAGS=-lintl +make install diff --git a/pintos-progos/misc/gdb-macros b/pintos-progos/misc/gdb-macros new file mode 100644 index 0000000..a0b68c3 --- /dev/null +++ b/pintos-progos/misc/gdb-macros @@ -0,0 +1,140 @@ +# +# A set of useful macros that can help debug Pintos. +# +# Include with "source" cmd in gdb. +# Use "help user-defined" for help. +# +# Author: Godmar Back , Feb 2006 +# +# $Id: gdb-macros,v 1.1 2006-04-07 18:29:34 blp Exp $ +# + +# for internal use +define offsetof + set $rc = (char*)&((struct $arg0 *)0)->$arg1 - (char*)0 +end + +define list_entry + offsetof $arg1 $arg2 + set $rc = ((struct $arg1 *) ((uint8_t *) ($arg0) - $rc)) +end + +# dump a Pintos list +define dumplist + set $list = $arg0 + set $e = $list->head.next + set $i = 0 + while $e != &(($arg0).tail) + list_entry $e $arg1 $arg2 + set $l = $rc + printf "pintos-debug: dumplist #%d: %p ", $i++, $l + output *$l + set $e = $e->next + printf "\n" + end +end + +document dumplist + Dump the content of a Pintos list, + invoke as dumplist name_of_list name_of_struct name_of_elem_in_list_struct +end + +# print a thread's backtrace, given a pointer to the struct thread * +define btthread + if $arg0 == ($esp - ((unsigned)$esp % 4096)) + bt + else + set $saveEIP = $eip + set $saveESP = $esp + set $saveEBP = $ebp + + set $esp = ((struct thread *)$arg0)->stack + set $ebp = ((void**)$esp)[2] + set $eip = ((void**)$esp)[4] + + bt + + set $eip = $saveEIP + set $esp = $saveESP + set $ebp = $saveEBP + end +end +document btthread + Show the backtrace of a thread, + invoke as btthread pointer_to_struct_thread +end + +# print backtraces associated with all threads in a list +define btthreadlist + set $list = $arg0 + set $e = $list->head.next + while $e != &(($arg0).tail) + list_entry $e thread $arg1 + printf "pintos-debug: dumping backtrace of thread '%s' @%p\n", \ + ((struct thread*)$rc)->name, $rc + btthread $rc + set $e = $e->next + printf "\n" + end +end +document btthreadlist + Given a list of threads, print each thread's backtrace + invoke as btthreadlist name_of_list name_of_elem_in_list_struct +end + +# print backtraces of all threads (based on 'all_list' all threads list) +define btthreadall + btthreadlist all_list allelem +end +document btthreadall + Print backtraces of all threads +end + +# print a correct backtrace by adjusting $eip +# this works best right at intr0e_stub +define btpagefault + set $saveeip = $eip + set $eip = ((void**)$esp)[1] + backtrace + set $eip = $saveeip +end +document btpagefault + Print a backtrace of the current thread after a pagefault +end + +# invoked whenever the program stops +define hook-stop + # stopped at stub #0E = #14 (page fault exception handler stub) + if ($eip == intr0e_stub) + set $savedeip = ((void**)$esp)[1] + # if this was in user mode, the OS should handle it + # either handle the page fault or terminate the process + if ($savedeip < 0xC0000000) + printf "pintos-debug: a page fault exception occurred in user mode\n" + printf "pintos-debug: hit 'c' to continue, or 's' to step to intr_handler\n" + else + # if this was in kernel mode, a stack trace might be useful + printf "pintos-debug: a page fault occurred in kernel mode\n" + btpagefault + end + end +end + +# load symbols for a Pintos user program +define loadusersymbols + shell objdump -h $arg0 | awk '/.text/ { print "add-symbol-file $arg0 0x"$4 }' > .loadsymbols + source .loadsymbols + shell rm -f .loadsymbols +end +document loadusersymbols + Load the symbols contained in a user program's executable. + Example: + loadusersymbols tests/userprog/exec-multiple +end + +define debugpintos + target remote localhost:1234 +end +document debugpintos + Attach debugger to pintos process +end diff --git a/pintos-progos/notes/1.txt b/pintos-progos/notes/1.txt new file mode 100644 index 0000000..9d478f7 --- /dev/null +++ b/pintos-progos/notes/1.txt @@ -0,0 +1,81 @@ +Getting Started with PINTOS +=========================== + +Building Project 1 +------------------ + +pintos $ cd src/threads +threads $ make + + +Building Bochs +-------------- +You should have a patched bochs install available. + +See + + http://courses.mpi-sws.org/os-ss11/assignments/pintos/pintos_12.html#SEC160 + +There is a build script src/misc/bochs-2.3.7-build.sh in the pintos fork from Saarland, +which (after small modifications) works on a modern Ubuntu x86. + +For Ubuntu 11 with a Linux 3.0 kernel, you need to: + + * Regenerate the configure script (autoconf configure.in) + * Patch the test for Linux 2.4 or 2.6 + +After building, copy bochs and bochs-gdb to the pintos/src/utils directory + +Running +------- + + # [pintos/src] + PATH=`pwd`/utils:$PATH + + cd threads/build + # [pintos/src/threads/build] + pintos run alarm-multiple > logfile + + +### Reproducability + +This command line flags to pintos influence reproducability. +Remember: you need the patched bochs build. + + -j seed ... Reproducible behavior + -r ... Real-Time behavior + +Running with qemu +----------------- + + # [pintos/src] + vim utils/pintos # comment line with -no-kqemu flag + + cd threads/build + # [pintos/src/threads/build] + pintos --qemu -- run alarm-multiple + +Debugging +--------- + +pintos $ vim utils/pintos-gdb + + GDBMACROS=${PINTOS_SRC}/misc/gdb-macros + +[pts/0 build] $ pintos --gdb -- run alarm-multiple +[pts/1 build] $ pintos-gdb kernel.o +(gdb) debugpintos + +Testing +------- + +* Running all tests + + build $ make check + +* Running a single test + + build $ rm tests/threads/alarm-multiple.result + build $ make tests/threads/alarm-multiple.result + + diff --git a/pintos-progos/notes/2.txt b/pintos-progos/notes/2.txt new file mode 100644 index 0000000..c81b980 --- /dev/null +++ b/pintos-progos/notes/2.txt @@ -0,0 +1,164 @@ +Projekt 1 - Threads +=================== + +alarm clock +----------- +The simplest strategy is to maintain a wait list for +all threads blocked for sleep. + + * In 'timer_interrupt', check for threads which can be + unblocked from sleeping + * In 'sleep', set sleep timeout in thread, block the + thread and put it on the sleep list + +Notes: + + * There are three places where a thread is added to the + ready list: + - thread_init + - thread_yield + - thread_unblock + * Iterate list with removal: + for (e = list_begin (&list); e != list_end (&list); ) + if(...) + e = list_remove(e)->prev; + /* Unblock must be called AFTER removing, as thread.elem is reused */ + else + e = list_next(e); + +Stats: + + pintos/src/devices/timer.c | 40 ++++++++++++++++++++++++++++++++++++++-- + pintos/src/threads/thread.h | 3 +++ + 2 files changed, 41 insertions(+), 2 deletions(-) + + Design & Implementation time: 4 hours + +Priority Scheduler +------------------ + +A simple implementation of the priority scheduler (64 priority levels, round robin within +one priority group). + + * If a new task arrives with a higher priority, switch to this group + * If the currently active group is empty, search for the group with the next highest priority + +Notes: + + * thread_{init,unblock,yield} now call thread_ready, which updates the lowest ready priority + * The thread_unblock operation does not yield a new thread immediately. Therefore, we need to check + later whether we need to switch to a higher priority thread (via thread_yield). + As thread_unblock is called with interrupts off, it seemed best to perform + this check when interrupts are enabled. This is only necessary if a higher priority task + is ready. + * First attempt passed alarm-priority, but failed to pass the priority-preempt test. + But the debugging facilities are fantastic, so it was easy to spot the problem + * Wolfgang suggested to yield a software interrupt when unblocking instead of modifying + interrupt_enable. + +Stats: + + pintos/src/threads/interrupt.c | 3 +- + pintos/src/threads/thread.c | 60 ++++++++++++++++++++++++++++++++-------- + pintos/src/threads/thread.h | 1 + + 3 files changed, 51 insertions(+), 13 deletions(-) + + Design and implementation time: 3 hours + +Priority Locks +-------------- + +We also need to select higher priority task first from locks, semaphores and condition variables. +This easiest implementation searches for the thread with the highest priority in the wait queue. + +Notes: + + * It is sufficient to implement the priority based selection twice, for sema_up and + cond_signal. cond_signal is a little bit harder, as we need to store the priority + (or the waiting thread) in the semaphore_elem type + * There are some handy list utility functions; in this case, list_max does a fine job + for both sema_up and cond_signal + * It is difficult to implement this in an efficient (sublinear) way, because priority donation + may boost a thread at any time! + +Stats: + + pintos/src/threads/synch.c | 40 ++++++++++++++++++++++++++++++++++------ + 1 files changed, 34 insertions(+), 6 deletions(-) + + Design and Implementation time: 1 hour + +Priority Donation +----------------- +If a thread aquires a lock, the lock holder needs to be boosted to the donated priority. +We need to deal with nesting and chaining: + + * Lock/Thread correspondence: Each lock is associated with at most one thread that holds it. + Therefore, donated priority can be associated with a lock. + * If a thread t wants to obtain a lock L, and a thread with a lower priority holds it, + the thread holding the lock is boosted to the priority of the requesting thread + * Chaining: If the boosted thread is also blocked on a lock, than we also need to donate + the priority to that lock, in a transitive way. + * Nesting: If a thread may hold more than one lock, we need to keep track of the donation + to each lock. When a lock is released or the static priority changes, the highest priority + donated to other locks is assigned to the thread. + +With this information, the following rules seem suitable (without proof of correctness): + + * If thread T tries to aquire lock L + + ==> if(L.owner) + T.locked_on = L + donate_priority (L, T.priority) + end + donate_priority (L ,p) := + L.priority v= p + L.holder.priority v= p + donate_priority( L.holder.locked_on, p) + end + + * If a thread T aquires a lock L + + ==> L.holder = T + T.locks += L + T.lock_on = none + + + * If a thread T releases a lock L + + ==> L.holder = none + T.locks -= L + T.priority = max (T.locks.priority, static_priority) + +To implement this, each thread needs to maintain a list of locks, a static priority, +and a reference to the lock blocking it. + +Notes: + + * Difficult to design, really a challenge. + Literature (short paper) could be helpful. + Maybe it would be interesting to ask for correctness proof sketches? + + * First design was wrong, because I assumed locks are aquired in FIFO fashion + * First implementation failed to pass donate tests, due to a typo. Debugging + facilities are fantastic, no problem to spot this one. + * Next try, priority-donate-lower failed. Looking at the source code revealed that + we need to recompute the priority at the end of thread_set_priority. + * Next try, priority-donate-chain failed. Chaining is tricky to get right; + in my implementation, chained donations were lost after one lock was released. + + * It would be an interesting option to ask for new test cases from the students + * I think it would also be a cool task to write a test for a RMS scheduling + scenario with blocking. + +Stats: + + pintos/src/threads/synch.c | 15 ++++++++++++ + pintos/src/threads/synch.h | 6 +++- + pintos/src/threads/thread.c | 53 +++++++++++++++++++++++++++++++++++++++++- + pintos/src/threads/thread.h | 9 ++++++- + pintos/src/utils/pintos | 2 +- + + Design: 5h + Implementation: 3h + diff --git a/pintos-progos/notes/3.txt b/pintos-progos/notes/3.txt new file mode 100644 index 0000000..bc64f88 --- /dev/null +++ b/pintos-progos/notes/3.txt @@ -0,0 +1,241 @@ +Project 2 +========= + +Working with Disks +------------------ + +Assumes you ran make in src/userprog and src/examples. + + * Create a 2 MB hard disk for pintos + + # [src/userprog/build] + pintos-mkdisk filesys.dsk --filesys-size=2 + + * Format Disk + + # -f ... format virtual disk + pintos -f -q + + * Copy file to filesystem + + # -p FILE ... file to put on virtual disk + # -a FILE ... newname on virtual disk + pintos -p ../../examples/echo -a echo -- -q + + * Execute echo, and get file 'echo' from the virtual disk + + pintos -g echo -- -q run 'echo x' + +Putting all together, we can run an minimal example like that: + + # [src/userprog/build] + pintos --filesys-size=2 -p ../../examples/halt -a halt -- -f -q run 'halt' + +Getting Started +--------------- + + * Fix the problem with the .note.gnu.build-id segment + + * Change the stack setup in process.c#setup_stack() to + + *esp = PHYS_BASE - 12; + + * Change process_wait() to an infinite loop + +This should be enough to see 'system call!' when executing +the 'halt' example. + +Next, we need to implement user memory access and the +the system call dispatcher, as well as the basic +system calls halt, exit and write. + +A simple implementation of user memory access first checks +whether the address is in user space, and the calls load_page. + +For an initial system call dispatcher, we convert the stack pointer +saved by the processor during the interrupt to kernel space, and +then dispatch to halt, exit and write. For now, exit just terminates +the process, and write uses printf, ignoring the fd argument. +The return value is stored into %eax. + +Notes: + * halt(): There is no function shutdown() in init.h, only + shutdown_poweroff in shutdown.h + + * When accessing data from user space in kernel space, we need to be + sure that the entire address ranged accessed is in user space. + Note that pointers are not necessarily aligned, and thus might + involve two user pages. + Furthermore, buffers need to be copied to kernel space; + otherwise, concurrent user space operations could corrupt the kernel. + Linux allows at most one kernel page for such buffers; we follow + the same route. + + * Debugging: the function hex_dump() is useful; no need to + reimplement it. + + * Something went wrong with the write system call, and this + is rather tricky to debug. + I invoked the system call directly, using inline + assembly; this worked fine? + Then I tried to debug the user space program; to this + end, lookup the code address you are interested in, + and use gdb together with objdump for debugging: + + Debugging 'write(1,"USA\n",4)' + + break *0x0804820e # break at + cont # pushl 0xc(%esp) + info registers # esp = 0xbfffffbc + x/1w (0xbfffffbc+0xc) # ==> 4 (length) + stepi # pushl 0x8(%esp) + info registers # esp = 0x......b8 + x/1w 0xbfffffb8 # ==> 4 (TOS) + x/1w (0xbfffffb8+8) # ==> 1 (wrong) !!! + + Apparently, the inline assembler in pintos does not use + the right constraints. + +Stat: + pintos/src/lib/user/syscall.c | 6 +- + pintos/src/userprog/process.c | 5 ++- + pintos/src/userprog/syscall.c | 92 ++++++++++++++++++++++++++++++++++++++-- + + Reading and Implementation Time: 6 hours + Debugging Syscalls: 5 hours + + +Argument Passing +---------------- +First, we tokenize the command using strtok_r, and then setup +the stack. + +Notes: + * As noted in the doc, just using strtok_r seems fine. + However, as strtok_r modifies the string even if only + the first token is needed, some copying is involved + if it is used to obtain the filename. + * Due to the detailed description in the documentation, + setting up the stack is mostly implementation work. + * One of the trickier implementation aspects is that we + modify the stack in kernel space, but need to convert + pointers to user space before pushing them on the stack. + * Debugging: Optimizations were really troublesome debugging + this task; making setup_stack non-static at least helped + to set a suitable breakpoint. In the end, printf was the + better debugging aid for this task. + +Stat: + pintos/src/userprog/process.c | 116 +++++++++++++++++++++++++++++++++-------- + + Design and Implementation Time: 4 hours + + +Process Management: exec, wait and exit +--------------------------------------- +The wait system call requires that all children +of a process are known, that the exit code of +a process is stored until collected by the parent, +and that the parent can block until the child +process terminates. + +One difficult aspect in the design is that kernel +threads are not processes, and that child threads +may exit after their parent. It is important to +note that threads do not need to wait for their +children, but that we need to keep the exit status +until the parent exits. + +In the original design, a thread is cleaned up when +in the scheduler right after it died. In our design +we delay the cleanup if the parent thread is still alive. + +Another issue is that thread_create needs to block +until the load process of the child thread has finished. + +Notes: + * I wanted to use the same semaphore for startup and wait. + This works, but we need one additional variable (or bit) + to distinguish failure at load time from failure at + runtime. + * Ugly 1: thread_create only gives back a tid, + so it is not possible to directly access the semaphore + in process_execute. Therefore we need to iterate over the + child list (which is not that bad, because if loading failed, + the child needs to be removed from the list anyway). + * Ugly 2: We up the semaphore used to synchronize + with process_execute and process_wait in thread.c, for + all threads. + * As also noted by rene, it is important to identifiy memory leaks, + as early as possible. To this end, first add debug messages to + page_alloc/page_free, and then run test programs to identify leaking + pages. Then debug, add conditional breakpoints to stop when a leaking + page is allocated, and inspect the stacktrace to find the culprit. + +Stats: + pintos/src/threads/thread.c | 31 +++++++++++++++++--- + pintos/src/threads/thread.h | 8 +++++ + pintos/src/userprog/process.c | 60 ++++++++++++++++++++++++++++++++-------- + pintos/src/userprog/syscall.c | 19 +++++++++--- + + Design and Implementation Time: 7 hours + +File I/O System Calls +--------------------- +For file I/O we need to implement synchronization (filesys is not thread safe). +The documentation states that it is not recommended to modify the code in +the filesys directory for now. A very simple solution is to use one lock for all filesystem operations, including process.c#load. +Furthermore, we need to deny writes to a a file currently running as a user +space process. + +Notes: + * init_thread() must not aquire locks, and thus not allocate pages. + Otherwise, the initialization of the init thread fails. + * The {lg,sm}-full tests failed in the initial implementation; + apparently, the read/write system calls should always read/write the + full amount of bytes specified to pass this tests. This was not + clear from the assignment. + * It is not obvious that file_close calls file_allow_write. But an + executable should not be writeable during its execution. Therefore, + one needs to make sure that it stays write protected after loading + has finished. I solve this by keeping the executable open during + execution. + * The multi-oom test failed again; debugging revealed that I forgot + to close all files at process_exit. + +Stats: + + pintos/src/threads/thread.c | 1 + + pintos/src/threads/thread.h | 6 +- + pintos/src/userprog/process.c | 53 ++++- + pintos/src/userprog/process.h | 2 + + pintos/src/userprog/syscall.c | 435 +++++++++++++++++++++++++++++++----- + pintos/src/userprog/syscall.h | 1 + + 6 files changed, 381 insertions(+), 117 deletions(-) + Design and Implementation Time: 8 hours + +Improved User Memory Access +--------------------------- +Looking at Project 3, it is a much better idea to not check whether a user +space page is valid, but just let the page fault handler do the job. +I decided to exit the process in the page fault handler if the address +is in user space. One needs to take care of temporary memory allocated +by the syscall handler, to avoid memory leaks. To this end, temporary kernel +pages allocated in the handler are recorded and either freed at the end +of the syscall or the end of the process. + +Notes: + * When using this approach, it is vital to copy user buffers + before reading or writing. With virtual memory, a page fault may + require to access the file system, and thus may cause race + conditions during access to the file system + +Stats: + pintos/src/threads/thread.h | 5 +- + pintos/src/userprog/exception.c | 17 ++- + pintos/src/userprog/process.c | 2 +- + pintos/src/userprog/syscall.c | 314 +++++++++++++++++++-------------------- + pintos/src/userprog/syscall.h | 2 +- + 5 files changed, 173 insertions(+), 167 deletions(-) + + Implementation Time: 3 hours diff --git a/pintos-progos/tests/Algorithm/Diff.pm b/pintos-progos/tests/Algorithm/Diff.pm new file mode 100644 index 0000000..904c530 --- /dev/null +++ b/pintos-progos/tests/Algorithm/Diff.pm @@ -0,0 +1,1713 @@ +package Algorithm::Diff; +# Skip to first "=head" line for documentation. +use strict; + +use integer; # see below in _replaceNextLargerWith() for mod to make + # if you don't use this +use vars qw( $VERSION @EXPORT_OK ); +$VERSION = 1.19_01; +# ^ ^^ ^^-- Incremented at will +# | \+----- Incremented for non-trivial changes to features +# \-------- Incremented for fundamental changes +require Exporter; +*import = \&Exporter::import; +@EXPORT_OK = qw( + prepare LCS LCDidx LCS_length + diff sdiff compact_diff + traverse_sequences traverse_balanced +); + +# McIlroy-Hunt diff algorithm +# Adapted from the Smalltalk code of Mario I. Wolczko, +# by Ned Konz, perl@bike-nomad.com +# Updates by Tye McQueen, http://perlmonks.org/?node=tye + +# Create a hash that maps each element of $aCollection to the set of +# positions it occupies in $aCollection, restricted to the elements +# within the range of indexes specified by $start and $end. +# The fourth parameter is a subroutine reference that will be called to +# generate a string to use as a key. +# Additional parameters, if any, will be passed to this subroutine. +# +# my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen ); + +sub _withPositionsOfInInterval +{ + my $aCollection = shift; # array ref + my $start = shift; + my $end = shift; + my $keyGen = shift; + my %d; + my $index; + for ( $index = $start ; $index <= $end ; $index++ ) + { + my $element = $aCollection->[$index]; + my $key = &$keyGen( $element, @_ ); + if ( exists( $d{$key} ) ) + { + unshift ( @{ $d{$key} }, $index ); + } + else + { + $d{$key} = [$index]; + } + } + return wantarray ? %d : \%d; +} + +# Find the place at which aValue would normally be inserted into the +# array. If that place is already occupied by aValue, do nothing, and +# return undef. If the place does not exist (i.e., it is off the end of +# the array), add it to the end, otherwise replace the element at that +# point with aValue. It is assumed that the array's values are numeric. +# This is where the bulk (75%) of the time is spent in this module, so +# try to make it fast! + +sub _replaceNextLargerWith +{ + my ( $array, $aValue, $high ) = @_; + $high ||= $#$array; + + # off the end? + if ( $high == -1 || $aValue > $array->[-1] ) + { + push ( @$array, $aValue ); + return $high + 1; + } + + # binary search for insertion point... + my $low = 0; + my $index; + my $found; + while ( $low <= $high ) + { + $index = ( $high + $low ) / 2; + + # $index = int(( $high + $low ) / 2); # without 'use integer' + $found = $array->[$index]; + + if ( $aValue == $found ) + { + return undef; + } + elsif ( $aValue > $found ) + { + $low = $index + 1; + } + else + { + $high = $index - 1; + } + } + + # now insertion point is in $low. + $array->[$low] = $aValue; # overwrite next larger + return $low; +} + +# This method computes the longest common subsequence in $a and $b. + +# Result is array or ref, whose contents is such that +# $a->[ $i ] == $b->[ $result[ $i ] ] +# foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined. + +# An additional argument may be passed; this is a hash or key generating +# function that should return a string that uniquely identifies the given +# element. It should be the case that if the key is the same, the elements +# will compare the same. If this parameter is undef or missing, the key +# will be the element as a string. + +# By default, comparisons will use "eq" and elements will be turned into keys +# using the default stringizing operator '""'. + +# Additional parameters, if any, will be passed to the key generation +# routine. + +sub _longestCommonSubsequence +{ + my $a = shift; # array ref or hash ref + my $b = shift; # array ref or hash ref + my $counting = shift; # scalar + my $keyGen = shift; # code ref + my $compare; # code ref + + if ( ref($a) eq 'HASH' ) + { # prepared hash must be in $b + my $tmp = $b; + $b = $a; + $a = $tmp; + } + + # Check for bogus (non-ref) argument values + if ( !ref($a) || !ref($b) ) + { + my @callerInfo = caller(1); + die 'error: must pass array or hash references to ' . $callerInfo[3]; + } + + # set up code refs + # Note that these are optimized. + if ( !defined($keyGen) ) # optimize for strings + { + $keyGen = sub { $_[0] }; + $compare = sub { my ( $a, $b ) = @_; $a eq $b }; + } + else + { + $compare = sub { + my $a = shift; + my $b = shift; + &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ ); + }; + } + + my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] ); + my ( $prunedCount, $bMatches ) = ( 0, {} ); + + if ( ref($b) eq 'HASH' ) # was $bMatches prepared for us? + { + $bMatches = $b; + } + else + { + my ( $bStart, $bFinish ) = ( 0, $#$b ); + + # First we prune off any common elements at the beginning + while ( $aStart <= $aFinish + and $bStart <= $bFinish + and &$compare( $a->[$aStart], $b->[$bStart], @_ ) ) + { + $matchVector->[ $aStart++ ] = $bStart++; + $prunedCount++; + } + + # now the end + while ( $aStart <= $aFinish + and $bStart <= $bFinish + and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) ) + { + $matchVector->[ $aFinish-- ] = $bFinish--; + $prunedCount++; + } + + # Now compute the equivalence classes of positions of elements + $bMatches = + _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ ); + } + my $thresh = []; + my $links = []; + + my ( $i, $ai, $j, $k ); + for ( $i = $aStart ; $i <= $aFinish ; $i++ ) + { + $ai = &$keyGen( $a->[$i], @_ ); + if ( exists( $bMatches->{$ai} ) ) + { + $k = 0; + for $j ( @{ $bMatches->{$ai} } ) + { + + # optimization: most of the time this will be true + if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j ) + { + $thresh->[$k] = $j; + } + else + { + $k = _replaceNextLargerWith( $thresh, $j, $k ); + } + + # oddly, it's faster to always test this (CPU cache?). + if ( defined($k) ) + { + $links->[$k] = + [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ]; + } + } + } + } + + if (@$thresh) + { + return $prunedCount + @$thresh if $counting; + for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] ) + { + $matchVector->[ $link->[1] ] = $link->[2]; + } + } + elsif ($counting) + { + return $prunedCount; + } + + return wantarray ? @$matchVector : $matchVector; +} + +sub traverse_sequences +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $callbacks = shift || {}; + my $keyGen = shift; + my $matchCallback = $callbacks->{'MATCH'} || sub { }; + my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; + my $finishedACallback = $callbacks->{'A_FINISHED'}; + my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; + my $finishedBCallback = $callbacks->{'B_FINISHED'}; + my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); + + # Process all the lines in @$matchVector + my $lastA = $#$a; + my $lastB = $#$b; + my $bi = 0; + my $ai; + + for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ ) + { + my $bLine = $matchVector->[$ai]; + if ( defined($bLine) ) # matched + { + &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine; + &$matchCallback( $ai, $bi++, @_ ); + } + else + { + &$discardACallback( $ai, $bi, @_ ); + } + } + + # The last entry (if any) processed was a match. + # $ai and $bi point just past the last matching lines in their sequences. + + while ( $ai <= $lastA or $bi <= $lastB ) + { + + # last A? + if ( $ai == $lastA + 1 and $bi <= $lastB ) + { + if ( defined($finishedACallback) ) + { + &$finishedACallback( $lastA, @_ ); + $finishedACallback = undef; + } + else + { + &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB; + } + } + + # last B? + if ( $bi == $lastB + 1 and $ai <= $lastA ) + { + if ( defined($finishedBCallback) ) + { + &$finishedBCallback( $lastB, @_ ); + $finishedBCallback = undef; + } + else + { + &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA; + } + } + + &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA; + &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB; + } + + return 1; +} + +sub traverse_balanced +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $callbacks = shift || {}; + my $keyGen = shift; + my $matchCallback = $callbacks->{'MATCH'} || sub { }; + my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; + my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; + my $changeCallback = $callbacks->{'CHANGE'}; + my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); + + # Process all the lines in match vector + my $lastA = $#$a; + my $lastB = $#$b; + my $bi = 0; + my $ai = 0; + my $ma = -1; + my $mb; + + while (1) + { + + # Find next match indices $ma and $mb + do { + $ma++; + } while( + $ma <= $#$matchVector + && !defined $matchVector->[$ma] + ); + + last if $ma > $#$matchVector; # end of matchVector? + $mb = $matchVector->[$ma]; + + # Proceed with discard a/b or change events until + # next match + while ( $ai < $ma || $bi < $mb ) + { + + if ( $ai < $ma && $bi < $mb ) + { + + # Change + if ( defined $changeCallback ) + { + &$changeCallback( $ai++, $bi++, @_ ); + } + else + { + &$discardACallback( $ai++, $bi, @_ ); + &$discardBCallback( $ai, $bi++, @_ ); + } + } + elsif ( $ai < $ma ) + { + &$discardACallback( $ai++, $bi, @_ ); + } + else + { + + # $bi < $mb + &$discardBCallback( $ai, $bi++, @_ ); + } + } + + # Match + &$matchCallback( $ai++, $bi++, @_ ); + } + + while ( $ai <= $lastA || $bi <= $lastB ) + { + if ( $ai <= $lastA && $bi <= $lastB ) + { + + # Change + if ( defined $changeCallback ) + { + &$changeCallback( $ai++, $bi++, @_ ); + } + else + { + &$discardACallback( $ai++, $bi, @_ ); + &$discardBCallback( $ai, $bi++, @_ ); + } + } + elsif ( $ai <= $lastA ) + { + &$discardACallback( $ai++, $bi, @_ ); + } + else + { + + # $bi <= $lastB + &$discardBCallback( $ai, $bi++, @_ ); + } + } + + return 1; +} + +sub prepare +{ + my $a = shift; # array ref + my $keyGen = shift; # code ref + + # set up code ref + $keyGen = sub { $_[0] } unless defined($keyGen); + + return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ ); +} + +sub LCS +{ + my $a = shift; # array ref + my $b = shift; # array ref or hash ref + my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ ); + my @retval; + my $i; + for ( $i = 0 ; $i <= $#$matchVector ; $i++ ) + { + if ( defined( $matchVector->[$i] ) ) + { + push ( @retval, $a->[$i] ); + } + } + return wantarray ? @retval : \@retval; +} + +sub LCS_length +{ + my $a = shift; # array ref + my $b = shift; # array ref or hash ref + return _longestCommonSubsequence( $a, $b, 1, @_ ); +} + +sub LCSidx +{ + my $a= shift @_; + my $b= shift @_; + my $match= _longestCommonSubsequence( $a, $b, 0, @_ ); + my @am= grep defined $match->[$_], 0..$#$match; + my @bm= @{$match}[@am]; + return \@am, \@bm; +} + +sub compact_diff +{ + my $a= shift @_; + my $b= shift @_; + my( $am, $bm )= LCSidx( $a, $b, @_ ); + my @cdiff; + my( $ai, $bi )= ( 0, 0 ); + push @cdiff, $ai, $bi; + while( 1 ) { + while( @$am && $ai == $am->[0] && $bi == $bm->[0] ) { + shift @$am; + shift @$bm; + ++$ai, ++$bi; + } + push @cdiff, $ai, $bi; + last if ! @$am; + $ai = $am->[0]; + $bi = $bm->[0]; + push @cdiff, $ai, $bi; + } + push @cdiff, 0+@$a, 0+@$b + if $ai < @$a || $bi < @$b; + return wantarray ? @cdiff : \@cdiff; +} + +sub diff +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $retval = []; + my $hunk = []; + my $discard = sub { + push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ]; + }; + my $add = sub { + push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ]; + }; + my $match = sub { + push @$retval, $hunk + if 0 < @$hunk; + $hunk = [] + }; + traverse_sequences( $a, $b, + { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ ); + &$match(); + return wantarray ? @$retval : $retval; +} + +sub sdiff +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $retval = []; + my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) }; + my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) }; + my $change = sub { + push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] ); + }; + my $match = sub { + push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] ); + }; + traverse_balanced( + $a, + $b, + { + MATCH => $match, + DISCARD_A => $discard, + DISCARD_B => $add, + CHANGE => $change, + }, + @_ + ); + return wantarray ? @$retval : $retval; +} + +######################################## +my $Root= __PACKAGE__; +package Algorithm::Diff::_impl; +use strict; + +sub _Idx() { 0 } # $me->[_Idx]: Ref to array of hunk indices + # 1 # $me->[1]: Ref to first sequence + # 2 # $me->[2]: Ref to second sequence +sub _End() { 3 } # $me->[_End]: Diff between forward and reverse pos +sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items +sub _Base() { 5 } # $me->[_Base]: Added to range's min and max +sub _Pos() { 6 } # $me->[_Pos]: Which hunk is currently selected +sub _Off() { 7 } # $me->[_Off]: Offset into _Idx for current position +sub _Min() { -2 } # Added to _Off to get min instead of max+1 + +sub Die +{ + require Carp; + Carp::confess( @_ ); +} + +sub _ChkPos +{ + my( $me )= @_; + return if $me->[_Pos]; + my $meth= ( caller(1) )[3]; + Die( "Called $meth on 'reset' object" ); +} + +sub _ChkSeq +{ + my( $me, $seq )= @_; + return $seq + $me->[_Off] + if 1 == $seq || 2 == $seq; + my $meth= ( caller(1) )[3]; + Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" ); +} + +sub getObjPkg +{ + my( $us )= @_; + return ref $us if ref $us; + return $us . "::_obj"; +} + +sub new +{ + my( $us, $seq1, $seq2, $opts ) = @_; + my @args; + for( $opts->{keyGen} ) { + push @args, $_ if $_; + } + for( $opts->{keyGenArgs} ) { + push @args, @$_ if $_; + } + my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args ); + my $same= 1; + if( 0 == $cdif->[2] && 0 == $cdif->[3] ) { + $same= 0; + splice @$cdif, 0, 2; + } + my @obj= ( $cdif, $seq1, $seq2 ); + $obj[_End] = (1+@$cdif)/2; + $obj[_Same] = $same; + $obj[_Base] = 0; + my $me = bless \@obj, $us->getObjPkg(); + $me->Reset( 0 ); + return $me; +} + +sub Reset +{ + my( $me, $pos )= @_; + $pos= int( $pos || 0 ); + $pos += $me->[_End] + if $pos < 0; + $pos= 0 + if $pos < 0 || $me->[_End] <= $pos; + $me->[_Pos]= $pos || !1; + $me->[_Off]= 2*$pos - 1; + return $me; +} + +sub Base +{ + my( $me, $base )= @_; + my $oldBase= $me->[_Base]; + $me->[_Base]= 0+$base if defined $base; + return $oldBase; +} + +sub Copy +{ + my( $me, $pos, $base )= @_; + my @obj= @$me; + my $you= bless \@obj, ref($me); + $you->Reset( $pos ) if defined $pos; + $you->Base( $base ); + return $you; +} + +sub Next { + my( $me, $steps )= @_; + $steps= 1 if ! defined $steps; + if( $steps ) { + my $pos= $me->[_Pos]; + my $new= $pos + $steps; + $new= 0 if $pos && $new < 0; + $me->Reset( $new ) + } + return $me->[_Pos]; +} + +sub Prev { + my( $me, $steps )= @_; + $steps= 1 if ! defined $steps; + my $pos= $me->Next(-$steps); + $pos -= $me->[_End] if $pos; + return $pos; +} + +sub Diff { + my( $me )= @_; + $me->_ChkPos(); + return 0 if $me->[_Same] == ( 1 & $me->[_Pos] ); + my $ret= 0; + my $off= $me->[_Off]; + for my $seq ( 1, 2 ) { + $ret |= $seq + if $me->[_Idx][ $off + $seq + _Min ] + < $me->[_Idx][ $off + $seq ]; + } + return $ret; +} + +sub Min { + my( $me, $seq, $base )= @_; + $me->_ChkPos(); + my $off= $me->_ChkSeq($seq); + $base= $me->[_Base] if !defined $base; + return $base + $me->[_Idx][ $off + _Min ]; +} + +sub Max { + my( $me, $seq, $base )= @_; + $me->_ChkPos(); + my $off= $me->_ChkSeq($seq); + $base= $me->[_Base] if !defined $base; + return $base + $me->[_Idx][ $off ] -1; +} + +sub Range { + my( $me, $seq, $base )= @_; + $me->_ChkPos(); + my $off = $me->_ChkSeq($seq); + if( !wantarray ) { + return $me->[_Idx][ $off ] + - $me->[_Idx][ $off + _Min ]; + } + $base= $me->[_Base] if !defined $base; + return ( $base + $me->[_Idx][ $off + _Min ] ) + .. ( $base + $me->[_Idx][ $off ] - 1 ); +} + +sub Items { + my( $me, $seq )= @_; + $me->_ChkPos(); + my $off = $me->_ChkSeq($seq); + if( !wantarray ) { + return $me->[_Idx][ $off ] + - $me->[_Idx][ $off + _Min ]; + } + return + @{$me->[$seq]}[ + $me->[_Idx][ $off + _Min ] + .. ( $me->[_Idx][ $off ] - 1 ) + ]; +} + +sub Same { + my( $me )= @_; + $me->_ChkPos(); + return wantarray ? () : 0 + if $me->[_Same] != ( 1 & $me->[_Pos] ); + return $me->Items(1); +} + +my %getName; +BEGIN { + %getName= ( + same => \&Same, + diff => \&Diff, + base => \&Base, + min => \&Min, + max => \&Max, + range=> \&Range, + items=> \&Items, # same thing + ); +} + +sub Get +{ + my $me= shift @_; + $me->_ChkPos(); + my @value; + for my $arg ( @_ ) { + for my $word ( split ' ', $arg ) { + my $meth; + if( $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/ + || not $meth= $getName{ lc $2 } + ) { + Die( $Root, ", Get: Invalid request ($word)" ); + } + my( $base, $name, $seq )= ( $1, $2, $3 ); + push @value, scalar( + 4 == length($name) + ? $meth->( $me ) + : $meth->( $me, $seq, $base ) + ); + } + } + if( wantarray ) { + return @value; + } elsif( 1 == @value ) { + return $value[0]; + } + Die( 0+@value, " values requested from ", + $Root, "'s Get in scalar context" ); +} + + +my $Obj= getObjPkg($Root); +no strict 'refs'; + +for my $meth ( qw( new getObjPkg ) ) { + *{$Root."::".$meth} = \&{$meth}; + *{$Obj ."::".$meth} = \&{$meth}; +} +for my $meth ( qw( + Next Prev Reset Copy Base Diff + Same Items Range Min Max Get + _ChkPos _ChkSeq +) ) { + *{$Obj."::".$meth} = \&{$meth}; +} + +1; +__END__ + +=head1 NAME + +Algorithm::Diff - Compute `intelligent' differences between two files / lists + +=head1 SYNOPSIS + + require Algorithm::Diff; + + # This example produces traditional 'diff' output: + + my $diff = Algorithm::Diff->new( \@seq1, \@seq2 ); + + $diff->Base( 1 ); # Return line numbers, not indices + while( $diff->Next() ) { + next if $diff->Same(); + my $sep = ''; + if( ! $diff->Items(2) ) { + sprintf "%d,%dd%d\n", + $diff->Get(qw( Min1 Max1 Max2 )); + } elsif( ! $diff->Items(1) ) { + sprint "%da%d,%d\n", + $diff->Get(qw( Max1 Min2 Max2 )); + } else { + $sep = "---\n"; + sprintf "%d,%dc%d,%d\n", + $diff->Get(qw( Min1 Max1 Min2 Max2 )); + } + print "< $_" for $diff->Items(1); + print $sep; + print "> $_" for $diff->Items(2); + } + + + # Alternate interfaces: + + use Algorithm::Diff qw( + LCS LCS_length LCSidx + diff sdiff compact_diff + traverse_sequences traverse_balanced ); + + @lcs = LCS( \@seq1, \@seq2 ); + $lcsref = LCS( \@seq1, \@seq2 ); + $count = LCS_length( \@seq1, \@seq2 ); + + ( $seq1idxref, $seq2idxref ) = LCSidx( \@seq1, \@seq2 ); + + + # Complicated interfaces: + + @diffs = diff( \@seq1, \@seq2 ); + + @sdiffs = sdiff( \@seq1, \@seq2 ); + + @cdiffs = compact_diff( \@seq1, \@seq2 ); + + traverse_sequences( + \@seq1, + \@seq2, + { MATCH => \&callback1, + DISCARD_A => \&callback2, + DISCARD_B => \&callback3, + }, + \&key_generator, + @extra_args, + ); + + traverse_balanced( + \@seq1, + \@seq2, + { MATCH => \&callback1, + DISCARD_A => \&callback2, + DISCARD_B => \&callback3, + CHANGE => \&callback4, + }, + \&key_generator, + @extra_args, + ); + + +=head1 INTRODUCTION + +(by Mark-Jason Dominus) + +I once read an article written by the authors of C; they said +that they worked very hard on the algorithm until they found the +right one. + +I think what they ended up using (and I hope someone will correct me, +because I am not very confident about this) was the `longest common +subsequence' method. In the LCS problem, you have two sequences of +items: + + a b c d f g h j q z + + a b c d e f g i j k r x y z + +and you want to find the longest sequence of items that is present in +both original sequences in the same order. That is, you want to find +a new sequence I which can be obtained from the first sequence by +deleting some items, and from the secend sequence by deleting other +items. You also want I to be as long as possible. In this case I +is + + a b c d f g j z + +From there it's only a small step to get diff-like output: + + e h i k q r x y + + - + + - + + + + +This module solves the LCS problem. It also includes a canned function +to generate C-like output. + +It might seem from the example above that the LCS of two sequences is +always pretty obvious, but that's not always the case, especially when +the two sequences have many repeated elements. For example, consider + + a x b y c z p d q + a b c a x b y c z + +A naive approach might start by matching up the C and C that +appear at the beginning of each sequence, like this: + + a x b y c z p d q + a b c a b y c z + +This finds the common subsequence C. But actually, the LCS +is C: + + a x b y c z p d q + a b c a x b y c z + +or + + a x b y c z p d q + a b c a x b y c z + +=head1 USAGE + +(See also the README file and several example +scripts include with this module.) + +This module now provides an object-oriented interface that uses less +memory and is easier to use than most of the previous procedural +interfaces. It also still provides several exportable functions. We'll +deal with these in ascending order of difficulty: C, +C, C, OO interface, C, C, C, +C, and C. + +=head2 C + +Given references to two lists of items, LCS returns an array containing +their longest common subsequence. In scalar context, it returns a +reference to such a list. + + @lcs = LCS( \@seq1, \@seq2 ); + $lcsref = LCS( \@seq1, \@seq2 ); + +C may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L. + + @lcs = LCS( \@seq1, \@seq2, \&keyGen, @args ); + $lcsref = LCS( \@seq1, \@seq2, \&keyGen, @args ); + +Additional parameters, if any, will be passed to the key generation +routine. + +=head2 C + +This is just like C except it only returns the length of the +longest common subsequence. This provides a performance gain of about +9% compared to C. + +=head2 C + +Like C except it returns references to two arrays. The first array +contains the indices into @seq1 where the LCS items are located. The +second array contains the indices into @seq2 where the LCS items are located. + +Therefore, the following three lists will contain the same values: + + my( $idx1, $idx2 ) = LCSidx( \@seq1, \@seq2 ); + my @list1 = @seq1[ @$idx1 ]; + my @list2 = @seq2[ @$idx2 ]; + my @list3 = LCS( \@seq1, \@seq2 ); + +=head2 C + + $diff = Algorithm::Diffs->new( \@seq1, \@seq2 ); + $diff = Algorithm::Diffs->new( \@seq1, \@seq2, \%opts ); + +C computes the smallest set of additions and deletions necessary +to turn the first sequence into the second and compactly records them +in the object. + +You use the object to iterate over I, where each hunk represents +a contiguous section of items which should be added, deleted, replaced, +or left unchanged. + +=over 4 + +The following summary of all of the methods looks a lot like Perl code +but some of the symbols have different meanings: + + [ ] Encloses optional arguments + : Is followed by the default value for an optional argument + | Separates alternate return results + +Method summary: + + $obj = Algorithm::Diff->new( \@seq1, \@seq2, [ \%opts ] ); + $pos = $obj->Next( [ $count : 1 ] ); + $revPos = $obj->Prev( [ $count : 1 ] ); + $obj = $obj->Reset( [ $pos : 0 ] ); + $copy = $obj->Copy( [ $pos, [ $newBase ] ] ); + $oldBase = $obj->Base( [ $newBase ] ); + +Note that all of the following methods C if used on an object that +is "reset" (not currently pointing at any hunk). + + $bits = $obj->Diff( ); + @items|$cnt = $obj->Same( ); + @items|$cnt = $obj->Items( $seqNum ); + @idxs |$cnt = $obj->Range( $seqNum, [ $base ] ); + $minIdx = $obj->Min( $seqNum, [ $base ] ); + $maxIdx = $obj->Max( $seqNum, [ $base ] ); + @values = $obj->Get( @names ); + +Passing in C for an optional argument is always treated the same +as if no argument were passed in. + +=item C + + $pos = $diff->Next(); # Move forward 1 hunk + $pos = $diff->Next( 2 ); # Move forward 2 hunks + $pos = $diff->Next(-5); # Move backward 5 hunks + +C moves the object to point at the next hunk. The object starts +out "reset", which means it isn't pointing at any hunk. If the object +is reset, then C moves to the first hunk. + +C returns a true value iff the move didn't go past the last hunk. +So C will return true iff the object is not reset. + +Actually, C returns the object's new position, which is a number +between 1 and the number of hunks (inclusive), or returns a false value. + +=item C + +C is almost identical to C; it moves to the $Nth +previous hunk. On a 'reset' object, C [and C] move +to the last hunk. + +The position returned by C is relative to the I of the +hunks; -1 for the last hunk, -2 for the second-to-last, etc. + +=item C + + $diff->Reset(); # Reset the object's position + $diff->Reset($pos); # Move to the specified hunk + $diff->Reset(1); # Move to the first hunk + $diff->Reset(-1); # Move to the last hunk + +C returns the object, so, for example, you could use +C<< $diff->Reset()->Next(-1) >> to get the number of hunks. + +=item C + + $copy = $diff->Copy( $newPos, $newBase ); + +C returns a copy of the object. The copy and the orignal object +share most of their data, so making copies takes very little memory. +The copy maintains its own position (separate from the original), which +is the main purpose of copies. It also maintains its own base. + +By default, the copy's position starts out the same as the original +object's position. But C takes an optional first argument to set the +new position, so the following three snippets are equivalent: + + $copy = $diff->Copy($pos); + + $copy = $diff->Copy(); + $copy->Reset($pos); + + $copy = $diff->Copy()->Reset($pos); + +C takes an optional second argument to set the base for +the copy. If you wish to change the base of the copy but leave +the position the same as in the original, here are two +equivalent ways: + + $copy = $diff->Copy(); + $copy->Base( 0 ); + + $copy = $diff->Copy(undef,0); + +Here are two equivalent way to get a "reset" copy: + + $copy = $diff->Copy(0); + + $copy = $diff->Copy()->Reset(); + +=item C + + $bits = $obj->Diff(); + +C returns a true value iff the current hunk contains items that are +different between the two sequences. It actually returns one of the +follow 4 values: + +=over 4 + +=item 3 + +C<3==(1|2)>. This hunk contains items from @seq1 and the items +from @seq2 that should replace them. Both sequence 1 and 2 +contain changed items so both the 1 and 2 bits are set. + +=item 2 + +This hunk only contains items from @seq2 that should be inserted (not +items from @seq1). Only sequence 2 contains changed items so only the 2 +bit is set. + +=item 1 + +This hunk only contains items from @seq1 that should be deleted (not +items from @seq2). Only sequence 1 contains changed items so only the 1 +bit is set. + +=item 0 + +This means that the items in this hunk are the same in both sequences. +Neither sequence 1 nor 2 contain changed items so neither the 1 nor the +2 bits are set. + +=back + +=item C + +C returns a true value iff the current hunk contains items that +are the same in both sequences. It actually returns the list of items +if they are the same or an emty list if they aren't. In a scalar +context, it returns the size of the list. + +=item C + + $count = $diff->Items(2); + @items = $diff->Items($seqNum); + +C returns the (number of) items from the specified sequence that +are part of the current hunk. + +If the current hunk contains only insertions, then +C<< $diff->Items(1) >> will return an empty list (0 in a scalar conext). +If the current hunk contains only deletions, then C<< $diff->Items(2) >> +will return an empty list (0 in a scalar conext). + +If the hunk contains replacements, then both C<< $diff->Items(1) >> and +C<< $diff->Items(2) >> will return different, non-empty lists. + +Otherwise, the hunk contains identical items and all of the following +will return the same lists: + + @items = $diff->Items(1); + @items = $diff->Items(2); + @items = $diff->Same(); + +=item C + + $count = $diff->Range( $seqNum ); + @indices = $diff->Range( $seqNum ); + @indices = $diff->Range( $seqNum, $base ); + +C is like C except that it returns a list of I to +the items rather than the items themselves. By default, the index of +the first item (in each sequence) is 0 but this can be changed by +calling the C method. So, by default, the following two snippets +return the same lists: + + @list = $diff->Items(2); + @list = @seq2[ $diff->Range(2) ]; + +You can also specify the base to use as the second argument. So the +following two snippets I return the same lists: + + @list = $diff->Items(1); + @list = @seq1[ $diff->Range(1,0) ]; + +=item C + + $curBase = $diff->Base(); + $oldBase = $diff->Base($newBase); + +C sets and/or returns the current base (usually 0 or 1) that is +used when you request range information. The base defaults to 0 so +that range information is returned as array indices. You can set the +base to 1 if you want to report traditional line numbers instead. + +=item C + + $min1 = $diff->Min(1); + $min = $diff->Min( $seqNum, $base ); + +C returns the first value that C would return (given the +same arguments) or returns C if C would return an empty +list. + +=item C + +C returns the last value that C would return or C. + +=item C + + ( $n, $x, $r ) = $diff->Get(qw( min1 max1 range1 )); + @values = $diff->Get(qw( 0min2 1max2 range2 same base )); + +C returns one or more scalar values. You pass in a list of the +names of the values you want returned. Each name must match one of the +following regexes: + + /^(-?\d+)?(min|max)[12]$/i + /^(range[12]|same|diff|base)$/i + +The 1 or 2 after a name says which sequence you want the information +for (and where allowed, it is required). The optional number before +"min" or "max" is the base to use. So the following equalities hold: + + $diff->Get('min1') == $diff->Min(1) + $diff->Get('0min2') == $diff->Min(2,0) + +Using C in a scalar context when you've passed in more than one +name is a fatal error (C is called). + +=back + +=head2 C + +Given a reference to a list of items, C returns a reference +to a hash which can be used when comparing this sequence to other +sequences with C or C. + + $prep = prepare( \@seq1 ); + for $i ( 0 .. 10_000 ) + { + @lcs = LCS( $prep, $seq[$i] ); + # do something useful with @lcs + } + +C may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L. + + $prep = prepare( \@seq1, \&keyGen ); + for $i ( 0 .. 10_000 ) + { + @lcs = LCS( $seq[$i], $prep, \&keyGen ); + # do something useful with @lcs + } + +Using C provides a performance gain of about 50% when calling LCS +many times compared with not preparing. + +=head2 C + + @diffs = diff( \@seq1, \@seq2 ); + $diffs_ref = diff( \@seq1, \@seq2 ); + +C computes the smallest set of additions and deletions necessary +to turn the first sequence into the second, and returns a description +of these changes. The description is a list of I; each hunk +represents a contiguous section of items which should be added, +deleted, or replaced. (Hunks containing unchanged items are not +included.) + +The return value of C is a list of hunks, or, in scalar context, a +reference to such a list. If there are no differences, the list will be +empty. + +Here is an example. Calling C for the following two sequences: + + a b c e h j l m n p + b c d e f j k l m r s t + +would produce the following list: + + ( + [ [ '-', 0, 'a' ] ], + + [ [ '+', 2, 'd' ] ], + + [ [ '-', 4, 'h' ], + [ '+', 4, 'f' ] ], + + [ [ '+', 6, 'k' ] ], + + [ [ '-', 8, 'n' ], + [ '-', 9, 'p' ], + [ '+', 9, 'r' ], + [ '+', 10, 's' ], + [ '+', 11, 't' ] ], + ) + +There are five hunks here. The first hunk says that the C at +position 0 of the first sequence should be deleted (C<->). The second +hunk says that the C at position 2 of the second sequence should +be inserted (C<+>). The third hunk says that the C at position 4 +of the first sequence should be removed and replaced with the C +from position 4 of the second sequence. And so on. + +C may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L. + +Additional parameters, if any, will be passed to the key generation +routine. + +=head2 C + + @sdiffs = sdiff( \@seq1, \@seq2 ); + $sdiffs_ref = sdiff( \@seq1, \@seq2 ); + +C computes all necessary components to show two sequences +and their minimized differences side by side, just like the +Unix-utility I does: + + same same + before | after + old < - + - > new + +It returns a list of array refs, each pointing to an array of +display instructions. In scalar context it returns a reference +to such a list. If there are no differences, the list will have one +entry per item, each indicating that the item was unchanged. + +Display instructions consist of three elements: A modifier indicator +(C<+>: Element added, C<->: Element removed, C: Element unmodified, +C: Element changed) and the value of the old and new elements, to +be displayed side-by-side. + +An C of the following two sequences: + + a b c e h j l m n p + b c d e f j k l m r s t + +results in + + ( [ '-', 'a', '' ], + [ 'u', 'b', 'b' ], + [ 'u', 'c', 'c' ], + [ '+', '', 'd' ], + [ 'u', 'e', 'e' ], + [ 'c', 'h', 'f' ], + [ 'u', 'j', 'j' ], + [ '+', '', 'k' ], + [ 'u', 'l', 'l' ], + [ 'u', 'm', 'm' ], + [ 'c', 'n', 'r' ], + [ 'c', 'p', 's' ], + [ '+', '', 't' ], + ) + +C may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L. + +Additional parameters, if any, will be passed to the key generation +routine. + +=head2 C + +C is much like C except it returns a much more +compact description consisting of just one flat list of indices. An +example helps explain the format: + + my @a = qw( a b c e h j l m n p ); + my @b = qw( b c d e f j k l m r s t ); + @cdiff = compact_diff( \@a, \@b ); + # Returns: + # @a @b @a @b + # start start values values + ( 0, 0, # = + 0, 0, # a ! + 1, 0, # b c = b c + 3, 2, # ! d + 3, 3, # e = e + 4, 4, # f ! h + 5, 5, # j = j + 6, 6, # ! k + 6, 7, # l m = l m + 8, 9, # n p ! r s t + 10, 12, # + ); + +The 0th, 2nd, 4th, etc. entries are all indices into @seq1 (@a in the +above example) indicating where a hunk begins. The 1st, 3rd, 5th, etc. +entries are all indices into @seq2 (@b in the above example) indicating +where the same hunk begins. + +So each pair of indices (except the last pair) describes where a hunk +begins (in each sequence). Since each hunk must end at the item just +before the item that starts the next hunk, the next pair of indices can +be used to determine where the hunk ends. + +So, the first 4 entries (0..3) describe the first hunk. Entries 0 and 1 +describe where the first hunk begins (and so are always both 0). +Entries 2 and 3 describe where the next hunk begins, so subtracting 1 +from each tells us where the first hunk ends. That is, the first hunk +contains items C<$diff[0]> through C<$diff[2] - 1> of the first sequence +and contains items C<$diff[1]> through C<$diff[3] - 1> of the second +sequence. + +In other words, the first hunk consists of the following two lists of items: + + # 1st pair 2nd pair + # of indices of indices + @list1 = @a[ $cdiff[0] .. $cdiff[2]-1 ]; + @list2 = @b[ $cdiff[1] .. $cdiff[3]-1 ]; + # Hunk start Hunk end + +Note that the hunks will always alternate between those that are part of +the LCS (those that contain unchanged items) and those that contain +changes. This means that all we need to be told is whether the first +hunk is a 'same' or 'diff' hunk and we can determine which of the other +hunks contain 'same' items or 'diff' items. + +By convention, we always make the first hunk contain unchanged items. +So the 1st, 3rd, 5th, etc. hunks (all odd-numbered hunks if you start +counting from 1) all contain unchanged items. And the 2nd, 4th, 6th, +etc. hunks (all even-numbered hunks if you start counting from 1) all +contain changed items. + +Since @a and @b don't begin with the same value, the first hunk in our +example is empty (otherwise we'd violate the above convention). Note +that the first 4 index values in our example are all zero. Plug these +values into our previous code block and we get: + + @hunk1a = @a[ 0 .. 0-1 ]; + @hunk1b = @b[ 0 .. 0-1 ]; + +And C<0..-1> returns the empty list. + +Move down one pair of indices (2..5) and we get the offset ranges for +the second hunk, which contains changed items. + +Since C<@diff[2..5]> contains (0,0,1,0) in our example, the second hunk +consists of these two lists of items: + + @hunk2a = @a[ $cdiff[2] .. $cdiff[4]-1 ]; + @hunk2b = @b[ $cdiff[3] .. $cdiff[5]-1 ]; + # or + @hunk2a = @a[ 0 .. 1-1 ]; + @hunk2b = @b[ 0 .. 0-1 ]; + # or + @hunk2a = @a[ 0 .. 0 ]; + @hunk2b = @b[ 0 .. -1 ]; + # or + @hunk2a = ( 'a' ); + @hunk2b = ( ); + +That is, we would delete item 0 ('a') from @a. + +Since C<@diff[4..7]> contains (1,0,3,2) in our example, the third hunk +consists of these two lists of items: + + @hunk3a = @a[ $cdiff[4] .. $cdiff[6]-1 ]; + @hunk3a = @b[ $cdiff[5] .. $cdiff[7]-1 ]; + # or + @hunk3a = @a[ 1 .. 3-1 ]; + @hunk3a = @b[ 0 .. 2-1 ]; + # or + @hunk3a = @a[ 1 .. 2 ]; + @hunk3a = @b[ 0 .. 1 ]; + # or + @hunk3a = qw( b c ); + @hunk3a = qw( b c ); + +Note that this third hunk contains unchanged items as our convention demands. + +You can continue this process until you reach the last two indices, +which will always be the number of items in each sequence. This is +required so that subtracting one from each will give you the indices to +the last items in each sequence. + +=head2 C + +C used to be the most general facility provided by +this module (the new OO interface is more powerful and much easier to +use). + +Imagine that there are two arrows. Arrow A points to an element of +sequence A, and arrow B points to an element of the sequence B. +Initially, the arrows point to the first elements of the respective +sequences. C will advance the arrows through the +sequences one element at a time, calling an appropriate user-specified +callback function before each advance. It willadvance the arrows in +such a way that if there are equal elements C<$A[$i]> and C<$B[$j]> +which are equal and which are part of the LCS, there will be some moment +during the execution of C when arrow A is pointing +to C<$A[$i]> and arrow B is pointing to C<$B[$j]>. When this happens, +C will call the C callback function and then +it will advance both arrows. + +Otherwise, one of the arrows is pointing to an element of its sequence +that is not part of the LCS. C will advance that +arrow and will call the C or the C callback, +depending on which arrow it advanced. If both arrows point to elements +that are not part of the LCS, then C will advance +one of them and call the appropriate callback, but it is not specified +which it will call. + +The arguments to C are the two sequences to +traverse, and a hash which specifies the callback functions, like this: + + traverse_sequences( + \@seq1, \@seq2, + { MATCH => $callback_1, + DISCARD_A => $callback_2, + DISCARD_B => $callback_3, + } + ); + +Callbacks for MATCH, DISCARD_A, and DISCARD_B are invoked with at least +the indices of the two arrows as their arguments. They are not expected +to return any values. If a callback is omitted from the table, it is +not called. + +Callbacks for A_FINISHED and B_FINISHED are invoked with at least the +corresponding index in A or B. + +If arrow A reaches the end of its sequence, before arrow B does, +C will call the C callback when it +advances arrow B, if there is such a function; if not it will call +C instead. Similarly if arrow B finishes first. +C returns when both arrows are at the ends of their +respective sequences. It returns true on success and false on failure. +At present there is no way to fail. + +C may be passed an optional fourth parameter; this +is a CODE reference to a key generation function. See L. + +Additional parameters, if any, will be passed to the key generation function. + +If you want to pass additional parameters to your callbacks, but don't +need a custom key generation function, you can get the default by +passing undef: + + traverse_sequences( + \@seq1, \@seq2, + { MATCH => $callback_1, + DISCARD_A => $callback_2, + DISCARD_B => $callback_3, + }, + undef, # default key-gen + $myArgument1, + $myArgument2, + $myArgument3, + ); + +C does not have a useful return value; you are +expected to plug in the appropriate behavior with the callback +functions. + +=head2 C + +C is an alternative to C. It +uses a different algorithm to iterate through the entries in the +computed LCS. Instead of sticking to one side and showing element changes +as insertions and deletions only, it will jump back and forth between +the two sequences and report I occurring as deletions on one +side followed immediatly by an insertion on the other side. + +In addition to the C, C, and C callbacks +supported by C, C supports +a C callback indicating that one element got C by another: + + traverse_balanced( + \@seq1, \@seq2, + { MATCH => $callback_1, + DISCARD_A => $callback_2, + DISCARD_B => $callback_3, + CHANGE => $callback_4, + } + ); + +If no C callback is specified, C +will map C events to C and C actions, +therefore resulting in a similar behaviour as C +with different order of events. + +C might be a bit slower than C, +noticable only while processing huge amounts of data. + +The C function of this module +is implemented as call to C. + +C does not have a useful return value; you are expected to +plug in the appropriate behavior with the callback functions. + +=head1 KEY GENERATION FUNCTIONS + +Most of the functions accept an optional extra parameter. This is a +CODE reference to a key generating (hashing) function that should return +a string that uniquely identifies a given element. It should be the +case that if two elements are to be considered equal, their keys should +be the same (and the other way around). If no key generation function +is provided, the key will be the element as a string. + +By default, comparisons will use "eq" and elements will be turned into keys +using the default stringizing operator '""'. + +Where this is important is when you're comparing something other than +strings. If it is the case that you have multiple different objects +that should be considered to be equal, you should supply a key +generation function. Otherwise, you have to make sure that your arrays +contain unique references. + +For instance, consider this example: + + package Person; + + sub new + { + my $package = shift; + return bless { name => '', ssn => '', @_ }, $package; + } + + sub clone + { + my $old = shift; + my $new = bless { %$old }, ref($old); + } + + sub hash + { + return shift()->{'ssn'}; + } + + my $person1 = Person->new( name => 'Joe', ssn => '123-45-6789' ); + my $person2 = Person->new( name => 'Mary', ssn => '123-47-0000' ); + my $person3 = Person->new( name => 'Pete', ssn => '999-45-2222' ); + my $person4 = Person->new( name => 'Peggy', ssn => '123-45-9999' ); + my $person5 = Person->new( name => 'Frank', ssn => '000-45-9999' ); + +If you did this: + + my $array1 = [ $person1, $person2, $person4 ]; + my $array2 = [ $person1, $person3, $person4, $person5 ]; + Algorithm::Diff::diff( $array1, $array2 ); + +everything would work out OK (each of the objects would be converted +into a string like "Person=HASH(0x82425b0)" for comparison). + +But if you did this: + + my $array1 = [ $person1, $person2, $person4 ]; + my $array2 = [ $person1, $person3, $person4->clone(), $person5 ]; + Algorithm::Diff::diff( $array1, $array2 ); + +$person4 and $person4->clone() (which have the same name and SSN) +would be seen as different objects. If you wanted them to be considered +equivalent, you would have to pass in a key generation function: + + my $array1 = [ $person1, $person2, $person4 ]; + my $array2 = [ $person1, $person3, $person4->clone(), $person5 ]; + Algorithm::Diff::diff( $array1, $array2, \&Person::hash ); + +This would use the 'ssn' field in each Person as a comparison key, and +so would consider $person4 and $person4->clone() as equal. + +You may also pass additional parameters to the key generation function +if you wish. + +=head1 ERROR CHECKING + +If you pass these routines a non-reference and they expect a reference, +they will die with a message. + +=head1 AUTHOR + +This version released by Tye McQueen (http://perlmonks.org/?node=tye). + +=head1 LICENSE + +Parts Copyright (c) 2000-2004 Ned Konz. All rights reserved. +Parts by Tye McQueen. + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl. + +=head1 MAILING LIST + +Mark-Jason still maintains a mailing list. To join a low-volume mailing +list for announcements related to diff and Algorithm::Diff, send an +empty mail message to mjd-perl-diff-request@plover.com. + +=head1 CREDITS + +Versions through 0.59 (and much of this documentation) were written by: + +Mark-Jason Dominus, mjd-perl-diff@plover.com + +This version borrows some documentation and routine names from +Mark-Jason's, but Diff.pm's code was completely replaced. + +This code was adapted from the Smalltalk code of Mario Wolczko +, which is available at +ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st + +C and C were written by Mike Schilli +. + +The algorithm is that described in +I, +CACM, vol.20, no.5, pp.350-353, May 1977, with a few +minor improvements to improve the speed. + +Much work was done by Ned Konz (perl@bike-nomad.com). + +The OO interface and some other changes are by Tye McQueen. + +=cut diff --git a/pintos-progos/tests/Make.tests b/pintos-progos/tests/Make.tests new file mode 100644 index 0000000..358e697 --- /dev/null +++ b/pintos-progos/tests/Make.tests @@ -0,0 +1,76 @@ +# -*- makefile -*- + +include $(patsubst %,$(SRCDIR)/%/Make.tests,$(TEST_SUBDIRS)) + +PROGS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_PROGS)) +TESTS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_TESTS)) +EXTRA_GRADES = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_EXTRA_GRADES)) + +OUTPUTS = $(addsuffix .output,$(TESTS) $(EXTRA_GRADES)) +ERRORS = $(addsuffix .errors,$(TESTS) $(EXTRA_GRADES)) +RESULTS = $(addsuffix .result,$(TESTS) $(EXTRA_GRADES)) + +ifdef PROGS +include ../../Makefile.userprog +endif + +TIMEOUT = 60 + +clean:: + rm -f $(OUTPUTS) $(ERRORS) $(RESULTS) + +grade:: results + $(SRCDIR)/tests/make-grade $(SRCDIR) $< $(GRADING_FILE) | tee $@ + +check:: results + @cat $< + @COUNT="`egrep '^(pass|FAIL) ' $< | wc -l | sed 's/[ ]//g;'`"; \ + FAILURES="`egrep '^FAIL ' $< | wc -l | sed 's/[ ]//g;'`"; \ + if [ $$FAILURES = 0 ]; then \ + echo "All $$COUNT tests passed."; \ + else \ + echo "$$FAILURES of $$COUNT tests failed."; \ + exit 1; \ + fi + +results: $(RESULTS) + @for d in $(TESTS) $(EXTRA_GRADES); do \ + if echo PASS | cmp -s $$d.result -; then \ + echo "pass $$d"; \ + else \ + echo "FAIL $$d"; \ + fi; \ + done > $@ + +outputs:: $(OUTPUTS) + +$(foreach prog,$(PROGS),$(eval $(prog).output: $(prog))) +$(foreach test,$(TESTS),$(eval $(test).output: $($(test)_PUTFILES))) +$(foreach test,$(TESTS),$(eval $(test).output: TEST = $(test))) + +# Prevent an environment variable VERBOSE from surprising us. +VERBOSE = + +TESTCMD = pintos -v -k -T $(TIMEOUT) +TESTCMD += $(SIMULATOR) +TESTCMD += $(PINTOSOPTS) +ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) +TESTCMD += $(FILESYSSOURCE) +TESTCMD += $(foreach file,$(PUTFILES),-p $(file) -a $(notdir $(file))) +endif +ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm) +TESTCMD += --swap-size=4 +endif +TESTCMD += -- -q +TESTCMD += $(KERNELFLAGS) +ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) +TESTCMD += -f +endif +TESTCMD += $(if $($(TEST)_ARGS),run '$(*F) $($(TEST)_ARGS)',run $(*F)) +TESTCMD += < /dev/null +TESTCMD += 2> $(TEST).errors $(if $(VERBOSE),|tee,>) $(TEST).output +%.output: kernel.bin loader.bin + $(TESTCMD) + +%.result: %.ck %.output + perl -I$(SRCDIR) $< $* $@ diff --git a/pintos-progos/tests/arc4.c b/pintos-progos/tests/arc4.c new file mode 100644 index 0000000..b033cc6 --- /dev/null +++ b/pintos-progos/tests/arc4.c @@ -0,0 +1,53 @@ +#include +#include "tests/arc4.h" + +/* Swap bytes. */ +static inline void +swap_byte (uint8_t *a, uint8_t *b) +{ + uint8_t t = *a; + *a = *b; + *b = t; +} + +void +arc4_init (struct arc4 *arc4, const void *key_, size_t size) +{ + const uint8_t *key = key_; + size_t key_idx; + uint8_t *s; + int i, j; + + s = arc4->s; + arc4->i = arc4->j = 0; + for (i = 0; i < 256; i++) + s[i] = i; + for (key_idx = 0, i = j = 0; i < 256; i++) + { + j = (j + s[i] + key[key_idx]) & 255; + swap_byte (s + i, s + j); + if (++key_idx >= size) + key_idx = 0; + } +} + +void +arc4_crypt (struct arc4 *arc4, void *buf_, size_t size) +{ + uint8_t *buf = buf_; + uint8_t *s; + uint8_t i, j; + + s = arc4->s; + i = arc4->i; + j = arc4->j; + while (size-- > 0) + { + i += 1; + j += s[i]; + swap_byte (s + i, s + j); + *buf++ ^= s[(s[i] + s[j]) & 255]; + } + arc4->i = i; + arc4->j = j; +} diff --git a/pintos-progos/tests/arc4.h b/pintos-progos/tests/arc4.h new file mode 100644 index 0000000..61c533a --- /dev/null +++ b/pintos-progos/tests/arc4.h @@ -0,0 +1,17 @@ +#ifndef TESTS_ARC4_H +#define TESTS_ARC4_H + +#include +#include + +/* Alleged RC4 algorithm encryption state. */ +struct arc4 + { + uint8_t s[256]; + uint8_t i, j; + }; + +void arc4_init (struct arc4 *, const void *, size_t); +void arc4_crypt (struct arc4 *, void *, size_t); + +#endif /* tests/arc4.h */ diff --git a/pintos-progos/tests/arc4.pm b/pintos-progos/tests/arc4.pm new file mode 100644 index 0000000..df19216 --- /dev/null +++ b/pintos-progos/tests/arc4.pm @@ -0,0 +1,29 @@ +use strict; +use warnings; + +sub arc4_init { + my ($key) = @_; + my (@s) = 0...255; + my ($j) = 0; + for my $i (0...255) { + $j = ($j + $s[$i] + ord (substr ($key, $i % length ($key), 1))) & 0xff; + @s[$i, $j] = @s[$j, $i]; + } + return (0, 0, @s); +} + +sub arc4_crypt { + my ($arc4, $buf) = @_; + my ($i, $j, @s) = @$arc4; + my ($out) = ""; + for my $c (split (//, $buf)) { + $i = ($i + 1) & 0xff; + $j = ($j + $s[$i]) & 0xff; + @s[$i, $j] = @s[$j, $i]; + $out .= chr (ord ($c) ^ $s[($s[$i] + $s[$j]) & 0xff]); + } + @$arc4 = ($i, $j, @s); + return $out; +} + +1; diff --git a/pintos-progos/tests/cksum.c b/pintos-progos/tests/cksum.c new file mode 100644 index 0000000..92a2995 --- /dev/null +++ b/pintos-progos/tests/cksum.c @@ -0,0 +1,92 @@ +/* crctab[] and cksum() are from the `cksum' entry in SUSv3. */ + +#include +#include "tests/cksum.h" + +static unsigned long crctab[] = { + 0x00000000, + 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, + 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, + 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, + 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, + 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, + 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, + 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, + 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, + 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, + 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, + 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, + 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, + 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, + 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, + 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, + 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, + 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, + 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, + 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, + 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, + 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, + 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, + 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, + 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, + 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, + 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, + 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, + 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, + 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, + 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, + 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, + 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, + 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, + 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +/* This is the algorithm used by the Posix `cksum' utility. */ +unsigned long +cksum (const void *b_, size_t n) +{ + const unsigned char *b = b_; + uint32_t s = 0; + size_t i; + for (i = n; i > 0; --i) + { + unsigned char c = *b++; + s = (s << 8) ^ crctab[(s >> 24) ^ c]; + } + while (n != 0) + { + unsigned char c = n; + n >>= 8; + s = (s << 8) ^ crctab[(s >> 24) ^ c]; + } + return ~s; +} + +#ifdef STANDALONE_TEST +#include +int +main (void) +{ + char buf[65536]; + int n = fread (buf, 1, sizeof buf, stdin); + printf ("%lu\n", cksum (buf, n)); + return 0; +} +#endif diff --git a/pintos-progos/tests/cksum.h b/pintos-progos/tests/cksum.h new file mode 100644 index 0000000..23a1fe9 --- /dev/null +++ b/pintos-progos/tests/cksum.h @@ -0,0 +1,8 @@ +#ifndef TESTS_CKSUM_H +#define TESTS_CKSUM_H + +#include + +unsigned long cksum(const void *, size_t); + +#endif /* tests/cksum.h */ diff --git a/pintos-progos/tests/cksum.pm b/pintos-progos/tests/cksum.pm new file mode 100644 index 0000000..73be5f2 --- /dev/null +++ b/pintos-progos/tests/cksum.pm @@ -0,0 +1,87 @@ +# From the `cksum' entry in SUSv3. + +use strict; +use warnings; + +my (@crctab) = + (0x00000000, + 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, + 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, + 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, + 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, + 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, + 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, + 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, + 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, + 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, + 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, + 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, + 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, + 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, + 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, + 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, + 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, + 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, + 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, + 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, + 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, + 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, + 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, + 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, + 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, + 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, + 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, + 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, + 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, + 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, + 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, + 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, + 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, + 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, + 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4); + +sub cksum { + my ($b) = @_; + my ($n) = length ($b); + my ($s) = 0; + for my $i (0...$n - 1) { + my ($c) = ord (substr ($b, $i, 1)); + $s = ($s << 8) ^ $crctab[($s >> 24) ^ $c]; + $s &= 0xffff_ffff; + } + while ($n != 0) { + my ($c) = $n & 0xff; + $n >>= 8; + $s = ($s << 8) ^ $crctab[($s >> 24) ^ $c]; + $s &= 0xffff_ffff; + } + return ~$s & 0xffff_ffff; +} + +sub cksum_file { + my ($file) = @_; + open (FILE, '<', $file) or die "$file: open: $!\n"; + my ($data); + sysread (FILE, $data, -s FILE) == -s FILE or die "$file: read: $!\n"; + close (FILE); + return cksum ($data); +} + +1; diff --git a/pintos-progos/tests/filesys/Grading.no-vm b/pintos-progos/tests/filesys/Grading.no-vm new file mode 100644 index 0000000..ee98fc1 --- /dev/null +++ b/pintos-progos/tests/filesys/Grading.no-vm @@ -0,0 +1,18 @@ +# Percentage of the testing point total designated for each set of +# tests. + +# This project is primarily about implementing the file system, but +# all the previous functionality should work too. It's not too easy +# to screw it up, thus the emphasis. + +# 65% for extended file system features. +30% tests/filesys/extended/Rubric.functionality +15% tests/filesys/extended/Rubric.robustness +20% tests/filesys/extended/Rubric.persistence + +# 20% to not break the provided file system features. +20% tests/filesys/base/Rubric + +# 15% for the rest. +10% tests/userprog/Rubric.functionality +5% tests/userprog/Rubric.robustness diff --git a/pintos-progos/tests/filesys/Grading.with-vm b/pintos-progos/tests/filesys/Grading.with-vm new file mode 100644 index 0000000..e7c041e --- /dev/null +++ b/pintos-progos/tests/filesys/Grading.with-vm @@ -0,0 +1,22 @@ +# Percentage of the testing point total designated for each set of +# tests. + +# This project is primarily about implementing the file system, but +# all the previous functionality should work too. It's not too easy +# to screw it up, thus the emphasis. + +# 65% for extended file system features. +30% tests/filesys/extended/Rubric.functionality +15% tests/filesys/extended/Rubric.robustness +20% tests/filesys/extended/Rubric.persistence + +# 20% to not break the provided file system features. +20% tests/filesys/base/Rubric + +# 15% for the rest. +10% tests/userprog/Rubric.functionality +5% tests/userprog/Rubric.robustness + +# Up to 10% bonus for working VM functionality. +8% tests/vm/Rubric.functionality +2% tests/vm/Rubric.robustness diff --git a/pintos-progos/tests/filesys/base/Make.tests b/pintos-progos/tests/filesys/base/Make.tests new file mode 100644 index 0000000..e475222 --- /dev/null +++ b/pintos-progos/tests/filesys/base/Make.tests @@ -0,0 +1,18 @@ +# -*- makefile -*- + +tests/filesys/base_TESTS = $(addprefix tests/filesys/base/,lg-create \ +lg-full lg-random lg-seq-block lg-seq-random sm-create sm-full \ +sm-random sm-seq-block sm-seq-random syn-read syn-remove syn-write) + +tests/filesys/base_PROGS = $(tests/filesys/base_TESTS) $(addprefix \ +tests/filesys/base/,child-syn-read child-syn-wrt) + +$(foreach prog,$(tests/filesys/base_PROGS), \ + $(eval $(prog)_SRC += $(prog).c tests/lib.c tests/filesys/seq-test.c)) +$(foreach prog,$(tests/filesys/base_TESTS), \ + $(eval $(prog)_SRC += tests/main.c)) + +tests/filesys/base/syn-read_PUTFILES = tests/filesys/base/child-syn-read +tests/filesys/base/syn-write_PUTFILES = tests/filesys/base/child-syn-wrt + +tests/filesys/base/syn-read.output: TIMEOUT = 300 diff --git a/pintos-progos/tests/filesys/base/Rubric b/pintos-progos/tests/filesys/base/Rubric new file mode 100644 index 0000000..49a9d15 --- /dev/null +++ b/pintos-progos/tests/filesys/base/Rubric @@ -0,0 +1,19 @@ +Functionality of base file system: +- Test basic support for small files. +1 sm-create +2 sm-full +2 sm-random +2 sm-seq-block +3 sm-seq-random + +- Test basic support for large files. +1 lg-create +2 lg-full +2 lg-random +2 lg-seq-block +3 lg-seq-random + +- Test synchronized multiprogram access to files. +4 syn-read +4 syn-write +2 syn-remove diff --git a/pintos-progos/tests/filesys/base/child-syn-read.c b/pintos-progos/tests/filesys/base/child-syn-read.c new file mode 100644 index 0000000..77a5e26 --- /dev/null +++ b/pintos-progos/tests/filesys/base/child-syn-read.c @@ -0,0 +1,44 @@ +/* Child process for syn-read test. + Reads the contents of a test file a byte at a time, in the + hope that this will take long enough that we can get a + significant amount of contention in the kernel file system + code. */ + +#include +#include +#include +#include +#include "tests/lib.h" +#include "tests/filesys/base/syn-read.h" + +const char *test_name = "child-syn-read"; + +static char buf[BUF_SIZE]; + +int +main (int argc, const char *argv[]) +{ + int child_idx; + int fd; + size_t i; + + quiet = true; + + CHECK (argc == 2, "argc must be 2, actually %d", argc); + child_idx = atoi (argv[1]); + + random_init (0); + random_bytes (buf, sizeof buf); + + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + for (i = 0; i < sizeof buf; i++) + { + char c; + CHECK (read (fd, &c, 1) > 0, "read \"%s\"", file_name); + compare_bytes (&c, buf + i, 1, i, file_name); + } + close (fd); + + return child_idx; +} + diff --git a/pintos-progos/tests/filesys/base/child-syn-wrt.c b/pintos-progos/tests/filesys/base/child-syn-wrt.c new file mode 100644 index 0000000..1b52584 --- /dev/null +++ b/pintos-progos/tests/filesys/base/child-syn-wrt.c @@ -0,0 +1,35 @@ +/* Child process for syn-read test. + Writes into part of a test file. Other processes will be + writing into other parts at the same time. */ + +#include +#include +#include +#include "tests/lib.h" +#include "tests/filesys/base/syn-write.h" + +char buf[BUF_SIZE]; + +int +main (int argc, char *argv[]) +{ + int child_idx; + int fd; + + quiet = true; + + CHECK (argc == 2, "argc must be 2, actually %d", argc); + child_idx = atoi (argv[1]); + + random_init (0); + random_bytes (buf, sizeof buf); + + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + seek (fd, CHUNK_SIZE * child_idx); + CHECK (write (fd, buf + CHUNK_SIZE * child_idx, CHUNK_SIZE) > 0, + "write \"%s\"", file_name); + msg ("close \"%s\"", file_name); + close (fd); + + return child_idx; +} diff --git a/pintos-progos/tests/filesys/base/full.inc b/pintos-progos/tests/filesys/base/full.inc new file mode 100644 index 0000000..38a0396 --- /dev/null +++ b/pintos-progos/tests/filesys/base/full.inc @@ -0,0 +1,20 @@ +/* -*- c -*- */ + +#include "tests/filesys/seq-test.h" +#include "tests/main.h" + +static char buf[TEST_SIZE]; + +static size_t +return_test_size (void) +{ + return TEST_SIZE; +} + +void +test_main (void) +{ + seq_test ("quux", + buf, sizeof buf, sizeof buf, + return_test_size, NULL); +} diff --git a/pintos-progos/tests/filesys/base/lg-create.c b/pintos-progos/tests/filesys/base/lg-create.c new file mode 100644 index 0000000..5c45eee --- /dev/null +++ b/pintos-progos/tests/filesys/base/lg-create.c @@ -0,0 +1,5 @@ +/* Tests that create properly zeros out the contents of a fairly + large file. */ + +#define TEST_SIZE 75678 +#include "tests/filesys/create.inc" diff --git a/pintos-progos/tests/filesys/base/lg-create.ck b/pintos-progos/tests/filesys/base/lg-create.ck new file mode 100644 index 0000000..86b2c51 --- /dev/null +++ b/pintos-progos/tests/filesys/base/lg-create.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(lg-create) begin +(lg-create) create "blargle" +(lg-create) open "blargle" for verification +(lg-create) verified contents of "blargle" +(lg-create) close "blargle" +(lg-create) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/lg-full.c b/pintos-progos/tests/filesys/base/lg-full.c new file mode 100644 index 0000000..5f7234d --- /dev/null +++ b/pintos-progos/tests/filesys/base/lg-full.c @@ -0,0 +1,6 @@ +/* Writes out the contents of a fairly large file all at once, + and then reads it back to make sure that it was written + properly. */ + +#define TEST_SIZE 75678 +#include "tests/filesys/base/full.inc" diff --git a/pintos-progos/tests/filesys/base/lg-full.ck b/pintos-progos/tests/filesys/base/lg-full.ck new file mode 100644 index 0000000..ee6c7f9 --- /dev/null +++ b/pintos-progos/tests/filesys/base/lg-full.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(lg-full) begin +(lg-full) create "quux" +(lg-full) open "quux" +(lg-full) writing "quux" +(lg-full) close "quux" +(lg-full) open "quux" for verification +(lg-full) verified contents of "quux" +(lg-full) close "quux" +(lg-full) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/lg-random.c b/pintos-progos/tests/filesys/base/lg-random.c new file mode 100644 index 0000000..b6f8873 --- /dev/null +++ b/pintos-progos/tests/filesys/base/lg-random.c @@ -0,0 +1,7 @@ +/* Writes out the content of a fairly large file in random order, + then reads it back in random order to verify that it was + written properly. */ + +#define BLOCK_SIZE 512 +#define TEST_SIZE (512 * 150) +#include "tests/filesys/base/random.inc" diff --git a/pintos-progos/tests/filesys/base/lg-random.ck b/pintos-progos/tests/filesys/base/lg-random.ck new file mode 100644 index 0000000..dd9f1dd --- /dev/null +++ b/pintos-progos/tests/filesys/base/lg-random.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(lg-random) begin +(lg-random) create "bazzle" +(lg-random) open "bazzle" +(lg-random) write "bazzle" in random order +(lg-random) read "bazzle" in random order +(lg-random) close "bazzle" +(lg-random) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/lg-seq-block.c b/pintos-progos/tests/filesys/base/lg-seq-block.c new file mode 100644 index 0000000..580c30b --- /dev/null +++ b/pintos-progos/tests/filesys/base/lg-seq-block.c @@ -0,0 +1,7 @@ +/* Writes out a fairly large file sequentially, one fixed-size + block at a time, then reads it back to verify that it was + written properly. */ + +#define TEST_SIZE 75678 +#define BLOCK_SIZE 513 +#include "tests/filesys/base/seq-block.inc" diff --git a/pintos-progos/tests/filesys/base/lg-seq-block.ck b/pintos-progos/tests/filesys/base/lg-seq-block.ck new file mode 100644 index 0000000..b789081 --- /dev/null +++ b/pintos-progos/tests/filesys/base/lg-seq-block.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(lg-seq-block) begin +(lg-seq-block) create "noodle" +(lg-seq-block) open "noodle" +(lg-seq-block) writing "noodle" +(lg-seq-block) close "noodle" +(lg-seq-block) open "noodle" for verification +(lg-seq-block) verified contents of "noodle" +(lg-seq-block) close "noodle" +(lg-seq-block) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/lg-seq-random.c b/pintos-progos/tests/filesys/base/lg-seq-random.c new file mode 100644 index 0000000..fbb6bba --- /dev/null +++ b/pintos-progos/tests/filesys/base/lg-seq-random.c @@ -0,0 +1,6 @@ +/* Writes out a fairly large file sequentially, one random-sized + block at a time, then reads it back to verify that it was + written properly. */ + +#define TEST_SIZE 75678 +#include "tests/filesys/base/seq-random.inc" diff --git a/pintos-progos/tests/filesys/base/lg-seq-random.ck b/pintos-progos/tests/filesys/base/lg-seq-random.ck new file mode 100644 index 0000000..6b2dc82 --- /dev/null +++ b/pintos-progos/tests/filesys/base/lg-seq-random.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(lg-seq-random) begin +(lg-seq-random) create "nibble" +(lg-seq-random) open "nibble" +(lg-seq-random) writing "nibble" +(lg-seq-random) close "nibble" +(lg-seq-random) open "nibble" for verification +(lg-seq-random) verified contents of "nibble" +(lg-seq-random) close "nibble" +(lg-seq-random) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/random.inc b/pintos-progos/tests/filesys/base/random.inc new file mode 100644 index 0000000..eeeea68 --- /dev/null +++ b/pintos-progos/tests/filesys/base/random.inc @@ -0,0 +1,59 @@ +/* -*- c -*- */ + +#include +#include +#include +#include +#include "tests/lib.h" +#include "tests/main.h" + +#if TEST_SIZE % BLOCK_SIZE != 0 +#error TEST_SIZE must be a multiple of BLOCK_SIZE +#endif + +#define BLOCK_CNT (TEST_SIZE / BLOCK_SIZE) + +char buf[TEST_SIZE]; +int order[BLOCK_CNT]; + +void +test_main (void) +{ + const char *file_name = "bazzle"; + int fd; + size_t i; + + random_init (57); + random_bytes (buf, sizeof buf); + + for (i = 0; i < BLOCK_CNT; i++) + order[i] = i; + + CHECK (create (file_name, TEST_SIZE), "create \"%s\"", file_name); + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + + msg ("write \"%s\" in random order", file_name); + shuffle (order, BLOCK_CNT, sizeof *order); + for (i = 0; i < BLOCK_CNT; i++) + { + size_t ofs = BLOCK_SIZE * order[i]; + seek (fd, ofs); + if (write (fd, buf + ofs, BLOCK_SIZE) != BLOCK_SIZE) + fail ("write %d bytes at offset %zu failed", (int) BLOCK_SIZE, ofs); + } + + msg ("read \"%s\" in random order", file_name); + shuffle (order, BLOCK_CNT, sizeof *order); + for (i = 0; i < BLOCK_CNT; i++) + { + char block[BLOCK_SIZE]; + size_t ofs = BLOCK_SIZE * order[i]; + seek (fd, ofs); + if (read (fd, block, BLOCK_SIZE) != BLOCK_SIZE) + fail ("read %d bytes at offset %zu failed", (int) BLOCK_SIZE, ofs); + compare_bytes (block, buf + ofs, BLOCK_SIZE, ofs, file_name); + } + + msg ("close \"%s\"", file_name); + close (fd); +} diff --git a/pintos-progos/tests/filesys/base/seq-block.inc b/pintos-progos/tests/filesys/base/seq-block.inc new file mode 100644 index 0000000..d4c1f57 --- /dev/null +++ b/pintos-progos/tests/filesys/base/seq-block.inc @@ -0,0 +1,20 @@ +/* -*- c -*- */ + +#include "tests/filesys/seq-test.h" +#include "tests/main.h" + +static char buf[TEST_SIZE]; + +static size_t +return_block_size (void) +{ + return BLOCK_SIZE; +} + +void +test_main (void) +{ + seq_test ("noodle", + buf, sizeof buf, sizeof buf, + return_block_size, NULL); +} diff --git a/pintos-progos/tests/filesys/base/seq-random.inc b/pintos-progos/tests/filesys/base/seq-random.inc new file mode 100644 index 0000000..a4da4c5 --- /dev/null +++ b/pintos-progos/tests/filesys/base/seq-random.inc @@ -0,0 +1,22 @@ +/* -*- c -*- */ + +#include +#include "tests/filesys/seq-test.h" +#include "tests/main.h" + +static char buf[TEST_SIZE]; + +static size_t +return_random (void) +{ + return random_ulong () % 1031 + 1; +} + +void +test_main (void) +{ + random_init (-1); + seq_test ("nibble", + buf, sizeof buf, sizeof buf, + return_random, NULL); +} diff --git a/pintos-progos/tests/filesys/base/sm-create.c b/pintos-progos/tests/filesys/base/sm-create.c new file mode 100644 index 0000000..6b97ac1 --- /dev/null +++ b/pintos-progos/tests/filesys/base/sm-create.c @@ -0,0 +1,5 @@ +/* Tests that create properly zeros out the contents of a fairly + small file. */ + +#define TEST_SIZE 5678 +#include "tests/filesys/create.inc" diff --git a/pintos-progos/tests/filesys/base/sm-create.ck b/pintos-progos/tests/filesys/base/sm-create.ck new file mode 100644 index 0000000..8ca80dc --- /dev/null +++ b/pintos-progos/tests/filesys/base/sm-create.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(sm-create) begin +(sm-create) create "blargle" +(sm-create) open "blargle" for verification +(sm-create) verified contents of "blargle" +(sm-create) close "blargle" +(sm-create) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/sm-full.c b/pintos-progos/tests/filesys/base/sm-full.c new file mode 100644 index 0000000..23ff3d4 --- /dev/null +++ b/pintos-progos/tests/filesys/base/sm-full.c @@ -0,0 +1,6 @@ +/* Writes out the contents of a fairly small file all at once, + and then reads it back to make sure that it was written + properly. */ + +#define TEST_SIZE 5678 +#include "tests/filesys/base/full.inc" diff --git a/pintos-progos/tests/filesys/base/sm-full.ck b/pintos-progos/tests/filesys/base/sm-full.ck new file mode 100644 index 0000000..2e0eb36 --- /dev/null +++ b/pintos-progos/tests/filesys/base/sm-full.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(sm-full) begin +(sm-full) create "quux" +(sm-full) open "quux" +(sm-full) writing "quux" +(sm-full) close "quux" +(sm-full) open "quux" for verification +(sm-full) verified contents of "quux" +(sm-full) close "quux" +(sm-full) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/sm-random.c b/pintos-progos/tests/filesys/base/sm-random.c new file mode 100644 index 0000000..42d670f --- /dev/null +++ b/pintos-progos/tests/filesys/base/sm-random.c @@ -0,0 +1,7 @@ +/* Writes out the content of a fairly small file in random order, + then reads it back in random order to verify that it was + written properly. */ + +#define BLOCK_SIZE 13 +#define TEST_SIZE (13 * 123) +#include "tests/filesys/base/random.inc" diff --git a/pintos-progos/tests/filesys/base/sm-random.ck b/pintos-progos/tests/filesys/base/sm-random.ck new file mode 100644 index 0000000..bda049d --- /dev/null +++ b/pintos-progos/tests/filesys/base/sm-random.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(sm-random) begin +(sm-random) create "bazzle" +(sm-random) open "bazzle" +(sm-random) write "bazzle" in random order +(sm-random) read "bazzle" in random order +(sm-random) close "bazzle" +(sm-random) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/sm-seq-block.c b/pintos-progos/tests/filesys/base/sm-seq-block.c new file mode 100644 index 0000000..e368327 --- /dev/null +++ b/pintos-progos/tests/filesys/base/sm-seq-block.c @@ -0,0 +1,7 @@ +/* Writes out a fairly small file sequentially, one fixed-size + block at a time, then reads it back to verify that it was + written properly. */ + +#define TEST_SIZE 5678 +#define BLOCK_SIZE 513 +#include "tests/filesys/base/seq-block.inc" diff --git a/pintos-progos/tests/filesys/base/sm-seq-block.ck b/pintos-progos/tests/filesys/base/sm-seq-block.ck new file mode 100644 index 0000000..0e2939d --- /dev/null +++ b/pintos-progos/tests/filesys/base/sm-seq-block.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(sm-seq-block) begin +(sm-seq-block) create "noodle" +(sm-seq-block) open "noodle" +(sm-seq-block) writing "noodle" +(sm-seq-block) close "noodle" +(sm-seq-block) open "noodle" for verification +(sm-seq-block) verified contents of "noodle" +(sm-seq-block) close "noodle" +(sm-seq-block) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/sm-seq-random.c b/pintos-progos/tests/filesys/base/sm-seq-random.c new file mode 100644 index 0000000..89e5b71 --- /dev/null +++ b/pintos-progos/tests/filesys/base/sm-seq-random.c @@ -0,0 +1,6 @@ +/* Writes out a fairly large file sequentially, one random-sized + block at a time, then reads it back to verify that it was + written properly. */ + +#define TEST_SIZE 5678 +#include "tests/filesys/base/seq-random.inc" diff --git a/pintos-progos/tests/filesys/base/sm-seq-random.ck b/pintos-progos/tests/filesys/base/sm-seq-random.ck new file mode 100644 index 0000000..2fb368b --- /dev/null +++ b/pintos-progos/tests/filesys/base/sm-seq-random.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(sm-seq-random) begin +(sm-seq-random) create "nibble" +(sm-seq-random) open "nibble" +(sm-seq-random) writing "nibble" +(sm-seq-random) close "nibble" +(sm-seq-random) open "nibble" for verification +(sm-seq-random) verified contents of "nibble" +(sm-seq-random) close "nibble" +(sm-seq-random) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/syn-read.c b/pintos-progos/tests/filesys/base/syn-read.c new file mode 100644 index 0000000..7c36a42 --- /dev/null +++ b/pintos-progos/tests/filesys/base/syn-read.c @@ -0,0 +1,31 @@ +/* Spawns 10 child processes, all of which read from the same + file and make sure that the contents are what they should + be. */ + +#include +#include +#include +#include "tests/lib.h" +#include "tests/main.h" +#include "tests/filesys/base/syn-read.h" + +static char buf[BUF_SIZE]; + +#define CHILD_CNT 10 + +void +test_main (void) +{ + pid_t children[CHILD_CNT]; + int fd; + + CHECK (create (file_name, sizeof buf), "create \"%s\"", file_name); + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + random_bytes (buf, sizeof buf); + CHECK (write (fd, buf, sizeof buf) > 0, "write \"%s\"", file_name); + msg ("close \"%s\"", file_name); + close (fd); + + exec_children ("child-syn-read", children, CHILD_CNT); + wait_children (children, CHILD_CNT); +} diff --git a/pintos-progos/tests/filesys/base/syn-read.ck b/pintos-progos/tests/filesys/base/syn-read.ck new file mode 100644 index 0000000..e2f68e8 --- /dev/null +++ b/pintos-progos/tests/filesys/base/syn-read.ck @@ -0,0 +1,33 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(syn-read) begin +(syn-read) create "data" +(syn-read) open "data" +(syn-read) write "data" +(syn-read) close "data" +(syn-read) exec child 1 of 10: "child-syn-read 0" +(syn-read) exec child 2 of 10: "child-syn-read 1" +(syn-read) exec child 3 of 10: "child-syn-read 2" +(syn-read) exec child 4 of 10: "child-syn-read 3" +(syn-read) exec child 5 of 10: "child-syn-read 4" +(syn-read) exec child 6 of 10: "child-syn-read 5" +(syn-read) exec child 7 of 10: "child-syn-read 6" +(syn-read) exec child 8 of 10: "child-syn-read 7" +(syn-read) exec child 9 of 10: "child-syn-read 8" +(syn-read) exec child 10 of 10: "child-syn-read 9" +(syn-read) wait for child 1 of 10 returned 0 (expected 0) +(syn-read) wait for child 2 of 10 returned 1 (expected 1) +(syn-read) wait for child 3 of 10 returned 2 (expected 2) +(syn-read) wait for child 4 of 10 returned 3 (expected 3) +(syn-read) wait for child 5 of 10 returned 4 (expected 4) +(syn-read) wait for child 6 of 10 returned 5 (expected 5) +(syn-read) wait for child 7 of 10 returned 6 (expected 6) +(syn-read) wait for child 8 of 10 returned 7 (expected 7) +(syn-read) wait for child 9 of 10 returned 8 (expected 8) +(syn-read) wait for child 10 of 10 returned 9 (expected 9) +(syn-read) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/syn-read.h b/pintos-progos/tests/filesys/base/syn-read.h new file mode 100644 index 0000000..bff8082 --- /dev/null +++ b/pintos-progos/tests/filesys/base/syn-read.h @@ -0,0 +1,7 @@ +#ifndef TESTS_FILESYS_BASE_SYN_READ_H +#define TESTS_FILESYS_BASE_SYN_READ_H + +#define BUF_SIZE 1024 +static const char file_name[] = "data"; + +#endif /* tests/filesys/base/syn-read.h */ diff --git a/pintos-progos/tests/filesys/base/syn-remove.c b/pintos-progos/tests/filesys/base/syn-remove.c new file mode 100644 index 0000000..c9ba110 --- /dev/null +++ b/pintos-progos/tests/filesys/base/syn-remove.c @@ -0,0 +1,30 @@ +/* Verifies that a deleted file may still be written to and read + from. */ + +#include +#include +#include +#include "tests/lib.h" +#include "tests/main.h" + +char buf1[1234]; +char buf2[1234]; + +void +test_main (void) +{ + const char *file_name = "deleteme"; + int fd; + + CHECK (create (file_name, sizeof buf1), "create \"%s\"", file_name); + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + CHECK (remove (file_name), "remove \"%s\"", file_name); + random_bytes (buf1, sizeof buf1); + CHECK (write (fd, buf1, sizeof buf1) > 0, "write \"%s\"", file_name); + msg ("seek \"%s\" to 0", file_name); + seek (fd, 0); + CHECK (read (fd, buf2, sizeof buf2) > 0, "read \"%s\"", file_name); + compare_bytes (buf2, buf1, sizeof buf1, 0, file_name); + msg ("close \"%s\"", file_name); + close (fd); +} diff --git a/pintos-progos/tests/filesys/base/syn-remove.ck b/pintos-progos/tests/filesys/base/syn-remove.ck new file mode 100644 index 0000000..16ff11e --- /dev/null +++ b/pintos-progos/tests/filesys/base/syn-remove.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(syn-remove) begin +(syn-remove) create "deleteme" +(syn-remove) open "deleteme" +(syn-remove) remove "deleteme" +(syn-remove) write "deleteme" +(syn-remove) seek "deleteme" to 0 +(syn-remove) read "deleteme" +(syn-remove) close "deleteme" +(syn-remove) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/syn-write.c b/pintos-progos/tests/filesys/base/syn-write.c new file mode 100644 index 0000000..1439862 --- /dev/null +++ b/pintos-progos/tests/filesys/base/syn-write.c @@ -0,0 +1,31 @@ +/* Spawns several child processes to write out different parts of + the contents of a file and waits for them to finish. Then + reads back the file and verifies its contents. */ + +#include +#include +#include +#include +#include "tests/filesys/base/syn-write.h" +#include "tests/lib.h" +#include "tests/main.h" + +char buf1[BUF_SIZE]; +char buf2[BUF_SIZE]; + +void +test_main (void) +{ + pid_t children[CHILD_CNT]; + int fd; + + CHECK (create (file_name, sizeof buf1), "create \"%s\"", file_name); + + exec_children ("child-syn-wrt", children, CHILD_CNT); + wait_children (children, CHILD_CNT); + + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + CHECK (read (fd, buf1, sizeof buf1) > 0, "read \"%s\"", file_name); + random_bytes (buf2, sizeof buf2); + compare_bytes (buf1, buf2, sizeof buf1, 0, file_name); +} diff --git a/pintos-progos/tests/filesys/base/syn-write.ck b/pintos-progos/tests/filesys/base/syn-write.ck new file mode 100644 index 0000000..629a7a2 --- /dev/null +++ b/pintos-progos/tests/filesys/base/syn-write.ck @@ -0,0 +1,32 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(syn-write) begin +(syn-write) create "stuff" +(syn-write) exec child 1 of 10: "child-syn-wrt 0" +(syn-write) exec child 2 of 10: "child-syn-wrt 1" +(syn-write) exec child 3 of 10: "child-syn-wrt 2" +(syn-write) exec child 4 of 10: "child-syn-wrt 3" +(syn-write) exec child 5 of 10: "child-syn-wrt 4" +(syn-write) exec child 6 of 10: "child-syn-wrt 5" +(syn-write) exec child 7 of 10: "child-syn-wrt 6" +(syn-write) exec child 8 of 10: "child-syn-wrt 7" +(syn-write) exec child 9 of 10: "child-syn-wrt 8" +(syn-write) exec child 10 of 10: "child-syn-wrt 9" +(syn-write) wait for child 1 of 10 returned 0 (expected 0) +(syn-write) wait for child 2 of 10 returned 1 (expected 1) +(syn-write) wait for child 3 of 10 returned 2 (expected 2) +(syn-write) wait for child 4 of 10 returned 3 (expected 3) +(syn-write) wait for child 5 of 10 returned 4 (expected 4) +(syn-write) wait for child 6 of 10 returned 5 (expected 5) +(syn-write) wait for child 7 of 10 returned 6 (expected 6) +(syn-write) wait for child 8 of 10 returned 7 (expected 7) +(syn-write) wait for child 9 of 10 returned 8 (expected 8) +(syn-write) wait for child 10 of 10 returned 9 (expected 9) +(syn-write) open "stuff" +(syn-write) read "stuff" +(syn-write) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/base/syn-write.h b/pintos-progos/tests/filesys/base/syn-write.h new file mode 100644 index 0000000..07a6d5a --- /dev/null +++ b/pintos-progos/tests/filesys/base/syn-write.h @@ -0,0 +1,9 @@ +#ifndef TESTS_FILESYS_BASE_SYN_WRITE_H +#define TESTS_FILESYS_BASE_SYN_WRITE_H + +#define CHILD_CNT 10 +#define CHUNK_SIZE 512 +#define BUF_SIZE (CHILD_CNT * CHUNK_SIZE) +static const char file_name[] = "stuff"; + +#endif /* tests/filesys/base/syn-write.h */ diff --git a/pintos-progos/tests/filesys/create.inc b/pintos-progos/tests/filesys/create.inc new file mode 100644 index 0000000..4baf771 --- /dev/null +++ b/pintos-progos/tests/filesys/create.inc @@ -0,0 +1,15 @@ +/* -*- c -*- */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +static char buf[TEST_SIZE]; + +void +test_main (void) +{ + const char *file_name = "blargle"; + CHECK (create (file_name, TEST_SIZE), "create \"%s\"", file_name); + check_file (file_name, buf, TEST_SIZE); +} diff --git a/pintos-progos/tests/filesys/extended/Make.tests b/pintos-progos/tests/filesys/extended/Make.tests new file mode 100644 index 0000000..e03b98d --- /dev/null +++ b/pintos-progos/tests/filesys/extended/Make.tests @@ -0,0 +1,61 @@ +# -*- makefile -*- + +raw_tests = dir-empty-name dir-mk-tree dir-mkdir dir-open \ +dir-over-file dir-rm-cwd dir-rm-parent dir-rm-root dir-rm-tree \ +dir-rmdir dir-under-file dir-vine grow-create grow-dir-lg \ +grow-file-size grow-root-lg grow-root-sm grow-seq-lg grow-seq-sm \ +grow-sparse grow-tell grow-two-files syn-rw + +tests/filesys/extended_TESTS = $(patsubst %,tests/filesys/extended/%,$(raw_tests)) +tests/filesys/extended_EXTRA_GRADES = $(patsubst %,tests/filesys/extended/%-persistence,$(raw_tests)) + +tests/filesys/extended_PROGS = $(tests/filesys/extended_TESTS) \ +tests/filesys/extended/child-syn-rw tests/filesys/extended/tar + +$(foreach prog,$(tests/filesys/extended_PROGS), \ + $(eval $(prog)_SRC += $(prog).c tests/lib.c tests/filesys/seq-test.c)) +$(foreach prog,$(tests/filesys/extended_TESTS), \ + $(eval $(prog)_SRC += tests/main.c)) +$(foreach prog,$(tests/filesys/extended_TESTS), \ + $(eval $(prog)_PUTFILES += tests/filesys/extended/tar)) +# The version of GNU make 3.80 on vine barfs if this is split at +# the last comma. +$(foreach test,$(tests/filesys/extended_TESTS),$(eval $(test).output: FILESYSSOURCE = --disk=tmp.dsk)) + +tests/filesys/extended/dir-mk-tree_SRC += tests/filesys/extended/mk-tree.c +tests/filesys/extended/dir-rm-tree_SRC += tests/filesys/extended/mk-tree.c + +tests/filesys/extended/syn-rw_PUTFILES += tests/filesys/extended/child-syn-rw + +tests/filesys/extended/dir-vine.output: TIMEOUT = 150 + +GETTIMEOUT = 60 + +GETCMD = pintos -v -k -T $(GETTIMEOUT) +GETCMD += $(PINTOSOPTS) +GETCMD += $(SIMULATOR) +GETCMD += $(FILESYSSOURCE) +GETCMD += -g fs.tar -a $(TEST).tar +ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm) +GETCMD += --swap-size=4 +endif +GETCMD += -- -q +GETCMD += $(KERNELFLAGS) +GETCMD += run 'tar fs.tar /' +GETCMD += < /dev/null +GETCMD += 2> $(TEST)-persistence.errors $(if $(VERBOSE),|tee,>) $(TEST)-persistence.output + +tests/filesys/extended/%.output: kernel.bin + rm -f tmp.dsk + pintos-mkdisk tmp.dsk --filesys-size=2 + $(TESTCMD) + $(GETCMD) + rm -f tmp.dsk +$(foreach raw_test,$(raw_tests),$(eval tests/filesys/extended/$(raw_test)-persistence.output: tests/filesys/extended/$(raw_test).output)) +$(foreach raw_test,$(raw_tests),$(eval tests/filesys/extended/$(raw_test)-persistence.result: tests/filesys/extended/$(raw_test).result)) + +TARS = $(addsuffix .tar,$(tests/filesys/extended_TESTS)) + +clean:: + rm -f $(TARS) + rm -f tests/filesys/extended/can-rmdir-cwd diff --git a/pintos-progos/tests/filesys/extended/Rubric.functionality b/pintos-progos/tests/filesys/extended/Rubric.functionality new file mode 100644 index 0000000..91ed6f0 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/Rubric.functionality @@ -0,0 +1,26 @@ +Functionality of extended file system: +- Test directory support. +1 dir-mkdir +3 dir-mk-tree + +1 dir-rmdir +3 dir-rm-tree + +5 dir-vine + +- Test file growth. +1 grow-create +1 grow-seq-sm +3 grow-seq-lg +3 grow-sparse +3 grow-two-files +1 grow-tell +1 grow-file-size + +- Test directory growth. +1 grow-dir-lg +1 grow-root-sm +1 grow-root-lg + +- Test writing from multiple processes. +5 syn-rw diff --git a/pintos-progos/tests/filesys/extended/Rubric.persistence b/pintos-progos/tests/filesys/extended/Rubric.persistence new file mode 100644 index 0000000..405620a --- /dev/null +++ b/pintos-progos/tests/filesys/extended/Rubric.persistence @@ -0,0 +1,24 @@ +Persistence of file system: +1 dir-empty-name-persistence +1 dir-mk-tree-persistence +1 dir-mkdir-persistence +1 dir-open-persistence +1 dir-over-file-persistence +1 dir-rm-cwd-persistence +1 dir-rm-parent-persistence +1 dir-rm-root-persistence +1 dir-rm-tree-persistence +1 dir-rmdir-persistence +1 dir-under-file-persistence +1 dir-vine-persistence +1 grow-create-persistence +1 grow-dir-lg-persistence +1 grow-file-size-persistence +1 grow-root-lg-persistence +1 grow-root-sm-persistence +1 grow-seq-lg-persistence +1 grow-seq-sm-persistence +1 grow-sparse-persistence +1 grow-tell-persistence +1 grow-two-files-persistence +1 syn-rw-persistence diff --git a/pintos-progos/tests/filesys/extended/Rubric.robustness b/pintos-progos/tests/filesys/extended/Rubric.robustness new file mode 100644 index 0000000..fb9f32f --- /dev/null +++ b/pintos-progos/tests/filesys/extended/Rubric.robustness @@ -0,0 +1,9 @@ +Robustness of file system: +1 dir-empty-name +1 dir-open +1 dir-over-file +1 dir-under-file + +3 dir-rm-cwd +2 dir-rm-parent +1 dir-rm-root diff --git a/pintos-progos/tests/filesys/extended/child-syn-rw.c b/pintos-progos/tests/filesys/extended/child-syn-rw.c new file mode 100644 index 0000000..0e2217d --- /dev/null +++ b/pintos-progos/tests/filesys/extended/child-syn-rw.c @@ -0,0 +1,53 @@ +/* Child process for syn-rw. + Reads from a file created by our parent process, which is + growing it. We loop until we've read the whole file + successfully. Many iterations through the loop will return 0 + bytes, because the file has not grown in the meantime. That + is, we are "busy waiting" for the file to grow. + (This test could be improved by adding a "yield" system call + and calling yield whenever we receive a 0-byte read.) */ + +#include +#include +#include +#include "tests/filesys/extended/syn-rw.h" +#include "tests/lib.h" + +const char *test_name = "child-syn-rw"; + +static char buf1[BUF_SIZE]; +static char buf2[BUF_SIZE]; + +int +main (int argc, const char *argv[]) +{ + int child_idx; + int fd; + size_t ofs; + + quiet = true; + + CHECK (argc == 2, "argc must be 2, actually %d", argc); + child_idx = atoi (argv[1]); + + random_init (0); + random_bytes (buf1, sizeof buf1); + + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + ofs = 0; + while (ofs < sizeof buf2) + { + int bytes_read = read (fd, buf2 + ofs, sizeof buf2 - ofs); + CHECK (bytes_read >= -1 && bytes_read <= (int) (sizeof buf2 - ofs), + "%zu-byte read on \"%s\" returned invalid value of %d", + sizeof buf2 - ofs, file_name, bytes_read); + if (bytes_read > 0) + { + compare_bytes (buf2 + ofs, buf1 + ofs, bytes_read, ofs, file_name); + ofs += bytes_read; + } + } + close (fd); + + return child_idx; +} diff --git a/pintos-progos/tests/filesys/extended/dir-empty-name-persistence.ck b/pintos-progos/tests/filesys/extended/dir-empty-name-persistence.ck new file mode 100644 index 0000000..562c451 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-empty-name-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({}); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-empty-name.c b/pintos-progos/tests/filesys/extended/dir-empty-name.c new file mode 100644 index 0000000..c4859d2 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-empty-name.c @@ -0,0 +1,12 @@ +/* Tries to create a directory named as the empty string, + which must return failure. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (!mkdir (""), "mkdir \"\" (must return false)"); +} diff --git a/pintos-progos/tests/filesys/extended/dir-empty-name.ck b/pintos-progos/tests/filesys/extended/dir-empty-name.ck new file mode 100644 index 0000000..d6c5621 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-empty-name.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-empty-name) begin +(dir-empty-name) mkdir "" (must return false) +(dir-empty-name) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-mk-tree-persistence.ck b/pintos-progos/tests/filesys/extended/dir-mk-tree-persistence.ck new file mode 100644 index 0000000..fb16afd --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-mk-tree-persistence.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +my ($tree); +for my $a (0...3) { + for my $b (0...2) { + for my $c (0...2) { + for my $d (0...3) { + $tree->{$a}{$b}{$c}{$d} = ['']; + } + } + } +} +check_archive ($tree); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-mk-tree.c b/pintos-progos/tests/filesys/extended/dir-mk-tree.c new file mode 100644 index 0000000..a714ff3 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-mk-tree.c @@ -0,0 +1,12 @@ +/* Creates directories /0/0/0 through /3/2/2 and creates files in + the leaf directories. */ + +#include "tests/filesys/extended/mk-tree.h" +#include "tests/main.h" + +void +test_main (void) +{ + make_tree (4, 3, 3, 4); +} + diff --git a/pintos-progos/tests/filesys/extended/dir-mk-tree.ck b/pintos-progos/tests/filesys/extended/dir-mk-tree.ck new file mode 100644 index 0000000..a8507e2 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-mk-tree.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-mk-tree) begin +(dir-mk-tree) creating /0/0/0/0 through /3/2/2/3... +(dir-mk-tree) open "/0/2/0/3" +(dir-mk-tree) close "/0/2/0/3" +(dir-mk-tree) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-mkdir-persistence.ck b/pintos-progos/tests/filesys/extended/dir-mkdir-persistence.ck new file mode 100644 index 0000000..7682900 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-mkdir-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({'a' => {'b' => ["\0" x 512]}}); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-mkdir.c b/pintos-progos/tests/filesys/extended/dir-mkdir.c new file mode 100644 index 0000000..994f41c --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-mkdir.c @@ -0,0 +1,15 @@ +/* Tests mkdir(). */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (mkdir ("a"), "mkdir \"a\""); + CHECK (create ("a/b", 512), "create \"a/b\""); + CHECK (chdir ("a"), "chdir \"a\""); + CHECK (open ("b") > 1, "open \"b\""); +} + diff --git a/pintos-progos/tests/filesys/extended/dir-mkdir.ck b/pintos-progos/tests/filesys/extended/dir-mkdir.ck new file mode 100644 index 0000000..4644f80 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-mkdir.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-mkdir) begin +(dir-mkdir) mkdir "a" +(dir-mkdir) create "a/b" +(dir-mkdir) chdir "a" +(dir-mkdir) open "b" +(dir-mkdir) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-open-persistence.ck b/pintos-progos/tests/filesys/extended/dir-open-persistence.ck new file mode 100644 index 0000000..26ff2f1 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-open-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"xyzzy" => {}}); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-open.c b/pintos-progos/tests/filesys/extended/dir-open.c new file mode 100644 index 0000000..29d18b8 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-open.c @@ -0,0 +1,21 @@ +/* Opens a directory, then tries to write to it, which must + fail. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int fd; + int retval; + + CHECK (mkdir ("xyzzy"), "mkdir \"xyzzy\""); + CHECK ((fd = open ("xyzzy")) > 1, "open \"xyzzy\""); + + msg ("write \"xyzzy\""); + retval = write (fd, "foobar", 6); + CHECK (retval == -1, + "write \"xyzzy\" (must return -1, actually %d)", retval); +} diff --git a/pintos-progos/tests/filesys/extended/dir-open.ck b/pintos-progos/tests/filesys/extended/dir-open.ck new file mode 100644 index 0000000..fccc563 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-open.ck @@ -0,0 +1,20 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(dir-open) begin +(dir-open) mkdir "xyzzy" +(dir-open) open "xyzzy" +(dir-open) write "xyzzy" +(dir-open) write "xyzzy" (must return -1, actually -1) +(dir-open) end +dir-open: exit(0) +EOF +(dir-open) begin +(dir-open) mkdir "xyzzy" +(dir-open) open "xyzzy" +(dir-open) write "xyzzy" +dir-open: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-over-file-persistence.ck b/pintos-progos/tests/filesys/extended/dir-over-file-persistence.ck new file mode 100644 index 0000000..56b4ed2 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-over-file-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"abc" => {}}); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-over-file.c b/pintos-progos/tests/filesys/extended/dir-over-file.c new file mode 100644 index 0000000..cdd2c62 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-over-file.c @@ -0,0 +1,13 @@ +/* Tries to create a file with the same name as an existing + directory, which must return failure. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (mkdir ("abc"), "mkdir \"abc\""); + CHECK (!create ("abc", 0), "create \"abc\" (must return false)"); +} diff --git a/pintos-progos/tests/filesys/extended/dir-over-file.ck b/pintos-progos/tests/filesys/extended/dir-over-file.ck new file mode 100644 index 0000000..aae1c1e --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-over-file.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-over-file) begin +(dir-over-file) mkdir "abc" +(dir-over-file) create "abc" (must return false) +(dir-over-file) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-rm-cwd-persistence.ck b/pintos-progos/tests/filesys/extended/dir-rm-cwd-persistence.ck new file mode 100644 index 0000000..7533570 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rm-cwd-persistence.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +my ($cwd_removable) = read_text_file ("tests/filesys/extended/can-rmdir-cwd"); +$cwd_removable eq 'YES' || $cwd_removable eq 'NO' or die; +check_archive ($cwd_removable eq 'YES' ? {} : {"a" => {}}); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-rm-cwd.c b/pintos-progos/tests/filesys/extended/dir-rm-cwd.c new file mode 100644 index 0000000..78e13de --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rm-cwd.c @@ -0,0 +1,75 @@ +/* Tries to remove the current directory, which may succeed or + fail. The requirements in each case are different; refer to + the assignment for details. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +static int +wrap_open (const char *name) +{ + static int fds[8], fd_cnt; + int fd, i; + + CHECK ((fd = open (name)) > 1, "open \"%s\"", name); + for (i = 0; i < fd_cnt; i++) + if (fds[i] == fd) + fail ("fd returned is not unique"); + fds[fd_cnt++] = fd; + return fd; +} + +void +test_main (void) +{ + int root_fd, a_fd0; + char name[READDIR_MAX_LEN + 1]; + + root_fd = wrap_open ("/"); + CHECK (mkdir ("a"), "mkdir \"a\""); + + a_fd0 = wrap_open ("/a"); + CHECK (!readdir (a_fd0, name), "verify \"/a\" is empty"); + CHECK (inumber (root_fd) != inumber (a_fd0), + "\"/\" and \"/a\" must have different inumbers"); + + CHECK (chdir ("a"), "chdir \"a\""); + + msg ("try to remove \"/a\""); + if (remove ("/a")) + { + msg ("remove successful"); + + CHECK (open ("/a") == -1, "open \"/a\" (must fail)"); + CHECK (open (".") == -1, "open \".\" (must fail)"); + CHECK (open ("..") == -1, "open \"..\" (must fail)"); + CHECK (!create ("x", 512), "create \"x\" (must fail)"); + } + else + { + int a_fd1, a_fd2, a_fd3; + + msg ("remove failed"); + + CHECK (!remove ("../a"), "try to remove \"../a\" (must fail)"); + CHECK (!remove (".././a"), "try to remove \".././a\" (must fail)"); + CHECK (!remove ("/./a"), "try to remove \"/./a\" (must fail)"); + + a_fd1 = wrap_open ("/a"); + a_fd2 = wrap_open ("."); + CHECK (inumber (a_fd1) == inumber (a_fd2), + "\"/a\" and \".\" must have same inumber"); + CHECK (inumber (root_fd) != inumber (a_fd1), + "\"/\" and \"/a\" must have different inumbers"); + + CHECK (chdir ("/a"), "chdir \"/a\""); + a_fd3 = wrap_open ("."); + CHECK (inumber (a_fd3) == inumber (a_fd1), + "\".\" must have same inumber as before"); + + CHECK (chdir ("/"), "chdir \"/\""); + CHECK (!remove ("a"), "try to remove \"a\" (must fail: still open)"); + } + CHECK (!readdir (a_fd0, name), "verify \"/a\" is empty"); +} diff --git a/pintos-progos/tests/filesys/extended/dir-rm-cwd.ck b/pintos-progos/tests/filesys/extended/dir-rm-cwd.ck new file mode 100644 index 0000000..6fa4739 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rm-cwd.ck @@ -0,0 +1,51 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +my ($cwd_removable) = check_expected (IGNORE_EXIT_CODES => 1, + {NO => <<'EOF', YES => <<'EOF'}); +(dir-rm-cwd) begin +(dir-rm-cwd) open "/" +(dir-rm-cwd) mkdir "a" +(dir-rm-cwd) open "/a" +(dir-rm-cwd) verify "/a" is empty +(dir-rm-cwd) "/" and "/a" must have different inumbers +(dir-rm-cwd) chdir "a" +(dir-rm-cwd) try to remove "/a" +(dir-rm-cwd) remove failed +(dir-rm-cwd) try to remove "../a" (must fail) +(dir-rm-cwd) try to remove ".././a" (must fail) +(dir-rm-cwd) try to remove "/./a" (must fail) +(dir-rm-cwd) open "/a" +(dir-rm-cwd) open "." +(dir-rm-cwd) "/a" and "." must have same inumber +(dir-rm-cwd) "/" and "/a" must have different inumbers +(dir-rm-cwd) chdir "/a" +(dir-rm-cwd) open "." +(dir-rm-cwd) "." must have same inumber as before +(dir-rm-cwd) chdir "/" +(dir-rm-cwd) try to remove "a" (must fail: still open) +(dir-rm-cwd) verify "/a" is empty +(dir-rm-cwd) end +EOF +(dir-rm-cwd) begin +(dir-rm-cwd) open "/" +(dir-rm-cwd) mkdir "a" +(dir-rm-cwd) open "/a" +(dir-rm-cwd) verify "/a" is empty +(dir-rm-cwd) "/" and "/a" must have different inumbers +(dir-rm-cwd) chdir "a" +(dir-rm-cwd) try to remove "/a" +(dir-rm-cwd) remove successful +(dir-rm-cwd) open "/a" (must fail) +(dir-rm-cwd) open "." (must fail) +(dir-rm-cwd) open ".." (must fail) +(dir-rm-cwd) create "x" (must fail) +(dir-rm-cwd) verify "/a" is empty +(dir-rm-cwd) end +EOF +open (CAN_RMDIR_CWD, ">tests/filesys/extended/can-rmdir-cwd") + or die "tests/filesys/extended/can-rmdir-cwd: create: $!\n"; +print CAN_RMDIR_CWD "$cwd_removable"; +close (CAN_RMDIR_CWD); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-rm-parent-persistence.ck b/pintos-progos/tests/filesys/extended/dir-rm-parent-persistence.ck new file mode 100644 index 0000000..f30b04a --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rm-parent-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"a" => {"b" => {}}}); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-rm-parent.c b/pintos-progos/tests/filesys/extended/dir-rm-parent.c new file mode 100644 index 0000000..eb43f5b --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rm-parent.c @@ -0,0 +1,16 @@ +/* Tries to remove a parent of the current directory. This must + fail, because that directory is non-empty. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (mkdir ("a"), "mkdir \"a\""); + CHECK (chdir ("a"), "chdir \"a\""); + CHECK (mkdir ("b"), "mkdir \"b\""); + CHECK (chdir ("b"), "chdir \"b\""); + CHECK (!remove ("/a"), "remove \"/a\" (must fail)"); +} diff --git a/pintos-progos/tests/filesys/extended/dir-rm-parent.ck b/pintos-progos/tests/filesys/extended/dir-rm-parent.ck new file mode 100644 index 0000000..9fea8f2 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rm-parent.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-rm-parent) begin +(dir-rm-parent) mkdir "a" +(dir-rm-parent) chdir "a" +(dir-rm-parent) mkdir "b" +(dir-rm-parent) chdir "b" +(dir-rm-parent) remove "/a" (must fail) +(dir-rm-parent) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-rm-root-persistence.ck b/pintos-progos/tests/filesys/extended/dir-rm-root-persistence.ck new file mode 100644 index 0000000..6315107 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rm-root-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"a" => ["\0" x 243]}); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-rm-root.c b/pintos-progos/tests/filesys/extended/dir-rm-root.c new file mode 100644 index 0000000..c47f1eb --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rm-root.c @@ -0,0 +1,13 @@ +/* Try to remove the root directory. + This must fail. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (!remove ("/"), "remove \"/\" (must fail)"); + CHECK (create ("/a", 243), "create \"/a\""); +} diff --git a/pintos-progos/tests/filesys/extended/dir-rm-root.ck b/pintos-progos/tests/filesys/extended/dir-rm-root.ck new file mode 100644 index 0000000..8a69ff3 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rm-root.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-rm-root) begin +(dir-rm-root) remove "/" (must fail) +(dir-rm-root) create "/a" +(dir-rm-root) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-rm-tree-persistence.ck b/pintos-progos/tests/filesys/extended/dir-rm-tree-persistence.ck new file mode 100644 index 0000000..562c451 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rm-tree-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({}); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-rm-tree.c b/pintos-progos/tests/filesys/extended/dir-rm-tree.c new file mode 100644 index 0000000..bab41a6 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rm-tree.c @@ -0,0 +1,62 @@ +/* Creates directories /0/0/0 through /3/2/2 and files in the + leaf directories, then removes them. */ + +#include +#include +#include +#include "tests/filesys/extended/mk-tree.h" +#include "tests/lib.h" +#include "tests/main.h" + +static void remove_tree (int at, int bt, int ct, int dt); + +void +test_main (void) +{ + make_tree (4, 3, 3, 4); + remove_tree (4, 3, 3, 4); +} + +static void do_remove (const char *format, ...) PRINTF_FORMAT (1, 2); + +static void +remove_tree (int at, int bt, int ct, int dt) +{ + char try[128]; + int a, b, c, d; + + msg ("removing /0/0/0/0 through /%d/%d/%d/%d...", + at - 1, bt - 1, ct - 1, dt - 1); + quiet = true; + for (a = 0; a < at; a++) + { + for (b = 0; b < bt; b++) + { + for (c = 0; c < ct; c++) + { + for (d = 0; d < dt; d++) + do_remove ("/%d/%d/%d/%d", a, b, c, d); + do_remove ("/%d/%d/%d", a, b, c); + } + do_remove ("/%d/%d", a, b); + } + do_remove ("/%d", a); + } + quiet = false; + + snprintf (try, sizeof (try), "/%d/%d/%d/%d", at - 1, 0, ct - 1, 0); + CHECK (open (try) == -1, "open \"%s\" (must return -1)", try); +} + +static void +do_remove (const char *format, ...) +{ + char name[128]; + va_list args; + + va_start (args, format); + vsnprintf (name, sizeof name, format, args); + va_end (args); + + CHECK (remove (name), "remove \"%s\"", name); +} diff --git a/pintos-progos/tests/filesys/extended/dir-rm-tree.ck b/pintos-progos/tests/filesys/extended/dir-rm-tree.ck new file mode 100644 index 0000000..587b493 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rm-tree.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-rm-tree) begin +(dir-rm-tree) creating /0/0/0/0 through /3/2/2/3... +(dir-rm-tree) open "/0/2/0/3" +(dir-rm-tree) close "/0/2/0/3" +(dir-rm-tree) removing /0/0/0/0 through /3/2/2/3... +(dir-rm-tree) open "/3/0/2/0" (must return -1) +(dir-rm-tree) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-rmdir-persistence.ck b/pintos-progos/tests/filesys/extended/dir-rmdir-persistence.ck new file mode 100644 index 0000000..562c451 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rmdir-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({}); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-rmdir.c b/pintos-progos/tests/filesys/extended/dir-rmdir.c new file mode 100644 index 0000000..b3cbc6f --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rmdir.c @@ -0,0 +1,14 @@ +/* Creates and removes a directory, then makes sure that it's + really gone. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (mkdir ("a"), "mkdir \"a\""); + CHECK (remove ("a"), "rmdir \"a\""); + CHECK (!chdir ("a"), "chdir \"a\" (must return false)"); +} diff --git a/pintos-progos/tests/filesys/extended/dir-rmdir.ck b/pintos-progos/tests/filesys/extended/dir-rmdir.ck new file mode 100644 index 0000000..e0d8922 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-rmdir.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-rmdir) begin +(dir-rmdir) mkdir "a" +(dir-rmdir) rmdir "a" +(dir-rmdir) chdir "a" (must return false) +(dir-rmdir) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-under-file-persistence.ck b/pintos-progos/tests/filesys/extended/dir-under-file-persistence.ck new file mode 100644 index 0000000..67ca528 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-under-file-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"abc" => ['']}); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-under-file.c b/pintos-progos/tests/filesys/extended/dir-under-file.c new file mode 100644 index 0000000..973a8b1 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-under-file.c @@ -0,0 +1,13 @@ +/* Tries to create a directory with the same name as an existing + file, which must return failure. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (create ("abc", 0), "create \"abc\""); + CHECK (!mkdir ("abc"), "mkdir \"abc\" (must return false)"); +} diff --git a/pintos-progos/tests/filesys/extended/dir-under-file.ck b/pintos-progos/tests/filesys/extended/dir-under-file.ck new file mode 100644 index 0000000..cce23b4 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-under-file.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-under-file) begin +(dir-under-file) create "abc" +(dir-under-file) mkdir "abc" (must return false) +(dir-under-file) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-vine-persistence.ck b/pintos-progos/tests/filesys/extended/dir-vine-persistence.ck new file mode 100644 index 0000000..698ef01 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-vine-persistence.ck @@ -0,0 +1,37 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +# The archive should look like this: +# +# 40642 dir-vine +# 42479 tar +# 0 start +# 11 start/file0 +# 0 start/dir0 +# 11 start/dir0/file1 +# 0 start/dir0/dir1 +# 11 start/dir0/dir1/file2 +# 0 start/dir0/dir1/dir2 +# 11 start/dir0/dir1/dir2/file3 +# 0 start/dir0/dir1/dir2/dir3 +# 11 start/dir0/dir1/dir2/dir3/file4 +# 0 start/dir0/dir1/dir2/dir3/dir4 +# 11 start/dir0/dir1/dir2/dir3/dir4/file5 +# 0 start/dir0/dir1/dir2/dir3/dir4/dir5 +# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/file6 +# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6 +# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/file7 +# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7 +# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file8 +# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8 +# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file9 +# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9 +my ($dir) = {}; +my ($root) = {"start" => $dir}; +for (my ($i) = 0; $i < 10; $i++) { + $dir->{"file$i"} = ["contents $i\n"]; + $dir = $dir->{"dir$i"} = {}; +} +check_archive ($root); +pass; diff --git a/pintos-progos/tests/filesys/extended/dir-vine.c b/pintos-progos/tests/filesys/extended/dir-vine.c new file mode 100644 index 0000000..8a31c38 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-vine.c @@ -0,0 +1,85 @@ +/* Create a very deep "vine" of directories: /dir0/dir1/dir2/... + and an ordinary file in each of them, until we fill up the + disk. + + Then delete most of them, for two reasons. First, "tar" + limits file names to 100 characters (which could be extended + to 256 without much trouble). Second, a full disk has no room + for the tar archive. */ + +#include +#include +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int i; + + msg ("creating many levels of files and directories..."); + quiet = true; + CHECK (mkdir ("start"), "mkdir \"start\""); + CHECK (chdir ("start"), "chdir \"start\""); + for (i = 0; ; i++) + { + char name[3][READDIR_MAX_LEN + 1]; + char file_name[16], dir_name[16]; + char contents[128]; + int fd; + + /* Create file. */ + snprintf (file_name, sizeof file_name, "file%d", i); + if (!create (file_name, 0)) + break; + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + snprintf (contents, sizeof contents, "contents %d\n", i); + if (write (fd, contents, strlen (contents)) != (int) strlen (contents)) + { + CHECK (remove (file_name), "remove \"%s\"", file_name); + close (fd); + break; + } + close (fd); + + /* Create directory. */ + snprintf (dir_name, sizeof dir_name, "dir%d", i); + if (!mkdir (dir_name)) + { + CHECK (remove (file_name), "remove \"%s\"", file_name); + break; + } + + /* Check for file and directory. */ + CHECK ((fd = open (".")) > 1, "open \".\""); + CHECK (readdir (fd, name[0]), "readdir \".\""); + CHECK (readdir (fd, name[1]), "readdir \".\""); + CHECK (!readdir (fd, name[2]), "readdir \".\" (should fail)"); + CHECK ((!strcmp (name[0], dir_name) && !strcmp (name[1], file_name)) + || (!strcmp (name[1], dir_name) && !strcmp (name[0], file_name)), + "names should be \"%s\" and \"%s\", " + "actually \"%s\" and \"%s\"", + file_name, dir_name, name[0], name[1]); + close (fd); + + /* Descend into directory. */ + CHECK (chdir (dir_name), "chdir \"%s\"", dir_name); + } + CHECK (i > 200, "created files and directories only to level %d", i); + quiet = false; + + msg ("removing all but top 10 levels of files and directories..."); + quiet = true; + while (i-- > 10) + { + char file_name[16], dir_name[16]; + + snprintf (file_name, sizeof file_name, "file%d", i); + snprintf (dir_name, sizeof dir_name, "dir%d", i); + CHECK (chdir (".."), "chdir \"..\""); + CHECK (remove (dir_name), "remove \"%s\"", dir_name); + CHECK (remove (file_name), "remove \"%s\"", file_name); + } + quiet = false; +} diff --git a/pintos-progos/tests/filesys/extended/dir-vine.ck b/pintos-progos/tests/filesys/extended/dir-vine.ck new file mode 100644 index 0000000..db452b0 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/dir-vine.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-vine) begin +(dir-vine) creating many levels of files and directories... +(dir-vine) removing all but top 10 levels of files and directories... +(dir-vine) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-create-persistence.ck b/pintos-progos/tests/filesys/extended/grow-create-persistence.ck new file mode 100644 index 0000000..bbcb24f --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-create-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"blargle" => ['']}); +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-create.c b/pintos-progos/tests/filesys/extended/grow-create.c new file mode 100644 index 0000000..9ccc4ea --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-create.c @@ -0,0 +1,4 @@ +/* Create a file of size 0. */ + +#define TEST_SIZE 0 +#include "tests/filesys/create.inc" diff --git a/pintos-progos/tests/filesys/extended/grow-create.ck b/pintos-progos/tests/filesys/extended/grow-create.ck new file mode 100644 index 0000000..b2e69d1 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-create.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-create) begin +(grow-create) create "blargle" +(grow-create) open "blargle" for verification +(grow-create) verified contents of "blargle" +(grow-create) close "blargle" +(grow-create) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-dir-lg-persistence.ck b/pintos-progos/tests/filesys/extended/grow-dir-lg-persistence.ck new file mode 100644 index 0000000..989a322 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-dir-lg-persistence.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +my ($fs); +$fs->{'x'}{"file$_"} = [random_bytes (512)] foreach 0...49; +check_archive ($fs); +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-dir-lg.c b/pintos-progos/tests/filesys/extended/grow-dir-lg.c new file mode 100644 index 0000000..20a194b --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-dir-lg.c @@ -0,0 +1,6 @@ +/* Creates a directory, + then creates 50 files in that directory. */ + +#define FILE_CNT 50 +#define DIRECTORY "/x" +#include "tests/filesys/extended/grow-dir.inc" diff --git a/pintos-progos/tests/filesys/extended/grow-dir-lg.ck b/pintos-progos/tests/filesys/extended/grow-dir-lg.ck new file mode 100644 index 0000000..ec58bd3 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-dir-lg.ck @@ -0,0 +1,61 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-dir-lg) begin +(grow-dir-lg) mkdir /x +(grow-dir-lg) creating and checking "/x/file0" +(grow-dir-lg) creating and checking "/x/file1" +(grow-dir-lg) creating and checking "/x/file2" +(grow-dir-lg) creating and checking "/x/file3" +(grow-dir-lg) creating and checking "/x/file4" +(grow-dir-lg) creating and checking "/x/file5" +(grow-dir-lg) creating and checking "/x/file6" +(grow-dir-lg) creating and checking "/x/file7" +(grow-dir-lg) creating and checking "/x/file8" +(grow-dir-lg) creating and checking "/x/file9" +(grow-dir-lg) creating and checking "/x/file10" +(grow-dir-lg) creating and checking "/x/file11" +(grow-dir-lg) creating and checking "/x/file12" +(grow-dir-lg) creating and checking "/x/file13" +(grow-dir-lg) creating and checking "/x/file14" +(grow-dir-lg) creating and checking "/x/file15" +(grow-dir-lg) creating and checking "/x/file16" +(grow-dir-lg) creating and checking "/x/file17" +(grow-dir-lg) creating and checking "/x/file18" +(grow-dir-lg) creating and checking "/x/file19" +(grow-dir-lg) creating and checking "/x/file20" +(grow-dir-lg) creating and checking "/x/file21" +(grow-dir-lg) creating and checking "/x/file22" +(grow-dir-lg) creating and checking "/x/file23" +(grow-dir-lg) creating and checking "/x/file24" +(grow-dir-lg) creating and checking "/x/file25" +(grow-dir-lg) creating and checking "/x/file26" +(grow-dir-lg) creating and checking "/x/file27" +(grow-dir-lg) creating and checking "/x/file28" +(grow-dir-lg) creating and checking "/x/file29" +(grow-dir-lg) creating and checking "/x/file30" +(grow-dir-lg) creating and checking "/x/file31" +(grow-dir-lg) creating and checking "/x/file32" +(grow-dir-lg) creating and checking "/x/file33" +(grow-dir-lg) creating and checking "/x/file34" +(grow-dir-lg) creating and checking "/x/file35" +(grow-dir-lg) creating and checking "/x/file36" +(grow-dir-lg) creating and checking "/x/file37" +(grow-dir-lg) creating and checking "/x/file38" +(grow-dir-lg) creating and checking "/x/file39" +(grow-dir-lg) creating and checking "/x/file40" +(grow-dir-lg) creating and checking "/x/file41" +(grow-dir-lg) creating and checking "/x/file42" +(grow-dir-lg) creating and checking "/x/file43" +(grow-dir-lg) creating and checking "/x/file44" +(grow-dir-lg) creating and checking "/x/file45" +(grow-dir-lg) creating and checking "/x/file46" +(grow-dir-lg) creating and checking "/x/file47" +(grow-dir-lg) creating and checking "/x/file48" +(grow-dir-lg) creating and checking "/x/file49" +(grow-dir-lg) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-dir.inc b/pintos-progos/tests/filesys/extended/grow-dir.inc new file mode 100644 index 0000000..bee0ba0 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-dir.inc @@ -0,0 +1,41 @@ +/* -*- c -*- */ + +#include +#include +#include "tests/filesys/seq-test.h" +#include "tests/lib.h" +#include "tests/main.h" + +static char buf[512]; + +static size_t +return_block_size (void) +{ + return sizeof buf; +} + +void +test_main (void) +{ + size_t i; + +#ifdef DIRECTORY + CHECK (mkdir (DIRECTORY), "mkdir %s", DIRECTORY); +#define DIR_PREFIX DIRECTORY "/" +#else +#define DIR_PREFIX "" +#endif + for (i = 0; i < FILE_CNT; i++) + { + char file_name[128]; + snprintf (file_name, sizeof file_name, "%sfile%zu", DIR_PREFIX, i); + + msg ("creating and checking \"%s\"", file_name); + + quiet = true; + seq_test (file_name, + buf, sizeof buf, sizeof buf, + return_block_size, NULL); + quiet = false; + } +} diff --git a/pintos-progos/tests/filesys/extended/grow-file-size-persistence.ck b/pintos-progos/tests/filesys/extended/grow-file-size-persistence.ck new file mode 100644 index 0000000..150f383 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-file-size-persistence.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_archive ({"testfile" => [random_bytes (2134)]}); +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-file-size.c b/pintos-progos/tests/filesys/extended/grow-file-size.c new file mode 100644 index 0000000..3ce8588 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-file-size.c @@ -0,0 +1,33 @@ +/* Grows a file from 0 bytes to 2,134 bytes, 37 bytes at a time, + and checks that the file's size is reported correctly at each + step. */ + +#include +#include "tests/filesys/seq-test.h" +#include "tests/lib.h" +#include "tests/main.h" + +static char buf[2134]; + +static size_t +return_block_size (void) +{ + return 37; +} + +static void +check_file_size (int fd, long ofs) +{ + long size = filesize (fd); + if (size != ofs) + fail ("filesize not updated properly: should be %ld, actually %ld", + ofs, size); +} + +void +test_main (void) +{ + seq_test ("testfile", + buf, sizeof buf, 0, + return_block_size, check_file_size); +} diff --git a/pintos-progos/tests/filesys/extended/grow-file-size.ck b/pintos-progos/tests/filesys/extended/grow-file-size.ck new file mode 100644 index 0000000..d81feff --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-file-size.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-file-size) begin +(grow-file-size) create "testfile" +(grow-file-size) open "testfile" +(grow-file-size) writing "testfile" +(grow-file-size) close "testfile" +(grow-file-size) open "testfile" for verification +(grow-file-size) verified contents of "testfile" +(grow-file-size) close "testfile" +(grow-file-size) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-root-lg-persistence.ck b/pintos-progos/tests/filesys/extended/grow-root-lg-persistence.ck new file mode 100644 index 0000000..1692f46 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-root-lg-persistence.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +my ($fs); +$fs->{"file$_"} = [random_bytes (512)] foreach 0...49; +check_archive ($fs); +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-root-lg.c b/pintos-progos/tests/filesys/extended/grow-root-lg.c new file mode 100644 index 0000000..d8d6c09 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-root-lg.c @@ -0,0 +1,4 @@ +/* Creates 50 files in the root directory. */ + +#define FILE_CNT 50 +#include "tests/filesys/extended/grow-dir.inc" diff --git a/pintos-progos/tests/filesys/extended/grow-root-lg.ck b/pintos-progos/tests/filesys/extended/grow-root-lg.ck new file mode 100644 index 0000000..b174bc9 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-root-lg.ck @@ -0,0 +1,60 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-root-lg) begin +(grow-root-lg) creating and checking "file0" +(grow-root-lg) creating and checking "file1" +(grow-root-lg) creating and checking "file2" +(grow-root-lg) creating and checking "file3" +(grow-root-lg) creating and checking "file4" +(grow-root-lg) creating and checking "file5" +(grow-root-lg) creating and checking "file6" +(grow-root-lg) creating and checking "file7" +(grow-root-lg) creating and checking "file8" +(grow-root-lg) creating and checking "file9" +(grow-root-lg) creating and checking "file10" +(grow-root-lg) creating and checking "file11" +(grow-root-lg) creating and checking "file12" +(grow-root-lg) creating and checking "file13" +(grow-root-lg) creating and checking "file14" +(grow-root-lg) creating and checking "file15" +(grow-root-lg) creating and checking "file16" +(grow-root-lg) creating and checking "file17" +(grow-root-lg) creating and checking "file18" +(grow-root-lg) creating and checking "file19" +(grow-root-lg) creating and checking "file20" +(grow-root-lg) creating and checking "file21" +(grow-root-lg) creating and checking "file22" +(grow-root-lg) creating and checking "file23" +(grow-root-lg) creating and checking "file24" +(grow-root-lg) creating and checking "file25" +(grow-root-lg) creating and checking "file26" +(grow-root-lg) creating and checking "file27" +(grow-root-lg) creating and checking "file28" +(grow-root-lg) creating and checking "file29" +(grow-root-lg) creating and checking "file30" +(grow-root-lg) creating and checking "file31" +(grow-root-lg) creating and checking "file32" +(grow-root-lg) creating and checking "file33" +(grow-root-lg) creating and checking "file34" +(grow-root-lg) creating and checking "file35" +(grow-root-lg) creating and checking "file36" +(grow-root-lg) creating and checking "file37" +(grow-root-lg) creating and checking "file38" +(grow-root-lg) creating and checking "file39" +(grow-root-lg) creating and checking "file40" +(grow-root-lg) creating and checking "file41" +(grow-root-lg) creating and checking "file42" +(grow-root-lg) creating and checking "file43" +(grow-root-lg) creating and checking "file44" +(grow-root-lg) creating and checking "file45" +(grow-root-lg) creating and checking "file46" +(grow-root-lg) creating and checking "file47" +(grow-root-lg) creating and checking "file48" +(grow-root-lg) creating and checking "file49" +(grow-root-lg) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-root-sm-persistence.ck b/pintos-progos/tests/filesys/extended/grow-root-sm-persistence.ck new file mode 100644 index 0000000..2b0b8ab --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-root-sm-persistence.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +my ($fs); +$fs->{"file$_"} = [random_bytes (512)] foreach 0...19; +check_archive ($fs); +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-root-sm.c b/pintos-progos/tests/filesys/extended/grow-root-sm.c new file mode 100644 index 0000000..ee375d5 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-root-sm.c @@ -0,0 +1,4 @@ +/* Creates 20 files in the root directory. */ + +#define FILE_CNT 20 +#include "tests/filesys/extended/grow-dir.inc" diff --git a/pintos-progos/tests/filesys/extended/grow-root-sm.ck b/pintos-progos/tests/filesys/extended/grow-root-sm.ck new file mode 100644 index 0000000..1aac7e9 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-root-sm.ck @@ -0,0 +1,30 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-root-sm) begin +(grow-root-sm) creating and checking "file0" +(grow-root-sm) creating and checking "file1" +(grow-root-sm) creating and checking "file2" +(grow-root-sm) creating and checking "file3" +(grow-root-sm) creating and checking "file4" +(grow-root-sm) creating and checking "file5" +(grow-root-sm) creating and checking "file6" +(grow-root-sm) creating and checking "file7" +(grow-root-sm) creating and checking "file8" +(grow-root-sm) creating and checking "file9" +(grow-root-sm) creating and checking "file10" +(grow-root-sm) creating and checking "file11" +(grow-root-sm) creating and checking "file12" +(grow-root-sm) creating and checking "file13" +(grow-root-sm) creating and checking "file14" +(grow-root-sm) creating and checking "file15" +(grow-root-sm) creating and checking "file16" +(grow-root-sm) creating and checking "file17" +(grow-root-sm) creating and checking "file18" +(grow-root-sm) creating and checking "file19" +(grow-root-sm) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-seq-lg-persistence.ck b/pintos-progos/tests/filesys/extended/grow-seq-lg-persistence.ck new file mode 100644 index 0000000..41aaae0 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-seq-lg-persistence.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_archive ({"testme" => [random_bytes (72943)]}); +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-seq-lg.c b/pintos-progos/tests/filesys/extended/grow-seq-lg.c new file mode 100644 index 0000000..3108d17 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-seq-lg.c @@ -0,0 +1,5 @@ +/* Grows a file from 0 bytes to 72,943 bytes, 1,234 bytes at a + time. */ + +#define TEST_SIZE 72943 +#include "tests/filesys/extended/grow-seq.inc" diff --git a/pintos-progos/tests/filesys/extended/grow-seq-lg.ck b/pintos-progos/tests/filesys/extended/grow-seq-lg.ck new file mode 100644 index 0000000..90fcd8c --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-seq-lg.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-seq-lg) begin +(grow-seq-lg) create "testme" +(grow-seq-lg) open "testme" +(grow-seq-lg) writing "testme" +(grow-seq-lg) close "testme" +(grow-seq-lg) open "testme" for verification +(grow-seq-lg) verified contents of "testme" +(grow-seq-lg) close "testme" +(grow-seq-lg) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-seq-sm-persistence.ck b/pintos-progos/tests/filesys/extended/grow-seq-sm-persistence.ck new file mode 100644 index 0000000..6cb0bd8 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-seq-sm-persistence.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_archive ({"testme" => [random_bytes (5678)]}); +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-seq-sm.c b/pintos-progos/tests/filesys/extended/grow-seq-sm.c new file mode 100644 index 0000000..3656e2e --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-seq-sm.c @@ -0,0 +1,5 @@ +/* Grows a file from 0 bytes to 5,678 bytes, 1,234 bytes at a + time. */ + +#define TEST_SIZE 5678 +#include "tests/filesys/extended/grow-seq.inc" diff --git a/pintos-progos/tests/filesys/extended/grow-seq-sm.ck b/pintos-progos/tests/filesys/extended/grow-seq-sm.ck new file mode 100644 index 0000000..5cf4518 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-seq-sm.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-seq-sm) begin +(grow-seq-sm) create "testme" +(grow-seq-sm) open "testme" +(grow-seq-sm) writing "testme" +(grow-seq-sm) close "testme" +(grow-seq-sm) open "testme" for verification +(grow-seq-sm) verified contents of "testme" +(grow-seq-sm) close "testme" +(grow-seq-sm) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-seq.inc b/pintos-progos/tests/filesys/extended/grow-seq.inc new file mode 100644 index 0000000..1b7710c --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-seq.inc @@ -0,0 +1,20 @@ +/* -*- c -*- */ + +#include "tests/filesys/seq-test.h" +#include "tests/main.h" + +static char buf[TEST_SIZE]; + +static size_t +return_block_size (void) +{ + return 1234; +} + +void +test_main (void) +{ + seq_test ("testme", + buf, sizeof buf, 0, + return_block_size, NULL); +} diff --git a/pintos-progos/tests/filesys/extended/grow-sparse-persistence.ck b/pintos-progos/tests/filesys/extended/grow-sparse-persistence.ck new file mode 100644 index 0000000..3f06a5b --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-sparse-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"testfile" => ["\0" x 76543]}); +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-sparse.c b/pintos-progos/tests/filesys/extended/grow-sparse.c new file mode 100644 index 0000000..6eab210 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-sparse.c @@ -0,0 +1,25 @@ +/* Tests that seeking past the end of a file and writing will + properly zero out the region in between. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +static char buf[76543]; + +void +test_main (void) +{ + const char *file_name = "testfile"; + char zero = 0; + int fd; + + CHECK (create (file_name, 0), "create \"%s\"", file_name); + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + msg ("seek \"%s\"", file_name); + seek (fd, sizeof buf - 1); + CHECK (write (fd, &zero, 1) > 0, "write \"%s\"", file_name); + msg ("close \"%s\"", file_name); + close (fd); + check_file (file_name, buf, sizeof buf); +} diff --git a/pintos-progos/tests/filesys/extended/grow-sparse.ck b/pintos-progos/tests/filesys/extended/grow-sparse.ck new file mode 100644 index 0000000..379ba2c --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-sparse.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-sparse) begin +(grow-sparse) create "testfile" +(grow-sparse) open "testfile" +(grow-sparse) seek "testfile" +(grow-sparse) write "testfile" +(grow-sparse) close "testfile" +(grow-sparse) open "testfile" for verification +(grow-sparse) verified contents of "testfile" +(grow-sparse) close "testfile" +(grow-sparse) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-tell-persistence.ck b/pintos-progos/tests/filesys/extended/grow-tell-persistence.ck new file mode 100644 index 0000000..d93a422 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-tell-persistence.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_archive ({"foobar" => [random_bytes (2134)]}); +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-tell.c b/pintos-progos/tests/filesys/extended/grow-tell.c new file mode 100644 index 0000000..5f5da5b --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-tell.c @@ -0,0 +1,32 @@ +/* Checks that growing a file updates the file position + correctly. */ + +#include +#include "tests/filesys/seq-test.h" +#include "tests/lib.h" +#include "tests/main.h" + +static char buf[2134]; + +static size_t +return_block_size (void) +{ + return 37; +} + +static void +check_tell (int fd, long ofs) +{ + long pos = tell (fd); + if (pos != ofs) + fail ("file position not updated properly: should be %ld, actually %ld", + ofs, pos); +} + +void +test_main (void) +{ + seq_test ("foobar", + buf, sizeof buf, 0, + return_block_size, check_tell); +} diff --git a/pintos-progos/tests/filesys/extended/grow-tell.ck b/pintos-progos/tests/filesys/extended/grow-tell.ck new file mode 100644 index 0000000..fe94707 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-tell.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-tell) begin +(grow-tell) create "foobar" +(grow-tell) open "foobar" +(grow-tell) writing "foobar" +(grow-tell) close "foobar" +(grow-tell) open "foobar" for verification +(grow-tell) verified contents of "foobar" +(grow-tell) close "foobar" +(grow-tell) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-two-files-persistence.ck b/pintos-progos/tests/filesys/extended/grow-two-files-persistence.ck new file mode 100644 index 0000000..1c4ced1 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-two-files-persistence.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +my ($a) = random_bytes (8143); +my ($b) = random_bytes (8143); +check_archive ({"a" => [$a], "b" => [$b]}); +pass; diff --git a/pintos-progos/tests/filesys/extended/grow-two-files.c b/pintos-progos/tests/filesys/extended/grow-two-files.c new file mode 100644 index 0000000..6a8fb1c --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-two-files.c @@ -0,0 +1,62 @@ +/* Grows two files in parallel and checks that their contents are + correct. */ + +#include +#include +#include "tests/lib.h" +#include "tests/main.h" + +#define FILE_SIZE 8143 +static char buf_a[FILE_SIZE]; +static char buf_b[FILE_SIZE]; + +static void +write_some_bytes (const char *file_name, int fd, const char *buf, size_t *ofs) +{ + if (*ofs < FILE_SIZE) + { + size_t block_size = random_ulong () % (FILE_SIZE / 8) + 1; + size_t ret_val; + if (block_size > FILE_SIZE - *ofs) + block_size = FILE_SIZE - *ofs; + + ret_val = write (fd, buf + *ofs, block_size); + if (ret_val != block_size) + fail ("write %zu bytes at offset %zu in \"%s\" returned %zu", + block_size, *ofs, file_name, ret_val); + *ofs += block_size; + } +} + +void +test_main (void) +{ + int fd_a, fd_b; + size_t ofs_a = 0, ofs_b = 0; + + random_init (0); + random_bytes (buf_a, sizeof buf_a); + random_bytes (buf_b, sizeof buf_b); + + CHECK (create ("a", 0), "create \"a\""); + CHECK (create ("b", 0), "create \"b\""); + + CHECK ((fd_a = open ("a")) > 1, "open \"a\""); + CHECK ((fd_b = open ("b")) > 1, "open \"b\""); + + msg ("write \"a\" and \"b\" alternately"); + while (ofs_a < FILE_SIZE || ofs_b < FILE_SIZE) + { + write_some_bytes ("a", fd_a, buf_a, &ofs_a); + write_some_bytes ("b", fd_b, buf_b, &ofs_b); + } + + msg ("close \"a\""); + close (fd_a); + + msg ("close \"b\""); + close (fd_b); + + check_file ("a", buf_a, FILE_SIZE); + check_file ("b", buf_b, FILE_SIZE); +} diff --git a/pintos-progos/tests/filesys/extended/grow-two-files.ck b/pintos-progos/tests/filesys/extended/grow-two-files.ck new file mode 100644 index 0000000..b5e754a --- /dev/null +++ b/pintos-progos/tests/filesys/extended/grow-two-files.ck @@ -0,0 +1,23 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-two-files) begin +(grow-two-files) create "a" +(grow-two-files) create "b" +(grow-two-files) open "a" +(grow-two-files) open "b" +(grow-two-files) write "a" and "b" alternately +(grow-two-files) close "a" +(grow-two-files) close "b" +(grow-two-files) open "a" for verification +(grow-two-files) verified contents of "a" +(grow-two-files) close "a" +(grow-two-files) open "b" for verification +(grow-two-files) verified contents of "b" +(grow-two-files) close "b" +(grow-two-files) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/mk-tree.c b/pintos-progos/tests/filesys/extended/mk-tree.c new file mode 100644 index 0000000..a36bb88 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/mk-tree.c @@ -0,0 +1,67 @@ +/* Library function for creating a tree of directories. */ + +#include +#include +#include "tests/filesys/extended/mk-tree.h" +#include "tests/lib.h" + +static void do_mkdir (const char *format, ...) PRINTF_FORMAT (1, 2); +static void do_touch (const char *format, ...) PRINTF_FORMAT (1, 2); + +void +make_tree (int at, int bt, int ct, int dt) +{ + char try[128]; + int a, b, c, d; + int fd; + + msg ("creating /0/0/0/0 through /%d/%d/%d/%d...", + at - 1, bt - 1, ct - 1, dt - 1); + quiet = true; + for (a = 0; a < at; a++) + { + do_mkdir ("/%d", a); + for (b = 0; b < bt; b++) + { + do_mkdir ("/%d/%d", a, b); + for (c = 0; c < ct; c++) + { + do_mkdir ("/%d/%d/%d", a, b, c); + for (d = 0; d < dt; d++) + do_touch ("/%d/%d/%d/%d", a, b, c, d); + } + } + } + quiet = false; + + snprintf (try, sizeof try, "/%d/%d/%d/%d", 0, bt - 1, 0, dt - 1); + CHECK ((fd = open (try)) > 1, "open \"%s\"", try); + msg ("close \"%s\"", try); + close (fd); +} + +static void +do_mkdir (const char *format, ...) +{ + char dir[128]; + va_list args; + + va_start (args, format); + vsnprintf (dir, sizeof dir, format, args); + va_end (args); + + CHECK (mkdir (dir), "mkdir \"%s\"", dir); +} + +static void +do_touch (const char *format, ...) +{ + char file[128]; + va_list args; + + va_start (args, format); + vsnprintf (file, sizeof file, format, args); + va_end (args); + + CHECK (create (file, 0), "create \"%s\"", file); +} diff --git a/pintos-progos/tests/filesys/extended/mk-tree.h b/pintos-progos/tests/filesys/extended/mk-tree.h new file mode 100644 index 0000000..df0d5a6 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/mk-tree.h @@ -0,0 +1,6 @@ +#ifndef TESTS_FILESYS_EXTENDED_MK_TREE_H +#define TESTS_FILESYS_EXTENDED_MK_TREE_H + +void make_tree (int at, int bt, int ct, int dt); + +#endif /* tests/filesys/extended/mk-tree.h */ diff --git a/pintos-progos/tests/filesys/extended/syn-rw-persistence.ck b/pintos-progos/tests/filesys/extended/syn-rw-persistence.ck new file mode 100644 index 0000000..62d57ee --- /dev/null +++ b/pintos-progos/tests/filesys/extended/syn-rw-persistence.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_archive ({"child-syn-rw" => "tests/filesys/extended/child-syn-rw", + "logfile" => [random_bytes (8 * 512)]}); +pass; diff --git a/pintos-progos/tests/filesys/extended/syn-rw.c b/pintos-progos/tests/filesys/extended/syn-rw.c new file mode 100644 index 0000000..657dfb5 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/syn-rw.c @@ -0,0 +1,35 @@ +/* Grows a file in chunks while subprocesses read the growing + file. */ + +#include +#include +#include "tests/filesys/extended/syn-rw.h" +#include "tests/lib.h" +#include "tests/main.h" + +char buf[BUF_SIZE]; + +#define CHILD_CNT 4 + +void +test_main (void) +{ + pid_t children[CHILD_CNT]; + size_t ofs; + int fd; + + CHECK (create (file_name, 0), "create \"%s\"", file_name); + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + + exec_children ("child-syn-rw", children, CHILD_CNT); + + random_bytes (buf, sizeof buf); + quiet = true; + for (ofs = 0; ofs < BUF_SIZE; ofs += CHUNK_SIZE) + CHECK (write (fd, buf + ofs, CHUNK_SIZE) > 0, + "write %d bytes at offset %zu in \"%s\"", + (int) CHUNK_SIZE, ofs, file_name); + quiet = false; + + wait_children (children, CHILD_CNT); +} diff --git a/pintos-progos/tests/filesys/extended/syn-rw.ck b/pintos-progos/tests/filesys/extended/syn-rw.ck new file mode 100644 index 0000000..ac82aa8 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/syn-rw.ck @@ -0,0 +1,20 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(syn-rw) begin +(syn-rw) create "logfile" +(syn-rw) open "logfile" +(syn-rw) exec child 1 of 4: "child-syn-rw 0" +(syn-rw) exec child 2 of 4: "child-syn-rw 1" +(syn-rw) exec child 3 of 4: "child-syn-rw 2" +(syn-rw) exec child 4 of 4: "child-syn-rw 3" +(syn-rw) wait for child 1 of 4 returned 0 (expected 0) +(syn-rw) wait for child 2 of 4 returned 1 (expected 1) +(syn-rw) wait for child 3 of 4 returned 2 (expected 2) +(syn-rw) wait for child 4 of 4 returned 3 (expected 3) +(syn-rw) end +EOF +pass; diff --git a/pintos-progos/tests/filesys/extended/syn-rw.h b/pintos-progos/tests/filesys/extended/syn-rw.h new file mode 100644 index 0000000..170aeb4 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/syn-rw.h @@ -0,0 +1,9 @@ +#ifndef TESTS_FILESYS_EXTENDED_SYN_RW_H +#define TESTS_FILESYS_EXTENDED_SYN_RW_H + +#define CHUNK_SIZE 8 +#define CHUNK_CNT 512 +#define BUF_SIZE (CHUNK_SIZE * CHUNK_CNT) +static const char file_name[] = "logfile"; + +#endif /* tests/filesys/extended/syn-rw.h */ diff --git a/pintos-progos/tests/filesys/extended/tar.c b/pintos-progos/tests/filesys/extended/tar.c new file mode 100644 index 0000000..9801484 --- /dev/null +++ b/pintos-progos/tests/filesys/extended/tar.c @@ -0,0 +1,208 @@ +/* tar.c + + Creates a tar archive. */ + +#include +#include +#include +#include + +static void usage (void); +static bool make_tar_archive (const char *archive_name, + char *files[], size_t file_cnt); + +int +main (int argc, char *argv[]) +{ + if (argc < 3) + usage (); + + return (make_tar_archive (argv[1], argv + 2, argc - 2) + ? EXIT_SUCCESS : EXIT_FAILURE); +} + +static void +usage (void) +{ + printf ("tar, tar archive creator\n" + "Usage: tar ARCHIVE FILE...\n" + "where ARCHIVE is the tar archive to create\n" + " and FILE... is a list of files or directories to put into it.\n" + "(ARCHIVE itself will not be included in the archive, even if it\n" + "is in a directory to be archived.)\n"); + exit (EXIT_FAILURE); +} + +static bool archive_file (char file_name[], size_t file_name_size, + int archive_fd, bool *write_error); + +static bool archive_ordinary_file (const char *file_name, int file_fd, + int archive_fd, bool *write_error); +static bool archive_directory (char file_name[], size_t file_name_size, + int file_fd, int archive_fd, bool *write_error); +static bool write_header (const char *file_name, enum ustar_type, int size, + int archive_fd, bool *write_error); + +static bool do_write (int fd, const char *buffer, int size, bool *write_error); + +static bool +make_tar_archive (const char *archive_name, char *files[], size_t file_cnt) +{ + static const char zeros[512]; + int archive_fd; + bool success = true; + bool write_error = false; + size_t i; + + if (!create (archive_name, 0)) + { + printf ("%s: create failed\n", archive_name); + return false; + } + archive_fd = open (archive_name); + if (archive_fd < 0) + { + printf ("%s: open failed\n", archive_name); + return false; + } + + for (i = 0; i < file_cnt; i++) + { + char file_name[128]; + + strlcpy (file_name, files[i], sizeof file_name); + if (!archive_file (file_name, sizeof file_name, + archive_fd, &write_error)) + success = false; + } + + if (!do_write (archive_fd, zeros, 512, &write_error) + || !do_write (archive_fd, zeros, 512, &write_error)) + success = false; + + close (archive_fd); + + return success; +} + +static bool +archive_file (char file_name[], size_t file_name_size, + int archive_fd, bool *write_error) +{ + int file_fd = open (file_name); + if (file_fd >= 0) + { + bool success; + + if (inumber (file_fd) != inumber (archive_fd)) + { + if (!isdir (file_fd)) + success = archive_ordinary_file (file_name, file_fd, + archive_fd, write_error); + else + success = archive_directory (file_name, file_name_size, file_fd, + archive_fd, write_error); + } + else + { + /* Nothing to do: don't try to archive the archive file. */ + success = true; + } + + close (file_fd); + + return success; + } + else + { + printf ("%s: open failed\n", file_name); + return false; + } +} + +static bool +archive_ordinary_file (const char *file_name, int file_fd, + int archive_fd, bool *write_error) +{ + bool read_error = false; + bool success = true; + int file_size = filesize (file_fd); + + if (!write_header (file_name, USTAR_REGULAR, file_size, + archive_fd, write_error)) + return false; + + while (file_size > 0) + { + static char buf[512]; + int chunk_size = file_size > 512 ? 512 : file_size; + int read_retval = read (file_fd, buf, chunk_size); + int bytes_read = read_retval > 0 ? read_retval : 0; + + if (bytes_read != chunk_size && !read_error) + { + printf ("%s: read error\n", file_name); + read_error = true; + success = false; + } + + memset (buf + bytes_read, 0, 512 - bytes_read); + if (!do_write (archive_fd, buf, 512, write_error)) + success = false; + + file_size -= chunk_size; + } + + return success; +} + +static bool +archive_directory (char file_name[], size_t file_name_size, int file_fd, + int archive_fd, bool *write_error) +{ + size_t dir_len; + bool success = true; + + dir_len = strlen (file_name); + if (dir_len + 1 + READDIR_MAX_LEN + 1 > file_name_size) + { + printf ("%s: file name too long\n", file_name); + return false; + } + + if (!write_header (file_name, USTAR_DIRECTORY, 0, archive_fd, write_error)) + return false; + + file_name[dir_len] = '/'; + while (readdir (file_fd, &file_name[dir_len + 1])) + if (!archive_file (file_name, file_name_size, archive_fd, write_error)) + success = false; + file_name[dir_len] = '\0'; + + return success; +} + +static bool +write_header (const char *file_name, enum ustar_type type, int size, + int archive_fd, bool *write_error) +{ + static char header[512]; + return (ustar_make_header (file_name, type, size, header) + && do_write (archive_fd, header, 512, write_error)); +} + +static bool +do_write (int fd, const char *buffer, int size, bool *write_error) +{ + if (write (fd, buffer, size) == size) + return true; + else + { + if (!*write_error) + { + printf ("error writing archive\n"); + *write_error = true; + } + return false; + } +} diff --git a/pintos-progos/tests/filesys/seq-test.c b/pintos-progos/tests/filesys/seq-test.c new file mode 100644 index 0000000..8ce222c --- /dev/null +++ b/pintos-progos/tests/filesys/seq-test.c @@ -0,0 +1,37 @@ +#include "tests/filesys/seq-test.h" +#include +#include +#include "tests/lib.h" + +void +seq_test (const char *file_name, void *buf, size_t size, size_t initial_size, + size_t (*block_size_func) (void), + void (*check_func) (int fd, long ofs)) +{ + size_t ofs; + int fd; + + random_bytes (buf, size); + CHECK (create (file_name, initial_size), "create \"%s\"", file_name); + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + + ofs = 0; + msg ("writing \"%s\"", file_name); + while (ofs < size) + { + size_t block_size = block_size_func (); + if (block_size > size - ofs) + block_size = size - ofs; + + if (write (fd, buf + ofs, block_size) != (int) block_size) + fail ("write %zu bytes at offset %zu in \"%s\" failed", + block_size, ofs, file_name); + + ofs += block_size; + if (check_func != NULL) + check_func (fd, ofs); + } + msg ("close \"%s\"", file_name); + close (fd); + check_file (file_name, buf, size); +} diff --git a/pintos-progos/tests/filesys/seq-test.h b/pintos-progos/tests/filesys/seq-test.h new file mode 100644 index 0000000..0697381 --- /dev/null +++ b/pintos-progos/tests/filesys/seq-test.h @@ -0,0 +1,11 @@ +#ifndef TESTS_FILESYS_SEQ_TEST_H +#define TESTS_FILESYS_SEQ_TEST_H + +#include + +void seq_test (const char *file_name, + void *buf, size_t size, size_t initial_size, + size_t (*block_size_func) (void), + void (*check_func) (int fd, long ofs)); + +#endif /* tests/filesys/seq-test.h */ diff --git a/pintos-progos/tests/internal/list.c b/pintos-progos/tests/internal/list.c new file mode 100644 index 0000000..836c69e --- /dev/null +++ b/pintos-progos/tests/internal/list.c @@ -0,0 +1,174 @@ +/* Test program for lib/kernel/list.c. + + Attempts to test the list functionality that is not + sufficiently tested elsewhere in Pintos. + + This is not a test we will run on your submitted projects. + It is here for completeness. +*/ + +#undef NDEBUG +#include +#include +#include +#include +#include "threads/test.h" + +/* Maximum number of elements in a linked list that we will + test. */ +#define MAX_SIZE 64 + +/* A linked list element. */ +struct value + { + struct list_elem elem; /* List element. */ + int value; /* Item value. */ + }; + +static void shuffle (struct value[], size_t); +static bool value_less (const struct list_elem *, const struct list_elem *, + void *); +static void verify_list_fwd (struct list *, int size); +static void verify_list_bkwd (struct list *, int size); + +/* Test the linked list implementation. */ +void +test (void) +{ + int size; + + printf ("testing various size lists:"); + for (size = 0; size < MAX_SIZE; size++) + { + int repeat; + + printf (" %d", size); + for (repeat = 0; repeat < 10; repeat++) + { + static struct value values[MAX_SIZE * 4]; + struct list list; + struct list_elem *e; + int i, ofs; + + /* Put values 0...SIZE in random order in VALUES. */ + for (i = 0; i < size; i++) + values[i].value = i; + shuffle (values, size); + + /* Assemble list. */ + list_init (&list); + for (i = 0; i < size; i++) + list_push_back (&list, &values[i].elem); + + /* Verify correct minimum and maximum elements. */ + e = list_min (&list, value_less, NULL); + ASSERT (size ? list_entry (e, struct value, elem)->value == 0 + : e == list_begin (&list)); + e = list_max (&list, value_less, NULL); + ASSERT (size ? list_entry (e, struct value, elem)->value == size - 1 + : e == list_begin (&list)); + + /* Sort and verify list. */ + list_sort (&list, value_less, NULL); + verify_list_fwd (&list, size); + + /* Reverse and verify list. */ + list_reverse (&list); + verify_list_bkwd (&list, size); + + /* Shuffle, insert using list_insert_ordered(), + and verify ordering. */ + shuffle (values, size); + list_init (&list); + for (i = 0; i < size; i++) + list_insert_ordered (&list, &values[i].elem, + value_less, NULL); + verify_list_fwd (&list, size); + + /* Duplicate some items, uniquify, and verify. */ + ofs = size; + for (e = list_begin (&list); e != list_end (&list); + e = list_next (e)) + { + struct value *v = list_entry (e, struct value, elem); + int copies = random_ulong () % 4; + while (copies-- > 0) + { + values[ofs].value = v->value; + list_insert (e, &values[ofs++].elem); + } + } + ASSERT ((size_t) ofs < sizeof values / sizeof *values); + list_unique (&list, NULL, value_less, NULL); + verify_list_fwd (&list, size); + } + } + + printf (" done\n"); + printf ("list: PASS\n"); +} + +/* Shuffles the CNT elements in ARRAY into random order. */ +static void +shuffle (struct value *array, size_t cnt) +{ + size_t i; + + for (i = 0; i < cnt; i++) + { + size_t j = i + random_ulong () % (cnt - i); + struct value t = array[j]; + array[j] = array[i]; + array[i] = t; + } +} + +/* Returns true if value A is less than value B, false + otherwise. */ +static bool +value_less (const struct list_elem *a_, const struct list_elem *b_, + void *aux UNUSED) +{ + const struct value *a = list_entry (a_, struct value, elem); + const struct value *b = list_entry (b_, struct value, elem); + + return a->value < b->value; +} + +/* Verifies that LIST contains the values 0...SIZE when traversed + in forward order. */ +static void +verify_list_fwd (struct list *list, int size) +{ + struct list_elem *e; + int i; + + for (i = 0, e = list_begin (list); + i < size && e != list_end (list); + i++, e = list_next (e)) + { + struct value *v = list_entry (e, struct value, elem); + ASSERT (i == v->value); + } + ASSERT (i == size); + ASSERT (e == list_end (list)); +} + +/* Verifies that LIST contains the values 0...SIZE when traversed + in reverse order. */ +static void +verify_list_bkwd (struct list *list, int size) +{ + struct list_elem *e; + int i; + + for (i = 0, e = list_rbegin (list); + i < size && e != list_rend (list); + i++, e = list_prev (e)) + { + struct value *v = list_entry (e, struct value, elem); + ASSERT (i == v->value); + } + ASSERT (i == size); + ASSERT (e == list_rend (list)); +} diff --git a/pintos-progos/tests/internal/stdio.c b/pintos-progos/tests/internal/stdio.c new file mode 100644 index 0000000..fb60cda --- /dev/null +++ b/pintos-progos/tests/internal/stdio.c @@ -0,0 +1,208 @@ +/* Test program for printf() in lib/stdio.c. + + Attempts to test printf() functionality that is not + sufficiently tested elsewhere in Pintos. + + This is not a test we will run on your submitted projects. + It is here for completeness. +*/ + +#undef NDEBUG +#include +#include +#include +#include +#include +#include +#include "threads/test.h" + +/* Number of failures so far. */ +static int failure_cnt; + +static void +checkf (const char *expect, const char *format, ...) +{ + char output[128]; + va_list args; + + printf ("\"%s\" -> \"%s\": ", format, expect); + + va_start (args, format); + vsnprintf (output, sizeof output, format, args); + va_end (args); + + if (strcmp (expect, output)) + { + printf ("\nFAIL: actual output \"%s\"\n", output); + failure_cnt++; + } + else + printf ("okay\n"); +} + +/* Test printf() implementation. */ +void +test (void) +{ + printf ("Testing formats:"); + + /* Check that commas show up in the right places, for positive + numbers. */ + checkf ("1", "%'d", 1); + checkf ("12", "%'d", 12); + checkf ("123", "%'d", 123); + checkf ("1,234", "%'d", 1234); + checkf ("12,345", "%'d", 12345); + checkf ("123,456", "%'ld", 123456L); + checkf ("1,234,567", "%'ld", 1234567L); + checkf ("12,345,678", "%'ld", 12345678L); + checkf ("123,456,789", "%'ld", 123456789L); + checkf ("1,234,567,890", "%'ld", 1234567890L); + checkf ("12,345,678,901", "%'lld", 12345678901LL); + checkf ("123,456,789,012", "%'lld", 123456789012LL); + checkf ("1,234,567,890,123", "%'lld", 1234567890123LL); + checkf ("12,345,678,901,234", "%'lld", 12345678901234LL); + checkf ("123,456,789,012,345", "%'lld", 123456789012345LL); + checkf ("1,234,567,890,123,456", "%'lld", 1234567890123456LL); + checkf ("12,345,678,901,234,567", "%'lld", 12345678901234567LL); + checkf ("123,456,789,012,345,678", "%'lld", 123456789012345678LL); + checkf ("1,234,567,890,123,456,789", "%'lld", 1234567890123456789LL); + + /* Check that commas show up in the right places, for positive + numbers. */ + checkf ("-1", "%'d", -1); + checkf ("-12", "%'d", -12); + checkf ("-123", "%'d", -123); + checkf ("-1,234", "%'d", -1234); + checkf ("-12,345", "%'d", -12345); + checkf ("-123,456", "%'ld", -123456L); + checkf ("-1,234,567", "%'ld", -1234567L); + checkf ("-12,345,678", "%'ld", -12345678L); + checkf ("-123,456,789", "%'ld", -123456789L); + checkf ("-1,234,567,890", "%'ld", -1234567890L); + checkf ("-12,345,678,901", "%'lld", -12345678901LL); + checkf ("-123,456,789,012", "%'lld", -123456789012LL); + checkf ("-1,234,567,890,123", "%'lld", -1234567890123LL); + checkf ("-12,345,678,901,234", "%'lld", -12345678901234LL); + checkf ("-123,456,789,012,345", "%'lld", -123456789012345LL); + checkf ("-1,234,567,890,123,456", "%'lld", -1234567890123456LL); + checkf ("-12,345,678,901,234,567", "%'lld", -12345678901234567LL); + checkf ("-123,456,789,012,345,678", "%'lld", -123456789012345678LL); + checkf ("-1,234,567,890,123,456,789", "%'lld", -1234567890123456789LL); + + /* Check signed integer conversions. */ + checkf (" 0", "%5d", 0); + checkf ("0 ", "%-5d", 0); + checkf (" +0", "%+5d", 0); + checkf ("+0 ", "%+-5d", 0); + checkf (" 0", "% 5d", 0); + checkf ("00000", "%05d", 0); + checkf (" ", "%5.0d", 0); + checkf (" 00", "%5.2d", 0); + checkf ("0", "%d", 0); + + checkf (" 1", "%5d", 1); + checkf ("1 ", "%-5d", 1); + checkf (" +1", "%+5d", 1); + checkf ("+1 ", "%+-5d", 1); + checkf (" 1", "% 5d", 1); + checkf ("00001", "%05d", 1); + checkf (" 1", "%5.0d", 1); + checkf (" 01", "%5.2d", 1); + checkf ("1", "%d", 1); + + checkf (" -1", "%5d", -1); + checkf ("-1 ", "%-5d", -1); + checkf (" -1", "%+5d", -1); + checkf ("-1 ", "%+-5d", -1); + checkf (" -1", "% 5d", -1); + checkf ("-0001", "%05d", -1); + checkf (" -1", "%5.0d", -1); + checkf (" -01", "%5.2d", -1); + checkf ("-1", "%d", -1); + + checkf ("12345", "%5d", 12345); + checkf ("12345", "%-5d", 12345); + checkf ("+12345", "%+5d", 12345); + checkf ("+12345", "%+-5d", 12345); + checkf (" 12345", "% 5d", 12345); + checkf ("12345", "%05d", 12345); + checkf ("12345", "%5.0d", 12345); + checkf ("12345", "%5.2d", 12345); + checkf ("12345", "%d", 12345); + + checkf ("123456", "%5d", 123456); + checkf ("123456", "%-5d", 123456); + checkf ("+123456", "%+5d", 123456); + checkf ("+123456", "%+-5d", 123456); + checkf (" 123456", "% 5d", 123456); + checkf ("123456", "%05d", 123456); + checkf ("123456", "%5.0d", 123456); + checkf ("123456", "%5.2d", 123456); + checkf ("123456", "%d", 123456); + + /* Check unsigned integer conversions. */ + checkf (" 0", "%5u", 0); + checkf (" 0", "%5o", 0); + checkf (" 0", "%5x", 0); + checkf (" 0", "%5X", 0); + checkf (" 0", "%#5o", 0); + checkf (" 0", "%#5x", 0); + checkf (" 0", "%#5X", 0); + checkf (" 00000000", "%#10.8x", 0); + + checkf (" 1", "%5u", 1); + checkf (" 1", "%5o", 1); + checkf (" 1", "%5x", 1); + checkf (" 1", "%5X", 1); + checkf (" 01", "%#5o", 1); + checkf (" 0x1", "%#5x", 1); + checkf (" 0X1", "%#5X", 1); + checkf ("0x00000001", "%#10.8x", 1); + + checkf ("123456", "%5u", 123456); + checkf ("361100", "%5o", 123456); + checkf ("1e240", "%5x", 123456); + checkf ("1E240", "%5X", 123456); + checkf ("0361100", "%#5o", 123456); + checkf ("0x1e240", "%#5x", 123456); + checkf ("0X1E240", "%#5X", 123456); + checkf ("0x0001e240", "%#10.8x", 123456); + + /* Character and string conversions. */ + checkf ("foobar", "%c%c%c%c%c%c", 'f', 'o', 'o', 'b', 'a', 'r'); + checkf (" left-right ", "%6s%s%-7s", "left", "-", "right"); + checkf ("trim", "%.4s", "trimoff"); + checkf ("%%", "%%%%"); + + /* From Cristian Cadar's automatic test case generator. */ + checkf (" abcdefgh", "%9s", "abcdefgh"); + checkf ("36657730000", "%- o", (unsigned) 036657730000); + checkf ("4139757568", "%- u", (unsigned) 4139757568UL); + checkf ("f6bfb000", "%- x", (unsigned) 0xf6bfb000); + checkf ("36657730000", "%-to", (ptrdiff_t) 036657730000); + checkf ("4139757568", "%-tu", (ptrdiff_t) 4139757568UL); + checkf ("-155209728", "%-zi", (size_t) -155209728); + checkf ("-155209728", "%-zd", (size_t) -155209728); + checkf ("036657730000", "%+#o", (unsigned) 036657730000); + checkf ("0xf6bfb000", "%+#x", (unsigned) 0xf6bfb000); + checkf ("-155209728", "% zi", (size_t) -155209728); + checkf ("-155209728", "% zd", (size_t) -155209728); + checkf ("4139757568", "% tu", (ptrdiff_t) 4139757568UL); + checkf ("036657730000", "% #o", (unsigned) 036657730000); + checkf ("0xf6bfb000", "% #x", (unsigned) 0xf6bfb000); + checkf ("0xf6bfb000", "%# x", (unsigned) 0xf6bfb000); + checkf ("-155209728", "%#zd", (size_t) -155209728); + checkf ("-155209728", "%0zi", (size_t) -155209728); + checkf ("4,139,757,568", "%'tu", (ptrdiff_t) 4139757568UL); + checkf ("-155,209,728", "%-'d", -155209728); + checkf ("-155209728", "%.zi", (size_t) -155209728); + checkf ("-155209728", "%zi", (size_t) -155209728); + checkf ("-155209728", "%zd", (size_t) -155209728); + checkf ("-155209728", "%+zi", (size_t) -155209728); + + if (failure_cnt == 0) + printf ("\nstdio: PASS\n"); + else + printf ("\nstdio: FAIL: %d tests failed\n", failure_cnt); +} diff --git a/pintos-progos/tests/internal/stdlib.c b/pintos-progos/tests/internal/stdlib.c new file mode 100644 index 0000000..ad0f0f9 --- /dev/null +++ b/pintos-progos/tests/internal/stdlib.c @@ -0,0 +1,114 @@ +/* Test program for sorting and searching in lib/stdlib.c. + + Attempts to test the sorting and searching functionality that + is not sufficiently tested elsewhere in Pintos. + + This is not a test we will run on your submitted projects. + It is here for completeness. +*/ + +#undef NDEBUG +#include +#include +#include +#include +#include +#include "threads/test.h" + +/* Maximum number of elements in an array that we will test. */ +#define MAX_CNT 4096 + +static void shuffle (int[], size_t); +static int compare_ints (const void *, const void *); +static void verify_order (const int[], size_t); +static void verify_bsearch (const int[], size_t); + +/* Test sorting and searching implementations. */ +void +test (void) +{ + int cnt; + + printf ("testing various size arrays:"); + for (cnt = 0; cnt < MAX_CNT; cnt = cnt * 4 / 3 + 1) + { + int repeat; + + printf (" %zu", cnt); + for (repeat = 0; repeat < 10; repeat++) + { + static int values[MAX_CNT]; + int i; + + /* Put values 0...CNT in random order in VALUES. */ + for (i = 0; i < cnt; i++) + values[i] = i; + shuffle (values, cnt); + + /* Sort VALUES, then verify ordering. */ + qsort (values, cnt, sizeof *values, compare_ints); + verify_order (values, cnt); + verify_bsearch (values, cnt); + } + } + + printf (" done\n"); + printf ("stdlib: PASS\n"); +} + +/* Shuffles the CNT elements in ARRAY into random order. */ +static void +shuffle (int *array, size_t cnt) +{ + size_t i; + + for (i = 0; i < cnt; i++) + { + size_t j = i + random_ulong () % (cnt - i); + int t = array[j]; + array[j] = array[i]; + array[i] = t; + } +} + +/* Returns 1 if *A is greater than *B, + 0 if *A equals *B, + -1 if *A is less than *B. */ +static int +compare_ints (const void *a_, const void *b_) +{ + const int *a = a_; + const int *b = b_; + + return *a < *b ? -1 : *a > *b; +} + +/* Verifies that ARRAY contains the CNT ints 0...CNT-1. */ +static void +verify_order (const int *array, size_t cnt) +{ + int i; + + for (i = 0; (size_t) i < cnt; i++) + ASSERT (array[i] == i); +} + +/* Checks that bsearch() works properly in ARRAY. ARRAY must + contain the values 0...CNT-1. */ +static void +verify_bsearch (const int *array, size_t cnt) +{ + int not_in_array[] = {0, -1, INT_MAX, MAX_CNT, MAX_CNT + 1, MAX_CNT * 2}; + int i; + + /* Check that all the values in the array are found properly. */ + for (i = 0; (size_t) i < cnt; i++) + ASSERT (bsearch (&i, array, cnt, sizeof *array, compare_ints) + == array + i); + + /* Check that some values not in the array are not found. */ + not_in_array[0] = cnt; + for (i = 0; (size_t) i < sizeof not_in_array / sizeof *not_in_array; i++) + ASSERT (bsearch (¬_in_array[i], array, cnt, sizeof *array, compare_ints) + == NULL); +} diff --git a/pintos-progos/tests/intro/Grading b/pintos-progos/tests/intro/Grading new file mode 100644 index 0000000..b1ff6b8 --- /dev/null +++ b/pintos-progos/tests/intro/Grading @@ -0,0 +1,5 @@ +# Percentage of the testing point total designated for each set of +# tests. + +50.0% tests/intro/alarm-clock/Rubric +50.0% tests/intro/userprog-args/Rubric diff --git a/pintos-progos/tests/intro/alarm-clock/Make.tests b/pintos-progos/tests/intro/alarm-clock/Make.tests new file mode 100644 index 0000000..55ad443 --- /dev/null +++ b/pintos-progos/tests/intro/alarm-clock/Make.tests @@ -0,0 +1,15 @@ +# -*- makefile -*- +tests/intro/alarm-clock/%.output: SIMULATOR = bochs +tests/intro/alarm-clock/%.output: PINTOSOPTS += --kernel-test +tests/intro/alarm-clock/%.output: FILESYSSOURCE = --filesys-size=1 + +# Test names. +tests/intro/alarm-clock_TESTS = $(addprefix tests/intro/alarm-clock/,alarm-single \ +alarm-multiple alarm-simultaneous alarm-zero alarm-negative) + +# Sources for tests. +tests/intro/alarm-clock_SRC = tests/intro/alarm-clock/tests.c +tests/intro/alarm-clock_SRC += tests/intro/alarm-clock/alarm-wait.c +tests/intro/alarm-clock_SRC += tests/intro/alarm-clock/alarm-simultaneous.c +tests/intro/alarm-clock_SRC += tests/intro/alarm-clock/alarm-zero.c +tests/intro/alarm-clock_SRC += tests/intro/alarm-clock/alarm-negative.c diff --git a/pintos-progos/tests/intro/alarm-clock/Rubric b/pintos-progos/tests/intro/alarm-clock/Rubric new file mode 100644 index 0000000..0cf3dc1 --- /dev/null +++ b/pintos-progos/tests/intro/alarm-clock/Rubric @@ -0,0 +1,7 @@ +Functionality and robustness of alarm clock: +4 alarm-single +4 alarm-multiple +4 alarm-simultaneous +1 alarm-zero +1 alarm-negative + diff --git a/pintos-progos/tests/intro/alarm-clock/alarm-multiple.ck b/pintos-progos/tests/intro/alarm-clock/alarm-multiple.ck new file mode 120000 index 0000000..f3a9edc --- /dev/null +++ b/pintos-progos/tests/intro/alarm-clock/alarm-multiple.ck @@ -0,0 +1 @@ +../../threads/alarm-multiple.ck \ No newline at end of file diff --git a/pintos-progos/tests/intro/alarm-clock/alarm-negative.c b/pintos-progos/tests/intro/alarm-clock/alarm-negative.c new file mode 120000 index 0000000..483aa63 --- /dev/null +++ b/pintos-progos/tests/intro/alarm-clock/alarm-negative.c @@ -0,0 +1 @@ +../../threads/alarm-negative.c \ No newline at end of file diff --git a/pintos-progos/tests/intro/alarm-clock/alarm-negative.ck b/pintos-progos/tests/intro/alarm-clock/alarm-negative.ck new file mode 120000 index 0000000..279520e --- /dev/null +++ b/pintos-progos/tests/intro/alarm-clock/alarm-negative.ck @@ -0,0 +1 @@ +../../threads/alarm-negative.ck \ No newline at end of file diff --git a/pintos-progos/tests/intro/alarm-clock/alarm-simultaneous.c b/pintos-progos/tests/intro/alarm-clock/alarm-simultaneous.c new file mode 120000 index 0000000..6362b61 --- /dev/null +++ b/pintos-progos/tests/intro/alarm-clock/alarm-simultaneous.c @@ -0,0 +1 @@ +../../threads/alarm-simultaneous.c \ No newline at end of file diff --git a/pintos-progos/tests/intro/alarm-clock/alarm-simultaneous.ck b/pintos-progos/tests/intro/alarm-clock/alarm-simultaneous.ck new file mode 120000 index 0000000..7226d0c --- /dev/null +++ b/pintos-progos/tests/intro/alarm-clock/alarm-simultaneous.ck @@ -0,0 +1 @@ +../../threads/alarm-simultaneous.ck \ No newline at end of file diff --git a/pintos-progos/tests/intro/alarm-clock/alarm-single.ck b/pintos-progos/tests/intro/alarm-clock/alarm-single.ck new file mode 120000 index 0000000..7f98a51 --- /dev/null +++ b/pintos-progos/tests/intro/alarm-clock/alarm-single.ck @@ -0,0 +1 @@ +../../threads/alarm-single.ck \ No newline at end of file diff --git a/pintos-progos/tests/intro/alarm-clock/alarm-wait.c b/pintos-progos/tests/intro/alarm-clock/alarm-wait.c new file mode 120000 index 0000000..2755ae5 --- /dev/null +++ b/pintos-progos/tests/intro/alarm-clock/alarm-wait.c @@ -0,0 +1 @@ +../../threads/alarm-wait.c \ No newline at end of file diff --git a/pintos-progos/tests/intro/alarm-clock/alarm-zero.c b/pintos-progos/tests/intro/alarm-clock/alarm-zero.c new file mode 120000 index 0000000..a1f3ca7 --- /dev/null +++ b/pintos-progos/tests/intro/alarm-clock/alarm-zero.c @@ -0,0 +1 @@ +../../threads/alarm-zero.c \ No newline at end of file diff --git a/pintos-progos/tests/intro/alarm-clock/alarm-zero.ck b/pintos-progos/tests/intro/alarm-clock/alarm-zero.ck new file mode 120000 index 0000000..3f98d64 --- /dev/null +++ b/pintos-progos/tests/intro/alarm-clock/alarm-zero.ck @@ -0,0 +1 @@ +../../threads/alarm-zero.ck \ No newline at end of file diff --git a/pintos-progos/tests/intro/alarm-clock/tests.c b/pintos-progos/tests/intro/alarm-clock/tests.c new file mode 100644 index 0000000..4a96360 --- /dev/null +++ b/pintos-progos/tests/intro/alarm-clock/tests.c @@ -0,0 +1,80 @@ +#include "tests/threads/tests.h" +#include +#include +#include + +struct test + { + const char *name; + test_func *function; + }; + +static const struct test tests[] = + { + {"alarm-single", test_alarm_single}, + {"alarm-multiple", test_alarm_multiple}, + {"alarm-simultaneous", test_alarm_simultaneous}, + {"alarm-zero", test_alarm_zero}, + {"alarm-negative", test_alarm_negative}, + }; + +static const char *test_name; + +/* Runs the test named NAME. */ +void +run_test (const char *name) +{ + const struct test *t; + + for (t = tests; t < tests + sizeof tests / sizeof *tests; t++) + if (!strcmp (name, t->name)) + { + test_name = name; + msg ("begin"); + t->function (); + msg ("end"); + return; + } + PANIC ("no test named \"%s\"", name); +} + +/* Prints FORMAT as if with printf(), + prefixing the output by the name of the test + and following it with a new-line character. */ +void +msg (const char *format, ...) +{ + va_list args; + + printf ("(%s) ", test_name); + va_start (args, format); + vprintf (format, args); + va_end (args); + putchar ('\n'); +} + +/* Prints failure message FORMAT as if with printf(), + prefixing the output by the name of the test and FAIL: + and following it with a new-line character, + and then panics the kernel. */ +void +fail (const char *format, ...) +{ + va_list args; + + printf ("(%s) FAIL: ", test_name); + va_start (args, format); + vprintf (format, args); + va_end (args); + putchar ('\n'); + + PANIC ("test failed"); +} + +/* Prints a message indicating the current test passed. */ +void +pass (void) +{ + printf ("(%s) PASS\n", test_name); +} + diff --git a/pintos-progos/tests/intro/userprog-args/Make.tests b/pintos-progos/tests/intro/userprog-args/Make.tests new file mode 100644 index 0000000..6f7a474 --- /dev/null +++ b/pintos-progos/tests/intro/userprog-args/Make.tests @@ -0,0 +1,27 @@ +# -*- makefile -*- + +tests/intro/userprog-args/%.output: FILESYSSOURCE = --filesys-size=2 +tests/intro/userprog-args/%.output: PUTFILES = $(filter-out kernel.bin loader.bin, $^) +tests/intro/userprog-args/%.output: SIMULATOR = --qemu +tests/intro/userprog-args_TESTS = $(addprefix tests/intro/userprog-args/,args-none \ +args-single args-multiple args-many args-dbl-space args-limit) + +tests/intro/userprog-args_PROGS = $(tests/intro/userprog-args_TESTS) $(addprefix \ +tests/intro/userprog-args/,child-simple child-args) + +tests/intro/userprog-args/args-none_SRC = tests/intro/userprog-args/args.c +tests/intro/userprog-args/args-single_SRC = tests/intro/userprog-args/args.c +tests/intro/userprog-args/args-multiple_SRC = tests/intro/userprog-args/args.c +tests/intro/userprog-args/args-many_SRC = tests/intro/userprog-args/args.c +tests/intro/userprog-args/args-dbl-space_SRC = tests/intro/userprog-args/args.c +tests/intro/userprog-args/args-limit_SRC = tests/intro/userprog-args/args-limit.c + +tests/intro/userprog-args/child-simple_SRC = tests/intro/userprog-args/child-simple.c +tests/intro/userprog-args/child-args_SRC = tests/intro/userprog-args/args.c + +$(foreach prog,$(tests/intro/userprog-args_PROGS),$(eval $(prog)_SRC += tests/lib.c)) + +tests/intro/userprog-args/args-single_ARGS = onearg +tests/intro/userprog-args/args-multiple_ARGS = some arguments for you! +tests/intro/userprog-args/args-many_ARGS = a b c d e f g h i j k l m n o p q r s t u v +tests/intro/userprog-args/args-dbl-space_ARGS = two spaces! diff --git a/pintos-progos/tests/intro/userprog-args/Rubric b/pintos-progos/tests/intro/userprog-args/Rubric new file mode 100644 index 0000000..f5474c5 --- /dev/null +++ b/pintos-progos/tests/intro/userprog-args/Rubric @@ -0,0 +1,7 @@ +Functionality of stack setup: +3 args-none +3 args-single +3 args-multiple +3 args-many +3 args-dbl-space +3 args-limit diff --git a/pintos-progos/tests/intro/userprog-args/args-dbl-space.ck b/pintos-progos/tests/intro/userprog-args/args-dbl-space.ck new file mode 120000 index 0000000..730d787 --- /dev/null +++ b/pintos-progos/tests/intro/userprog-args/args-dbl-space.ck @@ -0,0 +1 @@ +../../userprog/args-dbl-space.ck \ No newline at end of file diff --git a/pintos-progos/tests/intro/userprog-args/args-limit.c b/pintos-progos/tests/intro/userprog-args/args-limit.c new file mode 100644 index 0000000..159d868 --- /dev/null +++ b/pintos-progos/tests/intro/userprog-args/args-limit.c @@ -0,0 +1,70 @@ +/* Test the limit for (1) number of arguments and (2) total size of arguments */ +#include +#include +#include "tests/lib.h" + +#define MAX_SIZE 4096 + +static bool recurse (int, int); + +char cmd[MAX_SIZE * 4]; + +static bool +recurse (int argsize, int argcount) +{ + int i, j; + char *p; + strlcpy (cmd, "args-limit", 11); + p = cmd+strlen(cmd); + for (i = 0; i < argcount; i++) { + *p++ = ' '; + for (j = 0; j < argsize; j++) { + *p++ = 'X'; + } + } + *p = 0; + if (wait (exec (cmd)) < 0) { + return false; + } else { + return true; + } +} + +int +main (int argc, char **argv) +{ + test_name = argv[0]; + if(argc <= 1) { + int step; + int max_args = 0, max_size = 0; + + msg ("begin"); + + /* Binary search number of arguments */ + for (step = MAX_SIZE; step > 0 && max_args < MAX_SIZE; step>>=1) { + int t = max_args + step; + if (recurse (1, t)) { + max_args = t; + } + } + if (max_args > 63) { + msg ("success. at least 64 command line arguments are supported."); + } else { + msg ("FAIL: Only %d command line arguments are supported",max_args); + } + /* Binary search size of arguments */ + for (step = MAX_SIZE; step > 0 && max_size < MAX_SIZE; step>>=1) { + int t = max_size + step; + if (recurse (t, 1)) { + max_size = t; + } + } + if (max_size >= 100) { + msg ("success. arguments with at least 100 bytes are supported."); + } else { + msg ("FAIL: Arguments with more than %d bytes are not supported.",max_size); + } + msg ("end"); + } + return 0; +} diff --git a/pintos-progos/tests/intro/userprog-args/args-limit.ck b/pintos-progos/tests/intro/userprog-args/args-limit.ck new file mode 100644 index 0000000..ed8ead2 --- /dev/null +++ b/pintos-progos/tests/intro/userprog-args/args-limit.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(args-limit) begin +(args-limit) success. at least 64 command line arguments are supported. +(args-limit) success. arguments with at least 100 bytes are supported. +(args-limit) end +EOF +pass; diff --git a/pintos-progos/tests/intro/userprog-args/args-many.ck b/pintos-progos/tests/intro/userprog-args/args-many.ck new file mode 120000 index 0000000..ed175bd --- /dev/null +++ b/pintos-progos/tests/intro/userprog-args/args-many.ck @@ -0,0 +1 @@ +../../userprog/args-many.ck \ No newline at end of file diff --git a/pintos-progos/tests/intro/userprog-args/args-multiple.ck b/pintos-progos/tests/intro/userprog-args/args-multiple.ck new file mode 120000 index 0000000..4f9935f --- /dev/null +++ b/pintos-progos/tests/intro/userprog-args/args-multiple.ck @@ -0,0 +1 @@ +../../userprog/args-multiple.ck \ No newline at end of file diff --git a/pintos-progos/tests/intro/userprog-args/args-none.ck b/pintos-progos/tests/intro/userprog-args/args-none.ck new file mode 120000 index 0000000..861d319 --- /dev/null +++ b/pintos-progos/tests/intro/userprog-args/args-none.ck @@ -0,0 +1 @@ +../../userprog/args-none.ck \ No newline at end of file diff --git a/pintos-progos/tests/intro/userprog-args/args-single.ck b/pintos-progos/tests/intro/userprog-args/args-single.ck new file mode 120000 index 0000000..df8e737 --- /dev/null +++ b/pintos-progos/tests/intro/userprog-args/args-single.ck @@ -0,0 +1 @@ +../../userprog/args-single.ck \ No newline at end of file diff --git a/pintos-progos/tests/intro/userprog-args/args.c b/pintos-progos/tests/intro/userprog-args/args.c new file mode 120000 index 0000000..0d798ac --- /dev/null +++ b/pintos-progos/tests/intro/userprog-args/args.c @@ -0,0 +1 @@ +../../userprog/args.c \ No newline at end of file diff --git a/pintos-progos/tests/intro/userprog-args/child-simple.c b/pintos-progos/tests/intro/userprog-args/child-simple.c new file mode 120000 index 0000000..f8a3ce7 --- /dev/null +++ b/pintos-progos/tests/intro/userprog-args/child-simple.c @@ -0,0 +1 @@ +../../userprog/child-simple.c \ No newline at end of file diff --git a/pintos-progos/tests/lib.c b/pintos-progos/tests/lib.c new file mode 100644 index 0000000..ee36505 --- /dev/null +++ b/pintos-progos/tests/lib.c @@ -0,0 +1,196 @@ +#include "tests/lib.h" +#include +#include +#include +#include +#include + +const char *test_name; +bool quiet = false; + +static void +vmsg (const char *format, va_list args, const char *suffix) +{ + /* We go to some trouble to stuff the entire message into a + single buffer and output it in a single system call, because + that'll (typically) ensure that it gets sent to the console + atomically. Otherwise kernel messages like "foo: exit(0)" + can end up being interleaved if we're unlucky. */ + static char buf[1024]; + + snprintf (buf, sizeof buf, "(%s) ", test_name); + vsnprintf (buf + strlen (buf), sizeof buf - strlen (buf), format, args); + strlcpy (buf + strlen (buf), suffix, sizeof buf - strlen (buf)); + write (STDOUT_FILENO, buf, strlen (buf)); +} + +void +msg (const char *format, ...) +{ + va_list args; + + if (quiet) + return; + va_start (args, format); + vmsg (format, args, "\n"); + va_end (args); +} + +void +fail (const char *format, ...) +{ + va_list args; + + va_start (args, format); + vmsg (format, args, ": FAILED\n"); + va_end (args); + + exit (1); +} + +static void +swap (void *a_, void *b_, size_t size) +{ + uint8_t *a = a_; + uint8_t *b = b_; + size_t i; + + for (i = 0; i < size; i++) + { + uint8_t t = a[i]; + a[i] = b[i]; + b[i] = t; + } +} + +void +shuffle (void *buf_, size_t cnt, size_t size) +{ + char *buf = buf_; + size_t i; + + for (i = 0; i < cnt; i++) + { + size_t j = i + random_ulong () % (cnt - i); + swap (buf + i * size, buf + j * size, size); + } +} + +void +exec_children (const char *child_name, pid_t pids[], size_t child_cnt) +{ + size_t i; + + for (i = 0; i < child_cnt; i++) + { + char cmd_line[128]; + snprintf (cmd_line, sizeof cmd_line, "%s %zu", child_name, i); + CHECK ((pids[i] = exec (cmd_line)) != PID_ERROR, + "exec child %zu of %zu: \"%s\"", i + 1, child_cnt, cmd_line); + } +} + +void +wait_children (pid_t pids[], size_t child_cnt) +{ + size_t i; + + for (i = 0; i < child_cnt; i++) + { + int status = wait (pids[i]); + CHECK (status == (int) i, + "wait for child %zu of %zu returned %d (expected %zu)", + i + 1, child_cnt, status, i); + } +} + +void +check_file_handle (int fd, + const char *file_name, const void *buf_, size_t size) +{ + const char *buf = buf_; + size_t ofs = 0; + size_t file_size; + + /* Warn about file of wrong size. Don't fail yet because we + may still be able to get more information by reading the + file. */ + file_size = filesize (fd); + if (file_size != size) + msg ("size of %s (%zu) differs from expected (%zu)", + file_name, file_size, size); + + /* Read the file block-by-block, comparing data as we go. */ + while (ofs < size) + { + char block[512]; + size_t block_size, ret_val; + + block_size = size - ofs; + if (block_size > sizeof block) + block_size = sizeof block; + + ret_val = read (fd, block, block_size); + if (ret_val != block_size) + fail ("read of %zu bytes at offset %zu in \"%s\" returned %zu", + block_size, ofs, file_name, ret_val); + + compare_bytes (block, buf + ofs, block_size, ofs, file_name); + ofs += block_size; + } + + /* Now fail due to wrong file size. */ + if (file_size != size) + fail ("size of %s (%zu) differs from expected (%zu)", + file_name, file_size, size); + + msg ("verified contents of \"%s\"", file_name); +} + +void +check_file (const char *file_name, const void *buf, size_t size) +{ + int fd; + + CHECK ((fd = open (file_name)) > 1, "open \"%s\" for verification", + file_name); + check_file_handle (fd, file_name, buf, size); + msg ("close \"%s\"", file_name); + close (fd); +} + +void +compare_bytes (const void *read_data_, const void *expected_data_, size_t size, + size_t ofs, const char *file_name) +{ + const uint8_t *read_data = read_data_; + const uint8_t *expected_data = expected_data_; + size_t i, j; + size_t show_cnt; + + if (!memcmp (read_data, expected_data, size)) + return; + + for (i = 0; i < size; i++) + if (read_data[i] != expected_data[i]) + break; + for (j = i + 1; j < size; j++) + if (read_data[j] == expected_data[j]) + break; + + quiet = false; + msg ("%zu bytes read starting at offset %zu in \"%s\" differ " + "from expected.", j - i, ofs + i, file_name); + show_cnt = j - i; + if (j - i > 64) + { + show_cnt = 64; + msg ("Showing first differing %zu bytes.", show_cnt); + } + msg ("Data actually read:"); + hex_dump (ofs + i, read_data + i, show_cnt, true); + msg ("Expected data:"); + hex_dump (ofs + i, expected_data + i, show_cnt, true); + fail ("%zu bytes read starting at offset %zu in \"%s\" differ " + "from expected", j - i, ofs + i, file_name); +} diff --git a/pintos-progos/tests/lib.h b/pintos-progos/tests/lib.h new file mode 100644 index 0000000..648327b --- /dev/null +++ b/pintos-progos/tests/lib.h @@ -0,0 +1,50 @@ +#ifndef TESTS_LIB_H +#define TESTS_LIB_H + +#include +#include +#include +#include + +extern const char *test_name; +extern bool quiet; + +void msg (const char *, ...) PRINTF_FORMAT (1, 2); +void fail (const char *, ...) PRINTF_FORMAT (1, 2) NO_RETURN; + +/* Takes an expression to test for SUCCESS and a message, which + may include printf-style arguments. Logs the message, then + tests the expression. If it is zero, indicating failure, + emits the message as a failure. + + Somewhat tricky to use: + + - SUCCESS must not have side effects that affect the + message, because that will cause the original message and + the failure message to differ. + + - The message must not have side effects of its own, because + it will be printed twice on failure, or zero times on + success if quiet is set. */ +#define CHECK(SUCCESS, ...) \ + do \ + { \ + msg (__VA_ARGS__); \ + if (!(SUCCESS)) \ + fail (__VA_ARGS__); \ + } \ + while (0) + +void shuffle (void *, size_t cnt, size_t size); + +void exec_children (const char *child_name, pid_t pids[], size_t child_cnt); +void wait_children (pid_t pids[], size_t child_cnt); + +void check_file_handle (int fd, const char *file_name, + const void *buf_, size_t filesize); +void check_file (const char *file_name, const void *buf, size_t filesize); + +void compare_bytes (const void *read_data, const void *expected_data, + size_t size, size_t ofs, const char *file_name); + +#endif /* test/lib.h */ diff --git a/pintos-progos/tests/lib.pm b/pintos-progos/tests/lib.pm new file mode 100644 index 0000000..bc37ae5 --- /dev/null +++ b/pintos-progos/tests/lib.pm @@ -0,0 +1,19 @@ +use strict; +use warnings; + +use tests::random; + +sub shuffle { + my ($in, $cnt, $sz) = @_; + $cnt * $sz == length $in or die; + my (@a) = 0...$cnt - 1; + for my $i (0...$cnt - 1) { + my ($j) = $i + random_ulong () % ($cnt - $i); + @a[$i, $j] = @a[$j, $i]; + } + my ($out) = ""; + $out .= substr ($in, $_ * $sz, $sz) foreach @a; + return $out; +} + +1; diff --git a/pintos-progos/tests/main.c b/pintos-progos/tests/main.c new file mode 100644 index 0000000..ad1b0f1 --- /dev/null +++ b/pintos-progos/tests/main.c @@ -0,0 +1,15 @@ +#include +#include "tests/lib.h" +#include "tests/main.h" + +int +main (int argc UNUSED, char *argv[]) +{ + test_name = argv[0]; + + msg ("begin"); + random_init (0); + test_main (); + msg ("end"); + return 0; +} diff --git a/pintos-progos/tests/main.h b/pintos-progos/tests/main.h new file mode 100644 index 0000000..f0e8818 --- /dev/null +++ b/pintos-progos/tests/main.h @@ -0,0 +1,6 @@ +#ifndef TESTS_MAIN_H +#define TESTS_MAIN_H + +void test_main (void); + +#endif /* tests/main.h */ diff --git a/pintos-progos/tests/make-grade b/pintos-progos/tests/make-grade new file mode 100755 index 0000000..a3faa0e --- /dev/null +++ b/pintos-progos/tests/make-grade @@ -0,0 +1,152 @@ +#! /usr/bin/perl + +use strict; +use warnings; + +@ARGV == 3 || die; +my ($src_dir, $results_file, $grading_file) = @ARGV; + +# Read pass/file verdicts from $results_file. +open (RESULTS, '<', $results_file) || die "$results_file: open: $!\n"; +my (%verdicts, %verdict_counts); +while () { + my ($verdict, $test) = /^(pass|FAIL) (.*)$/ or die; + $verdicts{$test} = $verdict eq 'pass'; +} +close RESULTS; + +my (@failures); +my (@overall, @rubrics, @summary); +my ($pct_actual, $pct_possible) = (0, 0); + +# Read grading file. +my (@items); +open (GRADING, '<', $grading_file) || die "$grading_file: open: $!\n"; +while () { + s/#.*//; + next if /^\s*$/; + my ($max_pct, $rubric_suffix) = /^\s*(\d+(?:\.\d+)?)%\t(.*)/ or die; + my ($dir) = $rubric_suffix =~ /^(.*)\//; + my ($rubric_file) = "$src_dir/$rubric_suffix"; + open (RUBRIC, '<', $rubric_file) or die "$rubric_file: open: $!\n"; + + # Rubric file must begin with title line. + my $title = ; + chomp $title; + $title =~ s/:$// or die; + $title .= " ($rubric_suffix):"; + push (@rubrics, $title); + + my ($score, $possible) = (0, 0); + my ($cnt, $passed) = (0, 0); + my ($was_score) = 0; + while () { + chomp; + push (@rubrics, "\t$_"), next if /^-/; + push (@rubrics, ""), next if /^\s*$/; + my ($poss, $name) = /^(\d+)\t(.*)$/ or die; + my ($test) = "$dir/$name"; + my ($points) = 0; + if (!defined $verdicts{$test}) { + push (@overall, "warning: $test not tested, assuming failure"); + } elsif ($verdicts{$test}) { + $points = $poss; + $passed++; + } + push (@failures, $test) if !$points; + $verdict_counts{$test}++; + push (@rubrics, sprintf ("\t%4s%2d/%2d %s", + $points ? '' : '**', $points, $poss, $test)); + $score += $points; + $possible += $poss; + $cnt++; + } + close (RUBRIC); + + push (@rubrics, ""); + push (@rubrics, "\t- Section summary."); + push (@rubrics, sprintf ("\t%4s%3d/%3d %s", + '', $passed, $cnt, 'tests passed')); + push (@rubrics, sprintf ("\t%4s%3d/%3d %s", + '', $score, $possible, 'points subtotal')); + push (@rubrics, ''); + + my ($pct) = ($score / $possible) * $max_pct; + push (@summary, sprintf ("%-45s %3d/%3d %5.1f%%/%5.1f%%", + $rubric_suffix, + $score, $possible, + $pct, $max_pct)); + $pct_actual += $pct; + $pct_possible += $max_pct; +} +close GRADING; + +my ($sum_line) + = "--------------------------------------------- --- --- ------ ------"; +unshift (@summary, + "SUMMARY BY TEST SET", + '', + sprintf ("%-45s %3s %3s %6s %6s", + "Test Set", "Pts", "Max", "% Ttl", "% Max"), + $sum_line); +push (@summary, + $sum_line, + sprintf ("%-45s %3s %3s %5.1f%%/%5.1f%%", + 'Total', '', '', $pct_actual, $pct_possible)); + +unshift (@rubrics, + "SUMMARY OF INDIVIDUAL TESTS", + ''); + +foreach my $name (keys (%verdicts)) { + my ($count) = $verdict_counts{$name}; + if (!defined ($count) || $count != 1) { + if (!defined ($count) || !$count) { + push (@overall, "warning: test $name doesn't count for grading"); + } else { + push (@overall, + "warning: test $name counted $count times in grading"); + } + } +} +push (@overall, sprintf ("TOTAL TESTING SCORE: %.1f%%", $pct_actual)); +if (sprintf ("%.1f", $pct_actual) eq sprintf ("%.1f", $pct_possible)) { + push (@overall, "ALL TESTED PASSED -- PERFECT SCORE"); +} + +my (@divider) = ('', '- ' x 38, ''); + +print map ("$_\n", @overall, @divider, @summary, @divider, @rubrics); + +for my $test (@failures) { + print map ("$_\n", @divider); + print "DETAILS OF $test FAILURE:\n\n"; + + if (open (RESULT, '<', "$test.result")) { + my $first_line = ; + my ($cnt) = 0; + while () { + print; + $cnt++; + } + close (RESULT); + } + + if (open (OUTPUT, '<', "$test.output")) { + print "\nOUTPUT FROM $test:\n\n"; + + my ($panics, $boots) = (0, 0); + while () { + if (/PANIC/ && ++$panics > 2) { + print "[...details of additional panic(s) omitted...]\n"; + last; + } + print; + if (/Pintos booting/ && ++$boots > 1) { + print "[...details of reboot(s) omitted...]\n"; + last; + } + } + close (OUTPUT); + } +} diff --git a/pintos-progos/tests/random.pm b/pintos-progos/tests/random.pm new file mode 100644 index 0000000..be008ff --- /dev/null +++ b/pintos-progos/tests/random.pm @@ -0,0 +1,27 @@ +use strict; +use warnings; + +use tests::arc4; + +my (@arc4); + +sub random_init { + if (@arc4 == 0) { + my ($seed) = @_; + $seed = 0 if !defined $seed; + @arc4 = arc4_init (pack ("V", $seed)); + } +} + +sub random_bytes { + random_init (); + my ($n) = @_; + return arc4_crypt (\@arc4, "\0" x $n); +} + +sub random_ulong { + random_init (); + return unpack ("V", random_bytes (4)); +} + +1; diff --git a/pintos-progos/tests/tests.pm b/pintos-progos/tests/tests.pm new file mode 100644 index 0000000..4599cb9 --- /dev/null +++ b/pintos-progos/tests/tests.pm @@ -0,0 +1,625 @@ +use strict; +use warnings; +use tests::Algorithm::Diff; +use File::Temp 'tempfile'; +use Fcntl qw(SEEK_SET SEEK_CUR); + +sub fail; +sub pass; + +die if @ARGV != 2; +our ($test, $src_dir) = @ARGV; + +my ($msg_file) = tempfile (); +select ($msg_file); + +our (@prereq_tests) = (); +if ($test =~ /^(.*)-persistence$/) { + push (@prereq_tests, $1); +} +for my $prereq_test (@prereq_tests) { + my (@result) = read_text_file ("$prereq_test.result"); + fail "Prerequisite test $prereq_test failed.\n" if $result[0] ne 'PASS'; +} + + +# Generic testing. + +sub check_expected { + my ($expected) = pop @_; + my (@options) = @_; + my (@output) = read_text_file ("$test.output"); + common_checks ("run", @output); + compare_output ("run", @options, \@output, $expected); +} + +sub common_checks { + my ($run, @output) = @_; + + fail "\u$run produced no output at all\n" if @output == 0; + + check_for_panic ($run, @output); + check_for_keyword ($run, "FAIL", @output); + check_for_triple_fault ($run, @output); + check_for_keyword ($run, "TIMEOUT", @output); + + fail "\u$run didn't start up properly: no \"Pintos booting\" message\n" + if !grep (/Pintos booting with.*kB RAM\.\.\./, @output); + fail "\u$run didn't start up properly: no \"Boot complete\" message\n" + if !grep (/Boot complete/, @output); + fail "\u$run didn't shut down properly: no \"Timer: # ticks\" message\n" + if !grep (/Timer: \d+ ticks/, @output); + fail "\u$run didn't shut down properly: no \"Powering off\" message\n" + if !grep (/Powering off/, @output); +} + +sub check_for_panic { + my ($run, @output) = @_; + + my ($panic) = grep (/PANIC/, @output); + return unless defined $panic; + + print "Kernel panic in $run: ", substr ($panic, index ($panic, "PANIC")), + "\n"; + + my (@stack_line) = grep (/Call stack:/, @output); + if (@stack_line != 0) { + my ($addrs) = $stack_line[0] =~ /Call stack:((?: 0x[0-9a-f]+)+)/; + + # Find a user program to translate user virtual addresses. + my ($userprog) = ""; + $userprog = "$test" + if grep (hex ($_) < 0xc0000000, split (' ', $addrs)) > 0 && -e $test; + + # Get and print the backtrace. + my ($trace) = scalar (`backtrace kernel.o $userprog $addrs`); + print "Call stack:$addrs\n"; + print "Translation of call stack:\n"; + print $trace; + + # Print disclaimer. + if ($userprog ne '' && index ($trace, $userprog) >= 0) { + print <capacity/) { + print < 0; + + print < $_), @$expected)}; + } + foreach my $key (keys %$expected) { + my (@expected) = split ("\n", $expected->{$key}); + + $msg .= "Acceptable output:\n"; + $msg .= join ('', map (" $_\n", @expected)); + + # Check whether actual and expected match. + # If it's a perfect match, we're done. + if ($#output == $#expected) { + my ($eq) = 1; + for (my ($i) = 0; $i <= $#expected; $i++) { + $eq = 0 if $output[$i] ne $expected[$i]; + } + return $key if $eq; + } + + # They differ. Output a diff. + my (@diff) = ""; + my ($d) = Algorithm::Diff->new (\@expected, \@output); + while ($d->Next ()) { + my ($ef, $el, $af, $al) = $d->Get (qw (min1 max1 min2 max2)); + if ($d->Same ()) { + push (@diff, map (" $_\n", $d->Items (1))); + } else { + push (@diff, map ("- $_\n", $d->Items (1))) if $d->Items (1); + push (@diff, map ("+ $_\n", $d->Items (2))) if $d->Items (2); + } + } + + $msg .= "Differences in `diff -u' format:\n"; + $msg .= join ('', @diff); + } + + # Failed to match. Report failure. + $msg .= "\n(Process exit codes are excluded for matching purposes.)\n" + if $ignore_exit_codes; + $msg .= "\n(User fault messages are excluded for matching purposes.)\n" + if $ignore_user_faults; + fail "Test output failed to match any acceptable form.\n\n$msg"; +} + +# File system extraction. + +# check_archive (\%CONTENTS) +# +# Checks that the extracted file system's contents match \%CONTENTS. +# Each key in the hash is a file name. Each value may be: +# +# - $FILE: Name of a host file containing the expected contents. +# +# - [$FILE, $OFFSET, $LENGTH]: An excerpt of host file $FILE +# comprising the $LENGTH bytes starting at $OFFSET. +# +# - [$CONTENTS]: The literal expected file contents, as a string. +# +# - {SUBDIR}: A subdirectory, in the same form described here, +# recursively. +sub check_archive { + my ($expected_hier) = @_; + + my (@output) = read_text_file ("$test.output"); + common_checks ("file system extraction run", @output); + + @output = get_core_output ("file system extraction run", @output); + @output = grep (!/^[a-zA-Z0-9-_]+: exit\(\d+\)$/, @output); + fail join ("\n", "Error extracting file system:", @output) if @output; + + my ($test_base_name) = $test; + $test_base_name =~ s%.*/%%; + $test_base_name =~ s%-persistence$%%; + $expected_hier->{$test_base_name} = $prereq_tests[0]; + $expected_hier->{'tar'} = 'tests/filesys/extended/tar'; + + my (%expected) = normalize_fs (flatten_hierarchy ($expected_hier, "")); + my (%actual) = read_tar ("$prereq_tests[0].tar"); + + my ($errors) = 0; + foreach my $name (sort keys %expected) { + if (exists $actual{$name}) { + if (is_dir ($actual{$name}) && !is_dir ($expected{$name})) { + print "$name is a directory but should be an ordinary file.\n"; + $errors++; + } elsif (!is_dir ($actual{$name}) && is_dir ($expected{$name})) { + print "$name is an ordinary file but should be a directory.\n"; + $errors++; + } + } else { + print "$name is missing from the file system.\n"; + $errors++; + } + } + foreach my $name (sort keys %actual) { + if (!exists $expected{$name}) { + if ($name =~ /^[[:print:]]+$/) { + print "$name exists in the file system but it should not.\n"; + } else { + my ($esc_name) = $name; + $esc_name =~ s/[^[:print:]]/./g; + print <[0]); + $file = tempfile (); + syswrite ($file, $value->[0]) == $length + or die "writing temporary file: $!\n"; + sysseek ($file, 0, SEEK_SET); + } elsif (@$value == 3) { + $length = $value->[2]; + open ($file, '<', $value->[0]) or die "$value->[0]: open: $!\n"; + die "$value->[0]: file is smaller than expected\n" + if -s $file < $value->[1] + $length; + sysseek ($file, $value->[1], SEEK_SET); + } else { + die; + } + return ($file, $length); +} + +# compare_files ($A, $A_SIZE, $B, $B_SIZE, $NAME, $VERBOSE) +# +# Compares $A_SIZE bytes in $A to $B_SIZE bytes in $B. +# ($A and $B are handles.) +# If their contents differ, prints a brief message describing +# the differences, using $NAME to identify the file. +# The message contains more detail if $VERBOSE is nonzero. +# Returns 1 if the contents are identical, 0 otherwise. +sub compare_files { + my ($a, $a_size, $b, $b_size, $name, $verbose) = @_; + my ($ofs) = 0; + select(STDOUT); + for (;;) { + my ($a_amt) = $a_size >= 1024 ? 1024 : $a_size; + my ($b_amt) = $b_size >= 1024 ? 1024 : $b_size; + my ($a_data, $b_data); + if (!defined (sysread ($a, $a_data, $a_amt)) + || !defined (sysread ($b, $b_data, $b_amt))) { + die "reading $name: $!\n"; + } + + my ($a_len) = length $a_data; + my ($b_len) = length $b_data; + last if $a_len == 0 && $b_len == 0; + + if ($a_data ne $b_data) { + my ($min_len) = $a_len < $b_len ? $a_len : $b_len; + my ($diff_ofs); + for ($diff_ofs = 0; $diff_ofs < $min_len; $diff_ofs++) { + last if (substr ($a_data, $diff_ofs, 1) + ne substr ($b_data, $diff_ofs, 1)); + } + + printf "\nFile $name differs from expected " + . "starting at offset 0x%x.\n", $ofs + $diff_ofs; + if ($verbose ) { + print "Expected contents:\n"; + hex_dump (substr ($a_data, $diff_ofs, 64), $ofs + $diff_ofs); + print "Actual contents:\n"; + hex_dump (substr ($b_data, $diff_ofs, 64), $ofs + $diff_ofs); + } + return 0; + } + + $ofs += $a_len; + $a_size -= $a_len; + $b_size -= $b_len; + } + return 1; +} + +# hex_dump ($DATA, $OFS) +# +# Prints $DATA in hex and text formats. +# The first byte of $DATA corresponds to logical offset $OFS +# in whatever file the data comes from. +sub hex_dump { + my ($data, $ofs) = @_; + + if ($data eq '') { + printf " (File ends at offset %08x.)\n", $ofs; + return; + } + + my ($per_line) = 16; + while ((my $size = length ($data)) > 0) { + my ($start) = $ofs % $per_line; + my ($end) = $per_line; + $end = $start + $size if $end - $start > $size; + my ($n) = $end - $start; + + printf "0x%08x ", int ($ofs / $per_line) * $per_line; + + # Hex version. + print " " x $start; + for my $i ($start...$end - 1) { + printf "%02x", ord (substr ($data, $i - $start, 1)); + print $i == $per_line / 2 - 1 ? '-' : ' '; + } + print " " x ($per_line - $end); + + # Character version. + my ($esc_data) = substr ($data, 0, $n); + $esc_data =~ s/[^[:print:]]/./g; + print "|", " " x $start, $esc_data, " " x ($per_line - $end), "|"; + + print "\n"; + + $data = substr ($data, $n); + $ofs += $n; + } +} + +# print_fs (%FS) +# +# Prints a list of files in %FS, which must be a file system +# as flattened by flatten_hierarchy() and normalized by +# normalize_fs(). +sub print_fs { + my (%fs) = @_; + foreach my $name (sort keys %fs) { + my ($esc_name) = $name; + $esc_name =~ s/[^[:print:]]/./g; + print "$esc_name: "; + if (!is_dir ($fs{$name})) { + print +file_size ($fs{$name}), "-byte file"; + } else { + print "directory"; + } + print "\n"; + } + print "(empty)\n" if !@_; +} + +# normalize_fs (%FS) +# +# Takes a file system as flattened by flatten_hierarchy(). +# Returns a similar file system in which values of the form $FILE +# are replaced by those of the form [$FILE, $OFFSET, $LENGTH]. +sub normalize_fs { + my (%fs) = @_; + foreach my $name (keys %fs) { + my ($value) = $fs{$name}; + next if is_dir ($value) || ref ($value) ne ''; + die "can't open $value\n" if !stat $value; + $fs{$name} = [$value, 0, -s _]; + } + return %fs; +} + +# is_dir ($VALUE) +# +# Takes a value like one in the hash returned by flatten_hierarchy() +# and returns 1 if it represents a directory, 0 otherwise. +sub is_dir { + my ($value) = @_; + return ref ($value) eq '' && $value eq 'directory'; +} + +# file_size ($VALUE) +# +# Takes a value like one in the hash returned by flatten_hierarchy() +# and returns the size of the file it represents. +sub file_size { + my ($value) = @_; + die if is_dir ($value); + die if ref ($value) ne 'ARRAY'; + return @$value > 1 ? $value->[2] : length ($value->[0]); +} + +# flatten_hierarchy ($HIER_FS, $PREFIX) +# +# Takes a file system in the format expected by check_archive() and +# returns a "flattened" version in which file names include all parent +# directory names and the value of directories is just "directory". +sub flatten_hierarchy { + my (%hier_fs) = %{$_[0]}; + my ($prefix) = $_[1]; + my (%flat_fs); + for my $name (keys %hier_fs) { + my ($value) = $hier_fs{$name}; + if (ref $value eq 'HASH') { + %flat_fs = (%flat_fs, flatten_hierarchy ($value, "$prefix$name/")); + $flat_fs{"$prefix$name"} = 'directory'; + } else { + $flat_fs{"$prefix$name"} = $value; + } + } + return %flat_fs; +} + +# read_tar ($ARCHIVE) +# +# Reads the ustar-format tar file in $ARCHIVE +# and returns a flattened file system for it. +sub read_tar { + my ($archive) = @_; + my (%content); + open (ARCHIVE, '<', $archive) or fail "$archive: open: $!\n"; + for (;;) { + my ($header); + if ((my $retval = sysread (ARCHIVE, $header, 512)) != 512) { + fail "$archive: unexpected end of file\n" if $retval >= 0; + fail "$archive: read: $!\n"; + } + + last if $header eq "\0" x 512; + + # Verify magic numbers. + if (substr ($header, 257, 6) ne "ustar\0" + || substr ($header, 263, 2) ne '00') { + fail "$archive: corrupt ustar header\n"; + } + + # Verify checksum. + my ($chksum) = oct (unpack ("Z*", substr ($header, 148, 8, ' ' x 8))); + my ($correct_chksum) = unpack ("%32a*", $header); + fail "$archive: bad header checksum\n" if $chksum != $correct_chksum; + + # Get file name. + my ($name) = unpack ("Z100", $header); + my ($prefix) = unpack ("Z*", substr ($header, 345)); + $name = "$prefix/$name" if $prefix ne ''; + fail "$archive: contains file with empty name" if $name eq ''; + + # Get type. + my ($typeflag) = substr ($header, 156, 1); + $typeflag = '0' if $typeflag eq "\0"; + fail "unknown file type '$typeflag'\n" if $typeflag !~ /[05]/; + + # Get size. + my ($size) = oct (unpack ("Z*", substr ($header, 124, 12))); + fail "bad size $size\n" if $size < 0; + $size = 0 if $typeflag eq '5'; + + # Store content. + $name =~ s%^(/|\./|\.\./)*%%; # Strip leading "/", "./", "../". + $name = '' if $name eq '.' || $name eq '..'; + if (exists $content{$name}) { + fail "$archive: contains multiple entries for $name\n"; + } + if ($typeflag eq '5') { + $content{$name} = 'directory' if $name ne ''; + } else { + fail "$archive: contains file with empty name\n" if $name eq ''; + my ($position) = sysseek (ARCHIVE, 0, SEEK_CUR); + $content{$name} = [$archive, $position, $size]; + sysseek (ARCHIVE, int (($size + 511) / 512) * 512, SEEK_CUR); + } + } + close (ARCHIVE); + return %content; +} + +# Utilities. + +sub fail { + finish ("FAIL", @_); +} + +sub pass { + finish ("PASS", @_); +} + +sub finish { + my ($verdict, @messages) = @_; + + seek ($msg_file, 0, 0); + push (@messages, <$msg_file>); + close ($msg_file); + chomp (@messages); + + my ($result_fn) = "$test.result"; + open (RESULT, '>', $result_fn) or die "$result_fn: create: $!\n"; + print RESULT "$verdict\n"; + print RESULT "$_\n" foreach @messages; + close (RESULT); + + if ($verdict eq 'PASS') { + print STDOUT "pass $test\n"; + } else { + print STDOUT "FAIL $test\n"; + } + print STDOUT "$_\n" foreach @messages; + + exit 0; +} + +sub read_text_file { + my ($file_name) = @_; + open (FILE, '<', $file_name) or die "$file_name: open: $!\n"; + my (@content) = ; + chomp (@content); + close (FILE); + return @content; +} + +1; diff --git a/pintos-progos/tests/threads/Grading b/pintos-progos/tests/threads/Grading new file mode 100644 index 0000000..cc235f2 --- /dev/null +++ b/pintos-progos/tests/threads/Grading @@ -0,0 +1,11 @@ +# Percentage of the testing point total designated for each set of +# tests. + +# Priority Scheduling +90.0% tests/threads/Rubric.priority + +# Robustness +10.0% tests/threads/Rubric.alarm + +# Not used in SS 2012 +# XX.0% tests/threads/Rubric.mlfqs diff --git a/pintos-progos/tests/threads/Make.tests b/pintos-progos/tests/threads/Make.tests new file mode 100644 index 0000000..dbdfd0c --- /dev/null +++ b/pintos-progos/tests/threads/Make.tests @@ -0,0 +1,56 @@ +# -*- makefile -*- + +# Test names. +tests/threads_TESTS = $(addprefix tests/threads/,alarm-single \ +alarm-multiple alarm-simultaneous alarm-priority alarm-zero \ +alarm-negative priority-change priority-donate-one \ +priority-donate-multiple priority-donate-multiple2 \ +priority-donate-nest priority-donate-sema priority-donate-lower \ +priority-fifo priority-preempt priority-sema priority-condvar \ +priority-donate-chain) + + +# Sources for tests. +tests/threads_SRC = tests/threads/tests.c +tests/threads_SRC += tests/threads/alarm-wait.c +tests/threads_SRC += tests/threads/alarm-simultaneous.c +tests/threads_SRC += tests/threads/alarm-priority.c +tests/threads_SRC += tests/threads/alarm-zero.c +tests/threads_SRC += tests/threads/alarm-negative.c +tests/threads_SRC += tests/threads/priority-change.c +tests/threads_SRC += tests/threads/priority-donate-one.c +tests/threads_SRC += tests/threads/priority-donate-multiple.c +tests/threads_SRC += tests/threads/priority-donate-multiple2.c +tests/threads_SRC += tests/threads/priority-donate-nest.c +tests/threads_SRC += tests/threads/priority-donate-sema.c +tests/threads_SRC += tests/threads/priority-donate-lower.c +tests/threads_SRC += tests/threads/priority-fifo.c +tests/threads_SRC += tests/threads/priority-preempt.c +tests/threads_SRC += tests/threads/priority-sema.c +tests/threads_SRC += tests/threads/priority-condvar.c +tests/threads_SRC += tests/threads/priority-donate-chain.c + +# Not used in SS 2012 +MLFQS_TESTS = mlfqs-load-1 mlfqs-load-60 mlfqs-load-avg mlfqs-recent-1 \ +mlfqs-fair-2 mlfqs-fair-20 mlfqs-nice-2 mlfqs-nice-10 mlfqs-block) + +tests/threads_SRC += tests/threads/mlfqs-load-1.c +tests/threads_SRC += tests/threads/mlfqs-load-60.c +tests/threads_SRC += tests/threads/mlfqs-load-avg.c +tests/threads_SRC += tests/threads/mlfqs-recent-1.c +tests/threads_SRC += tests/threads/mlfqs-fair.c +tests/threads_SRC += tests/threads/mlfqs-block.c + +MLFQS_OUTPUTS = \ +tests/threads/mlfqs-load-1.output \ +tests/threads/mlfqs-load-60.output \ +tests/threads/mlfqs-load-avg.output \ +tests/threads/mlfqs-recent-1.output \ +tests/threads/mlfqs-fair-2.output \ +tests/threads/mlfqs-fair-20.output \ +tests/threads/mlfqs-nice-2.output \ +tests/threads/mlfqs-nice-10.output \ +tests/threads/mlfqs-block.output + +$(MLFQS_OUTPUTS): KERNELFLAGS += -mlfqs +$(MLFQS_OUTPUTS): TIMEOUT = 480 diff --git a/pintos-progos/tests/threads/Rubric.alarm b/pintos-progos/tests/threads/Rubric.alarm new file mode 100644 index 0000000..61abe85 --- /dev/null +++ b/pintos-progos/tests/threads/Rubric.alarm @@ -0,0 +1,8 @@ +Functionality and robustness of alarm clock: +4 alarm-single +4 alarm-multiple +4 alarm-simultaneous +4 alarm-priority + +1 alarm-zero +1 alarm-negative diff --git a/pintos-progos/tests/threads/Rubric.mlfqs b/pintos-progos/tests/threads/Rubric.mlfqs new file mode 100644 index 0000000..f260091 --- /dev/null +++ b/pintos-progos/tests/threads/Rubric.mlfqs @@ -0,0 +1,14 @@ +Functionality of advanced scheduler: +5 mlfqs-load-1 +5 mlfqs-load-60 +3 mlfqs-load-avg + +5 mlfqs-recent-1 + +5 mlfqs-fair-2 +3 mlfqs-fair-20 + +4 mlfqs-nice-2 +2 mlfqs-nice-10 + +5 mlfqs-block diff --git a/pintos-progos/tests/threads/Rubric.priority b/pintos-progos/tests/threads/Rubric.priority new file mode 100644 index 0000000..652bc99 --- /dev/null +++ b/pintos-progos/tests/threads/Rubric.priority @@ -0,0 +1,15 @@ +Functionality of priority scheduler: +3 priority-change +3 priority-preempt + +3 priority-fifo +3 priority-sema +3 priority-condvar + +3 priority-donate-one +3 priority-donate-multiple +3 priority-donate-multiple2 +3 priority-donate-nest +5 priority-donate-chain +3 priority-donate-sema +3 priority-donate-lower diff --git a/pintos-progos/tests/threads/alarm-multiple.ck b/pintos-progos/tests/threads/alarm-multiple.ck new file mode 100644 index 0000000..fd83bcd --- /dev/null +++ b/pintos-progos/tests/threads/alarm-multiple.ck @@ -0,0 +1,4 @@ +# -*- perl -*- +use tests::tests; +use tests::threads::alarm; +check_alarm (7); diff --git a/pintos-progos/tests/threads/alarm-negative.c b/pintos-progos/tests/threads/alarm-negative.c new file mode 100644 index 0000000..aec52cf --- /dev/null +++ b/pintos-progos/tests/threads/alarm-negative.c @@ -0,0 +1,15 @@ +/* Tests timer_sleep(-100). Only requirement is that it not crash. */ + +#include +#include "tests/threads/tests.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +void +test_alarm_negative (void) +{ + timer_sleep (-100); + pass (); +} diff --git a/pintos-progos/tests/threads/alarm-negative.ck b/pintos-progos/tests/threads/alarm-negative.ck new file mode 100644 index 0000000..0d2bab0 --- /dev/null +++ b/pintos-progos/tests/threads/alarm-negative.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-negative) begin +(alarm-negative) PASS +(alarm-negative) end +EOF +pass; diff --git a/pintos-progos/tests/threads/alarm-priority.c b/pintos-progos/tests/threads/alarm-priority.c new file mode 100644 index 0000000..2288ff6 --- /dev/null +++ b/pintos-progos/tests/threads/alarm-priority.c @@ -0,0 +1,58 @@ +/* Checks that when the alarm clock wakes up threads, the + higher-priority threads run first. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static thread_func alarm_priority_thread; +static int64_t wake_time; +static struct semaphore wait_sema; + +void +test_alarm_priority (void) +{ + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + wake_time = timer_ticks () + 5 * TIMER_FREQ; + sema_init (&wait_sema, 0); + + for (i = 0; i < 10; i++) + { + int priority = PRI_DEFAULT - (i + 5) % 10 - 1; + char name[16]; + snprintf (name, sizeof name, "priority %d", priority); + thread_create (name, priority, alarm_priority_thread, NULL); + } + + thread_set_priority (PRI_MIN); + + for (i = 0; i < 10; i++) + sema_down (&wait_sema); +} + +static void +alarm_priority_thread (void *aux UNUSED) +{ + /* Busy-wait until the current time changes. */ + int64_t start_time = timer_ticks (); + while (timer_elapsed (start_time) == 0) + continue; + + /* Now we know we're at the very beginning of a timer tick, so + we can call timer_sleep() without worrying about races + between checking the time and a timer interrupt. */ + timer_sleep (wake_time - timer_ticks ()); + + /* Print a message on wake-up. */ + msg ("Thread %s woke up.", thread_name ()); + + sema_up (&wait_sema); +} diff --git a/pintos-progos/tests/threads/alarm-priority.ck b/pintos-progos/tests/threads/alarm-priority.ck new file mode 100644 index 0000000..b57c78b --- /dev/null +++ b/pintos-progos/tests/threads/alarm-priority.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-priority) begin +(alarm-priority) Thread priority 30 woke up. +(alarm-priority) Thread priority 29 woke up. +(alarm-priority) Thread priority 28 woke up. +(alarm-priority) Thread priority 27 woke up. +(alarm-priority) Thread priority 26 woke up. +(alarm-priority) Thread priority 25 woke up. +(alarm-priority) Thread priority 24 woke up. +(alarm-priority) Thread priority 23 woke up. +(alarm-priority) Thread priority 22 woke up. +(alarm-priority) Thread priority 21 woke up. +(alarm-priority) end +EOF +pass; diff --git a/pintos-progos/tests/threads/alarm-simultaneous.c b/pintos-progos/tests/threads/alarm-simultaneous.c new file mode 100644 index 0000000..844eea4 --- /dev/null +++ b/pintos-progos/tests/threads/alarm-simultaneous.c @@ -0,0 +1,94 @@ +/* Creates N threads, each of which sleeps a different, fixed + duration, M times. Records the wake-up order and verifies + that it is valid. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void test_sleep (int thread_cnt, int iterations); + +void +test_alarm_simultaneous (void) +{ + test_sleep (3, 5); +} + +/* Information about the test. */ +struct sleep_test + { + int64_t start; /* Current time at start of test. */ + int iterations; /* Number of iterations per thread. */ + int *output_pos; /* Current position in output buffer. */ + }; + +static void sleeper (void *); + +/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */ +static void +test_sleep (int thread_cnt, int iterations) +{ + struct sleep_test test; + int *output; + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations); + msg ("Each thread sleeps 10 ticks each time."); + msg ("Within an iteration, all threads should wake up on the same tick."); + + /* Allocate memory. */ + output = malloc (sizeof *output * iterations * thread_cnt * 2); + if (output == NULL) + PANIC ("couldn't allocate memory for test"); + + /* Initialize test. */ + test.start = timer_ticks () + 100; + test.iterations = iterations; + test.output_pos = output; + + /* Start threads. */ + ASSERT (output != NULL); + for (i = 0; i < thread_cnt; i++) + { + char name[16]; + snprintf (name, sizeof name, "thread %d", i); + thread_create (name, PRI_DEFAULT, sleeper, &test); + } + + /* Wait long enough for all the threads to finish. */ + timer_sleep (100 + iterations * 10 + 100); + + /* Print completion order. */ + msg ("iteration 0, thread 0: woke up after %d ticks", output[0]); + for (i = 1; i < test.output_pos - output; i++) + msg ("iteration %d, thread %d: woke up %d ticks later", + i / thread_cnt, i % thread_cnt, output[i] - output[i - 1]); + + free (output); +} + +/* Sleeper thread. */ +static void +sleeper (void *test_) +{ + struct sleep_test *test = test_; + int i; + + /* Make sure we're at the beginning of a timer tick. */ + timer_sleep (1); + + for (i = 1; i <= test->iterations; i++) + { + int64_t sleep_until = test->start + i * 10; + timer_sleep (sleep_until - timer_ticks ()); + *test->output_pos++ = timer_ticks () - test->start; + thread_yield (); + } +} diff --git a/pintos-progos/tests/threads/alarm-simultaneous.ck b/pintos-progos/tests/threads/alarm-simultaneous.ck new file mode 100644 index 0000000..406b8b0 --- /dev/null +++ b/pintos-progos/tests/threads/alarm-simultaneous.ck @@ -0,0 +1,27 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-simultaneous) begin +(alarm-simultaneous) Creating 3 threads to sleep 5 times each. +(alarm-simultaneous) Each thread sleeps 10 ticks each time. +(alarm-simultaneous) Within an iteration, all threads should wake up on the same tick. +(alarm-simultaneous) iteration 0, thread 0: woke up after 10 ticks +(alarm-simultaneous) iteration 0, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 0, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 1, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 1, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 1, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 2, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 2, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 2, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 3, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 3, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 3, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 4, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 4, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 4, thread 2: woke up 0 ticks later +(alarm-simultaneous) end +EOF +pass; diff --git a/pintos-progos/tests/threads/alarm-single.ck b/pintos-progos/tests/threads/alarm-single.ck new file mode 100644 index 0000000..31215df --- /dev/null +++ b/pintos-progos/tests/threads/alarm-single.ck @@ -0,0 +1,4 @@ +# -*- perl -*- +use tests::tests; +use tests::threads::alarm; +check_alarm (1); diff --git a/pintos-progos/tests/threads/alarm-wait.c b/pintos-progos/tests/threads/alarm-wait.c new file mode 100644 index 0000000..37d3afc --- /dev/null +++ b/pintos-progos/tests/threads/alarm-wait.c @@ -0,0 +1,152 @@ +/* Creates N threads, each of which sleeps a different, fixed + duration, M times. Records the wake-up order and verifies + that it is valid. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void test_sleep (int thread_cnt, int iterations); + +void +test_alarm_single (void) +{ + test_sleep (5, 1); +} + +void +test_alarm_multiple (void) +{ + test_sleep (5, 7); +} + +/* Information about the test. */ +struct sleep_test + { + int64_t start; /* Current time at start of test. */ + int iterations; /* Number of iterations per thread. */ + + /* Output. */ + struct lock output_lock; /* Lock protecting output buffer. */ + int *output_pos; /* Current position in output buffer. */ + }; + +/* Information about an individual thread in the test. */ +struct sleep_thread + { + struct sleep_test *test; /* Info shared between all threads. */ + int id; /* Sleeper ID. */ + int duration; /* Number of ticks to sleep. */ + int iterations; /* Iterations counted so far. */ + }; + +static void sleeper (void *); + +/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */ +static void +test_sleep (int thread_cnt, int iterations) +{ + struct sleep_test test; + struct sleep_thread *threads; + int *output, *op; + int product; + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations); + msg ("Thread 0 sleeps 10 ticks each time,"); + msg ("thread 1 sleeps 20 ticks each time, and so on."); + msg ("If successful, product of iteration count and"); + msg ("sleep duration will appear in nondescending order."); + + /* Allocate memory. */ + threads = malloc (sizeof *threads * thread_cnt); + output = malloc (sizeof *output * iterations * thread_cnt * 2); + if (threads == NULL || output == NULL) + PANIC ("couldn't allocate memory for test"); + + /* Initialize test. */ + test.start = timer_ticks () + 100; + test.iterations = iterations; + lock_init (&test.output_lock); + test.output_pos = output; + + /* Start threads. */ + ASSERT (output != NULL); + for (i = 0; i < thread_cnt; i++) + { + struct sleep_thread *t = threads + i; + char name[16]; + + t->test = &test; + t->id = i; + t->duration = (i + 1) * 10; + t->iterations = 0; + + snprintf (name, sizeof name, "thread %d", i); + thread_create (name, PRI_DEFAULT, sleeper, t); + } + + /* Wait long enough for all the threads to finish. */ + timer_sleep (100 + thread_cnt * iterations * 10 + 100); + + /* Acquire the output lock in case some rogue thread is still + running. */ + lock_acquire (&test.output_lock); + + /* Print completion order. */ + product = 0; + for (op = output; op < test.output_pos; op++) + { + struct sleep_thread *t; + int new_prod; + + ASSERT (*op >= 0 && *op < thread_cnt); + t = threads + *op; + + new_prod = ++t->iterations * t->duration; + + msg ("thread %d: duration=%d, iteration=%d, product=%d", + t->id, t->duration, t->iterations, new_prod); + + if (new_prod >= product) + product = new_prod; + else + fail ("thread %d woke up out of order (%d > %d)!", + t->id, product, new_prod); + } + + /* Verify that we had the proper number of wakeups. */ + for (i = 0; i < thread_cnt; i++) + if (threads[i].iterations != iterations) + fail ("thread %d woke up %d times instead of %d", + i, threads[i].iterations, iterations); + + lock_release (&test.output_lock); + free (output); + free (threads); +} + +/* Sleeper thread. */ +static void +sleeper (void *t_) +{ + struct sleep_thread *t = t_; + struct sleep_test *test = t->test; + int i; + + for (i = 1; i <= test->iterations; i++) + { + int64_t sleep_until = test->start + i * t->duration; + timer_sleep (sleep_until - timer_ticks ()); + lock_acquire (&test->output_lock); + *test->output_pos++ = t->id; + lock_release (&test->output_lock); + } +} diff --git a/pintos-progos/tests/threads/alarm-zero.c b/pintos-progos/tests/threads/alarm-zero.c new file mode 100644 index 0000000..c8a3ee2 --- /dev/null +++ b/pintos-progos/tests/threads/alarm-zero.c @@ -0,0 +1,15 @@ +/* Tests timer_sleep(0), which should return immediately. */ + +#include +#include "tests/threads/tests.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +void +test_alarm_zero (void) +{ + timer_sleep (0); + pass (); +} diff --git a/pintos-progos/tests/threads/alarm-zero.ck b/pintos-progos/tests/threads/alarm-zero.ck new file mode 100644 index 0000000..a6b1a3c --- /dev/null +++ b/pintos-progos/tests/threads/alarm-zero.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-zero) begin +(alarm-zero) PASS +(alarm-zero) end +EOF +pass; diff --git a/pintos-progos/tests/threads/alarm.pm b/pintos-progos/tests/threads/alarm.pm new file mode 100644 index 0000000..84b3b7f --- /dev/null +++ b/pintos-progos/tests/threads/alarm.pm @@ -0,0 +1,32 @@ +sub check_alarm { + my ($iterations) = @_; + our ($test); + + @output = read_text_file ("$test.output"); + common_checks ("run", @output); + + my (@products); + for (my ($i) = 0; $i < $iterations; $i++) { + for (my ($t) = 0; $t < 5; $t++) { + push (@products, ($i + 1) * ($t + 1) * 10); + } + } + @products = sort {$a <=> $b} @products; + + local ($_); + foreach (@output) { + fail $_ if /out of order/i; + + my ($p) = /product=(\d+)$/; + next if !defined $p; + + my ($q) = shift (@products); + fail "Too many wakeups.\n" if !defined $q; + fail "Out of order wakeups ($p vs. $q).\n" if $p != $q; # FIXME + } + fail scalar (@products) . " fewer wakeups than expected.\n" + if @products != 0; + pass; +} + +1; diff --git a/pintos-progos/tests/threads/mlfqs-block.c b/pintos-progos/tests/threads/mlfqs-block.c new file mode 100644 index 0000000..6d4992d --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-block.c @@ -0,0 +1,64 @@ +/* Checks that recent_cpu and priorities are updated for blocked + threads. + + The main thread sleeps for 25 seconds, spins for 5 seconds, + then releases a lock. The "block" thread spins for 20 seconds + then attempts to acquire the lock, which will block for 10 + seconds (until the main thread releases it). If recent_cpu + decays properly while the "block" thread sleeps, then the + block thread should be immediately scheduled when the main + thread releases the lock. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void block_thread (void *lock_); + +void +test_mlfqs_block (void) +{ + int64_t start_time; + struct lock lock; + + ASSERT (thread_mlfqs); + + msg ("Main thread acquiring lock."); + lock_init (&lock); + lock_acquire (&lock); + + msg ("Main thread creating block thread, sleeping 25 seconds..."); + thread_create ("block", PRI_DEFAULT, block_thread, &lock); + timer_sleep (25 * TIMER_FREQ); + + msg ("Main thread spinning for 5 seconds..."); + start_time = timer_ticks (); + while (timer_elapsed (start_time) < 5 * TIMER_FREQ) + continue; + + msg ("Main thread releasing lock."); + lock_release (&lock); + + msg ("Block thread should have already acquired lock."); +} + +static void +block_thread (void *lock_) +{ + struct lock *lock = lock_; + int64_t start_time; + + msg ("Block thread spinning for 20 seconds..."); + start_time = timer_ticks (); + while (timer_elapsed (start_time) < 20 * TIMER_FREQ) + continue; + + msg ("Block thread acquiring lock..."); + lock_acquire (lock); + + msg ("...got it."); +} diff --git a/pintos-progos/tests/threads/mlfqs-block.ck b/pintos-progos/tests/threads/mlfqs-block.ck new file mode 100644 index 0000000..8833a3a --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-block.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(mlfqs-block) begin +(mlfqs-block) Main thread acquiring lock. +(mlfqs-block) Main thread creating block thread, sleeping 25 seconds... +(mlfqs-block) Block thread spinning for 20 seconds... +(mlfqs-block) Block thread acquiring lock... +(mlfqs-block) Main thread spinning for 5 seconds... +(mlfqs-block) Main thread releasing lock. +(mlfqs-block) ...got it. +(mlfqs-block) Block thread should have already acquired lock. +(mlfqs-block) end +EOF +pass; diff --git a/pintos-progos/tests/threads/mlfqs-fair-2.ck b/pintos-progos/tests/threads/mlfqs-fair-2.ck new file mode 100644 index 0000000..5b19ff1 --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-fair-2.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +check_mlfqs_fair ([0, 0], 50); diff --git a/pintos-progos/tests/threads/mlfqs-fair-20.ck b/pintos-progos/tests/threads/mlfqs-fair-20.ck new file mode 100644 index 0000000..bb4d051 --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-fair-20.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +check_mlfqs_fair ([(0) x 20], 20); diff --git a/pintos-progos/tests/threads/mlfqs-fair.c b/pintos-progos/tests/threads/mlfqs-fair.c new file mode 100644 index 0000000..3b1bea5 --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-fair.c @@ -0,0 +1,124 @@ +/* Measures the correctness of the "nice" implementation. + + The "fair" tests run either 2 or 20 threads all niced to 0. + The threads should all receive approximately the same number + of ticks. Each test runs for 30 seconds, so the ticks should + also sum to approximately 30 * 100 == 3000 ticks. + + The mlfqs-nice-2 test runs 2 threads, one with nice 0, the + other with nice 5, which should receive 1,904 and 1,096 ticks, + respectively, over 30 seconds. + + The mlfqs-nice-10 test runs 10 threads with nice 0 through 9. + They should receive 672, 588, 492, 408, 316, 232, 152, 92, 40, + and 8 ticks, respectively, over 30 seconds. + + (The above are computed via simulation in mlfqs.pm.) */ + +#include +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/palloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void test_mlfqs_fair (int thread_cnt, int nice_min, int nice_step); + +void +test_mlfqs_fair_2 (void) +{ + test_mlfqs_fair (2, 0, 0); +} + +void +test_mlfqs_fair_20 (void) +{ + test_mlfqs_fair (20, 0, 0); +} + +void +test_mlfqs_nice_2 (void) +{ + test_mlfqs_fair (2, 0, 5); +} + +void +test_mlfqs_nice_10 (void) +{ + test_mlfqs_fair (10, 0, 1); +} + +#define MAX_THREAD_CNT 20 + +struct thread_info + { + int64_t start_time; + int tick_count; + int nice; + }; + +static void load_thread (void *aux); + +static void +test_mlfqs_fair (int thread_cnt, int nice_min, int nice_step) +{ + struct thread_info info[MAX_THREAD_CNT]; + int64_t start_time; + int nice; + int i; + + ASSERT (thread_mlfqs); + ASSERT (thread_cnt <= MAX_THREAD_CNT); + ASSERT (nice_min >= -10); + ASSERT (nice_step >= 0); + ASSERT (nice_min + nice_step * (thread_cnt - 1) <= 20); + + thread_set_nice (-20); + + start_time = timer_ticks (); + msg ("Starting %d threads...", thread_cnt); + nice = nice_min; + for (i = 0; i < thread_cnt; i++) + { + struct thread_info *ti = &info[i]; + char name[16]; + + ti->start_time = start_time; + ti->tick_count = 0; + ti->nice = nice; + + snprintf(name, sizeof name, "load %d", i); + thread_create (name, PRI_DEFAULT, load_thread, ti); + + nice += nice_step; + } + msg ("Starting threads took %"PRId64" ticks.", timer_elapsed (start_time)); + + msg ("Sleeping 40 seconds to let threads run, please wait..."); + timer_sleep (40 * TIMER_FREQ); + + for (i = 0; i < thread_cnt; i++) + msg ("Thread %d received %d ticks.", i, info[i].tick_count); +} + +static void +load_thread (void *ti_) +{ + struct thread_info *ti = ti_; + int64_t sleep_time = 5 * TIMER_FREQ; + int64_t spin_time = sleep_time + 30 * TIMER_FREQ; + int64_t last_time = 0; + + thread_set_nice (ti->nice); + timer_sleep (sleep_time - timer_elapsed (ti->start_time)); + while (timer_elapsed (ti->start_time) < spin_time) + { + int64_t cur_time = timer_ticks (); + if (cur_time != last_time) + ti->tick_count++; + last_time = cur_time; + } +} diff --git a/pintos-progos/tests/threads/mlfqs-load-1.c b/pintos-progos/tests/threads/mlfqs-load-1.c new file mode 100644 index 0000000..a39eea2 --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-load-1.c @@ -0,0 +1,60 @@ +/* Verifies that a single busy thread raises the load average to + 0.5 in 38 to 45 seconds. The expected time is 42 seconds, as + you can verify: + perl -e '$i++,$a=(59*$a+1)/60while$a<=.5;print "$i\n"' + + Then, verifies that 10 seconds of inactivity drop the load + average back below 0.5 again. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +void +test_mlfqs_load_1 (void) +{ + int64_t start_time; + int elapsed; + int load_avg; + + ASSERT (thread_mlfqs); + + msg ("spinning for up to 45 seconds, please wait..."); + + start_time = timer_ticks (); + for (;;) + { + load_avg = thread_get_load_avg (); + ASSERT (load_avg >= 0); + elapsed = timer_elapsed (start_time) / TIMER_FREQ; + if (load_avg > 100) + fail ("load average is %d.%02d " + "but should be between 0 and 1 (after %d seconds)", + load_avg / 100, load_avg % 100, elapsed); + else if (load_avg > 50) + break; + else if (elapsed > 45) + fail ("load average stayed below 0.5 for more than 45 seconds"); + } + + if (elapsed < 38) + fail ("load average took only %d seconds to rise above 0.5", elapsed); + msg ("load average rose to 0.5 after %d seconds", elapsed); + + msg ("sleeping for another 10 seconds, please wait..."); + timer_sleep (TIMER_FREQ * 10); + + load_avg = thread_get_load_avg (); + if (load_avg < 0) + fail ("load average fell below 0"); + if (load_avg > 50) + fail ("load average stayed above 0.5 for more than 10 seconds"); + msg ("load average fell back below 0.5 (to %d.%02d)", + load_avg / 100, load_avg % 100); + + pass (); +} diff --git a/pintos-progos/tests/threads/mlfqs-load-1.ck b/pintos-progos/tests/threads/mlfqs-load-1.ck new file mode 100644 index 0000000..faf0ffa --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-load-1.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks ("run", @output); + +@output = get_core_output ("run", @output); +fail "missing PASS in output" + unless grep ($_ eq '(mlfqs-load-1) PASS', @output); + +pass; diff --git a/pintos-progos/tests/threads/mlfqs-load-60.c b/pintos-progos/tests/threads/mlfqs-load-60.c new file mode 100644 index 0000000..b6a3eb6 --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-load-60.c @@ -0,0 +1,155 @@ +/* Starts 60 threads that each sleep for 10 seconds, then spin in + a tight loop for 60 seconds, and sleep for another 60 seconds. + Every 2 seconds after the initial sleep, the main thread + prints the load average. + + The expected output is this (some margin of error is allowed): + + After 0 seconds, load average=1.00. + After 2 seconds, load average=2.95. + After 4 seconds, load average=4.84. + After 6 seconds, load average=6.66. + After 8 seconds, load average=8.42. + After 10 seconds, load average=10.13. + After 12 seconds, load average=11.78. + After 14 seconds, load average=13.37. + After 16 seconds, load average=14.91. + After 18 seconds, load average=16.40. + After 20 seconds, load average=17.84. + After 22 seconds, load average=19.24. + After 24 seconds, load average=20.58. + After 26 seconds, load average=21.89. + After 28 seconds, load average=23.15. + After 30 seconds, load average=24.37. + After 32 seconds, load average=25.54. + After 34 seconds, load average=26.68. + After 36 seconds, load average=27.78. + After 38 seconds, load average=28.85. + After 40 seconds, load average=29.88. + After 42 seconds, load average=30.87. + After 44 seconds, load average=31.84. + After 46 seconds, load average=32.77. + After 48 seconds, load average=33.67. + After 50 seconds, load average=34.54. + After 52 seconds, load average=35.38. + After 54 seconds, load average=36.19. + After 56 seconds, load average=36.98. + After 58 seconds, load average=37.74. + After 60 seconds, load average=37.48. + After 62 seconds, load average=36.24. + After 64 seconds, load average=35.04. + After 66 seconds, load average=33.88. + After 68 seconds, load average=32.76. + After 70 seconds, load average=31.68. + After 72 seconds, load average=30.63. + After 74 seconds, load average=29.62. + After 76 seconds, load average=28.64. + After 78 seconds, load average=27.69. + After 80 seconds, load average=26.78. + After 82 seconds, load average=25.89. + After 84 seconds, load average=25.04. + After 86 seconds, load average=24.21. + After 88 seconds, load average=23.41. + After 90 seconds, load average=22.64. + After 92 seconds, load average=21.89. + After 94 seconds, load average=21.16. + After 96 seconds, load average=20.46. + After 98 seconds, load average=19.79. + After 100 seconds, load average=19.13. + After 102 seconds, load average=18.50. + After 104 seconds, load average=17.89. + After 106 seconds, load average=17.30. + After 108 seconds, load average=16.73. + After 110 seconds, load average=16.17. + After 112 seconds, load average=15.64. + After 114 seconds, load average=15.12. + After 116 seconds, load average=14.62. + After 118 seconds, load average=14.14. + After 120 seconds, load average=13.67. + After 122 seconds, load average=13.22. + After 124 seconds, load average=12.78. + After 126 seconds, load average=12.36. + After 128 seconds, load average=11.95. + After 130 seconds, load average=11.56. + After 132 seconds, load average=11.17. + After 134 seconds, load average=10.80. + After 136 seconds, load average=10.45. + After 138 seconds, load average=10.10. + After 140 seconds, load average=9.77. + After 142 seconds, load average=9.45. + After 144 seconds, load average=9.13. + After 146 seconds, load average=8.83. + After 148 seconds, load average=8.54. + After 150 seconds, load average=8.26. + After 152 seconds, load average=7.98. + After 154 seconds, load average=7.72. + After 156 seconds, load average=7.47. + After 158 seconds, load average=7.22. + After 160 seconds, load average=6.98. + After 162 seconds, load average=6.75. + After 164 seconds, load average=6.53. + After 166 seconds, load average=6.31. + After 168 seconds, load average=6.10. + After 170 seconds, load average=5.90. + After 172 seconds, load average=5.70. + After 174 seconds, load average=5.52. + After 176 seconds, load average=5.33. + After 178 seconds, load average=5.16. +*/ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static int64_t start_time; + +static void load_thread (void *aux); + +#define THREAD_CNT 60 + +void +test_mlfqs_load_60 (void) +{ + int i; + + ASSERT (thread_mlfqs); + + start_time = timer_ticks (); + msg ("Starting %d niced load threads...", THREAD_CNT); + for (i = 0; i < THREAD_CNT; i++) + { + char name[16]; + snprintf(name, sizeof name, "load %d", i); + thread_create (name, PRI_DEFAULT, load_thread, NULL); + } + msg ("Starting threads took %d seconds.", + timer_elapsed (start_time) / TIMER_FREQ); + + for (i = 0; i < 90; i++) + { + int64_t sleep_until = start_time + TIMER_FREQ * (2 * i + 10); + int load_avg; + timer_sleep (sleep_until - timer_ticks ()); + load_avg = thread_get_load_avg (); + msg ("After %d seconds, load average=%d.%02d.", + i * 2, load_avg / 100, load_avg % 100); + } +} + +static void +load_thread (void *aux UNUSED) +{ + int64_t sleep_time = 10 * TIMER_FREQ; + int64_t spin_time = sleep_time + 60 * TIMER_FREQ; + int64_t exit_time = spin_time + 60 * TIMER_FREQ; + + thread_set_nice (20); + timer_sleep (sleep_time - timer_elapsed (start_time)); + while (timer_elapsed (start_time) < spin_time) + continue; + timer_sleep (exit_time - timer_elapsed (start_time)); +} diff --git a/pintos-progos/tests/threads/mlfqs-load-60.ck b/pintos-progos/tests/threads/mlfqs-load-60.ck new file mode 100644 index 0000000..cb69220 --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-load-60.ck @@ -0,0 +1,36 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +our ($test); + +my (@output) = read_text_file ("$test.output"); +common_checks ("run", @output); +@output = get_core_output ("run", @output); + +# Get actual values. +local ($_); +my (@actual); +foreach (@output) { + my ($t, $load_avg) = /After (\d+) seconds, load average=(\d+\.\d+)\./ + or next; + $actual[$t] = $load_avg; +} + +# Calculate expected values. +my ($load_avg) = 0; +my ($recent) = 0; +my (@expected); +for (my ($t) = 0; $t < 180; $t++) { + my ($ready) = $t < 60 ? 60 : 0; + $load_avg = (59/60) * $load_avg + (1/60) * $ready; + $expected[$t] = $load_avg; +} + +mlfqs_compare ("time", "%.2f", \@actual, \@expected, 3.5, [2, 178, 2], + "Some load average values were missing or " + . "differed from those expected " + . "by more than 3.5."); +pass; diff --git a/pintos-progos/tests/threads/mlfqs-load-avg.c b/pintos-progos/tests/threads/mlfqs-load-avg.c new file mode 100644 index 0000000..50e83e2 --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-load-avg.c @@ -0,0 +1,167 @@ +/* Starts 60 threads numbered 0 through 59. Thread #i sleeps for + (10+i) seconds, then spins in a loop for 60 seconds, then + sleeps until a total of 120 seconds have passed. Every 2 + seconds, starting 10 seconds in, the main thread prints the + load average. + + The expected output is listed below. Some margin of error is + allowed. + + If your implementation fails this test but passes most other + tests, then consider whether you are doing too much work in + the timer interrupt. If the timer interrupt handler takes too + long, then the test's main thread will not have enough time to + do its own work (printing a message) and go back to sleep + before the next tick arrives. Then the main thread will be + ready, instead of sleeping, when the tick arrives, + artificially driving up the load average. + + After 0 seconds, load average=0.00. + After 2 seconds, load average=0.05. + After 4 seconds, load average=0.16. + After 6 seconds, load average=0.34. + After 8 seconds, load average=0.58. + After 10 seconds, load average=0.87. + After 12 seconds, load average=1.22. + After 14 seconds, load average=1.63. + After 16 seconds, load average=2.09. + After 18 seconds, load average=2.60. + After 20 seconds, load average=3.16. + After 22 seconds, load average=3.76. + After 24 seconds, load average=4.42. + After 26 seconds, load average=5.11. + After 28 seconds, load average=5.85. + After 30 seconds, load average=6.63. + After 32 seconds, load average=7.46. + After 34 seconds, load average=8.32. + After 36 seconds, load average=9.22. + After 38 seconds, load average=10.15. + After 40 seconds, load average=11.12. + After 42 seconds, load average=12.13. + After 44 seconds, load average=13.16. + After 46 seconds, load average=14.23. + After 48 seconds, load average=15.33. + After 50 seconds, load average=16.46. + After 52 seconds, load average=17.62. + After 54 seconds, load average=18.81. + After 56 seconds, load average=20.02. + After 58 seconds, load average=21.26. + After 60 seconds, load average=22.52. + After 62 seconds, load average=23.71. + After 64 seconds, load average=24.80. + After 66 seconds, load average=25.78. + After 68 seconds, load average=26.66. + After 70 seconds, load average=27.45. + After 72 seconds, load average=28.14. + After 74 seconds, load average=28.75. + After 76 seconds, load average=29.27. + After 78 seconds, load average=29.71. + After 80 seconds, load average=30.06. + After 82 seconds, load average=30.34. + After 84 seconds, load average=30.55. + After 86 seconds, load average=30.68. + After 88 seconds, load average=30.74. + After 90 seconds, load average=30.73. + After 92 seconds, load average=30.66. + After 94 seconds, load average=30.52. + After 96 seconds, load average=30.32. + After 98 seconds, load average=30.06. + After 100 seconds, load average=29.74. + After 102 seconds, load average=29.37. + After 104 seconds, load average=28.95. + After 106 seconds, load average=28.47. + After 108 seconds, load average=27.94. + After 110 seconds, load average=27.36. + After 112 seconds, load average=26.74. + After 114 seconds, load average=26.07. + After 116 seconds, load average=25.36. + After 118 seconds, load average=24.60. + After 120 seconds, load average=23.81. + After 122 seconds, load average=23.02. + After 124 seconds, load average=22.26. + After 126 seconds, load average=21.52. + After 128 seconds, load average=20.81. + After 130 seconds, load average=20.12. + After 132 seconds, load average=19.46. + After 134 seconds, load average=18.81. + After 136 seconds, load average=18.19. + After 138 seconds, load average=17.59. + After 140 seconds, load average=17.01. + After 142 seconds, load average=16.45. + After 144 seconds, load average=15.90. + After 146 seconds, load average=15.38. + After 148 seconds, load average=14.87. + After 150 seconds, load average=14.38. + After 152 seconds, load average=13.90. + After 154 seconds, load average=13.44. + After 156 seconds, load average=13.00. + After 158 seconds, load average=12.57. + After 160 seconds, load average=12.15. + After 162 seconds, load average=11.75. + After 164 seconds, load average=11.36. + After 166 seconds, load average=10.99. + After 168 seconds, load average=10.62. + After 170 seconds, load average=10.27. + After 172 seconds, load average=9.93. + After 174 seconds, load average=9.61. + After 176 seconds, load average=9.29. + After 178 seconds, load average=8.98. +*/ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static int64_t start_time; + +static void load_thread (void *seq_no); + +#define THREAD_CNT 60 + +void +test_mlfqs_load_avg (void) +{ + int i; + + ASSERT (thread_mlfqs); + + start_time = timer_ticks (); + msg ("Starting %d load threads...", THREAD_CNT); + for (i = 0; i < THREAD_CNT; i++) + { + char name[16]; + snprintf(name, sizeof name, "load %d", i); + thread_create (name, PRI_DEFAULT, load_thread, (void *) i); + } + msg ("Starting threads took %d seconds.", + timer_elapsed (start_time) / TIMER_FREQ); + thread_set_nice (-20); + + for (i = 0; i < 90; i++) + { + int64_t sleep_until = start_time + TIMER_FREQ * (2 * i + 10); + int load_avg; + timer_sleep (sleep_until - timer_ticks ()); + load_avg = thread_get_load_avg (); + msg ("After %d seconds, load average=%d.%02d.", + i * 2, load_avg / 100, load_avg % 100); + } +} + +static void +load_thread (void *seq_no_) +{ + int seq_no = (int) seq_no_; + int sleep_time = TIMER_FREQ * (10 + seq_no); + int spin_time = sleep_time + TIMER_FREQ * THREAD_CNT; + int exit_time = TIMER_FREQ * (THREAD_CNT * 2); + + timer_sleep (sleep_time - timer_elapsed (start_time)); + while (timer_elapsed (start_time) < spin_time) + continue; + timer_sleep (exit_time - timer_elapsed (start_time)); +} diff --git a/pintos-progos/tests/threads/mlfqs-load-avg.ck b/pintos-progos/tests/threads/mlfqs-load-avg.ck new file mode 100644 index 0000000..2254d05 --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-load-avg.ck @@ -0,0 +1,36 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks ("run", @output); +@output = get_core_output ("run", @output); + +# Get actual values. +local ($_); +my (@actual); +foreach (@output) { + my ($t, $load_avg) = /After (\d+) seconds, load average=(\d+\.\d+)\./ + or next; + $actual[$t] = $load_avg; +} + +# Calculate expected values. +my ($load_avg) = 0; +my ($recent) = 0; +my (@expected); +for (my ($t) = 0; $t < 180; $t++) { + my ($ready) = $t < 60 ? $t : $t < 120 ? 120 - $t : 0; + $load_avg = (59/60) * $load_avg + (1/60) * $ready; + $expected[$t] = $load_avg; +} + +mlfqs_compare ("time", "%.2f", \@actual, \@expected, 2.5, [2, 178, 2], + "Some load average values were missing or " + . "differed from those expected " + . "by more than 2.5."); +pass; diff --git a/pintos-progos/tests/threads/mlfqs-nice-10.ck b/pintos-progos/tests/threads/mlfqs-nice-10.ck new file mode 100644 index 0000000..53e0abe --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-nice-10.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +check_mlfqs_fair ([0...9], 25); diff --git a/pintos-progos/tests/threads/mlfqs-nice-2.ck b/pintos-progos/tests/threads/mlfqs-nice-2.ck new file mode 100644 index 0000000..ada366b --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-nice-2.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +check_mlfqs_fair ([0, 5], 50); diff --git a/pintos-progos/tests/threads/mlfqs-recent-1.c b/pintos-progos/tests/threads/mlfqs-recent-1.c new file mode 100644 index 0000000..4258671 --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-recent-1.c @@ -0,0 +1,144 @@ +/* Checks that recent_cpu is calculated properly for the case of + a single ready process. + + The expected output is this (some margin of error is allowed): + + After 2 seconds, recent_cpu is 6.40, load_avg is 0.03. + After 4 seconds, recent_cpu is 12.60, load_avg is 0.07. + After 6 seconds, recent_cpu is 18.61, load_avg is 0.10. + After 8 seconds, recent_cpu is 24.44, load_avg is 0.13. + After 10 seconds, recent_cpu is 30.08, load_avg is 0.15. + After 12 seconds, recent_cpu is 35.54, load_avg is 0.18. + After 14 seconds, recent_cpu is 40.83, load_avg is 0.21. + After 16 seconds, recent_cpu is 45.96, load_avg is 0.24. + After 18 seconds, recent_cpu is 50.92, load_avg is 0.26. + After 20 seconds, recent_cpu is 55.73, load_avg is 0.29. + After 22 seconds, recent_cpu is 60.39, load_avg is 0.31. + After 24 seconds, recent_cpu is 64.90, load_avg is 0.33. + After 26 seconds, recent_cpu is 69.27, load_avg is 0.35. + After 28 seconds, recent_cpu is 73.50, load_avg is 0.38. + After 30 seconds, recent_cpu is 77.60, load_avg is 0.40. + After 32 seconds, recent_cpu is 81.56, load_avg is 0.42. + After 34 seconds, recent_cpu is 85.40, load_avg is 0.44. + After 36 seconds, recent_cpu is 89.12, load_avg is 0.45. + After 38 seconds, recent_cpu is 92.72, load_avg is 0.47. + After 40 seconds, recent_cpu is 96.20, load_avg is 0.49. + After 42 seconds, recent_cpu is 99.57, load_avg is 0.51. + After 44 seconds, recent_cpu is 102.84, load_avg is 0.52. + After 46 seconds, recent_cpu is 106.00, load_avg is 0.54. + After 48 seconds, recent_cpu is 109.06, load_avg is 0.55. + After 50 seconds, recent_cpu is 112.02, load_avg is 0.57. + After 52 seconds, recent_cpu is 114.89, load_avg is 0.58. + After 54 seconds, recent_cpu is 117.66, load_avg is 0.60. + After 56 seconds, recent_cpu is 120.34, load_avg is 0.61. + After 58 seconds, recent_cpu is 122.94, load_avg is 0.62. + After 60 seconds, recent_cpu is 125.46, load_avg is 0.64. + After 62 seconds, recent_cpu is 127.89, load_avg is 0.65. + After 64 seconds, recent_cpu is 130.25, load_avg is 0.66. + After 66 seconds, recent_cpu is 132.53, load_avg is 0.67. + After 68 seconds, recent_cpu is 134.73, load_avg is 0.68. + After 70 seconds, recent_cpu is 136.86, load_avg is 0.69. + After 72 seconds, recent_cpu is 138.93, load_avg is 0.70. + After 74 seconds, recent_cpu is 140.93, load_avg is 0.71. + After 76 seconds, recent_cpu is 142.86, load_avg is 0.72. + After 78 seconds, recent_cpu is 144.73, load_avg is 0.73. + After 80 seconds, recent_cpu is 146.54, load_avg is 0.74. + After 82 seconds, recent_cpu is 148.29, load_avg is 0.75. + After 84 seconds, recent_cpu is 149.99, load_avg is 0.76. + After 86 seconds, recent_cpu is 151.63, load_avg is 0.76. + After 88 seconds, recent_cpu is 153.21, load_avg is 0.77. + After 90 seconds, recent_cpu is 154.75, load_avg is 0.78. + After 92 seconds, recent_cpu is 156.23, load_avg is 0.79. + After 94 seconds, recent_cpu is 157.67, load_avg is 0.79. + After 96 seconds, recent_cpu is 159.06, load_avg is 0.80. + After 98 seconds, recent_cpu is 160.40, load_avg is 0.81. + After 100 seconds, recent_cpu is 161.70, load_avg is 0.81. + After 102 seconds, recent_cpu is 162.96, load_avg is 0.82. + After 104 seconds, recent_cpu is 164.18, load_avg is 0.83. + After 106 seconds, recent_cpu is 165.35, load_avg is 0.83. + After 108 seconds, recent_cpu is 166.49, load_avg is 0.84. + After 110 seconds, recent_cpu is 167.59, load_avg is 0.84. + After 112 seconds, recent_cpu is 168.66, load_avg is 0.85. + After 114 seconds, recent_cpu is 169.69, load_avg is 0.85. + After 116 seconds, recent_cpu is 170.69, load_avg is 0.86. + After 118 seconds, recent_cpu is 171.65, load_avg is 0.86. + After 120 seconds, recent_cpu is 172.58, load_avg is 0.87. + After 122 seconds, recent_cpu is 173.49, load_avg is 0.87. + After 124 seconds, recent_cpu is 174.36, load_avg is 0.88. + After 126 seconds, recent_cpu is 175.20, load_avg is 0.88. + After 128 seconds, recent_cpu is 176.02, load_avg is 0.88. + After 130 seconds, recent_cpu is 176.81, load_avg is 0.89. + After 132 seconds, recent_cpu is 177.57, load_avg is 0.89. + After 134 seconds, recent_cpu is 178.31, load_avg is 0.89. + After 136 seconds, recent_cpu is 179.02, load_avg is 0.90. + After 138 seconds, recent_cpu is 179.72, load_avg is 0.90. + After 140 seconds, recent_cpu is 180.38, load_avg is 0.90. + After 142 seconds, recent_cpu is 181.03, load_avg is 0.91. + After 144 seconds, recent_cpu is 181.65, load_avg is 0.91. + After 146 seconds, recent_cpu is 182.26, load_avg is 0.91. + After 148 seconds, recent_cpu is 182.84, load_avg is 0.92. + After 150 seconds, recent_cpu is 183.41, load_avg is 0.92. + After 152 seconds, recent_cpu is 183.96, load_avg is 0.92. + After 154 seconds, recent_cpu is 184.49, load_avg is 0.92. + After 156 seconds, recent_cpu is 185.00, load_avg is 0.93. + After 158 seconds, recent_cpu is 185.49, load_avg is 0.93. + After 160 seconds, recent_cpu is 185.97, load_avg is 0.93. + After 162 seconds, recent_cpu is 186.43, load_avg is 0.93. + After 164 seconds, recent_cpu is 186.88, load_avg is 0.94. + After 166 seconds, recent_cpu is 187.31, load_avg is 0.94. + After 168 seconds, recent_cpu is 187.73, load_avg is 0.94. + After 170 seconds, recent_cpu is 188.14, load_avg is 0.94. + After 172 seconds, recent_cpu is 188.53, load_avg is 0.94. + After 174 seconds, recent_cpu is 188.91, load_avg is 0.95. + After 176 seconds, recent_cpu is 189.27, load_avg is 0.95. + After 178 seconds, recent_cpu is 189.63, load_avg is 0.95. + After 180 seconds, recent_cpu is 189.97, load_avg is 0.95. +*/ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +/* Sensitive to assumption that recent_cpu updates happen exactly + when timer_ticks() % TIMER_FREQ == 0. */ + +void +test_mlfqs_recent_1 (void) +{ + int64_t start_time; + int last_elapsed = 0; + + ASSERT (thread_mlfqs); + + do + { + msg ("Sleeping 10 seconds to allow recent_cpu to decay, please wait..."); + start_time = timer_ticks (); + timer_sleep (DIV_ROUND_UP (start_time, TIMER_FREQ) - start_time + + 10 * TIMER_FREQ); + } + while (thread_get_recent_cpu () > 700); + + start_time = timer_ticks (); + for (;;) + { + int elapsed = timer_elapsed (start_time); + if (elapsed % (TIMER_FREQ * 2) == 0 && elapsed > last_elapsed) + { + int recent_cpu = thread_get_recent_cpu (); + int load_avg = thread_get_load_avg (); + int elapsed_seconds = elapsed / TIMER_FREQ; + msg ("After %d seconds, recent_cpu is %d.%02d, load_avg is %d.%02d.", + elapsed_seconds, + recent_cpu / 100, recent_cpu % 100, + load_avg / 100, load_avg % 100); + if (elapsed_seconds >= 180) + break; + } + last_elapsed = elapsed; + } +} diff --git a/pintos-progos/tests/threads/mlfqs-recent-1.ck b/pintos-progos/tests/threads/mlfqs-recent-1.ck new file mode 100644 index 0000000..a2ba44d --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs-recent-1.ck @@ -0,0 +1,31 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +our ($test); +my (@output) = read_text_file ("$test.output"); +common_checks ("run", @output); +@output = get_core_output ("run", @output); + +# Get actual values. +local ($_); +my (@actual); +foreach (@output) { + my ($t, $recent_cpu) = /After (\d+) seconds, recent_cpu is (\d+\.\d+),/ + or next; + $actual[$t] = $recent_cpu; +} + +# Calculate expected values. +my ($expected_load_avg, $expected_recent_cpu) + = mlfqs_expected_load ([(1) x 180], [(100) x 180]); +my (@expected) = @$expected_recent_cpu; + +# Compare actual and expected values. +mlfqs_compare ("time", "%.2f", \@actual, \@expected, 2.5, [2, 178, 2], + "Some recent_cpu values were missing or " + . "differed from those expected " + . "by more than 2.5."); +pass; diff --git a/pintos-progos/tests/threads/mlfqs.pm b/pintos-progos/tests/threads/mlfqs.pm new file mode 100644 index 0000000..184ac16 --- /dev/null +++ b/pintos-progos/tests/threads/mlfqs.pm @@ -0,0 +1,146 @@ +# -*- perl -*- +use strict; +use warnings; + +sub mlfqs_expected_load { + my ($ready, $recent_delta) = @_; + my (@load_avg) = 0; + my (@recent_cpu) = 0; + my ($load_avg) = 0; + my ($recent_cpu) = 0; + for my $i (0...$#$ready) { + $load_avg = (59/60) * $load_avg + (1/60) * $ready->[$i]; + push (@load_avg, $load_avg); + + if (defined $recent_delta->[$i]) { + my ($twice_load) = $load_avg * 2; + my ($load_factor) = $twice_load / ($twice_load + 1); + $recent_cpu = ($recent_cpu + $recent_delta->[$i]) * $load_factor; + push (@recent_cpu, $recent_cpu); + } + } + return (\@load_avg, \@recent_cpu); +} + +sub mlfqs_expected_ticks { + my (@nice) = @_; + my ($thread_cnt) = scalar (@nice); + my (@recent_cpu) = (0) x $thread_cnt; + my (@slices) = (0) x $thread_cnt; + my (@fifo) = (0) x $thread_cnt; + my ($next_fifo) = 1; + my ($load_avg) = 0; + for my $i (1...750) { + if ($i % 25 == 0) { + # Update load average. + $load_avg = (59/60) * $load_avg + (1/60) * $thread_cnt; + + # Update recent_cpu. + my ($twice_load) = $load_avg * 2; + my ($load_factor) = $twice_load / ($twice_load + 1); + $recent_cpu[$_] = $recent_cpu[$_] * $load_factor + $nice[$_] + foreach 0...($thread_cnt - 1); + } + + # Update priorities. + my (@priority); + foreach my $j (0...($thread_cnt - 1)) { + my ($priority) = int ($recent_cpu[$j] / 4 + $nice[$j] * 2); + $priority = 0 if $priority < 0; + $priority = 63 if $priority > 63; + push (@priority, $priority); + } + + # Choose thread to run. + my $max = 0; + for my $j (1...$#priority) { + if ($priority[$j] < $priority[$max] + || ($priority[$j] == $priority[$max] + && $fifo[$j] < $fifo[$max])) { + $max = $j; + } + } + $fifo[$max] = $next_fifo++; + + # Run thread. + $recent_cpu[$max] += 4; + $slices[$max] += 4; + } + return @slices; +} + +sub check_mlfqs_fair { + my ($nice, $maxdiff) = @_; + our ($test); + my (@output) = read_text_file ("$test.output"); + common_checks ("run", @output); + @output = get_core_output ("run", @output); + + my (@actual); + local ($_); + foreach (@output) { + my ($id, $count) = /Thread (\d+) received (\d+) ticks\./ or next; + $actual[$id] = $count; + } + + my (@expected) = mlfqs_expected_ticks (@$nice); + mlfqs_compare ("thread", "%d", + \@actual, \@expected, $maxdiff, [0, $#$nice, 1], + "Some tick counts were missing or differed from those " + . "expected by more than $maxdiff."); + pass; +} + +sub mlfqs_compare { + my ($indep_var, $format, + $actual_ref, $expected_ref, $maxdiff, $t_range, $message) = @_; + my ($t_min, $t_max, $t_step) = @$t_range; + + my ($ok) = 1; + for (my ($t) = $t_min; $t <= $t_max; $t += $t_step) { + my ($actual) = $actual_ref->[$t]; + my ($expected) = $expected_ref->[$t]; + $ok = 0, last + if !defined ($actual) || abs ($actual - $expected) > $maxdiff + .01; + } + return if $ok; + + print "$message\n"; + mlfqs_row ($indep_var, "actual", "<->", "expected", "explanation"); + mlfqs_row ("------", "--------", "---", "--------", '-' x 40); + for (my ($t) = $t_min; $t <= $t_max; $t += $t_step) { + my ($actual) = $actual_ref->[$t]; + my ($expected) = $expected_ref->[$t]; + my ($diff, $rationale); + if (!defined $actual) { + $actual = 'undef' ; + $diff = ''; + $rationale = 'Missing value.'; + } else { + my ($delta) = abs ($actual - $expected); + if ($delta > $maxdiff + .01) { + my ($excess) = $delta - $maxdiff; + if ($actual > $expected) { + $diff = '>>>'; + $rationale = sprintf "Too big, by $format.", $excess; + } else { + $diff = '<<<'; + $rationale = sprintf "Too small, by $format.", $excess; + } + } else { + $diff = ' = '; + $rationale = ''; + } + $actual = sprintf ($format, $actual); + } + $expected = sprintf ($format, $expected); + mlfqs_row ($t, $actual, $diff, $expected, $rationale); + } + fail; +} + +sub mlfqs_row { + printf "%6s %8s %3s %-8s %s\n", @_; +} + +1; diff --git a/pintos-progos/tests/threads/priority-change.c b/pintos-progos/tests/threads/priority-change.c new file mode 100644 index 0000000..810b05a --- /dev/null +++ b/pintos-progos/tests/threads/priority-change.c @@ -0,0 +1,31 @@ +/* Verifies that lowering a thread's priority so that it is no + longer the highest-priority thread in the system causes it to + yield immediately. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/thread.h" + +static thread_func changing_thread; + +void +test_priority_change (void) +{ + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + msg ("Creating a high-priority thread 2."); + thread_create ("thread 2", PRI_DEFAULT + 1, changing_thread, NULL); + msg ("Thread 2 should have just lowered its priority."); + thread_set_priority (PRI_DEFAULT - 2); + msg ("Thread 2 should have just exited."); +} + +static void +changing_thread (void *aux UNUSED) +{ + msg ("Thread 2 now lowering priority."); + thread_set_priority (PRI_DEFAULT - 1); + msg ("Thread 2 exiting."); +} diff --git a/pintos-progos/tests/threads/priority-change.ck b/pintos-progos/tests/threads/priority-change.ck new file mode 100644 index 0000000..f4d9b2f --- /dev/null +++ b/pintos-progos/tests/threads/priority-change.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-change) begin +(priority-change) Creating a high-priority thread 2. +(priority-change) Thread 2 now lowering priority. +(priority-change) Thread 2 should have just lowered its priority. +(priority-change) Thread 2 exiting. +(priority-change) Thread 2 should have just exited. +(priority-change) end +EOF +pass; diff --git a/pintos-progos/tests/threads/priority-condvar.c b/pintos-progos/tests/threads/priority-condvar.c new file mode 100644 index 0000000..c1efb1b --- /dev/null +++ b/pintos-progos/tests/threads/priority-condvar.c @@ -0,0 +1,53 @@ +/* Tests that cond_signal() wakes up the highest-priority thread + waiting in cond_wait(). */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static thread_func priority_condvar_thread; +static struct lock lock; +static struct condition condition; + +void +test_priority_condvar (void) +{ + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + lock_init (&lock); + cond_init (&condition); + + thread_set_priority (PRI_MIN); + for (i = 0; i < 10; i++) + { + int priority = PRI_DEFAULT - (i + 7) % 10 - 1; + char name[16]; + snprintf (name, sizeof name, "priority %d", priority); + thread_create (name, priority, priority_condvar_thread, NULL); + } + + for (i = 0; i < 10; i++) + { + lock_acquire (&lock); + msg ("Signaling..."); + cond_signal (&condition, &lock); + lock_release (&lock); + } +} + +static void +priority_condvar_thread (void *aux UNUSED) +{ + msg ("Thread %s starting.", thread_name ()); + lock_acquire (&lock); + cond_wait (&condition, &lock); + msg ("Thread %s woke up.", thread_name ()); + lock_release (&lock); +} diff --git a/pintos-progos/tests/threads/priority-condvar.ck b/pintos-progos/tests/threads/priority-condvar.ck new file mode 100644 index 0000000..195c1ab --- /dev/null +++ b/pintos-progos/tests/threads/priority-condvar.ck @@ -0,0 +1,39 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-condvar) begin +(priority-condvar) Thread priority 23 starting. +(priority-condvar) Thread priority 22 starting. +(priority-condvar) Thread priority 21 starting. +(priority-condvar) Thread priority 30 starting. +(priority-condvar) Thread priority 29 starting. +(priority-condvar) Thread priority 28 starting. +(priority-condvar) Thread priority 27 starting. +(priority-condvar) Thread priority 26 starting. +(priority-condvar) Thread priority 25 starting. +(priority-condvar) Thread priority 24 starting. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 30 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 29 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 28 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 27 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 26 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 25 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 24 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 23 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 22 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 21 woke up. +(priority-condvar) end +EOF +pass; diff --git a/pintos-progos/tests/threads/priority-donate-chain.c b/pintos-progos/tests/threads/priority-donate-chain.c new file mode 100644 index 0000000..3ffabca --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-chain.c @@ -0,0 +1,114 @@ +/* The main thread set its priority to PRI_MIN and creates 7 threads + (thread 1..7) with priorities PRI_MIN + 3, 6, 9, 12, ... + The main thread initializes 8 locks: lock 0..7 and acquires lock 0. + + When thread[i] starts, it first acquires lock[i] (unless i == 7.) + Subsequently, thread[i] attempts to acquire lock[i-1], which is held by + thread[i-1], except for lock[0], which is held by the main thread. + Because the lock is held, thread[i] donates its priority to thread[i-1], + which donates to thread[i-2], and so on until the main thread + receives the donation. + + After threads[1..7] have been created and are blocked on locks[0..7], + the main thread releases lock[0], unblocking thread[1], and being + preempted by it. + Thread[1] then completes acquiring lock[0], then releases lock[0], + then releases lock[1], unblocking thread[2], etc. + Thread[7] finally acquires & releases lock[7] and exits, allowing + thread[6], then thread[5] etc. to run and exit until finally the + main thread exits. + + In addition, interloper threads are created at priority levels + p = PRI_MIN + 2, 5, 8, 11, ... which should not be run until the + corresponding thread with priority p + 1 has finished. + + Written by Godmar Back */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +#define NESTING_DEPTH 8 + +struct lock_pair + { + struct lock *second; + struct lock *first; + }; + +static thread_func donor_thread_func; +static thread_func interloper_thread_func; + +void +test_priority_donate_chain (void) +{ + int i; + struct lock locks[NESTING_DEPTH - 1]; + struct lock_pair lock_pairs[NESTING_DEPTH]; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + thread_set_priority (PRI_MIN); + + for (i = 0; i < NESTING_DEPTH - 1; i++) + lock_init (&locks[i]); + + lock_acquire (&locks[0]); + msg ("%s got lock.", thread_name ()); + + for (i = 1; i < NESTING_DEPTH; i++) + { + char name[16]; + int thread_priority; + + snprintf (name, sizeof name, "thread %d", i); + thread_priority = PRI_MIN + i * 3; + lock_pairs[i].first = i < NESTING_DEPTH - 1 ? locks + i: NULL; + lock_pairs[i].second = locks + i - 1; + + thread_create (name, thread_priority, donor_thread_func, lock_pairs + i); + msg ("%s should have priority %d. Actual priority: %d.", + thread_name (), thread_priority, thread_get_priority ()); + + snprintf (name, sizeof name, "interloper %d", i); + thread_create (name, thread_priority - 1, interloper_thread_func, NULL); + } + + lock_release (&locks[0]); + msg ("%s finishing with priority %d.", thread_name (), + thread_get_priority ()); +} + +static void +donor_thread_func (void *locks_) +{ + struct lock_pair *locks = locks_; + + if (locks->first) + lock_acquire (locks->first); + + lock_acquire (locks->second); + msg ("%s got lock", thread_name ()); + + lock_release (locks->second); + msg ("%s should have priority %d. Actual priority: %d", + thread_name (), (NESTING_DEPTH - 1) * 3, + thread_get_priority ()); + + if (locks->first) + lock_release (locks->first); + + msg ("%s finishing with priority %d.", thread_name (), + thread_get_priority ()); +} + +static void +interloper_thread_func (void *arg_ UNUSED) +{ + msg ("%s finished.", thread_name ()); +} + +// vim: sw=2 diff --git a/pintos-progos/tests/threads/priority-donate-chain.ck b/pintos-progos/tests/threads/priority-donate-chain.ck new file mode 100644 index 0000000..213e649 --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-chain.ck @@ -0,0 +1,46 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-chain) begin +(priority-donate-chain) main got lock. +(priority-donate-chain) main should have priority 3. Actual priority: 3. +(priority-donate-chain) main should have priority 6. Actual priority: 6. +(priority-donate-chain) main should have priority 9. Actual priority: 9. +(priority-donate-chain) main should have priority 12. Actual priority: 12. +(priority-donate-chain) main should have priority 15. Actual priority: 15. +(priority-donate-chain) main should have priority 18. Actual priority: 18. +(priority-donate-chain) main should have priority 21. Actual priority: 21. +(priority-donate-chain) thread 1 got lock +(priority-donate-chain) thread 1 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 2 got lock +(priority-donate-chain) thread 2 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 3 got lock +(priority-donate-chain) thread 3 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 4 got lock +(priority-donate-chain) thread 4 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 5 got lock +(priority-donate-chain) thread 5 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 6 got lock +(priority-donate-chain) thread 6 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 7 got lock +(priority-donate-chain) thread 7 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 7 finishing with priority 21. +(priority-donate-chain) interloper 7 finished. +(priority-donate-chain) thread 6 finishing with priority 18. +(priority-donate-chain) interloper 6 finished. +(priority-donate-chain) thread 5 finishing with priority 15. +(priority-donate-chain) interloper 5 finished. +(priority-donate-chain) thread 4 finishing with priority 12. +(priority-donate-chain) interloper 4 finished. +(priority-donate-chain) thread 3 finishing with priority 9. +(priority-donate-chain) interloper 3 finished. +(priority-donate-chain) thread 2 finishing with priority 6. +(priority-donate-chain) interloper 2 finished. +(priority-donate-chain) thread 1 finishing with priority 3. +(priority-donate-chain) interloper 1 finished. +(priority-donate-chain) main finishing with priority 0. +(priority-donate-chain) end +EOF +pass; diff --git a/pintos-progos/tests/threads/priority-donate-lower.c b/pintos-progos/tests/threads/priority-donate-lower.c new file mode 100644 index 0000000..4965d75 --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-lower.c @@ -0,0 +1,51 @@ +/* The main thread acquires a lock. Then it creates a + higher-priority thread that blocks acquiring the lock, causing + it to donate their priorities to the main thread. The main + thread attempts to lower its priority, which should not take + effect until the donation is released. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func acquire_thread_func; + +void +test_priority_donate_lower (void) +{ + struct lock lock; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&lock); + lock_acquire (&lock); + thread_create ("acquire", PRI_DEFAULT + 10, acquire_thread_func, &lock); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 10, thread_get_priority ()); + + msg ("Lowering base priority..."); + thread_set_priority (PRI_DEFAULT - 10); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 10, thread_get_priority ()); + lock_release (&lock); + msg ("acquire must already have finished."); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT - 10, thread_get_priority ()); +} + +static void +acquire_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("acquire: got the lock"); + lock_release (lock); + msg ("acquire: done"); +} diff --git a/pintos-progos/tests/threads/priority-donate-lower.ck b/pintos-progos/tests/threads/priority-donate-lower.ck new file mode 100644 index 0000000..c9bb61b --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-lower.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-lower) begin +(priority-donate-lower) Main thread should have priority 41. Actual priority: 41. +(priority-donate-lower) Lowering base priority... +(priority-donate-lower) Main thread should have priority 41. Actual priority: 41. +(priority-donate-lower) acquire: got the lock +(priority-donate-lower) acquire: done +(priority-donate-lower) acquire must already have finished. +(priority-donate-lower) Main thread should have priority 21. Actual priority: 21. +(priority-donate-lower) end +EOF +pass; diff --git a/pintos-progos/tests/threads/priority-donate-multiple.c b/pintos-progos/tests/threads/priority-donate-multiple.c new file mode 100644 index 0000000..df4689c --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-multiple.c @@ -0,0 +1,77 @@ +/* The main thread acquires locks A and B, then it creates two + higher-priority threads. Each of these threads blocks + acquiring one of the locks and thus donate their priority to + the main thread. The main thread releases the locks in turn + and relinquishes its donated priorities. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by Matt Franklin , + Greg Hutchins , Yu Ping Hu + . Modified by arens. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func a_thread_func; +static thread_func b_thread_func; + +void +test_priority_donate_multiple (void) +{ + struct lock a, b; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&a); + lock_init (&b); + + lock_acquire (&a); + lock_acquire (&b); + + thread_create ("a", PRI_DEFAULT + 1, a_thread_func, &a); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 1, thread_get_priority ()); + + thread_create ("b", PRI_DEFAULT + 2, b_thread_func, &b); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 2, thread_get_priority ()); + + lock_release (&b); + msg ("Thread b should have just finished."); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 1, thread_get_priority ()); + + lock_release (&a); + msg ("Thread a should have just finished."); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT, thread_get_priority ()); +} + +static void +a_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("Thread a acquired lock a."); + lock_release (lock); + msg ("Thread a finished."); +} + +static void +b_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("Thread b acquired lock b."); + lock_release (lock); + msg ("Thread b finished."); +} diff --git a/pintos-progos/tests/threads/priority-donate-multiple.ck b/pintos-progos/tests/threads/priority-donate-multiple.ck new file mode 100644 index 0000000..0afd20b --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-multiple.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-multiple) begin +(priority-donate-multiple) Main thread should have priority 32. Actual priority: 32. +(priority-donate-multiple) Main thread should have priority 33. Actual priority: 33. +(priority-donate-multiple) Thread b acquired lock b. +(priority-donate-multiple) Thread b finished. +(priority-donate-multiple) Thread b should have just finished. +(priority-donate-multiple) Main thread should have priority 32. Actual priority: 32. +(priority-donate-multiple) Thread a acquired lock a. +(priority-donate-multiple) Thread a finished. +(priority-donate-multiple) Thread a should have just finished. +(priority-donate-multiple) Main thread should have priority 31. Actual priority: 31. +(priority-donate-multiple) end +EOF +pass; diff --git a/pintos-progos/tests/threads/priority-donate-multiple2.c b/pintos-progos/tests/threads/priority-donate-multiple2.c new file mode 100644 index 0000000..7f65fef --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-multiple2.c @@ -0,0 +1,90 @@ +/* The main thread acquires locks A and B, then it creates three + higher-priority threads. The first two of these threads block + acquiring one of the locks and thus donate their priority to + the main thread. The main thread releases the locks in turn + and relinquishes its donated priorities, allowing the third thread + to run. + + In this test, the main thread releases the locks in a different + order compared to priority-donate-multiple.c. + + Written by Godmar Back . + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by Matt Franklin , + Greg Hutchins , Yu Ping Hu + . Modified by arens. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func a_thread_func; +static thread_func b_thread_func; +static thread_func c_thread_func; + +void +test_priority_donate_multiple2 (void) +{ + struct lock a, b; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&a); + lock_init (&b); + + lock_acquire (&a); + lock_acquire (&b); + + thread_create ("a", PRI_DEFAULT + 3, a_thread_func, &a); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 3, thread_get_priority ()); + + thread_create ("c", PRI_DEFAULT + 1, c_thread_func, NULL); + + thread_create ("b", PRI_DEFAULT + 5, b_thread_func, &b); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 5, thread_get_priority ()); + + lock_release (&a); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 5, thread_get_priority ()); + + lock_release (&b); + msg ("Threads b, a, c should have just finished, in that order."); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT, thread_get_priority ()); +} + +static void +a_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("Thread a acquired lock a."); + lock_release (lock); + msg ("Thread a finished."); +} + +static void +b_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("Thread b acquired lock b."); + lock_release (lock); + msg ("Thread b finished."); +} + +static void +c_thread_func (void *a_ UNUSED) +{ + msg ("Thread c finished."); +} diff --git a/pintos-progos/tests/threads/priority-donate-multiple2.ck b/pintos-progos/tests/threads/priority-donate-multiple2.ck new file mode 100644 index 0000000..b23533a --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-multiple2.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-multiple2) begin +(priority-donate-multiple2) Main thread should have priority 34. Actual priority: 34. +(priority-donate-multiple2) Main thread should have priority 36. Actual priority: 36. +(priority-donate-multiple2) Main thread should have priority 36. Actual priority: 36. +(priority-donate-multiple2) Thread b acquired lock b. +(priority-donate-multiple2) Thread b finished. +(priority-donate-multiple2) Thread a acquired lock a. +(priority-donate-multiple2) Thread a finished. +(priority-donate-multiple2) Thread c finished. +(priority-donate-multiple2) Threads b, a, c should have just finished, in that order. +(priority-donate-multiple2) Main thread should have priority 31. Actual priority: 31. +(priority-donate-multiple2) end +EOF +pass; diff --git a/pintos-progos/tests/threads/priority-donate-nest.c b/pintos-progos/tests/threads/priority-donate-nest.c new file mode 100644 index 0000000..3a3a9a5 --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-nest.c @@ -0,0 +1,94 @@ +/* Low-priority main thread L acquires lock A. Medium-priority + thread M then acquires lock B then blocks on acquiring lock A. + High-priority thread H then blocks on acquiring lock B. Thus, + thread H donates its priority to M, which in turn donates it + to thread L. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by Matt Franklin , + Greg Hutchins , Yu Ping Hu + . Modified by arens. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +struct locks + { + struct lock *a; + struct lock *b; + }; + +static thread_func medium_thread_func; +static thread_func high_thread_func; + +void +test_priority_donate_nest (void) +{ + struct lock a, b; + struct locks locks; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&a); + lock_init (&b); + + lock_acquire (&a); + + locks.a = &a; + locks.b = &b; + thread_create ("medium", PRI_DEFAULT + 1, medium_thread_func, &locks); + thread_yield (); + msg ("Low thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 1, thread_get_priority ()); + + thread_create ("high", PRI_DEFAULT + 2, high_thread_func, &b); + thread_yield (); + msg ("Low thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 2, thread_get_priority ()); + + lock_release (&a); + thread_yield (); + msg ("Medium thread should just have finished."); + msg ("Low thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT, thread_get_priority ()); +} + +static void +medium_thread_func (void *locks_) +{ + struct locks *locks = locks_; + + lock_acquire (locks->b); + lock_acquire (locks->a); + + msg ("Medium thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 2, thread_get_priority ()); + msg ("Medium thread got the lock."); + + lock_release (locks->a); + thread_yield (); + + lock_release (locks->b); + thread_yield (); + + msg ("High thread should have just finished."); + msg ("Middle thread finished."); +} + +static void +high_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("High thread got the lock."); + lock_release (lock); + msg ("High thread finished."); +} diff --git a/pintos-progos/tests/threads/priority-donate-nest.ck b/pintos-progos/tests/threads/priority-donate-nest.ck new file mode 100644 index 0000000..923460e --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-nest.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-nest) begin +(priority-donate-nest) Low thread should have priority 32. Actual priority: 32. +(priority-donate-nest) Low thread should have priority 33. Actual priority: 33. +(priority-donate-nest) Medium thread should have priority 33. Actual priority: 33. +(priority-donate-nest) Medium thread got the lock. +(priority-donate-nest) High thread got the lock. +(priority-donate-nest) High thread finished. +(priority-donate-nest) High thread should have just finished. +(priority-donate-nest) Middle thread finished. +(priority-donate-nest) Medium thread should just have finished. +(priority-donate-nest) Low thread should have priority 31. Actual priority: 31. +(priority-donate-nest) end +EOF +pass; diff --git a/pintos-progos/tests/threads/priority-donate-one.c b/pintos-progos/tests/threads/priority-donate-one.c new file mode 100644 index 0000000..3189f3a --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-one.c @@ -0,0 +1,65 @@ +/* The main thread acquires a lock. Then it creates two + higher-priority threads that block acquiring the lock, causing + them to donate their priorities to the main thread. When the + main thread releases the lock, the other threads should + acquire it in priority order. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by Matt Franklin , + Greg Hutchins , Yu Ping Hu + . Modified by arens. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func acquire1_thread_func; +static thread_func acquire2_thread_func; + +void +test_priority_donate_one (void) +{ + struct lock lock; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&lock); + lock_acquire (&lock); + thread_create ("acquire1", PRI_DEFAULT + 1, acquire1_thread_func, &lock); + msg ("This thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 1, thread_get_priority ()); + thread_create ("acquire2", PRI_DEFAULT + 2, acquire2_thread_func, &lock); + msg ("This thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 2, thread_get_priority ()); + lock_release (&lock); + msg ("acquire2, acquire1 must already have finished, in that order."); + msg ("This should be the last line before finishing this test."); +} + +static void +acquire1_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("acquire1: got the lock"); + lock_release (lock); + msg ("acquire1: done"); +} + +static void +acquire2_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("acquire2: got the lock"); + lock_release (lock); + msg ("acquire2: done"); +} diff --git a/pintos-progos/tests/threads/priority-donate-one.ck b/pintos-progos/tests/threads/priority-donate-one.ck new file mode 100644 index 0000000..b7c8e6f --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-one.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-one) begin +(priority-donate-one) This thread should have priority 32. Actual priority: 32. +(priority-donate-one) This thread should have priority 33. Actual priority: 33. +(priority-donate-one) acquire2: got the lock +(priority-donate-one) acquire2: done +(priority-donate-one) acquire1: got the lock +(priority-donate-one) acquire1: done +(priority-donate-one) acquire2, acquire1 must already have finished, in that order. +(priority-donate-one) This should be the last line before finishing this test. +(priority-donate-one) end +EOF +pass; diff --git a/pintos-progos/tests/threads/priority-donate-sema.c b/pintos-progos/tests/threads/priority-donate-sema.c new file mode 100644 index 0000000..b33cb72 --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-sema.c @@ -0,0 +1,82 @@ +/* Low priority thread L acquires a lock, then blocks downing a + semaphore. Medium priority thread M then blocks waiting on + the same semaphore. Next, high priority thread H attempts to + acquire the lock, donating its priority to L. + + Next, the main thread ups the semaphore, waking up L. L + releases the lock, which wakes up H. H "up"s the semaphore, + waking up M. H terminates, then M, then L, and finally the + main thread. + + Written by Godmar Back . */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +struct lock_and_sema + { + struct lock lock; + struct semaphore sema; + }; + +static thread_func l_thread_func; +static thread_func m_thread_func; +static thread_func h_thread_func; + +void +test_priority_donate_sema (void) +{ + struct lock_and_sema ls; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&ls.lock); + sema_init (&ls.sema, 0); + thread_create ("low", PRI_DEFAULT + 1, l_thread_func, &ls); + thread_create ("med", PRI_DEFAULT + 3, m_thread_func, &ls); + thread_create ("high", PRI_DEFAULT + 5, h_thread_func, &ls); + sema_up (&ls.sema); + msg ("Main thread finished."); +} + +static void +l_thread_func (void *ls_) +{ + struct lock_and_sema *ls = ls_; + + lock_acquire (&ls->lock); + msg ("Thread L acquired lock."); + sema_down (&ls->sema); + msg ("Thread L downed semaphore."); + lock_release (&ls->lock); + msg ("Thread L finished."); +} + +static void +m_thread_func (void *ls_) +{ + struct lock_and_sema *ls = ls_; + + sema_down (&ls->sema); + msg ("Thread M finished."); +} + +static void +h_thread_func (void *ls_) +{ + struct lock_and_sema *ls = ls_; + + lock_acquire (&ls->lock); + msg ("Thread H acquired lock."); + + sema_up (&ls->sema); + lock_release (&ls->lock); + msg ("Thread H finished."); +} diff --git a/pintos-progos/tests/threads/priority-donate-sema.ck b/pintos-progos/tests/threads/priority-donate-sema.ck new file mode 100644 index 0000000..92b8d07 --- /dev/null +++ b/pintos-progos/tests/threads/priority-donate-sema.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-sema) begin +(priority-donate-sema) Thread L acquired lock. +(priority-donate-sema) Thread L downed semaphore. +(priority-donate-sema) Thread H acquired lock. +(priority-donate-sema) Thread H finished. +(priority-donate-sema) Thread M finished. +(priority-donate-sema) Thread L finished. +(priority-donate-sema) Main thread finished. +(priority-donate-sema) end +EOF +pass; diff --git a/pintos-progos/tests/threads/priority-fifo.c b/pintos-progos/tests/threads/priority-fifo.c new file mode 100644 index 0000000..3af98a3 --- /dev/null +++ b/pintos-progos/tests/threads/priority-fifo.c @@ -0,0 +1,99 @@ +/* Creates several threads all at the same priority and ensures + that they consistently run in the same round-robin order. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by by Matt Franklin + , Greg Hutchins + , Yu Ping Hu . + Modified by arens. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "devices/timer.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" + +struct simple_thread_data + { + int id; /* Sleeper ID. */ + int iterations; /* Iterations so far. */ + struct lock *lock; /* Lock on output. */ + int **op; /* Output buffer position. */ + }; + +#define THREAD_CNT 16 +#define ITER_CNT 16 + +static thread_func simple_thread_func; + +void +test_priority_fifo (void) +{ + struct simple_thread_data data[THREAD_CNT]; + struct lock lock; + int *output, *op; + int i, cnt; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + msg ("%d threads will iterate %d times in the same order each time.", + THREAD_CNT, ITER_CNT); + msg ("If the order varies then there is a bug."); + + output = op = malloc (sizeof *output * THREAD_CNT * ITER_CNT * 2); + ASSERT (output != NULL); + lock_init (&lock); + + thread_set_priority (PRI_DEFAULT + 2); + for (i = 0; i < THREAD_CNT; i++) + { + char name[16]; + struct simple_thread_data *d = data + i; + snprintf (name, sizeof name, "%d", i); + d->id = i; + d->iterations = 0; + d->lock = &lock; + d->op = &op; + thread_create (name, PRI_DEFAULT + 1, simple_thread_func, d); + } + + thread_set_priority (PRI_DEFAULT); + /* All the other threads now run to termination here. */ + ASSERT (lock.holder == NULL); + + cnt = 0; + for (; output < op; output++) + { + struct simple_thread_data *d; + + ASSERT (*output >= 0 && *output < THREAD_CNT); + d = data + *output; + if (cnt % THREAD_CNT == 0) + printf ("(priority-fifo) iteration:"); + printf (" %d", d->id); + if (++cnt % THREAD_CNT == 0) + printf ("\n"); + d->iterations++; + } +} + +static void +simple_thread_func (void *data_) +{ + struct simple_thread_data *data = data_; + int i; + + for (i = 0; i < ITER_CNT; i++) + { + lock_acquire (data->lock); + *(*data->op)++ = data->id; + lock_release (data->lock); + thread_yield (); + } +} diff --git a/pintos-progos/tests/threads/priority-fifo.ck b/pintos-progos/tests/threads/priority-fifo.ck new file mode 100644 index 0000000..11f1dd3 --- /dev/null +++ b/pintos-progos/tests/threads/priority-fifo.ck @@ -0,0 +1,63 @@ +# -*- perl -*- + +# The expected output looks like this: +# +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# +# A different permutation of 0...15 is acceptable, but every line must +# be in the same order. + +use strict; +use warnings; +use tests::tests; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks ("run", @output); + +my ($thread_cnt) = 16; +my ($iter_cnt) = 16; +my (@order); +my (@t) = (-1) x $thread_cnt; + +my (@iterations) = grep (/iteration:/, @output); +fail "No iterations found in output.\n" if !@iterations; + +my (@numbering) = $iterations[0] =~ /(\d+)/g; +fail "First iteration does not list exactly $thread_cnt threads.\n" + if @numbering != $thread_cnt; + +my (@sorted_numbering) = sort { $a <=> $b } @numbering; +for my $i (0...$#sorted_numbering) { + if ($sorted_numbering[$i] != $i) { + fail "First iteration does not list all threads " + . "0...$#sorted_numbering\n"; + } +} + +for my $i (1...$#iterations) { + if ($iterations[$i] ne $iterations[0]) { + fail "Iteration $i differs from iteration 0\n"; + } +} + +fail "$iter_cnt iterations expected but " . scalar (@iterations) . " found\n" + if $iter_cnt != @iterations; + +pass; diff --git a/pintos-progos/tests/threads/priority-preempt.c b/pintos-progos/tests/threads/priority-preempt.c new file mode 100644 index 0000000..3c3aacb --- /dev/null +++ b/pintos-progos/tests/threads/priority-preempt.c @@ -0,0 +1,41 @@ +/* Ensures that a high-priority thread really preempts. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by by Matt Franklin + , Greg Hutchins + , Yu Ping Hu . + Modified by arens. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func simple_thread_func; + +void +test_priority_preempt (void) +{ + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + thread_create ("high-priority", PRI_DEFAULT + 1, simple_thread_func, NULL); + msg ("The high-priority thread should have already completed."); +} + +static void +simple_thread_func (void *aux UNUSED) +{ + int i; + + for (i = 0; i < 5; i++) + { + msg ("Thread %s iteration %d", thread_name (), i); + thread_yield (); + } + msg ("Thread %s done!", thread_name ()); +} diff --git a/pintos-progos/tests/threads/priority-preempt.ck b/pintos-progos/tests/threads/priority-preempt.ck new file mode 100644 index 0000000..43a26ee --- /dev/null +++ b/pintos-progos/tests/threads/priority-preempt.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-preempt) begin +(priority-preempt) Thread high-priority iteration 0 +(priority-preempt) Thread high-priority iteration 1 +(priority-preempt) Thread high-priority iteration 2 +(priority-preempt) Thread high-priority iteration 3 +(priority-preempt) Thread high-priority iteration 4 +(priority-preempt) Thread high-priority done! +(priority-preempt) The high-priority thread should have already completed. +(priority-preempt) end +EOF +pass; diff --git a/pintos-progos/tests/threads/priority-sema.c b/pintos-progos/tests/threads/priority-sema.c new file mode 100644 index 0000000..2834a88 --- /dev/null +++ b/pintos-progos/tests/threads/priority-sema.c @@ -0,0 +1,45 @@ +/* Tests that the highest-priority thread waiting on a semaphore + is the first to wake up. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static thread_func priority_sema_thread; +static struct semaphore sema; + +void +test_priority_sema (void) +{ + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + sema_init (&sema, 0); + thread_set_priority (PRI_MIN); + for (i = 0; i < 10; i++) + { + int priority = PRI_DEFAULT - (i + 3) % 10 - 1; + char name[16]; + snprintf (name, sizeof name, "priority %d", priority); + thread_create (name, priority, priority_sema_thread, NULL); + } + + for (i = 0; i < 10; i++) + { + sema_up (&sema); + msg ("Back in main thread."); + } +} + +static void +priority_sema_thread (void *aux UNUSED) +{ + sema_down (&sema); + msg ("Thread %s woke up.", thread_name ()); +} diff --git a/pintos-progos/tests/threads/priority-sema.ck b/pintos-progos/tests/threads/priority-sema.ck new file mode 100644 index 0000000..559988d --- /dev/null +++ b/pintos-progos/tests/threads/priority-sema.ck @@ -0,0 +1,29 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-sema) begin +(priority-sema) Thread priority 30 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 29 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 28 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 27 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 26 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 25 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 24 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 23 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 22 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 21 woke up. +(priority-sema) Back in main thread. +(priority-sema) end +EOF +pass; diff --git a/pintos-progos/tests/threads/tests.c b/pintos-progos/tests/threads/tests.c new file mode 100644 index 0000000..af15aee --- /dev/null +++ b/pintos-progos/tests/threads/tests.c @@ -0,0 +1,102 @@ +#include "tests/threads/tests.h" +#include +#include +#include + +struct test + { + const char *name; + test_func *function; + }; + +static const struct test tests[] = + { + {"alarm-single", test_alarm_single}, + {"alarm-multiple", test_alarm_multiple}, + {"alarm-simultaneous", test_alarm_simultaneous}, + {"alarm-priority", test_alarm_priority}, + {"alarm-zero", test_alarm_zero}, + {"alarm-negative", test_alarm_negative}, + {"priority-change", test_priority_change}, + {"priority-donate-one", test_priority_donate_one}, + {"priority-donate-multiple", test_priority_donate_multiple}, + {"priority-donate-multiple2", test_priority_donate_multiple2}, + {"priority-donate-nest", test_priority_donate_nest}, + {"priority-donate-sema", test_priority_donate_sema}, + {"priority-donate-lower", test_priority_donate_lower}, + {"priority-donate-chain", test_priority_donate_chain}, + {"priority-fifo", test_priority_fifo}, + {"priority-preempt", test_priority_preempt}, + {"priority-sema", test_priority_sema}, + {"priority-condvar", test_priority_condvar}, + {"mlfqs-load-1", test_mlfqs_load_1}, + {"mlfqs-load-60", test_mlfqs_load_60}, + {"mlfqs-load-avg", test_mlfqs_load_avg}, + {"mlfqs-recent-1", test_mlfqs_recent_1}, + {"mlfqs-fair-2", test_mlfqs_fair_2}, + {"mlfqs-fair-20", test_mlfqs_fair_20}, + {"mlfqs-nice-2", test_mlfqs_nice_2}, + {"mlfqs-nice-10", test_mlfqs_nice_10}, + {"mlfqs-block", test_mlfqs_block}, + }; + +static const char *test_name; + +/* Runs the test named NAME. */ +void +run_test (const char *name) +{ + const struct test *t; + + for (t = tests; t < tests + sizeof tests / sizeof *tests; t++) + if (!strcmp (name, t->name)) + { + test_name = name; + msg ("begin"); + t->function (); + msg ("end"); + return; + } + PANIC ("no test named \"%s\"", name); +} + +/* Prints FORMAT as if with printf(), + prefixing the output by the name of the test + and following it with a new-line character. */ +void +msg (const char *format, ...) +{ + va_list args; + + printf ("(%s) ", test_name); + va_start (args, format); + vprintf (format, args); + va_end (args); + putchar ('\n'); +} + +/* Prints failure message FORMAT as if with printf(), + prefixing the output by the name of the test and FAIL: + and following it with a new-line character, + and then panics the kernel. */ +void +fail (const char *format, ...) +{ + va_list args; + + printf ("(%s) FAIL: ", test_name); + va_start (args, format); + vprintf (format, args); + va_end (args); + putchar ('\n'); + + PANIC ("test failed"); +} + +/* Prints a message indicating the current test passed. */ +void +pass (void) +{ + printf ("(%s) PASS\n", test_name); +} + diff --git a/pintos-progos/tests/threads/tests.h b/pintos-progos/tests/threads/tests.h new file mode 100644 index 0000000..cd9d489 --- /dev/null +++ b/pintos-progos/tests/threads/tests.h @@ -0,0 +1,41 @@ +#ifndef TESTS_THREADS_TESTS_H +#define TESTS_THREADS_TESTS_H + +void run_test (const char *); + +typedef void test_func (void); + +extern test_func test_alarm_single; +extern test_func test_alarm_multiple; +extern test_func test_alarm_simultaneous; +extern test_func test_alarm_priority; +extern test_func test_alarm_zero; +extern test_func test_alarm_negative; +extern test_func test_priority_change; +extern test_func test_priority_donate_one; +extern test_func test_priority_donate_multiple; +extern test_func test_priority_donate_multiple2; +extern test_func test_priority_donate_sema; +extern test_func test_priority_donate_nest; +extern test_func test_priority_donate_lower; +extern test_func test_priority_donate_chain; +extern test_func test_priority_fifo; +extern test_func test_priority_preempt; +extern test_func test_priority_sema; +extern test_func test_priority_condvar; +extern test_func test_mlfqs_load_1; +extern test_func test_mlfqs_load_60; +extern test_func test_mlfqs_load_avg; +extern test_func test_mlfqs_recent_1; +extern test_func test_mlfqs_fair_2; +extern test_func test_mlfqs_fair_20; +extern test_func test_mlfqs_nice_2; +extern test_func test_mlfqs_nice_10; +extern test_func test_mlfqs_block; + +void msg (const char *, ...); +void fail (const char *, ...); +void pass (void); + +#endif /* tests/threads/tests.h */ + diff --git a/pintos-progos/tests/userprog/Make.tests b/pintos-progos/tests/userprog/Make.tests new file mode 100644 index 0000000..88b0737 --- /dev/null +++ b/pintos-progos/tests/userprog/Make.tests @@ -0,0 +1,133 @@ +# -*- makefile -*- + +tests/%.output: FILESYSSOURCE = --filesys-size=2 +tests/%.output: PUTFILES = $(filter-out kernel.bin loader.bin, $^) + +tests/userprog_TESTS = $(addprefix tests/userprog/,args-none \ +args-single args-multiple args-many args-dbl-space sc-bad-sp \ +sc-bad-arg sc-boundary sc-boundary-2 halt exit create-normal \ +create-empty create-null create-bad-ptr create-long create-exists \ +create-bound open-normal open-missing open-boundary open-empty \ +open-null open-bad-ptr open-twice close-normal close-twice close-stdin \ +close-stdout close-bad-fd read-normal read-bad-ptr read-boundary \ +read-zero read-stdout read-bad-fd write-normal write-bad-ptr \ +write-boundary write-zero write-stdin write-bad-fd exec-once exec-arg \ +exec-multiple exec-missing exec-bad-ptr wait-simple wait-twice \ +wait-killed wait-bad-pid multi-recurse multi-child-fd rox-simple \ +rox-child rox-multichild bad-read bad-write bad-read2 bad-write2 \ +bad-jump bad-jump2) + +tests/userprog_PROGS = $(tests/userprog_TESTS) $(addprefix \ +tests/userprog/,child-simple child-args child-bad child-close child-rox) + +tests/userprog/args-none_SRC = tests/userprog/args.c +tests/userprog/args-single_SRC = tests/userprog/args.c +tests/userprog/args-multiple_SRC = tests/userprog/args.c +tests/userprog/args-many_SRC = tests/userprog/args.c +tests/userprog/args-dbl-space_SRC = tests/userprog/args.c +tests/userprog/sc-bad-sp_SRC = tests/userprog/sc-bad-sp.c tests/main.c +tests/userprog/sc-bad-arg_SRC = tests/userprog/sc-bad-arg.c tests/main.c +tests/userprog/bad-read_SRC = tests/userprog/bad-read.c tests/main.c +tests/userprog/bad-write_SRC = tests/userprog/bad-write.c tests/main.c +tests/userprog/bad-jump_SRC = tests/userprog/bad-jump.c tests/main.c +tests/userprog/bad-read2_SRC = tests/userprog/bad-read2.c tests/main.c +tests/userprog/bad-write2_SRC = tests/userprog/bad-write2.c tests/main.c +tests/userprog/bad-jump2_SRC = tests/userprog/bad-jump2.c tests/main.c +tests/userprog/sc-boundary_SRC = tests/userprog/sc-boundary.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/sc-boundary-2_SRC = tests/userprog/sc-boundary-2.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/halt_SRC = tests/userprog/halt.c tests/main.c +tests/userprog/exit_SRC = tests/userprog/exit.c tests/main.c +tests/userprog/create-normal_SRC = tests/userprog/create-normal.c tests/main.c +tests/userprog/create-empty_SRC = tests/userprog/create-empty.c tests/main.c +tests/userprog/create-null_SRC = tests/userprog/create-null.c tests/main.c +tests/userprog/create-bad-ptr_SRC = tests/userprog/create-bad-ptr.c \ +tests/main.c +tests/userprog/create-long_SRC = tests/userprog/create-long.c tests/main.c +tests/userprog/create-exists_SRC = tests/userprog/create-exists.c tests/main.c +tests/userprog/create-bound_SRC = tests/userprog/create-bound.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/open-normal_SRC = tests/userprog/open-normal.c tests/main.c +tests/userprog/open-missing_SRC = tests/userprog/open-missing.c tests/main.c +tests/userprog/open-boundary_SRC = tests/userprog/open-boundary.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/open-empty_SRC = tests/userprog/open-empty.c tests/main.c +tests/userprog/open-null_SRC = tests/userprog/open-null.c tests/main.c +tests/userprog/open-bad-ptr_SRC = tests/userprog/open-bad-ptr.c tests/main.c +tests/userprog/open-twice_SRC = tests/userprog/open-twice.c tests/main.c +tests/userprog/close-normal_SRC = tests/userprog/close-normal.c tests/main.c +tests/userprog/close-twice_SRC = tests/userprog/close-twice.c tests/main.c +tests/userprog/close-stdin_SRC = tests/userprog/close-stdin.c tests/main.c +tests/userprog/close-stdout_SRC = tests/userprog/close-stdout.c tests/main.c +tests/userprog/close-bad-fd_SRC = tests/userprog/close-bad-fd.c tests/main.c +tests/userprog/read-normal_SRC = tests/userprog/read-normal.c tests/main.c +tests/userprog/read-bad-ptr_SRC = tests/userprog/read-bad-ptr.c tests/main.c +tests/userprog/read-boundary_SRC = tests/userprog/read-boundary.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/read-zero_SRC = tests/userprog/read-zero.c tests/main.c +tests/userprog/read-stdout_SRC = tests/userprog/read-stdout.c tests/main.c +tests/userprog/read-bad-fd_SRC = tests/userprog/read-bad-fd.c tests/main.c +tests/userprog/write-normal_SRC = tests/userprog/write-normal.c tests/main.c +tests/userprog/write-bad-ptr_SRC = tests/userprog/write-bad-ptr.c tests/main.c +tests/userprog/write-boundary_SRC = tests/userprog/write-boundary.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/write-zero_SRC = tests/userprog/write-zero.c tests/main.c +tests/userprog/write-stdin_SRC = tests/userprog/write-stdin.c tests/main.c +tests/userprog/write-bad-fd_SRC = tests/userprog/write-bad-fd.c tests/main.c +tests/userprog/exec-once_SRC = tests/userprog/exec-once.c tests/main.c +tests/userprog/exec-arg_SRC = tests/userprog/exec-arg.c tests/main.c +tests/userprog/exec-multiple_SRC = tests/userprog/exec-multiple.c tests/main.c +tests/userprog/exec-missing_SRC = tests/userprog/exec-missing.c tests/main.c +tests/userprog/exec-bad-ptr_SRC = tests/userprog/exec-bad-ptr.c tests/main.c +tests/userprog/wait-simple_SRC = tests/userprog/wait-simple.c tests/main.c +tests/userprog/wait-twice_SRC = tests/userprog/wait-twice.c tests/main.c +tests/userprog/wait-killed_SRC = tests/userprog/wait-killed.c tests/main.c +tests/userprog/wait-bad-pid_SRC = tests/userprog/wait-bad-pid.c tests/main.c +tests/userprog/multi-recurse_SRC = tests/userprog/multi-recurse.c +tests/userprog/multi-child-fd_SRC = tests/userprog/multi-child-fd.c \ +tests/main.c +tests/userprog/rox-simple_SRC = tests/userprog/rox-simple.c tests/main.c +tests/userprog/rox-child_SRC = tests/userprog/rox-child.c tests/main.c +tests/userprog/rox-multichild_SRC = tests/userprog/rox-multichild.c \ +tests/main.c + +tests/userprog/child-simple_SRC = tests/userprog/child-simple.c +tests/userprog/child-args_SRC = tests/userprog/args.c +tests/userprog/child-bad_SRC = tests/userprog/child-bad.c tests/main.c +tests/userprog/child-close_SRC = tests/userprog/child-close.c +tests/userprog/child-rox_SRC = tests/userprog/child-rox.c + +$(foreach prog,$(tests/userprog_PROGS),$(eval $(prog)_SRC += tests/lib.c)) + +tests/userprog/args-single_ARGS = onearg +tests/userprog/args-multiple_ARGS = some arguments for you! +tests/userprog/args-many_ARGS = a b c d e f g h i j k l m n o p q r s t u v +tests/userprog/args-dbl-space_ARGS = two spaces! +tests/userprog/multi-recurse_ARGS = 15 + +tests/userprog/open-normal_PUTFILES += tests/userprog/sample.txt +tests/userprog/open-boundary_PUTFILES += tests/userprog/sample.txt +tests/userprog/open-twice_PUTFILES += tests/userprog/sample.txt +tests/userprog/close-normal_PUTFILES += tests/userprog/sample.txt +tests/userprog/close-twice_PUTFILES += tests/userprog/sample.txt +tests/userprog/read-normal_PUTFILES += tests/userprog/sample.txt +tests/userprog/read-bad-ptr_PUTFILES += tests/userprog/sample.txt +tests/userprog/read-boundary_PUTFILES += tests/userprog/sample.txt +tests/userprog/read-zero_PUTFILES += tests/userprog/sample.txt +tests/userprog/write-normal_PUTFILES += tests/userprog/sample.txt +tests/userprog/write-bad-ptr_PUTFILES += tests/userprog/sample.txt +tests/userprog/write-boundary_PUTFILES += tests/userprog/sample.txt +tests/userprog/write-zero_PUTFILES += tests/userprog/sample.txt +tests/userprog/multi-child-fd_PUTFILES += tests/userprog/sample.txt + +tests/userprog/exec-once_PUTFILES += tests/userprog/child-simple +tests/userprog/exec-multiple_PUTFILES += tests/userprog/child-simple +tests/userprog/wait-simple_PUTFILES += tests/userprog/child-simple +tests/userprog/wait-twice_PUTFILES += tests/userprog/child-simple + +tests/userprog/exec-arg_PUTFILES += tests/userprog/child-args +tests/userprog/multi-child-fd_PUTFILES += tests/userprog/child-close +tests/userprog/wait-killed_PUTFILES += tests/userprog/child-bad +tests/userprog/rox-child_PUTFILES += tests/userprog/child-rox +tests/userprog/rox-multichild_PUTFILES += tests/userprog/child-rox diff --git a/pintos-progos/tests/userprog/Rubric.functionality b/pintos-progos/tests/userprog/Rubric.functionality new file mode 100644 index 0000000..ea76c44 --- /dev/null +++ b/pintos-progos/tests/userprog/Rubric.functionality @@ -0,0 +1,52 @@ +Functionality of system calls: +- Test argument passing on Pintos command line. +3 args-none +3 args-single +3 args-multiple +3 args-many +3 args-dbl-space + +- Test "create" system call. +3 create-empty +3 create-long +3 create-normal +3 create-exists + +- Test "open" system call. +3 open-missing +3 open-normal +3 open-twice + +- Test "read" system call. +3 read-normal +3 read-zero + +- Test "write" system call. +3 write-normal +3 write-zero + +- Test "close" system call. +3 close-normal + +- Test "exec" system call. +5 exec-once +5 exec-multiple +5 exec-arg + +- Test "wait" system call. +5 wait-simple +5 wait-twice + +- Test "exit" system call. +5 exit + +- Test "halt" system call. +3 halt + +- Test recursive execution of user programs. +15 multi-recurse + +- Test read-only executable feature. +3 rox-simple +3 rox-child +3 rox-multichild diff --git a/pintos-progos/tests/userprog/Rubric.robustness b/pintos-progos/tests/userprog/Rubric.robustness new file mode 100644 index 0000000..b7d1035 --- /dev/null +++ b/pintos-progos/tests/userprog/Rubric.robustness @@ -0,0 +1,48 @@ +Robustness of system calls: +- Test robustness of file descriptor handling. +2 close-stdin +2 close-stdout +2 close-bad-fd +2 close-twice +2 read-bad-fd +2 read-stdout +2 write-bad-fd +2 write-stdin +2 multi-child-fd + +- Test robustness of pointer handling. +3 create-bad-ptr +3 exec-bad-ptr +3 open-bad-ptr +3 read-bad-ptr +3 write-bad-ptr + +- Test robustness of buffer copying across page boundaries. +3 create-bound +3 open-boundary +3 read-boundary +3 write-boundary + +- Test handling of null pointer and empty strings. +2 create-null +2 open-null +2 open-empty + +- Test robustness of system call implementation. +3 sc-bad-arg +3 sc-bad-sp +5 sc-boundary +5 sc-boundary-2 + +- Test robustness of "exec" and "wait" system calls. +5 exec-missing +5 wait-bad-pid +5 wait-killed + +- Test robustness of exception handling. +1 bad-read +1 bad-write +1 bad-jump +1 bad-read2 +1 bad-write2 +1 bad-jump2 diff --git a/pintos-progos/tests/userprog/args-dbl-space.ck b/pintos-progos/tests/userprog/args-dbl-space.ck new file mode 100644 index 0000000..dfbcf4b --- /dev/null +++ b/pintos-progos/tests/userprog/args-dbl-space.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(args) begin +(args) argc = 3 +(args) argv[0] = 'args-dbl-space' +(args) argv[1] = 'two' +(args) argv[2] = 'spaces!' +(args) argv[3] = null +(args) end +args-dbl-space: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/args-many.ck b/pintos-progos/tests/userprog/args-many.ck new file mode 100644 index 0000000..214574a --- /dev/null +++ b/pintos-progos/tests/userprog/args-many.ck @@ -0,0 +1,35 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(args) begin +(args) argc = 23 +(args) argv[0] = 'args-many' +(args) argv[1] = 'a' +(args) argv[2] = 'b' +(args) argv[3] = 'c' +(args) argv[4] = 'd' +(args) argv[5] = 'e' +(args) argv[6] = 'f' +(args) argv[7] = 'g' +(args) argv[8] = 'h' +(args) argv[9] = 'i' +(args) argv[10] = 'j' +(args) argv[11] = 'k' +(args) argv[12] = 'l' +(args) argv[13] = 'm' +(args) argv[14] = 'n' +(args) argv[15] = 'o' +(args) argv[16] = 'p' +(args) argv[17] = 'q' +(args) argv[18] = 'r' +(args) argv[19] = 's' +(args) argv[20] = 't' +(args) argv[21] = 'u' +(args) argv[22] = 'v' +(args) argv[23] = null +(args) end +args-many: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/args-multiple.ck b/pintos-progos/tests/userprog/args-multiple.ck new file mode 100644 index 0000000..227e6cc --- /dev/null +++ b/pintos-progos/tests/userprog/args-multiple.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(args) begin +(args) argc = 5 +(args) argv[0] = 'args-multiple' +(args) argv[1] = 'some' +(args) argv[2] = 'arguments' +(args) argv[3] = 'for' +(args) argv[4] = 'you!' +(args) argv[5] = null +(args) end +args-multiple: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/args-none.ck b/pintos-progos/tests/userprog/args-none.ck new file mode 100644 index 0000000..146318e --- /dev/null +++ b/pintos-progos/tests/userprog/args-none.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(args) begin +(args) argc = 1 +(args) argv[0] = 'args-none' +(args) argv[1] = null +(args) end +args-none: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/args-single.ck b/pintos-progos/tests/userprog/args-single.ck new file mode 100644 index 0000000..24582b4 --- /dev/null +++ b/pintos-progos/tests/userprog/args-single.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(args) begin +(args) argc = 2 +(args) argv[0] = 'args-single' +(args) argv[1] = 'onearg' +(args) argv[2] = null +(args) end +args-single: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/args.c b/pintos-progos/tests/userprog/args.c new file mode 100644 index 0000000..20eda44 --- /dev/null +++ b/pintos-progos/tests/userprog/args.c @@ -0,0 +1,25 @@ +/* Prints the command-line arguments. + This program is used for all of the args-* tests. Grading is + done differently for each of the args-* tests based on the + output. */ + +#include "tests/lib.h" + +int +main (int argc, char *argv[]) +{ + int i; + + test_name = "args"; + + msg ("begin"); + msg ("argc = %d", argc); + for (i = 0; i <= argc; i++) + if (argv[i] != NULL) + msg ("argv[%d] = '%s'", i, argv[i]); + else + msg ("argv[%d] = null", i); + msg ("end"); + + return 0; +} diff --git a/pintos-progos/tests/userprog/bad-jump.c b/pintos-progos/tests/userprog/bad-jump.c new file mode 100644 index 0000000..51b7c9f --- /dev/null +++ b/pintos-progos/tests/userprog/bad-jump.c @@ -0,0 +1,13 @@ +/* This program attempts to execute code at address 0, which is not mapped. + This should terminate the process with a -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("Congratulations - you have successfully called NULL: %d", + ((int (*)(void))NULL)()); + fail ("should have exited with -1"); +} diff --git a/pintos-progos/tests/userprog/bad-jump.ck b/pintos-progos/tests/userprog/bad-jump.ck new file mode 100644 index 0000000..e1c178b --- /dev/null +++ b/pintos-progos/tests/userprog/bad-jump.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(bad-jump) begin +bad-jump: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/bad-jump2.c b/pintos-progos/tests/userprog/bad-jump2.c new file mode 100644 index 0000000..dc7c2a7 --- /dev/null +++ b/pintos-progos/tests/userprog/bad-jump2.c @@ -0,0 +1,13 @@ +/* This program attempts to execute code at a kernel virtual address. + This should terminate the process with a -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("Congratulations - you have successfully called kernel code: %d", + ((int (*)(void))0xC0000000)()); + fail ("should have exited with -1"); +} diff --git a/pintos-progos/tests/userprog/bad-jump2.ck b/pintos-progos/tests/userprog/bad-jump2.ck new file mode 100644 index 0000000..35f0f97 --- /dev/null +++ b/pintos-progos/tests/userprog/bad-jump2.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(bad-jump2) begin +bad-jump2: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/bad-read.c b/pintos-progos/tests/userprog/bad-read.c new file mode 100644 index 0000000..904c278 --- /dev/null +++ b/pintos-progos/tests/userprog/bad-read.c @@ -0,0 +1,13 @@ +/* This program attempts to read memory at an address that is not mapped. + This should terminate the process with a -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("Congratulations - you have successfully dereferenced NULL: %d", + *(int *)NULL); + fail ("should have exited with -1"); +} diff --git a/pintos-progos/tests/userprog/bad-read.ck b/pintos-progos/tests/userprog/bad-read.ck new file mode 100644 index 0000000..4d4d926 --- /dev/null +++ b/pintos-progos/tests/userprog/bad-read.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(bad-read) begin +bad-read: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/bad-read2.c b/pintos-progos/tests/userprog/bad-read2.c new file mode 100644 index 0000000..a2fc237 --- /dev/null +++ b/pintos-progos/tests/userprog/bad-read2.c @@ -0,0 +1,13 @@ +/* This program attempts to read kernel memory. + This should terminate the process with a -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("Congratulations - you have successfully read kernel memory: %d", + *(int *)0xC0000000); + fail ("should have exited with -1"); +} diff --git a/pintos-progos/tests/userprog/bad-read2.ck b/pintos-progos/tests/userprog/bad-read2.ck new file mode 100644 index 0000000..fa27c7d --- /dev/null +++ b/pintos-progos/tests/userprog/bad-read2.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(bad-read2) begin +bad-read2: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/bad-write.c b/pintos-progos/tests/userprog/bad-write.c new file mode 100644 index 0000000..000c26b --- /dev/null +++ b/pintos-progos/tests/userprog/bad-write.c @@ -0,0 +1,12 @@ +/* This program attempts to write to memory at an address that is not mapped. + This should terminate the process with a -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + *(int *)NULL = 42; + fail ("should have exited with -1"); +} diff --git a/pintos-progos/tests/userprog/bad-write.ck b/pintos-progos/tests/userprog/bad-write.ck new file mode 100644 index 0000000..d213b49 --- /dev/null +++ b/pintos-progos/tests/userprog/bad-write.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(bad-write) begin +bad-write: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/bad-write2.c b/pintos-progos/tests/userprog/bad-write2.c new file mode 100644 index 0000000..753da1e --- /dev/null +++ b/pintos-progos/tests/userprog/bad-write2.c @@ -0,0 +1,12 @@ +/* This program attempts to write to kernel memory. + This should terminate the process with a -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + *(int *)0xC0000000 = 42; + fail ("should have exited with -1"); +} diff --git a/pintos-progos/tests/userprog/bad-write2.ck b/pintos-progos/tests/userprog/bad-write2.ck new file mode 100644 index 0000000..c6a3420 --- /dev/null +++ b/pintos-progos/tests/userprog/bad-write2.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(bad-write2) begin +bad-write2: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/boundary.c b/pintos-progos/tests/userprog/boundary.c new file mode 100644 index 0000000..59907ec --- /dev/null +++ b/pintos-progos/tests/userprog/boundary.c @@ -0,0 +1,33 @@ +/* Utility function for tests that try to break system calls by + passing them data that crosses from one virtual page to + another. */ + +#include +#include +#include +#include "tests/userprog/boundary.h" + +static char dst[8192]; + +/* Returns the beginning of a page. There are at least 2048 + modifiable bytes on either side of the pointer returned. */ +void * +get_boundary_area (void) +{ + char *p = (char *) ROUND_UP ((uintptr_t) dst, 4096); + if (p - dst < 2048) + p += 4096; + return p; +} + +/* Returns a copy of SRC split across the boundary between two + pages. */ +char * +copy_string_across_boundary (const char *src) +{ + char *p = get_boundary_area (); + p -= strlen (src) < 4096 ? strlen (src) / 2 : 4096; + strlcpy (p, src, 4096); + return p; +} + diff --git a/pintos-progos/tests/userprog/boundary.h b/pintos-progos/tests/userprog/boundary.h new file mode 100644 index 0000000..c8e4b3b --- /dev/null +++ b/pintos-progos/tests/userprog/boundary.h @@ -0,0 +1,7 @@ +#ifndef TESTS_USERPROG_BOUNDARY_H +#define TESTS_USERPROG_BOUNDARY_H + +void *get_boundary_area (void); +char *copy_string_across_boundary (const char *); + +#endif /* tests/userprog/boundary.h */ diff --git a/pintos-progos/tests/userprog/child-bad.c b/pintos-progos/tests/userprog/child-bad.c new file mode 100644 index 0000000..77d7a69 --- /dev/null +++ b/pintos-progos/tests/userprog/child-bad.c @@ -0,0 +1,14 @@ +/* Child process run by wait-killed test. + Sets the stack pointer (%esp) to an invalid value and invokes + a system call, which should then terminate the process with a + -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile ("movl $0x20101234, %esp; int $0x30"); + fail ("should have exited with -1"); +} diff --git a/pintos-progos/tests/userprog/child-close.c b/pintos-progos/tests/userprog/child-close.c new file mode 100644 index 0000000..ac948c8 --- /dev/null +++ b/pintos-progos/tests/userprog/child-close.c @@ -0,0 +1,28 @@ +/* Child process run by multi-child-fd test. + + Attempts to close the file descriptor passed as the first + command-line argument. This is invalid, because file + descriptors are not inherited in Pintos. Two results are + allowed: either the system call should return without taking + any action, or the kernel should terminate the process with a + -1 exit code. */ + +#include +#include +#include +#include +#include "tests/lib.h" + +const char *test_name = "child-close"; + +int +main (int argc UNUSED, char *argv[]) +{ + msg ("begin"); + if (!isdigit (*argv[1])) + fail ("bad command-line arguments"); + close (atoi (argv[1])); + msg ("end"); + + return 0; +} diff --git a/pintos-progos/tests/userprog/child-rox.c b/pintos-progos/tests/userprog/child-rox.c new file mode 100644 index 0000000..aba808b --- /dev/null +++ b/pintos-progos/tests/userprog/child-rox.c @@ -0,0 +1,55 @@ +/* Child process run by rox-child and rox-multichild tests. + Opens and tries to write to its own executable, verifying that + that is disallowed. + Then recursively executes itself to the depth indicated by the + first command-line argument. */ + +#include +#include +#include +#include +#include "tests/lib.h" + +const char *test_name = "child-rox"; + +static void +try_write (void) +{ + int handle; + char buffer[19]; + + quiet = true; + CHECK ((handle = open ("child-rox")) > 1, "open \"child-rox\""); + quiet = false; + + CHECK (write (handle, buffer, sizeof buffer) == 0, + "try to write \"child-rox\""); + + close (handle); +} + +int +main (int argc UNUSED, char *argv[]) +{ + msg ("begin"); + try_write (); + + if (!isdigit (*argv[1])) + fail ("bad command-line arguments"); + if (atoi (argv[1]) > 1) + { + char cmd[128]; + int child; + + snprintf (cmd, sizeof cmd, "child-rox %d", atoi (argv[1]) - 1); + CHECK ((child = exec (cmd)) != -1, "exec \"%s\"", cmd); + quiet = true; + CHECK (wait (child) == 12, "wait for \"child-rox\""); + quiet = false; + } + + try_write (); + msg ("end"); + + return 12; +} diff --git a/pintos-progos/tests/userprog/child-simple.c b/pintos-progos/tests/userprog/child-simple.c new file mode 100644 index 0000000..0d2dacf --- /dev/null +++ b/pintos-progos/tests/userprog/child-simple.c @@ -0,0 +1,15 @@ +/* Child process run by exec-multiple, exec-one, wait-simple, and + wait-twice tests. + Just prints a single message and terminates. */ + +#include +#include "tests/lib.h" + +const char *test_name = "child-simple"; + +int +main (void) +{ + msg ("run"); + return 81; +} diff --git a/pintos-progos/tests/userprog/close-bad-fd.c b/pintos-progos/tests/userprog/close-bad-fd.c new file mode 100644 index 0000000..f63bb9a --- /dev/null +++ b/pintos-progos/tests/userprog/close-bad-fd.c @@ -0,0 +1,11 @@ +/* Tries to close an invalid fd, which must either fail silently + or terminate with exit code -1. */ + +#include +#include "tests/main.h" + +void +test_main (void) +{ + close (0x20101234); +} diff --git a/pintos-progos/tests/userprog/close-bad-fd.ck b/pintos-progos/tests/userprog/close-bad-fd.ck new file mode 100644 index 0000000..497b17c --- /dev/null +++ b/pintos-progos/tests/userprog/close-bad-fd.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(close-bad-fd) begin +(close-bad-fd) end +close-bad-fd: exit(0) +EOF +(close-bad-fd) begin +close-bad-fd: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/close-normal.c b/pintos-progos/tests/userprog/close-normal.c new file mode 100644 index 0000000..8ce04e3 --- /dev/null +++ b/pintos-progos/tests/userprog/close-normal.c @@ -0,0 +1,14 @@ +/* Opens a file and then closes it. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + msg ("close \"sample.txt\""); + close (handle); +} diff --git a/pintos-progos/tests/userprog/close-normal.ck b/pintos-progos/tests/userprog/close-normal.ck new file mode 100644 index 0000000..fe41342 --- /dev/null +++ b/pintos-progos/tests/userprog/close-normal.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(close-normal) begin +(close-normal) open "sample.txt" +(close-normal) close "sample.txt" +(close-normal) end +close-normal: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/close-stdin.c b/pintos-progos/tests/userprog/close-stdin.c new file mode 100644 index 0000000..9bbf9f2 --- /dev/null +++ b/pintos-progos/tests/userprog/close-stdin.c @@ -0,0 +1,11 @@ +/* Tries to close the keyboard input stream, which must either + fail silently or terminate with exit code -1. */ + +#include +#include "tests/main.h" + +void +test_main (void) +{ + close (0); +} diff --git a/pintos-progos/tests/userprog/close-stdin.ck b/pintos-progos/tests/userprog/close-stdin.ck new file mode 100644 index 0000000..3d28507 --- /dev/null +++ b/pintos-progos/tests/userprog/close-stdin.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(close-stdin) begin +(close-stdin) end +close-stdin: exit(0) +EOF +(close-stdin) begin +close-stdin: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/close-stdout.c b/pintos-progos/tests/userprog/close-stdout.c new file mode 100644 index 0000000..886523f --- /dev/null +++ b/pintos-progos/tests/userprog/close-stdout.c @@ -0,0 +1,11 @@ +/* Tries to close the console output stream, which must either + fail silently or terminate with exit code -1. */ + +#include +#include "tests/main.h" + +void +test_main (void) +{ + close (1); +} diff --git a/pintos-progos/tests/userprog/close-stdout.ck b/pintos-progos/tests/userprog/close-stdout.ck new file mode 100644 index 0000000..3cbbcff --- /dev/null +++ b/pintos-progos/tests/userprog/close-stdout.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(close-stdout) begin +(close-stdout) end +close-stdout: exit(0) +EOF +(close-stdout) begin +close-stdout: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/close-twice.c b/pintos-progos/tests/userprog/close-twice.c new file mode 100644 index 0000000..830bccf --- /dev/null +++ b/pintos-progos/tests/userprog/close-twice.c @@ -0,0 +1,18 @@ +/* Opens a file and then tries to close it twice. The second + close must either fail silently or terminate with exit code + -1. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + msg ("close \"sample.txt\""); + close (handle); + msg ("close \"sample.txt\" again"); + close (handle); +} diff --git a/pintos-progos/tests/userprog/close-twice.ck b/pintos-progos/tests/userprog/close-twice.ck new file mode 100644 index 0000000..deb55a6 --- /dev/null +++ b/pintos-progos/tests/userprog/close-twice.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(close-twice) begin +(close-twice) open "sample.txt" +(close-twice) close "sample.txt" +(close-twice) close "sample.txt" again +(close-twice) end +close-twice: exit(0) +EOF +(close-twice) begin +(close-twice) open "sample.txt" +(close-twice) close "sample.txt" +(close-twice) close "sample.txt" again +close-twice: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/create-bad-ptr.c b/pintos-progos/tests/userprog/create-bad-ptr.c new file mode 100644 index 0000000..4a07bb3 --- /dev/null +++ b/pintos-progos/tests/userprog/create-bad-ptr.c @@ -0,0 +1,12 @@ +/* Passes a bad pointer to the create system call, + which must cause the process to be terminated with exit code + -1. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("create(0x20101234): %d", create ((char *) 0x20101234, 0)); +} diff --git a/pintos-progos/tests/userprog/create-bad-ptr.ck b/pintos-progos/tests/userprog/create-bad-ptr.ck new file mode 100644 index 0000000..ac13405 --- /dev/null +++ b/pintos-progos/tests/userprog/create-bad-ptr.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-bad-ptr) begin +create-bad-ptr: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/create-bound.c b/pintos-progos/tests/userprog/create-bound.c new file mode 100644 index 0000000..0a829f3 --- /dev/null +++ b/pintos-progos/tests/userprog/create-bound.c @@ -0,0 +1,14 @@ +/* Opens a file whose name spans the boundary between two pages. + This is valid, so it must succeed. */ + +#include +#include "tests/userprog/boundary.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("create(\"quux.dat\"): %d", + create (copy_string_across_boundary ("quux.dat"), 0)); +} diff --git a/pintos-progos/tests/userprog/create-bound.ck b/pintos-progos/tests/userprog/create-bound.ck new file mode 100644 index 0000000..7656b7f --- /dev/null +++ b/pintos-progos/tests/userprog/create-bound.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-bound) begin +(create-bound) create("quux.dat"): 1 +(create-bound) end +create-bound: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/create-empty.c b/pintos-progos/tests/userprog/create-empty.c new file mode 100644 index 0000000..fa26b43 --- /dev/null +++ b/pintos-progos/tests/userprog/create-empty.c @@ -0,0 +1,10 @@ +/* Tries to create a file with the empty string as its name. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("create(\"\"): %d", create ("", 0)); +} diff --git a/pintos-progos/tests/userprog/create-empty.ck b/pintos-progos/tests/userprog/create-empty.ck new file mode 100644 index 0000000..93a1058 --- /dev/null +++ b/pintos-progos/tests/userprog/create-empty.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(create-empty) begin +(create-empty) create(""): 0 +(create-empty) end +create-empty: exit(0) +EOF +(create-empty) begin +create-empty: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/create-exists.c b/pintos-progos/tests/userprog/create-exists.c new file mode 100644 index 0000000..d395008 --- /dev/null +++ b/pintos-progos/tests/userprog/create-exists.c @@ -0,0 +1,16 @@ +/* Verifies that trying to create a file under a name that + already exists will fail. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (create ("quux.dat", 0), "create quux.dat"); + CHECK (create ("warble.dat", 0), "create warble.dat"); + CHECK (!create ("quux.dat", 0), "try to re-create quux.dat"); + CHECK (create ("baffle.dat", 0), "create baffle.dat"); + CHECK (!create ("warble.dat", 0), "try to re-create quux.dat"); +} diff --git a/pintos-progos/tests/userprog/create-exists.ck b/pintos-progos/tests/userprog/create-exists.ck new file mode 100644 index 0000000..006885e --- /dev/null +++ b/pintos-progos/tests/userprog/create-exists.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-exists) begin +(create-exists) create quux.dat +(create-exists) create warble.dat +(create-exists) try to re-create quux.dat +(create-exists) create baffle.dat +(create-exists) try to re-create quux.dat +(create-exists) end +create-exists: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/create-long.c b/pintos-progos/tests/userprog/create-long.c new file mode 100644 index 0000000..16b31bd --- /dev/null +++ b/pintos-progos/tests/userprog/create-long.c @@ -0,0 +1,17 @@ +/* Tries to create a file with a name that is much too long, + which must fail. */ + +#include +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + static char name[512]; + memset (name, 'x', sizeof name); + name[sizeof name - 1] = '\0'; + + msg ("create(\"x...\"): %d", create (name, 0)); +} diff --git a/pintos-progos/tests/userprog/create-long.ck b/pintos-progos/tests/userprog/create-long.ck new file mode 100644 index 0000000..628411c --- /dev/null +++ b/pintos-progos/tests/userprog/create-long.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-long) begin +(create-long) create("x..."): 0 +(create-long) end +create-long: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/create-normal.c b/pintos-progos/tests/userprog/create-normal.c new file mode 100644 index 0000000..3cbc463 --- /dev/null +++ b/pintos-progos/tests/userprog/create-normal.c @@ -0,0 +1,10 @@ +/* Creates an ordinary empty file. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (create ("quux.dat", 0), "create quux.dat"); +} diff --git a/pintos-progos/tests/userprog/create-normal.ck b/pintos-progos/tests/userprog/create-normal.ck new file mode 100644 index 0000000..ca74a6e --- /dev/null +++ b/pintos-progos/tests/userprog/create-normal.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-normal) begin +(create-normal) create quux.dat +(create-normal) end +create-normal: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/create-null.c b/pintos-progos/tests/userprog/create-null.c new file mode 100644 index 0000000..287cb23 --- /dev/null +++ b/pintos-progos/tests/userprog/create-null.c @@ -0,0 +1,11 @@ +/* Tries to create a file with the null pointer as its name. + The process must be terminated with exit code -1. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("create(NULL): %d", create (NULL, 0)); +} diff --git a/pintos-progos/tests/userprog/create-null.ck b/pintos-progos/tests/userprog/create-null.ck new file mode 100644 index 0000000..09b7872 --- /dev/null +++ b/pintos-progos/tests/userprog/create-null.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-null) begin +create-null: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/exec-arg.c b/pintos-progos/tests/userprog/exec-arg.c new file mode 100644 index 0000000..82d0744 --- /dev/null +++ b/pintos-progos/tests/userprog/exec-arg.c @@ -0,0 +1,10 @@ +/* Tests argument passing to child processes. */ + +#include +#include "tests/main.h" + +void +test_main (void) +{ + wait (exec ("child-args childarg")); +} diff --git a/pintos-progos/tests/userprog/exec-arg.ck b/pintos-progos/tests/userprog/exec-arg.ck new file mode 100644 index 0000000..b7533ed --- /dev/null +++ b/pintos-progos/tests/userprog/exec-arg.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(exec-arg) begin +(args) begin +(args) argc = 2 +(args) argv[0] = 'child-args' +(args) argv[1] = 'childarg' +(args) argv[2] = null +(args) end +child-args: exit(0) +(exec-arg) end +exec-arg: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/exec-bad-ptr.c b/pintos-progos/tests/userprog/exec-bad-ptr.c new file mode 100644 index 0000000..0abadd3 --- /dev/null +++ b/pintos-progos/tests/userprog/exec-bad-ptr.c @@ -0,0 +1,11 @@ +/* Passes an invalid pointer to the exec system call. + The process must be terminated with -1 exit code. */ + +#include +#include "tests/main.h" + +void +test_main (void) +{ + exec ((char *) 0x20101234); +} diff --git a/pintos-progos/tests/userprog/exec-bad-ptr.ck b/pintos-progos/tests/userprog/exec-bad-ptr.ck new file mode 100644 index 0000000..63f5f78 --- /dev/null +++ b/pintos-progos/tests/userprog/exec-bad-ptr.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(exec-bad-ptr) begin +(exec-bad-ptr) end +exec-bad-ptr: exit(0) +EOF +(exec-bad-ptr) begin +exec-bad-ptr: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/exec-missing.c b/pintos-progos/tests/userprog/exec-missing.c new file mode 100644 index 0000000..bf08cad --- /dev/null +++ b/pintos-progos/tests/userprog/exec-missing.c @@ -0,0 +1,12 @@ +/* Tries to execute a nonexistent process. + The exec system call must return -1. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("exec(\"no-such-file\"): %d", exec ("no-such-file")); +} diff --git a/pintos-progos/tests/userprog/exec-missing.ck b/pintos-progos/tests/userprog/exec-missing.ck new file mode 100644 index 0000000..0ef7aaa --- /dev/null +++ b/pintos-progos/tests/userprog/exec-missing.ck @@ -0,0 +1,31 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF', <<'EOF', <<'EOF']); +(exec-missing) begin +load: no-such-file: open failed +(exec-missing) exec("no-such-file"): -1 +(exec-missing) end +exec-missing: exit(0) +EOF +(exec-missing) begin +(exec-missing) exec("no-such-file"): -1 +(exec-missing) end +exec-missing: exit(0) +EOF +(exec-missing) begin +load: no-such-file: open failed +no-such-file: exit(-1) +(exec-missing) exec("no-such-file"): -1 +(exec-missing) end +exec-missing: exit(0) +EOF +(exec-missing) begin +load: no-such-file: open failed +(exec-missing) exec("no-such-file"): -1 +no-such-file: exit(-1) +(exec-missing) end +exec-missing: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/exec-multiple.c b/pintos-progos/tests/userprog/exec-multiple.c new file mode 100644 index 0000000..ba4c26e --- /dev/null +++ b/pintos-progos/tests/userprog/exec-multiple.c @@ -0,0 +1,14 @@ +/* Executes and waits for multiple child processes. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + wait (exec ("child-simple")); + wait (exec ("child-simple")); + wait (exec ("child-simple")); + wait (exec ("child-simple")); +} diff --git a/pintos-progos/tests/userprog/exec-multiple.ck b/pintos-progos/tests/userprog/exec-multiple.ck new file mode 100644 index 0000000..99624cd --- /dev/null +++ b/pintos-progos/tests/userprog/exec-multiple.ck @@ -0,0 +1,18 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(exec-multiple) begin +(child-simple) run +child-simple: exit(81) +(child-simple) run +child-simple: exit(81) +(child-simple) run +child-simple: exit(81) +(child-simple) run +child-simple: exit(81) +(exec-multiple) end +exec-multiple: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/exec-once.c b/pintos-progos/tests/userprog/exec-once.c new file mode 100644 index 0000000..7bae5a1 --- /dev/null +++ b/pintos-progos/tests/userprog/exec-once.c @@ -0,0 +1,11 @@ +/* Executes and waits for a single child process. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + wait (exec ("child-simple")); +} diff --git a/pintos-progos/tests/userprog/exec-once.ck b/pintos-progos/tests/userprog/exec-once.ck new file mode 100644 index 0000000..00b59ed --- /dev/null +++ b/pintos-progos/tests/userprog/exec-once.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(exec-once) begin +(child-simple) run +child-simple: exit(81) +(exec-once) end +exec-once: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/exit.c b/pintos-progos/tests/userprog/exit.c new file mode 100644 index 0000000..cb4eb8f --- /dev/null +++ b/pintos-progos/tests/userprog/exit.c @@ -0,0 +1,11 @@ +/* Tests the exit system call. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + exit (57); + fail ("should have called exit(57)"); +} diff --git a/pintos-progos/tests/userprog/exit.ck b/pintos-progos/tests/userprog/exit.ck new file mode 100644 index 0000000..a552702 --- /dev/null +++ b/pintos-progos/tests/userprog/exit.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(exit) begin +exit: exit(57) +EOF +pass; diff --git a/pintos-progos/tests/userprog/halt.c b/pintos-progos/tests/userprog/halt.c new file mode 100644 index 0000000..4a99bce --- /dev/null +++ b/pintos-progos/tests/userprog/halt.c @@ -0,0 +1,11 @@ +/* Tests the halt system call. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + halt (); + fail ("should have halted"); +} diff --git a/pintos-progos/tests/userprog/halt.ck b/pintos-progos/tests/userprog/halt.ck new file mode 100644 index 0000000..1b701ed --- /dev/null +++ b/pintos-progos/tests/userprog/halt.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks ("run", @output); + +fail "missing 'begin' message\n" + if !grep ($_ eq '(halt) begin', @output); +fail "found 'fail' message--halt didn't really halt\n" + if grep ($_ eq '(halt) fail', @output); +pass; diff --git a/pintos-progos/tests/userprog/lib/.gitignore b/pintos-progos/tests/userprog/lib/.gitignore new file mode 100644 index 0000000..a438335 --- /dev/null +++ b/pintos-progos/tests/userprog/lib/.gitignore @@ -0,0 +1 @@ +*.d diff --git a/pintos-progos/tests/userprog/lib/user/.dummy b/pintos-progos/tests/userprog/lib/user/.dummy new file mode 100644 index 0000000..e69de29 diff --git a/pintos-progos/tests/userprog/lib/user/.gitignore b/pintos-progos/tests/userprog/lib/user/.gitignore new file mode 100644 index 0000000..a438335 --- /dev/null +++ b/pintos-progos/tests/userprog/lib/user/.gitignore @@ -0,0 +1 @@ +*.d diff --git a/pintos-progos/tests/userprog/multi-child-fd.c b/pintos-progos/tests/userprog/multi-child-fd.c new file mode 100644 index 0000000..48de4b4 --- /dev/null +++ b/pintos-progos/tests/userprog/multi-child-fd.c @@ -0,0 +1,25 @@ +/* Opens a file and then runs a subprocess that tries to close + the file. (Pintos does not have inheritance of file handles, + so this must fail.) The parent process then attempts to use + the file handle, which must succeed. */ + +#include +#include +#include "tests/userprog/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char child_cmd[128]; + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + snprintf (child_cmd, sizeof child_cmd, "child-close %d", handle); + + msg ("wait(exec()) = %d", wait (exec (child_cmd))); + + check_file_handle (handle, "sample.txt", sample, sizeof sample - 1); +} diff --git a/pintos-progos/tests/userprog/multi-child-fd.ck b/pintos-progos/tests/userprog/multi-child-fd.ck new file mode 100644 index 0000000..d0b3a33 --- /dev/null +++ b/pintos-progos/tests/userprog/multi-child-fd.ck @@ -0,0 +1,25 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(multi-child-fd) begin +(multi-child-fd) open "sample.txt" +(child-close) begin +(child-close) end +child-close: exit(0) +(multi-child-fd) wait(exec()) = 0 +(multi-child-fd) verified contents of "sample.txt" +(multi-child-fd) end +multi-child-fd: exit(0) +EOF +(multi-child-fd) begin +(multi-child-fd) open "sample.txt" +(child-close) begin +child-close: exit(-1) +(multi-child-fd) wait(exec()) = -1 +(multi-child-fd) verified contents of "sample.txt" +(multi-child-fd) end +multi-child-fd: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/multi-recurse.c b/pintos-progos/tests/userprog/multi-recurse.c new file mode 100644 index 0000000..7172ec3 --- /dev/null +++ b/pintos-progos/tests/userprog/multi-recurse.c @@ -0,0 +1,34 @@ +/* Executes itself recursively to the depth indicated by the + first command-line argument. */ + +#include +#include +#include +#include +#include "tests/lib.h" + +const char *test_name = "multi-recurse"; + +int +main (int argc UNUSED, char *argv[]) +{ + int n = atoi (argv[1]); + + msg ("begin %d", n); + if (n != 0) + { + char child_cmd[128]; + pid_t child_pid; + int code; + + snprintf (child_cmd, sizeof child_cmd, "multi-recurse %d", n - 1); + CHECK ((child_pid = exec (child_cmd)) != -1, "exec(\"%s\")", child_cmd); + + code = wait (child_pid); + if (code != n - 1) + fail ("wait(exec(\"%s\")) returned %d", child_cmd, code); + } + + msg ("end %d", n); + return n; +} diff --git a/pintos-progos/tests/userprog/multi-recurse.ck b/pintos-progos/tests/userprog/multi-recurse.ck new file mode 100644 index 0000000..41eb4a6 --- /dev/null +++ b/pintos-progos/tests/userprog/multi-recurse.ck @@ -0,0 +1,70 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(multi-recurse) begin 15 +(multi-recurse) exec("multi-recurse 14") +(multi-recurse) begin 14 +(multi-recurse) exec("multi-recurse 13") +(multi-recurse) begin 13 +(multi-recurse) exec("multi-recurse 12") +(multi-recurse) begin 12 +(multi-recurse) exec("multi-recurse 11") +(multi-recurse) begin 11 +(multi-recurse) exec("multi-recurse 10") +(multi-recurse) begin 10 +(multi-recurse) exec("multi-recurse 9") +(multi-recurse) begin 9 +(multi-recurse) exec("multi-recurse 8") +(multi-recurse) begin 8 +(multi-recurse) exec("multi-recurse 7") +(multi-recurse) begin 7 +(multi-recurse) exec("multi-recurse 6") +(multi-recurse) begin 6 +(multi-recurse) exec("multi-recurse 5") +(multi-recurse) begin 5 +(multi-recurse) exec("multi-recurse 4") +(multi-recurse) begin 4 +(multi-recurse) exec("multi-recurse 3") +(multi-recurse) begin 3 +(multi-recurse) exec("multi-recurse 2") +(multi-recurse) begin 2 +(multi-recurse) exec("multi-recurse 1") +(multi-recurse) begin 1 +(multi-recurse) exec("multi-recurse 0") +(multi-recurse) begin 0 +(multi-recurse) end 0 +multi-recurse: exit(0) +(multi-recurse) end 1 +multi-recurse: exit(1) +(multi-recurse) end 2 +multi-recurse: exit(2) +(multi-recurse) end 3 +multi-recurse: exit(3) +(multi-recurse) end 4 +multi-recurse: exit(4) +(multi-recurse) end 5 +multi-recurse: exit(5) +(multi-recurse) end 6 +multi-recurse: exit(6) +(multi-recurse) end 7 +multi-recurse: exit(7) +(multi-recurse) end 8 +multi-recurse: exit(8) +(multi-recurse) end 9 +multi-recurse: exit(9) +(multi-recurse) end 10 +multi-recurse: exit(10) +(multi-recurse) end 11 +multi-recurse: exit(11) +(multi-recurse) end 12 +multi-recurse: exit(12) +(multi-recurse) end 13 +multi-recurse: exit(13) +(multi-recurse) end 14 +multi-recurse: exit(14) +(multi-recurse) end 15 +multi-recurse: exit(15) +EOF +pass; diff --git a/pintos-progos/tests/userprog/no-vm/Make.tests b/pintos-progos/tests/userprog/no-vm/Make.tests new file mode 100644 index 0000000..a545e18 --- /dev/null +++ b/pintos-progos/tests/userprog/no-vm/Make.tests @@ -0,0 +1,8 @@ +# -*- makefile -*- + +tests/userprog/no-vm_TESTS = tests/userprog/no-vm/multi-oom +tests/userprog/no-vm_PROGS = $(tests/userprog/no-vm_TESTS) +tests/userprog/no-vm/multi-oom_SRC = tests/userprog/no-vm/multi-oom.c \ +tests/lib.c + +tests/userprog/no-vm/multi-oom.output: TIMEOUT = 360 diff --git a/pintos-progos/tests/userprog/no-vm/Rubric b/pintos-progos/tests/userprog/no-vm/Rubric new file mode 100644 index 0000000..c3816c6 --- /dev/null +++ b/pintos-progos/tests/userprog/no-vm/Rubric @@ -0,0 +1,3 @@ +Functionality of features that VM might break: + +1 multi-oom diff --git a/pintos-progos/tests/userprog/no-vm/multi-oom.c b/pintos-progos/tests/userprog/no-vm/multi-oom.c new file mode 100644 index 0000000..6a4472d --- /dev/null +++ b/pintos-progos/tests/userprog/no-vm/multi-oom.c @@ -0,0 +1,179 @@ +/* Recursively executes itself until the child fails to execute. + We expect that at least 30 copies can run. + + We count how many children your kernel was able to execute + before it fails to start a new process. We require that, + if a process doesn't actually get to start, exec() must + return -1, not a valid PID. + + We repeat this process 10 times, checking that your kernel + allows for the same level of depth every time. + + In addition, some processes will spawn children that terminate + abnormally after allocating some resources. + + Written by Godmar Back + */ + +#include +#include +#include +#include +#include +#include +#include +#include "tests/lib.h" + +static const int EXPECTED_DEPTH_TO_PASS = 30; +static const int EXPECTED_REPETITIONS = 10; + +const char *test_name = "multi-oom"; + +enum child_termination_mode { RECURSE, CRASH }; + +/* Spawn a recursive copy of ourselves, passing along instructions + for the child. */ +static pid_t +spawn_child (int c, enum child_termination_mode mode) +{ + char child_cmd[128]; + snprintf (child_cmd, sizeof child_cmd, + "%s %d %s", test_name, c, mode == CRASH ? "-k" : ""); + return exec (child_cmd); +} + +/* Open a number of files (and fail to close them). + The kernel must free any kernel resources associated + with these file descriptors. */ +static void +consume_some_resources (void) +{ + int fd, fdmax = 126; + + /* Open as many files as we can, up to fdmax. + Depending on how file descriptors are allocated inside + the kernel, open() may fail if the kernel is low on memory. + A low-memory condition in open() should not lead to the + termination of the process. */ + for (fd = 0; fd < fdmax; fd++) + if (open (test_name) == -1) + break; +} + +/* Consume some resources, then terminate this process + in some abnormal way. */ +static int NO_INLINE +consume_some_resources_and_die (int seed) +{ + consume_some_resources (); + random_init (seed); + int *PHYS_BASE = (int *)0xC0000000; + + switch (random_ulong () % 5) + { + case 0: + *(int *) NULL = 42; + + case 1: + return *(int *) NULL; + + case 2: + return *PHYS_BASE; + + case 3: + *PHYS_BASE = 42; + + case 4: + open ((char *)PHYS_BASE); + exit (-1); + + default: + NOT_REACHED (); + } + return 0; +} + +/* The first copy is invoked without command line arguments. + Subsequent copies are invoked with a parameter 'depth' + that describes how many parent processes preceded them. + Each process spawns one or multiple recursive copies of + itself, passing 'depth+1' as depth. + + Some children are started with the '-k' flag, which will + result in abnormal termination. + */ +int +main (int argc, char *argv[]) +{ + int n; + + n = argc > 1 ? atoi (argv[1]) : 0; + bool is_at_root = (n == 0); + if (is_at_root) + msg ("begin"); + + /* If -k is passed, crash this process. */ + if (argc > 2 && !strcmp(argv[2], "-k")) + { + consume_some_resources_and_die (n); + NOT_REACHED (); + } + + int howmany = is_at_root ? EXPECTED_REPETITIONS : 1; + int i, expected_depth = -1; + + for (i = 0; i < howmany; i++) + { + pid_t child_pid; + + /* Spawn a child that will be abnormally terminated. + To speed the test up, do this only for processes + spawned at a certain depth. */ + if (n > EXPECTED_DEPTH_TO_PASS/2) + { + child_pid = spawn_child (n + 1, CRASH); + if (child_pid != -1) + { + if (wait (child_pid) != -1) + fail ("crashed child should return -1."); + } + /* If spawning this child failed, so should + the next spawn_child below. */ + } + + /* Now spawn the child that will recurse. */ + child_pid = spawn_child (n + 1, RECURSE); + + /* If maximum depth is reached, return result. */ + if (child_pid == -1) + return n; + + /* Else wait for child to report how deeply it was able to recurse. */ + int reached_depth = wait (child_pid); + if (reached_depth == -1) + fail ("wait returned -1."); + + /* Record the depth reached during the first run; on subsequent + runs, fail if those runs do not match the depth achieved on the + first run. */ + if (i == 0) + expected_depth = reached_depth; + else if (expected_depth != reached_depth) + fail ("after run %d/%d, expected depth %d, actual depth %d.", + i, howmany, expected_depth, reached_depth); + ASSERT (expected_depth == reached_depth); + } + + consume_some_resources (); + + if (n == 0) + { + if (expected_depth < EXPECTED_DEPTH_TO_PASS) + fail ("should have forked at least %d times.", EXPECTED_DEPTH_TO_PASS); + msg ("success. program forked %d times.", howmany); + msg ("end"); + } + + return expected_depth; +} +// vim: sw=2 diff --git a/pintos-progos/tests/userprog/no-vm/multi-oom.ck b/pintos-progos/tests/userprog/no-vm/multi-oom.ck new file mode 100644 index 0000000..59a0bcd --- /dev/null +++ b/pintos-progos/tests/userprog/no-vm/multi-oom.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, IGNORE_EXIT_CODES => 1, [<<'EOF']); +(multi-oom) begin +(multi-oom) success. program forked 10 times. +(multi-oom) end +EOF +pass; diff --git a/pintos-progos/tests/userprog/null.ck b/pintos-progos/tests/userprog/null.ck new file mode 100644 index 0000000..980de35 --- /dev/null +++ b/pintos-progos/tests/userprog/null.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +system call! +EOF +pass; diff --git a/pintos-progos/tests/userprog/open-bad-ptr.c b/pintos-progos/tests/userprog/open-bad-ptr.c new file mode 100644 index 0000000..9cd4edf --- /dev/null +++ b/pintos-progos/tests/userprog/open-bad-ptr.c @@ -0,0 +1,13 @@ +/* Passes an invalid pointer to the open system call. + The process must be terminated with -1 exit code. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("open(0x20101234): %d", open ((char *) 0x20101234)); + fail ("should have called exit(-1)"); +} diff --git a/pintos-progos/tests/userprog/open-bad-ptr.ck b/pintos-progos/tests/userprog/open-bad-ptr.ck new file mode 100644 index 0000000..45349e2 --- /dev/null +++ b/pintos-progos/tests/userprog/open-bad-ptr.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(open-bad-ptr) begin +(open-bad-ptr) end +open-bad-ptr: exit(0) +EOF +(open-bad-ptr) begin +open-bad-ptr: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/open-boundary.c b/pintos-progos/tests/userprog/open-boundary.c new file mode 100644 index 0000000..cc8ff8b --- /dev/null +++ b/pintos-progos/tests/userprog/open-boundary.c @@ -0,0 +1,14 @@ +/* Creates a file whose name spans the boundary between two pages. + This is valid, so it must succeed. */ + +#include +#include "tests/userprog/boundary.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (open (copy_string_across_boundary ("sample.txt")) > 1, + "open \"sample.txt\""); +} diff --git a/pintos-progos/tests/userprog/open-boundary.ck b/pintos-progos/tests/userprog/open-boundary.ck new file mode 100644 index 0000000..8060d22 --- /dev/null +++ b/pintos-progos/tests/userprog/open-boundary.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-boundary) begin +(open-boundary) open "sample.txt" +(open-boundary) end +open-boundary: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/open-empty.c b/pintos-progos/tests/userprog/open-empty.c new file mode 100644 index 0000000..3ea9907 --- /dev/null +++ b/pintos-progos/tests/userprog/open-empty.c @@ -0,0 +1,13 @@ +/* Tries to open a file with the empty string as its name. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle = open (""); + if (handle != -1) + fail ("open() returned %d instead of -1", handle); +} diff --git a/pintos-progos/tests/userprog/open-empty.ck b/pintos-progos/tests/userprog/open-empty.ck new file mode 100644 index 0000000..885fb41 --- /dev/null +++ b/pintos-progos/tests/userprog/open-empty.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-empty) begin +(open-empty) end +open-empty: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/open-missing.c b/pintos-progos/tests/userprog/open-missing.c new file mode 100644 index 0000000..13ecbda --- /dev/null +++ b/pintos-progos/tests/userprog/open-missing.c @@ -0,0 +1,13 @@ +/* Tries to open a nonexistent file. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle = open ("no-such-file"); + if (handle != -1) + fail ("open() returned %d", handle); +} diff --git a/pintos-progos/tests/userprog/open-missing.ck b/pintos-progos/tests/userprog/open-missing.ck new file mode 100644 index 0000000..d72d878 --- /dev/null +++ b/pintos-progos/tests/userprog/open-missing.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-missing) begin +(open-missing) end +open-missing: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/open-normal.c b/pintos-progos/tests/userprog/open-normal.c new file mode 100644 index 0000000..5132465 --- /dev/null +++ b/pintos-progos/tests/userprog/open-normal.c @@ -0,0 +1,13 @@ +/* Open a file. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle = open ("sample.txt"); + if (handle < 2) + fail ("open() returned %d", handle); +} diff --git a/pintos-progos/tests/userprog/open-normal.ck b/pintos-progos/tests/userprog/open-normal.ck new file mode 100644 index 0000000..4f6c342 --- /dev/null +++ b/pintos-progos/tests/userprog/open-normal.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-normal) begin +(open-normal) end +open-normal: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/open-null.c b/pintos-progos/tests/userprog/open-null.c new file mode 100644 index 0000000..bb418b8 --- /dev/null +++ b/pintos-progos/tests/userprog/open-null.c @@ -0,0 +1,12 @@ +/* Tries to open a file with the null pointer as its name. + The process must be terminated with exit code -1. */ + +#include +#include +#include "tests/main.h" + +void +test_main (void) +{ + open (NULL); +} diff --git a/pintos-progos/tests/userprog/open-null.ck b/pintos-progos/tests/userprog/open-null.ck new file mode 100644 index 0000000..b4a3bcb --- /dev/null +++ b/pintos-progos/tests/userprog/open-null.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(open-null) begin +(open-null) end +open-null: exit(0) +EOF +(open-null) begin +open-null: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/open-twice.c b/pintos-progos/tests/userprog/open-twice.c new file mode 100644 index 0000000..dd333af --- /dev/null +++ b/pintos-progos/tests/userprog/open-twice.c @@ -0,0 +1,19 @@ +/* Tries to open the same file twice, + which must succeed and must return a different file descriptor + in each case. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int h1 = open ("sample.txt"); + int h2 = open ("sample.txt"); + + CHECK ((h1 = open ("sample.txt")) > 1, "open \"sample.txt\" once"); + CHECK ((h2 = open ("sample.txt")) > 1, "open \"sample.txt\" again"); + if (h1 == h2) + fail ("open() returned %d both times", h1); +} diff --git a/pintos-progos/tests/userprog/open-twice.ck b/pintos-progos/tests/userprog/open-twice.ck new file mode 100644 index 0000000..64fa805 --- /dev/null +++ b/pintos-progos/tests/userprog/open-twice.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-twice) begin +(open-twice) open "sample.txt" once +(open-twice) open "sample.txt" again +(open-twice) end +open-twice: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/read-bad-fd.c b/pintos-progos/tests/userprog/read-bad-fd.c new file mode 100644 index 0000000..a8b190d --- /dev/null +++ b/pintos-progos/tests/userprog/read-bad-fd.c @@ -0,0 +1,21 @@ +/* Tries to read from an invalid fd, + which must either fail silently or terminate the process with + exit code -1. */ + +#include +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char buf; + read (0x20101234, &buf, 1); + read (5, &buf, 1); + read (1234, &buf, 1); + read (-1, &buf, 1); + read (-1024, &buf, 1); + read (INT_MIN, &buf, 1); + read (INT_MAX, &buf, 1); +} diff --git a/pintos-progos/tests/userprog/read-bad-fd.ck b/pintos-progos/tests/userprog/read-bad-fd.ck new file mode 100644 index 0000000..5fedcc7 --- /dev/null +++ b/pintos-progos/tests/userprog/read-bad-fd.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(read-bad-fd) begin +(read-bad-fd) end +read-bad-fd: exit(0) +EOF +(read-bad-fd) begin +read-bad-fd: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/read-bad-ptr.c b/pintos-progos/tests/userprog/read-bad-ptr.c new file mode 100644 index 0000000..8fe756e --- /dev/null +++ b/pintos-progos/tests/userprog/read-bad-ptr.c @@ -0,0 +1,16 @@ +/* Passes an invalid pointer to the read system call. + The process must be terminated with -1 exit code. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + read (handle, (char *) 0xc0100000, 123); + fail ("should not have survived read()"); +} diff --git a/pintos-progos/tests/userprog/read-bad-ptr.ck b/pintos-progos/tests/userprog/read-bad-ptr.ck new file mode 100644 index 0000000..d10accf --- /dev/null +++ b/pintos-progos/tests/userprog/read-bad-ptr.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(read-bad-ptr) begin +(read-bad-ptr) open "sample.txt" +(read-bad-ptr) end +read-bad-ptr: exit(0) +EOF +(read-bad-ptr) begin +(read-bad-ptr) open "sample.txt" +read-bad-ptr: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/read-boundary.c b/pintos-progos/tests/userprog/read-boundary.c new file mode 100644 index 0000000..9c19966 --- /dev/null +++ b/pintos-progos/tests/userprog/read-boundary.c @@ -0,0 +1,30 @@ +/* Reads data spanning two pages in virtual address space, + which must succeed. */ + +#include +#include +#include "tests/userprog/boundary.h" +#include "tests/userprog/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + int byte_cnt; + char *buffer; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + buffer = get_boundary_area () - sizeof sample / 2; + byte_cnt = read (handle, buffer, sizeof sample - 1); + if (byte_cnt != sizeof sample - 1) + fail ("read() returned %d instead of %zu", byte_cnt, sizeof sample - 1); + else if (strcmp (sample, buffer)) + { + msg ("expected text:\n%s", sample); + msg ("text actually read:\n%s", buffer); + fail ("expected text differs from actual"); + } +} diff --git a/pintos-progos/tests/userprog/read-boundary.ck b/pintos-progos/tests/userprog/read-boundary.ck new file mode 100644 index 0000000..08dc161 --- /dev/null +++ b/pintos-progos/tests/userprog/read-boundary.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(read-boundary) begin +(read-boundary) open "sample.txt" +(read-boundary) end +read-boundary: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/read-normal.c b/pintos-progos/tests/userprog/read-normal.c new file mode 100644 index 0000000..16d15cc --- /dev/null +++ b/pintos-progos/tests/userprog/read-normal.c @@ -0,0 +1,11 @@ +/* Try reading a file in the most normal way. */ + +#include "tests/userprog/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + check_file ("sample.txt", sample, sizeof sample - 1); +} diff --git a/pintos-progos/tests/userprog/read-normal.ck b/pintos-progos/tests/userprog/read-normal.ck new file mode 100644 index 0000000..0ed2998 --- /dev/null +++ b/pintos-progos/tests/userprog/read-normal.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(read-normal) begin +(read-normal) open "sample.txt" for verification +(read-normal) verified contents of "sample.txt" +(read-normal) close "sample.txt" +(read-normal) end +read-normal: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/read-stdout.c b/pintos-progos/tests/userprog/read-stdout.c new file mode 100644 index 0000000..d0630b9 --- /dev/null +++ b/pintos-progos/tests/userprog/read-stdout.c @@ -0,0 +1,14 @@ +/* Try reading from fd 1 (stdout), + which may just fail or terminate the process with -1 exit + code. */ + +#include +#include +#include "tests/main.h" + +void +test_main (void) +{ + char buf; + read (STDOUT_FILENO, &buf, 1); +} diff --git a/pintos-progos/tests/userprog/read-stdout.ck b/pintos-progos/tests/userprog/read-stdout.ck new file mode 100644 index 0000000..7d87b52 --- /dev/null +++ b/pintos-progos/tests/userprog/read-stdout.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(read-stdout) begin +(read-stdout) end +read-stdout: exit(0) +EOF +(read-stdout) begin +read-stdout: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/read-zero.c b/pintos-progos/tests/userprog/read-zero.c new file mode 100644 index 0000000..e441817 --- /dev/null +++ b/pintos-progos/tests/userprog/read-zero.c @@ -0,0 +1,22 @@ +/* Try a 0-byte read, which should return 0 without reading + anything. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle, byte_cnt; + char buf; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + buf = 123; + byte_cnt = read (handle, &buf, 0); + if (byte_cnt != 0) + fail ("read() returned %d instead of 0", byte_cnt); + else if (buf != 123) + fail ("0-byte read() modified buffer"); +} diff --git a/pintos-progos/tests/userprog/read-zero.ck b/pintos-progos/tests/userprog/read-zero.ck new file mode 100644 index 0000000..8346dbc --- /dev/null +++ b/pintos-progos/tests/userprog/read-zero.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(read-zero) begin +(read-zero) open "sample.txt" +(read-zero) end +read-zero: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/rox-child.c b/pintos-progos/tests/userprog/rox-child.c new file mode 100644 index 0000000..30afba2 --- /dev/null +++ b/pintos-progos/tests/userprog/rox-child.c @@ -0,0 +1,5 @@ +/* Ensure that the executable of a running process cannot be + modified, even by a child process. */ + +#define CHILD_CNT "1" +#include "tests/userprog/rox-child.inc" diff --git a/pintos-progos/tests/userprog/rox-child.ck b/pintos-progos/tests/userprog/rox-child.ck new file mode 100644 index 0000000..e6363fb --- /dev/null +++ b/pintos-progos/tests/userprog/rox-child.ck @@ -0,0 +1,20 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(rox-child) begin +(rox-child) open "child-rox" +(rox-child) read "child-rox" +(rox-child) write "child-rox" +(rox-child) exec "child-rox 1" +(child-rox) begin +(child-rox) try to write "child-rox" +(child-rox) try to write "child-rox" +(child-rox) end +child-rox: exit(12) +(rox-child) write "child-rox" +(rox-child) end +rox-child: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/rox-child.inc b/pintos-progos/tests/userprog/rox-child.inc new file mode 100644 index 0000000..1e2ade9 --- /dev/null +++ b/pintos-progos/tests/userprog/rox-child.inc @@ -0,0 +1,33 @@ +/* -*- c -*- */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + const char *child_cmd = "child-rox " CHILD_CNT; + int handle; + pid_t child; + char buffer[16]; + + /* Open child-rox, read from it, write back same data. */ + CHECK ((handle = open ("child-rox")) > 1, "open \"child-rox\""); + CHECK (read (handle, buffer, sizeof buffer) == (int) sizeof buffer, + "read \"child-rox\""); + seek (handle, 0); + CHECK (write (handle, buffer, sizeof buffer) == (int) sizeof buffer, + "write \"child-rox\""); + + /* Execute child-rox and wait for it. */ + CHECK ((child = exec (child_cmd)) != -1, "exec \"%s\"", child_cmd); + quiet = true; + CHECK (wait (child) == 12, "wait for child"); + quiet = false; + + /* Write to child-rox again. */ + seek (handle, 0); + CHECK (write (handle, buffer, sizeof buffer) == (int) sizeof buffer, + "write \"child-rox\""); +} diff --git a/pintos-progos/tests/userprog/rox-multichild.c b/pintos-progos/tests/userprog/rox-multichild.c new file mode 100644 index 0000000..8e74dab --- /dev/null +++ b/pintos-progos/tests/userprog/rox-multichild.c @@ -0,0 +1,5 @@ +/* Ensure that the executable of a running process cannot be + modified, even in the presence of multiple children. */ + +#define CHILD_CNT "5" +#include "tests/userprog/rox-child.inc" diff --git a/pintos-progos/tests/userprog/rox-multichild.ck b/pintos-progos/tests/userprog/rox-multichild.ck new file mode 100644 index 0000000..14b27db --- /dev/null +++ b/pintos-progos/tests/userprog/rox-multichild.ck @@ -0,0 +1,44 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(rox-multichild) begin +(rox-multichild) open "child-rox" +(rox-multichild) read "child-rox" +(rox-multichild) write "child-rox" +(rox-multichild) exec "child-rox 5" +(child-rox) begin +(child-rox) try to write "child-rox" +(child-rox) exec "child-rox 4" +(child-rox) begin +(child-rox) try to write "child-rox" +(child-rox) exec "child-rox 3" +(child-rox) begin +(child-rox) try to write "child-rox" +(child-rox) exec "child-rox 2" +(child-rox) begin +(child-rox) try to write "child-rox" +(child-rox) exec "child-rox 1" +(child-rox) begin +(child-rox) try to write "child-rox" +(child-rox) try to write "child-rox" +(child-rox) end +child-rox: exit(12) +(child-rox) try to write "child-rox" +(child-rox) end +child-rox: exit(12) +(child-rox) try to write "child-rox" +(child-rox) end +child-rox: exit(12) +(child-rox) try to write "child-rox" +(child-rox) end +child-rox: exit(12) +(child-rox) try to write "child-rox" +(child-rox) end +child-rox: exit(12) +(rox-multichild) write "child-rox" +(rox-multichild) end +rox-multichild: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/rox-simple.c b/pintos-progos/tests/userprog/rox-simple.c new file mode 100644 index 0000000..e84a064 --- /dev/null +++ b/pintos-progos/tests/userprog/rox-simple.c @@ -0,0 +1,19 @@ +/* Ensure that the executable of a running process cannot be + modified. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + char buffer[16]; + + CHECK ((handle = open ("rox-simple")) > 1, "open \"rox-simple\""); + CHECK (read (handle, buffer, sizeof buffer) == (int) sizeof buffer, + "read \"rox-simple\""); + CHECK (write (handle, buffer, sizeof buffer) == 0, + "try to write \"rox-simple\""); +} diff --git a/pintos-progos/tests/userprog/rox-simple.ck b/pintos-progos/tests/userprog/rox-simple.ck new file mode 100644 index 0000000..c9dcc66 --- /dev/null +++ b/pintos-progos/tests/userprog/rox-simple.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(rox-simple) begin +(rox-simple) open "rox-simple" +(rox-simple) read "rox-simple" +(rox-simple) try to write "rox-simple" +(rox-simple) end +rox-simple: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/sample.inc b/pintos-progos/tests/userprog/sample.inc new file mode 100644 index 0000000..59f2bcb --- /dev/null +++ b/pintos-progos/tests/userprog/sample.inc @@ -0,0 +1,6 @@ +char sample[] = { + "\"Amazing Electronic Fact: If you scuffed your feet long enough without\n" + " touching anything, you would build up so many electrons that your\n" + " finger would explode! But this is nothing to worry about unless you\n" + " have carpeting.\" --Dave Barry\n" +}; diff --git a/pintos-progos/tests/userprog/sample.txt b/pintos-progos/tests/userprog/sample.txt new file mode 100644 index 0000000..5050fec --- /dev/null +++ b/pintos-progos/tests/userprog/sample.txt @@ -0,0 +1,4 @@ +"Amazing Electronic Fact: If you scuffed your feet long enough without + touching anything, you would build up so many electrons that your + finger would explode! But this is nothing to worry about unless you + have carpeting." --Dave Barry diff --git a/pintos-progos/tests/userprog/sc-bad-arg.c b/pintos-progos/tests/userprog/sc-bad-arg.c new file mode 100644 index 0000000..0b512a0 --- /dev/null +++ b/pintos-progos/tests/userprog/sc-bad-arg.c @@ -0,0 +1,17 @@ +/* Sticks a system call number (SYS_EXIT) at the very top of the + stack, then invokes a system call with the stack pointer + (%esp) set to its address. The process must be terminated + with -1 exit code because the argument to the system call + would be above the top of the user address space. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile ("movl $0xbffffffc, %%esp; movl %0, (%%esp); int $0x30" + : : "i" (SYS_EXIT)); + fail ("should have called exit(-1)"); +} diff --git a/pintos-progos/tests/userprog/sc-bad-arg.ck b/pintos-progos/tests/userprog/sc-bad-arg.ck new file mode 100644 index 0000000..8981105 --- /dev/null +++ b/pintos-progos/tests/userprog/sc-bad-arg.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(sc-bad-arg) begin +sc-bad-arg: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/sc-bad-sp.c b/pintos-progos/tests/userprog/sc-bad-sp.c new file mode 100644 index 0000000..39cce84 --- /dev/null +++ b/pintos-progos/tests/userprog/sc-bad-sp.c @@ -0,0 +1,20 @@ +/* Invokes a system call with the stack pointer (%esp) set to a + bad address. The process must be terminated with -1 exit + code. + + For Project 3: The bad address lies approximately 64MB below + the code segment, so there is no ambiguity that this attempt + must be rejected even after stack growth is implemented. + Moreover, a good stack growth heuristics should probably not + grow the stack for the purpose of reading the system call + number and arguments. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile ("movl $.-(64*1024*1024), %esp; int $0x30"); + fail ("should have called exit(-1)"); +} diff --git a/pintos-progos/tests/userprog/sc-bad-sp.ck b/pintos-progos/tests/userprog/sc-bad-sp.ck new file mode 100644 index 0000000..498cec1 --- /dev/null +++ b/pintos-progos/tests/userprog/sc-bad-sp.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(sc-bad-sp) begin +sc-bad-sp: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/sc-boundary-2.c b/pintos-progos/tests/userprog/sc-boundary-2.c new file mode 100644 index 0000000..8acf036 --- /dev/null +++ b/pintos-progos/tests/userprog/sc-boundary-2.c @@ -0,0 +1,22 @@ +/* Invokes a system call with one byte of the system call's + argument on a separate page from the rest of the bytes. This + must work. */ + +#include +#include "tests/userprog/boundary.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + /* Make one byte of a syscall argument hang over into a second + page. */ + int *p = (int *) ((char *) get_boundary_area () - 7); + p[0] = SYS_EXIT; + p[1] = 67; + + /* Invoke the system call. */ + asm volatile ("movl %0, %%esp; int $0x30" : : "g" (p)); + fail ("should have called exit(67)"); +} diff --git a/pintos-progos/tests/userprog/sc-boundary-2.ck b/pintos-progos/tests/userprog/sc-boundary-2.ck new file mode 100644 index 0000000..43766bf --- /dev/null +++ b/pintos-progos/tests/userprog/sc-boundary-2.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(sc-boundary-2) begin +sc-boundary-2: exit(67) +EOF +pass; diff --git a/pintos-progos/tests/userprog/sc-boundary.c b/pintos-progos/tests/userprog/sc-boundary.c new file mode 100644 index 0000000..d889535 --- /dev/null +++ b/pintos-progos/tests/userprog/sc-boundary.c @@ -0,0 +1,22 @@ +/* Invokes a system call with the system call number and its + argument on separate pages. This must work. */ + +#include +#include "tests/userprog/boundary.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + /* Put a syscall number at the end of one page + and its argument at the beginning of another. */ + int *p = get_boundary_area (); + p--; + p[0] = SYS_EXIT; + p[1] = 42; + + /* Invoke the system call. */ + asm volatile ("movl %0, %%esp; int $0x30" : : "g" (p)); + fail ("should have called exit(42)"); +} diff --git a/pintos-progos/tests/userprog/sc-boundary.ck b/pintos-progos/tests/userprog/sc-boundary.ck new file mode 100644 index 0000000..3f7cbaf --- /dev/null +++ b/pintos-progos/tests/userprog/sc-boundary.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(sc-boundary) begin +sc-boundary: exit(42) +EOF +pass; diff --git a/pintos-progos/tests/userprog/wait-bad-pid.c b/pintos-progos/tests/userprog/wait-bad-pid.c new file mode 100644 index 0000000..3fe8ee4 --- /dev/null +++ b/pintos-progos/tests/userprog/wait-bad-pid.c @@ -0,0 +1,11 @@ +/* Waits for an invalid pid. This may fail or terminate the + process with -1 exit code. */ + +#include +#include "tests/main.h" + +void +test_main (void) +{ + wait ((pid_t) 0x0c020301); +} diff --git a/pintos-progos/tests/userprog/wait-bad-pid.ck b/pintos-progos/tests/userprog/wait-bad-pid.ck new file mode 100644 index 0000000..db63fb9 --- /dev/null +++ b/pintos-progos/tests/userprog/wait-bad-pid.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(wait-bad-pid) begin +(wait-bad-pid) end +wait-bad-pid: exit(0) +EOF +(wait-bad-pid) begin +wait-bad-pid: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/wait-killed.c b/pintos-progos/tests/userprog/wait-killed.c new file mode 100644 index 0000000..6a2a6b5 --- /dev/null +++ b/pintos-progos/tests/userprog/wait-killed.c @@ -0,0 +1,11 @@ +/* Wait for a process that will be killed for bad behavior. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("wait(exec()) = %d", wait (exec ("child-bad"))); +} diff --git a/pintos-progos/tests/userprog/wait-killed.ck b/pintos-progos/tests/userprog/wait-killed.ck new file mode 100644 index 0000000..5df0e9c --- /dev/null +++ b/pintos-progos/tests/userprog/wait-killed.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(wait-killed) begin +(child-bad) begin +child-bad: exit(-1) +(wait-killed) wait(exec()) = -1 +(wait-killed) end +wait-killed: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/wait-simple.c b/pintos-progos/tests/userprog/wait-simple.c new file mode 100644 index 0000000..d3afcf3 --- /dev/null +++ b/pintos-progos/tests/userprog/wait-simple.c @@ -0,0 +1,11 @@ +/* Wait for a subprocess to finish. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("wait(exec()) = %d", wait (exec ("child-simple"))); +} diff --git a/pintos-progos/tests/userprog/wait-simple.ck b/pintos-progos/tests/userprog/wait-simple.ck new file mode 100644 index 0000000..93dd577 --- /dev/null +++ b/pintos-progos/tests/userprog/wait-simple.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(wait-simple) begin +(child-simple) run +child-simple: exit(81) +(wait-simple) wait(exec()) = 81 +(wait-simple) end +wait-simple: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/wait-twice.c b/pintos-progos/tests/userprog/wait-twice.c new file mode 100644 index 0000000..785e684 --- /dev/null +++ b/pintos-progos/tests/userprog/wait-twice.c @@ -0,0 +1,15 @@ +/* Wait for a subprocess to finish, twice. + The first call must wait in the usual way and return the exit code. + The second wait call must return -1 immediately. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + pid_t child = exec ("child-simple"); + msg ("wait(exec()) = %d", wait (child)); + msg ("wait(exec()) = %d", wait (child)); +} diff --git a/pintos-progos/tests/userprog/wait-twice.ck b/pintos-progos/tests/userprog/wait-twice.ck new file mode 100644 index 0000000..6d53843 --- /dev/null +++ b/pintos-progos/tests/userprog/wait-twice.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(wait-twice) begin +(child-simple) run +child-simple: exit(81) +(wait-twice) wait(exec()) = 81 +(wait-twice) wait(exec()) = -1 +(wait-twice) end +wait-twice: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/write-bad-fd.c b/pintos-progos/tests/userprog/write-bad-fd.c new file mode 100644 index 0000000..f3b1151 --- /dev/null +++ b/pintos-progos/tests/userprog/write-bad-fd.c @@ -0,0 +1,20 @@ +/* Tries to write to an invalid fd, + which must either fail silently or terminate the process with + exit code -1. */ + +#include +#include +#include "tests/main.h" + +void +test_main (void) +{ + char buf = 123; + write (0x01012342, &buf, 1); + write (7, &buf, 1); + write (2546, &buf, 1); + write (-5, &buf, 1); + write (-8192, &buf, 1); + write (INT_MIN + 1, &buf, 1); + write (INT_MAX - 1, &buf, 1); +} diff --git a/pintos-progos/tests/userprog/write-bad-fd.ck b/pintos-progos/tests/userprog/write-bad-fd.ck new file mode 100644 index 0000000..8da7a8b --- /dev/null +++ b/pintos-progos/tests/userprog/write-bad-fd.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(write-bad-fd) begin +(write-bad-fd) end +write-bad-fd: exit(0) +EOF +(write-bad-fd) begin +write-bad-fd: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/write-bad-ptr.c b/pintos-progos/tests/userprog/write-bad-ptr.c new file mode 100644 index 0000000..5336479 --- /dev/null +++ b/pintos-progos/tests/userprog/write-bad-ptr.c @@ -0,0 +1,16 @@ +/* Passes an invalid pointer to the write system call. + The process must be terminated with -1 exit code. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + write (handle, (char *) 0x10123420, 123); + fail ("should have exited with -1"); +} diff --git a/pintos-progos/tests/userprog/write-bad-ptr.ck b/pintos-progos/tests/userprog/write-bad-ptr.ck new file mode 100644 index 0000000..ad9f399 --- /dev/null +++ b/pintos-progos/tests/userprog/write-bad-ptr.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(write-bad-ptr) begin +(write-bad-ptr) open "sample.txt" +(write-bad-ptr) end +write-bad-ptr: exit(0) +EOF +(write-bad-ptr) begin +(write-bad-ptr) open "sample.txt" +write-bad-ptr: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/write-boundary.c b/pintos-progos/tests/userprog/write-boundary.c new file mode 100644 index 0000000..d2de1d4 --- /dev/null +++ b/pintos-progos/tests/userprog/write-boundary.c @@ -0,0 +1,25 @@ +/* Writes data spanning two pages in virtual address space, + which must succeed. */ + +#include +#include +#include "tests/userprog/boundary.h" +#include "tests/userprog/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + int byte_cnt; + char *sample_p; + + sample_p = copy_string_across_boundary (sample); + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + byte_cnt = write (handle, sample_p, sizeof sample - 1); + if (byte_cnt != sizeof sample - 1) + fail ("write() returned %d instead of %zu", byte_cnt, sizeof sample - 1); +} diff --git a/pintos-progos/tests/userprog/write-boundary.ck b/pintos-progos/tests/userprog/write-boundary.ck new file mode 100644 index 0000000..7883781 --- /dev/null +++ b/pintos-progos/tests/userprog/write-boundary.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(write-boundary) begin +(write-boundary) open "sample.txt" +(write-boundary) end +write-boundary: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/write-normal.c b/pintos-progos/tests/userprog/write-normal.c new file mode 100644 index 0000000..e0297aa --- /dev/null +++ b/pintos-progos/tests/userprog/write-normal.c @@ -0,0 +1,20 @@ +/* Try writing a file in the most normal way. */ + +#include +#include "tests/userprog/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle, byte_cnt; + + CHECK (create ("test.txt", sizeof sample - 1), "create \"test.txt\""); + CHECK ((handle = open ("test.txt")) > 1, "open \"test.txt\""); + + byte_cnt = write (handle, sample, sizeof sample - 1); + if (byte_cnt != sizeof sample - 1) + fail ("write() returned %d instead of %zu", byte_cnt, sizeof sample - 1); +} + diff --git a/pintos-progos/tests/userprog/write-normal.ck b/pintos-progos/tests/userprog/write-normal.ck new file mode 100644 index 0000000..9fa6024 --- /dev/null +++ b/pintos-progos/tests/userprog/write-normal.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(write-normal) begin +(write-normal) create "test.txt" +(write-normal) open "test.txt" +(write-normal) end +write-normal: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/userprog/write-stdin.c b/pintos-progos/tests/userprog/write-stdin.c new file mode 100644 index 0000000..491ea53 --- /dev/null +++ b/pintos-progos/tests/userprog/write-stdin.c @@ -0,0 +1,14 @@ +/* Try writing to fd 0 (stdin), + which may just fail or terminate the process with -1 exit + code. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char buf = 123; + write (0, &buf, 1); +} diff --git a/pintos-progos/tests/userprog/write-stdin.ck b/pintos-progos/tests/userprog/write-stdin.ck new file mode 100644 index 0000000..a6caf81 --- /dev/null +++ b/pintos-progos/tests/userprog/write-stdin.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(write-stdin) begin +(write-stdin) end +write-stdin: exit(0) +EOF +(write-stdin) begin +write-stdin: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/userprog/write-zero.c b/pintos-progos/tests/userprog/write-zero.c new file mode 100644 index 0000000..d8dac9b --- /dev/null +++ b/pintos-progos/tests/userprog/write-zero.c @@ -0,0 +1,20 @@ +/* Try a 0-byte write, which should return 0 without writing + anything. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle, byte_cnt; + char buf; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + buf = 123; + byte_cnt = write (handle, &buf, 0); + if (byte_cnt != 0) + fail("write() returned %d instead of 0", byte_cnt); +} diff --git a/pintos-progos/tests/userprog/write-zero.ck b/pintos-progos/tests/userprog/write-zero.ck new file mode 100644 index 0000000..cc4cd60 --- /dev/null +++ b/pintos-progos/tests/userprog/write-zero.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(write-zero) begin +(write-zero) open "sample.txt" +(write-zero) end +write-zero: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/vm/Grading b/pintos-progos/tests/vm/Grading new file mode 100644 index 0000000..f0c2c13 --- /dev/null +++ b/pintos-progos/tests/vm/Grading @@ -0,0 +1,12 @@ +# Percentage of the testing point total designated for each set of +# tests. + +# This project is primarily about virtual memory, but all the previous +# functionality should work too, and it's easy to screw it up, thus +# the equal weight placed on each. + +50% tests/vm/Rubric.functionality +15% tests/vm/Rubric.robustness +10% tests/userprog/Rubric.functionality +5% tests/userprog/Rubric.robustness +20% tests/filesys/base/Rubric diff --git a/pintos-progos/tests/vm/Make.tests b/pintos-progos/tests/vm/Make.tests new file mode 100644 index 0000000..0307ccc --- /dev/null +++ b/pintos-progos/tests/vm/Make.tests @@ -0,0 +1,107 @@ +# -*- makefile -*- + +tests/vm_TESTS = $(addprefix tests/vm/,pt-grow-stack pt-grow-pusha \ +pt-grow-bad pt-big-stk-obj pt-bad-addr pt-bad-read pt-write-code \ +pt-write-code2 pt-grow-stk-sc mmap-read \ +mmap-close mmap-unmap mmap-overlap mmap-twice mmap-write mmap-exit \ +mmap-shuffle mmap-bad-fd mmap-clean mmap-inherit mmap-misalign \ +mmap-null mmap-over-code mmap-over-data mmap-over-stk mmap-remove \ +mmap-zero mmap-lazy-seq) + +# Deactivated +PAGE_TESTS=page-linear page-parallel page-merge-seq \ +page-merge-par page-merge-stk page-merge-mm page-shuffle + +tests/vm_PROGS = $(tests/vm_TESTS) $(addprefix tests/vm/,child-linear \ +child-sort child-qsort child-qsort-mm child-mm-wrt child-inherit) + +tests/vm/pt-grow-stack_SRC = tests/vm/pt-grow-stack.c tests/arc4.c \ +tests/cksum.c tests/lib.c tests/main.c +tests/vm/pt-grow-pusha_SRC = tests/vm/pt-grow-pusha.c tests/lib.c \ +tests/main.c +tests/vm/pt-grow-bad_SRC = tests/vm/pt-grow-bad.c tests/lib.c tests/main.c +tests/vm/pt-big-stk-obj_SRC = tests/vm/pt-big-stk-obj.c tests/arc4.c \ +tests/cksum.c tests/lib.c tests/main.c +tests/vm/pt-bad-addr_SRC = tests/vm/pt-bad-addr.c tests/lib.c tests/main.c +tests/vm/pt-bad-read_SRC = tests/vm/pt-bad-read.c tests/lib.c tests/main.c +tests/vm/pt-write-code_SRC = tests/vm/pt-write-code.c tests/lib.c tests/main.c +tests/vm/pt-write-code2_SRC = tests/vm/pt-write-code-2.c tests/lib.c tests/main.c +tests/vm/pt-grow-stk-sc_SRC = tests/vm/pt-grow-stk-sc.c tests/lib.c tests/main.c +tests/vm/page-linear_SRC = tests/vm/page-linear.c tests/arc4.c \ +tests/lib.c tests/main.c +tests/vm/page-parallel_SRC = tests/vm/page-parallel.c tests/lib.c tests/main.c +tests/vm/page-merge-seq_SRC = tests/vm/page-merge-seq.c tests/arc4.c \ +tests/lib.c tests/main.c +tests/vm/page-merge-par_SRC = tests/vm/page-merge-par.c \ +tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c +tests/vm/page-merge-stk_SRC = tests/vm/page-merge-stk.c \ +tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c +tests/vm/page-merge-mm_SRC = tests/vm/page-merge-mm.c \ +tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c +tests/vm/page-shuffle_SRC = tests/vm/page-shuffle.c tests/arc4.c \ +tests/cksum.c tests/lib.c tests/main.c +tests/vm/mmap-read_SRC = tests/vm/mmap-read.c tests/lib.c tests/main.c +tests/vm/mmap-close_SRC = tests/vm/mmap-close.c tests/lib.c tests/main.c +tests/vm/mmap-unmap_SRC = tests/vm/mmap-unmap.c tests/lib.c tests/main.c +tests/vm/mmap-overlap_SRC = tests/vm/mmap-overlap.c tests/lib.c tests/main.c +tests/vm/mmap-twice_SRC = tests/vm/mmap-twice.c tests/lib.c tests/main.c +tests/vm/mmap-write_SRC = tests/vm/mmap-write.c tests/lib.c tests/main.c +tests/vm/mmap-lazy-seq_SRC = tests/vm/mmap-lazy-seq.c tests/lib.c tests/main.c +tests/vm/mmap-exit_SRC = tests/vm/mmap-exit.c tests/lib.c tests/main.c +tests/vm/mmap-shuffle_SRC = tests/vm/mmap-shuffle.c tests/arc4.c \ +tests/cksum.c tests/lib.c tests/main.c +tests/vm/mmap-bad-fd_SRC = tests/vm/mmap-bad-fd.c tests/lib.c tests/main.c +tests/vm/mmap-clean_SRC = tests/vm/mmap-clean.c tests/lib.c tests/main.c +tests/vm/mmap-inherit_SRC = tests/vm/mmap-inherit.c tests/lib.c tests/main.c +tests/vm/mmap-misalign_SRC = tests/vm/mmap-misalign.c tests/lib.c \ +tests/main.c +tests/vm/mmap-null_SRC = tests/vm/mmap-null.c tests/lib.c tests/main.c +tests/vm/mmap-over-code_SRC = tests/vm/mmap-over-code.c tests/lib.c \ +tests/main.c +tests/vm/mmap-over-data_SRC = tests/vm/mmap-over-data.c tests/lib.c \ +tests/main.c +tests/vm/mmap-over-stk_SRC = tests/vm/mmap-over-stk.c tests/lib.c tests/main.c +tests/vm/mmap-remove_SRC = tests/vm/mmap-remove.c tests/lib.c tests/main.c +tests/vm/mmap-zero_SRC = tests/vm/mmap-zero.c tests/lib.c tests/main.c + +tests/vm/child-linear_SRC = tests/vm/child-linear.c tests/arc4.c tests/lib.c +tests/vm/child-qsort_SRC = tests/vm/child-qsort.c tests/vm/qsort.c tests/lib.c +tests/vm/child-qsort-mm_SRC = tests/vm/child-qsort-mm.c tests/vm/qsort.c \ +tests/lib.c +tests/vm/child-sort_SRC = tests/vm/child-sort.c tests/lib.c +tests/vm/child-mm-wrt_SRC = tests/vm/child-mm-wrt.c tests/lib.c tests/main.c +tests/vm/child-inherit_SRC = tests/vm/child-inherit.c tests/lib.c tests/main.c + +tests/vm/pt-bad-read_PUTFILES = tests/vm/sample.txt +tests/vm/pt-write-code2_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-close_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-read_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-unmap_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-twice_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-overlap_PUTFILES = tests/vm/zeros +tests/vm/mmap-exit_PUTFILES = tests/vm/child-mm-wrt +tests/vm/page-parallel_PUTFILES = tests/vm/child-linear +tests/vm/page-merge-seq_PUTFILES = tests/vm/child-sort +tests/vm/page-merge-par_PUTFILES = tests/vm/child-sort +tests/vm/page-merge-stk_PUTFILES = tests/vm/child-qsort +tests/vm/page-merge-mm_PUTFILES = tests/vm/child-qsort-mm +tests/vm/mmap-clean_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-inherit_PUTFILES = tests/vm/sample.txt tests/vm/child-inherit +tests/vm/mmap-misalign_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-null_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-over-code_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-over-data_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-over-stk_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-remove_PUTFILES = tests/vm/sample.txt + +tests/vm/page-linear.output: TIMEOUT = 300 +tests/vm/page-shuffle.output: TIMEOUT = 600 +tests/vm/mmap-shuffle.output: TIMEOUT = 600 +tests/vm/page-merge-seq.output: TIMEOUT = 600 +tests/vm/page-merge-par.output: TIMEOUT = 600 + +tests/vm/zeros: + dd if=/dev/zero of=$@ bs=1024 count=6 + +clean:: + rm -f tests/vm/zeros diff --git a/pintos-progos/tests/vm/Rubric.functionality b/pintos-progos/tests/vm/Rubric.functionality new file mode 100644 index 0000000..ede0221 --- /dev/null +++ b/pintos-progos/tests/vm/Rubric.functionality @@ -0,0 +1,21 @@ +Functionality of virtual memory subsystem: +- Test stack growth. +3 pt-grow-stack +3 pt-grow-stk-sc +3 pt-big-stk-obj +3 pt-grow-pusha + +- Test "mmap" system call. +2 mmap-read +2 mmap-write +2 mmap-shuffle + +2 mmap-twice + +2 mmap-unmap +1 mmap-exit + +3 mmap-clean + +2 mmap-close +2 mmap-remove diff --git a/pintos-progos/tests/vm/Rubric.paging b/pintos-progos/tests/vm/Rubric.paging new file mode 100644 index 0000000..cf1d871 --- /dev/null +++ b/pintos-progos/tests/vm/Rubric.paging @@ -0,0 +1,8 @@ +- Test paging behavior. +3 page-linear +3 page-parallel +3 page-shuffle +4 page-merge-seq +4 page-merge-par +4 page-merge-mm +4 page-merge-stk diff --git a/pintos-progos/tests/vm/Rubric.robustness b/pintos-progos/tests/vm/Rubric.robustness new file mode 100644 index 0000000..eca0af4 --- /dev/null +++ b/pintos-progos/tests/vm/Rubric.robustness @@ -0,0 +1,21 @@ +Robustness of virtual memory subsystem: +- Test robustness of page table support. +2 pt-bad-addr +3 pt-bad-read +2 pt-write-code +3 pt-write-code2 +4 pt-grow-bad + +- Test robustness of "mmap" system call. +1 mmap-bad-fd +1 mmap-inherit +1 mmap-null +1 mmap-zero + +2 mmap-misalign + +2 mmap-over-code +2 mmap-over-data +2 mmap-over-stk +2 mmap-overlap +4 mmap-lazy-seq diff --git a/pintos-progos/tests/vm/child-inherit.c b/pintos-progos/tests/vm/child-inherit.c new file mode 100644 index 0000000..d3186a1 --- /dev/null +++ b/pintos-progos/tests/vm/child-inherit.c @@ -0,0 +1,16 @@ +/* Child process for mmap-inherit test. + Tries to write to a mapping present in the parent. + The process must be terminated with -1 exit code. */ + +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + memset ((char *) 0x54321000, 0, 4096); + fail ("child can modify parent's memory mappings"); +} + diff --git a/pintos-progos/tests/vm/child-linear.c b/pintos-progos/tests/vm/child-linear.c new file mode 100644 index 0000000..eca3e3f --- /dev/null +++ b/pintos-progos/tests/vm/child-linear.c @@ -0,0 +1,36 @@ +/* Child process of page-parallel. + Encrypts 1 MB of zeros, then decrypts it, and ensures that + the zeros are back. */ + +#include +#include "tests/arc4.h" +#include "tests/lib.h" +#include "tests/main.h" + +const char *test_name = "child-linear"; + +#define SIZE (1024 * 1024) +static char buf[SIZE]; + +int +main (int argc, char *argv[]) +{ + const char *key = argv[argc - 1]; + struct arc4 arc4; + size_t i; + + /* Encrypt zeros. */ + arc4_init (&arc4, key, strlen (key)); + arc4_crypt (&arc4, buf, SIZE); + + /* Decrypt back to zeros. */ + arc4_init (&arc4, key, strlen (key)); + arc4_crypt (&arc4, buf, SIZE); + + /* Check that it's all zeros. */ + for (i = 0; i < SIZE; i++) + if (buf[i] != '\0') + fail ("byte %zu != 0", i); + + return 0x42; +} diff --git a/pintos-progos/tests/vm/child-mm-wrt.c b/pintos-progos/tests/vm/child-mm-wrt.c new file mode 100644 index 0000000..8419788 --- /dev/null +++ b/pintos-progos/tests/vm/child-mm-wrt.c @@ -0,0 +1,24 @@ +/* Child process of mmap-exit. + Mmaps a file and writes to it via the mmap'ing, then exits + without calling munmap. The data in the mapped region must be + written out at program termination. */ + +#include +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +#define ACTUAL ((void *) 0x10000000) + +void +test_main (void) +{ + int handle; + + CHECK (create ("sample.txt", sizeof sample), "create \"sample.txt\""); + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, ACTUAL) != MAP_FAILED, "mmap \"sample.txt\""); + memcpy (ACTUAL, sample, sizeof sample); +} + diff --git a/pintos-progos/tests/vm/child-qsort-mm.c b/pintos-progos/tests/vm/child-qsort-mm.c new file mode 100644 index 0000000..db45499 --- /dev/null +++ b/pintos-progos/tests/vm/child-qsort-mm.c @@ -0,0 +1,25 @@ +/* Mmaps a 128 kB file "sorts" the bytes in it, using quick sort, + a multi-pass divide and conquer algorithm. */ + +#include +#include +#include "tests/lib.h" +#include "tests/main.h" +#include "tests/vm/qsort.h" + +const char *test_name = "child-qsort-mm"; + +int +main (int argc UNUSED, char *argv[]) +{ + int handle; + unsigned char *p = (unsigned char *) 0x10000000; + + quiet = true; + + CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]); + CHECK (mmap (handle, p) != MAP_FAILED, "mmap \"%s\"", argv[1]); + qsort_bytes (p, 1024 * 128); + + return 80; +} diff --git a/pintos-progos/tests/vm/child-qsort.c b/pintos-progos/tests/vm/child-qsort.c new file mode 100644 index 0000000..355f4eb --- /dev/null +++ b/pintos-progos/tests/vm/child-qsort.c @@ -0,0 +1,32 @@ +/* Reads a 128 kB file onto the stack and "sorts" the bytes in + it, using quick sort, a multi-pass divide and conquer + algorithm. The sorted data is written back to the same file + in-place. */ + +#include +#include +#include "tests/lib.h" +#include "tests/main.h" +#include "tests/vm/qsort.h" + +const char *test_name = "child-qsort"; + +int +main (int argc UNUSED, char *argv[]) +{ + int handle; + unsigned char buf[128 * 1024]; + size_t size; + + quiet = true; + + CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]); + + size = read (handle, buf, sizeof buf); + qsort_bytes (buf, sizeof buf); + seek (handle, 0); + write (handle, buf, size); + close (handle); + + return 72; +} diff --git a/pintos-progos/tests/vm/child-sort.c b/pintos-progos/tests/vm/child-sort.c new file mode 100644 index 0000000..dff2c77 --- /dev/null +++ b/pintos-progos/tests/vm/child-sort.c @@ -0,0 +1,42 @@ +/* Reads a 128 kB file into static data and "sorts" the bytes in + it, using counting sort, a single-pass algorithm. The sorted + data is written back to the same file in-place. */ + +#include +#include +#include "tests/lib.h" +#include "tests/main.h" + +const char *test_name = "child-sort"; + +unsigned char buf[128 * 1024]; +size_t histogram[256]; + +int +main (int argc UNUSED, char *argv[]) +{ + int handle; + unsigned char *p; + size_t size; + size_t i; + + quiet = true; + + CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]); + + size = read (handle, buf, sizeof buf); + for (i = 0; i < size; i++) + histogram[buf[i]]++; + p = buf; + for (i = 0; i < sizeof histogram / sizeof *histogram; i++) + { + size_t j = histogram[i]; + while (j-- > 0) + *p++ = i; + } + seek (handle, 0); + write (handle, buf, size); + close (handle); + + return 123; +} diff --git a/pintos-progos/tests/vm/mmap-bad-fd.c b/pintos-progos/tests/vm/mmap-bad-fd.c new file mode 100644 index 0000000..76a7b50 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-bad-fd.c @@ -0,0 +1,15 @@ +/* Tries to mmap an invalid fd, + which must either fail silently or terminate the process with + exit code -1. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (mmap (0x5678, (void *) 0x10000000) == MAP_FAILED, + "try to mmap invalid fd"); +} + diff --git a/pintos-progos/tests/vm/mmap-bad-fd.ck b/pintos-progos/tests/vm/mmap-bad-fd.ck new file mode 100644 index 0000000..f3f58d5 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-bad-fd.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(mmap-bad-fd) begin +(mmap-bad-fd) try to mmap invalid fd +(mmap-bad-fd) end +mmap-bad-fd: exit(0) +EOF +(mmap-bad-fd) begin +(mmap-bad-fd) try to mmap invalid fd +mmap-bad-fd: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-clean.c b/pintos-progos/tests/vm/mmap-clean.c new file mode 100644 index 0000000..ea1dc9c --- /dev/null +++ b/pintos-progos/tests/vm/mmap-clean.c @@ -0,0 +1,53 @@ +/* Verifies that mmap'd regions are only written back on munmap + if the data was actually modified in memory. */ + +#include +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + static const char overwrite[] = "Now is the time for all good..."; + static char buffer[sizeof sample - 1]; + char *actual = (char *) 0x54321000; + int handle; + mapid_t map; + + /* Open file, map, verify data. */ + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK ((map = mmap (handle, actual)) != MAP_FAILED, "mmap \"sample.txt\""); + if (memcmp (actual, sample, strlen (sample))) + fail ("read of mmap'd file reported bad data"); + + /* Modify file. */ + CHECK (write (handle, overwrite, strlen (overwrite)) + == (int) strlen (overwrite), + "write \"sample.txt\""); + + /* Close mapping. Data should not be written back, because we + didn't modify it via the mapping. */ + msg ("munmap \"sample.txt\""); + munmap (map); + + /* Read file back. */ + msg ("seek \"sample.txt\""); + seek (handle, 0); + CHECK (read (handle, buffer, sizeof buffer) == sizeof buffer, + "read \"sample.txt\""); + + /* Verify that file overwrite worked. */ + if (memcmp (buffer, overwrite, strlen (overwrite)) + || memcmp (buffer + strlen (overwrite), sample + strlen (overwrite), + strlen (sample) - strlen (overwrite))) + { + if (!memcmp (buffer, sample, strlen (sample))) + fail ("munmap wrote back clean page"); + else + fail ("read surprising data from file"); + } + else + msg ("file change was retained after munmap"); +} diff --git a/pintos-progos/tests/vm/mmap-clean.ck b/pintos-progos/tests/vm/mmap-clean.ck new file mode 100644 index 0000000..1666d6c --- /dev/null +++ b/pintos-progos/tests/vm/mmap-clean.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-clean) begin +(mmap-clean) open "sample.txt" +(mmap-clean) mmap "sample.txt" +(mmap-clean) write "sample.txt" +(mmap-clean) munmap "sample.txt" +(mmap-clean) seek "sample.txt" +(mmap-clean) read "sample.txt" +(mmap-clean) file change was retained after munmap +(mmap-clean) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-close.c b/pintos-progos/tests/vm/mmap-close.c new file mode 100644 index 0000000..d016ee3 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-close.c @@ -0,0 +1,27 @@ +/* Verifies that memory mappings persist after file close. */ + +#include +#include +#include "tests/vm/sample.inc" +#include "tests/arc4.h" +#include "tests/lib.h" +#include "tests/main.h" + +#define ACTUAL ((void *) 0x10000000) + +void +test_main (void) +{ + int handle; + mapid_t map; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK ((map = mmap (handle, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\""); + + close (handle); + + if (memcmp (ACTUAL, sample, strlen (sample))) + fail ("read of mmap'd file reported bad data"); + + munmap (map); +} diff --git a/pintos-progos/tests/vm/mmap-close.ck b/pintos-progos/tests/vm/mmap-close.ck new file mode 100644 index 0000000..d15e41a --- /dev/null +++ b/pintos-progos/tests/vm/mmap-close.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-close) begin +(mmap-close) open "sample.txt" +(mmap-close) mmap "sample.txt" +(mmap-close) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-exit.c b/pintos-progos/tests/vm/mmap-exit.c new file mode 100644 index 0000000..7a2278a --- /dev/null +++ b/pintos-progos/tests/vm/mmap-exit.c @@ -0,0 +1,22 @@ +/* Executes child-mm-wrt and verifies that the writes that should + have occurred really did. */ + +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + pid_t child; + + /* Make child write file. */ + quiet = true; + CHECK ((child = exec ("child-mm-wrt")) != -1, "exec \"child-mm-wrt\""); + CHECK (wait (child) == 0, "wait for child (should return 0)"); + quiet = false; + + /* Check file contents. */ + check_file ("sample.txt", sample, sizeof sample); +} diff --git a/pintos-progos/tests/vm/mmap-exit.ck b/pintos-progos/tests/vm/mmap-exit.ck new file mode 100644 index 0000000..457d34a --- /dev/null +++ b/pintos-progos/tests/vm/mmap-exit.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-exit) begin +(child-mm-wrt) begin +(child-mm-wrt) create "sample.txt" +(child-mm-wrt) open "sample.txt" +(child-mm-wrt) mmap "sample.txt" +(child-mm-wrt) end +(mmap-exit) open "sample.txt" for verification +(mmap-exit) verified contents of "sample.txt" +(mmap-exit) close "sample.txt" +(mmap-exit) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-inherit.c b/pintos-progos/tests/vm/mmap-inherit.c new file mode 100644 index 0000000..7fa9607 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-inherit.c @@ -0,0 +1,32 @@ +/* Maps a file into memory and runs child-inherit to verify that + mappings are not inherited. */ + +#include +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char *actual = (char *) 0x54321000; + int handle; + pid_t child; + + /* Open file, map, verify data. */ + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, actual) != MAP_FAILED, "mmap \"sample.txt\""); + if (memcmp (actual, sample, strlen (sample))) + fail ("read of mmap'd file reported bad data"); + + /* Spawn child and wait. */ + CHECK ((child = exec ("child-inherit")) != -1, "exec \"child-inherit\""); + quiet = true; + CHECK (wait (child) == -1, "wait for child (should return -1)"); + quiet = false; + + /* Verify data again. */ + CHECK (!memcmp (actual, sample, strlen (sample)), + "checking that mmap'd file still has same data"); +} diff --git a/pintos-progos/tests/vm/mmap-inherit.ck b/pintos-progos/tests/vm/mmap-inherit.ck new file mode 100644 index 0000000..c54638a --- /dev/null +++ b/pintos-progos/tests/vm/mmap-inherit.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(mmap-inherit) begin +(mmap-inherit) open "sample.txt" +(mmap-inherit) mmap "sample.txt" +(mmap-inherit) exec "child-inherit" +(child-inherit) begin +child-inherit: exit(-1) +(mmap-inherit) checking that mmap'd file still has same data +(mmap-inherit) end +mmap-inherit: exit(0) +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-lazy-seq.c b/pintos-progos/tests/vm/mmap-lazy-seq.c new file mode 100644 index 0000000..b8f07bd --- /dev/null +++ b/pintos-progos/tests/vm/mmap-lazy-seq.c @@ -0,0 +1,52 @@ +/* Create a large file, and mmap it several times, writing to + different pages. Then unmaps the file, and reads the data back + to verify */ + +#include +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +/* Offset needs to be larger or equal to page size */ +#define OFFSET(i) (8192*(i)) +/* Number of times file is mmapped */ +#define N (8) +/* Size of file */ +#define FILE_SIZE (1024*1024) +/* Address for mmap */ +#define ACTUAL(i) ((void *) (0x10000000 + (i)*FILE_SIZE)) + + +void +test_main (void) +{ + int i; + int handle; + mapid_t map[N]; + char buf[1024]; + /* create file */ + CHECK (create ("sample.txt", FILE_SIZE), "create \"sample.txt\""); + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + /* mmap */ + for (i = 0; i < N; i++) { + CHECK ((map[i] = mmap (handle, ACTUAL(i))) != MAP_FAILED, "mmap \"sample.txt\""); + } + /* write */ + for (i = 0; i < N; i++) { + memcpy (buf, ACTUAL(i)+OFFSET(i+N), 1024); /* not checked */ + memcpy (ACTUAL(i)+OFFSET(i), sample, strlen (sample)); + } + /* munmap */ + for (i = 0; i < N; i++) { + munmap (map[i]); + } + /* Read back via read(). */ + for (i = 0; i < N; i++) { + seek (handle, OFFSET(i)); + read (handle, buf, strlen (sample)); + CHECK (!memcmp (buf, sample, strlen (sample)), + "compare read data against written data"); + } + close (handle); +} diff --git a/pintos-progos/tests/vm/mmap-lazy-seq.ck b/pintos-progos/tests/vm/mmap-lazy-seq.ck new file mode 100644 index 0000000..dd0e240 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-lazy-seq.ck @@ -0,0 +1,27 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-lazy-seq) begin +(mmap-lazy-seq) create "sample.txt" +(mmap-lazy-seq) open "sample.txt" +(mmap-lazy-seq) mmap "sample.txt" +(mmap-lazy-seq) mmap "sample.txt" +(mmap-lazy-seq) mmap "sample.txt" +(mmap-lazy-seq) mmap "sample.txt" +(mmap-lazy-seq) mmap "sample.txt" +(mmap-lazy-seq) mmap "sample.txt" +(mmap-lazy-seq) mmap "sample.txt" +(mmap-lazy-seq) mmap "sample.txt" +(mmap-lazy-seq) compare read data against written data +(mmap-lazy-seq) compare read data against written data +(mmap-lazy-seq) compare read data against written data +(mmap-lazy-seq) compare read data against written data +(mmap-lazy-seq) compare read data against written data +(mmap-lazy-seq) compare read data against written data +(mmap-lazy-seq) compare read data against written data +(mmap-lazy-seq) compare read data against written data +(mmap-lazy-seq) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-misalign.c b/pintos-progos/tests/vm/mmap-misalign.c new file mode 100644 index 0000000..34141a9 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-misalign.c @@ -0,0 +1,16 @@ +/* Verifies that misaligned memory mappings are disallowed. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, (void *) 0x10001234) == MAP_FAILED, + "try to mmap at misaligned address"); +} + diff --git a/pintos-progos/tests/vm/mmap-misalign.ck b/pintos-progos/tests/vm/mmap-misalign.ck new file mode 100644 index 0000000..145a2e8 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-misalign.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-misalign) begin +(mmap-misalign) open "sample.txt" +(mmap-misalign) try to mmap at misaligned address +(mmap-misalign) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-null.c b/pintos-progos/tests/vm/mmap-null.c new file mode 100644 index 0000000..f8ef075 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-null.c @@ -0,0 +1,15 @@ +/* Verifies that memory mappings at address 0 are disallowed. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, NULL) == MAP_FAILED, "try to mmap at address 0"); +} + diff --git a/pintos-progos/tests/vm/mmap-null.ck b/pintos-progos/tests/vm/mmap-null.ck new file mode 100644 index 0000000..aacdd65 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-null.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-null) begin +(mmap-null) open "sample.txt" +(mmap-null) try to mmap at address 0 +(mmap-null) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-over-code.c b/pintos-progos/tests/vm/mmap-over-code.c new file mode 100644 index 0000000..d3619a3 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-over-code.c @@ -0,0 +1,19 @@ +/* Verifies that mapping over the code segment is disallowed. */ + +#include +#include +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + uintptr_t test_main_page = ROUND_DOWN ((uintptr_t) test_main, 4096); + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, (void *) test_main_page) == MAP_FAILED, + "try to mmap over code segment"); +} + diff --git a/pintos-progos/tests/vm/mmap-over-code.ck b/pintos-progos/tests/vm/mmap-over-code.ck new file mode 100644 index 0000000..b5b23c7 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-over-code.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-over-code) begin +(mmap-over-code) open "sample.txt" +(mmap-over-code) try to mmap over code segment +(mmap-over-code) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-over-data.c b/pintos-progos/tests/vm/mmap-over-data.c new file mode 100644 index 0000000..9ea5d49 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-over-data.c @@ -0,0 +1,21 @@ +/* Verifies that mapping over the data segment is disallowed. */ + +#include +#include +#include +#include "tests/lib.h" +#include "tests/main.h" + +static char x; + +void +test_main (void) +{ + uintptr_t x_page = ROUND_DOWN ((uintptr_t) &x, 4096); + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, (void *) x_page) == MAP_FAILED, + "try to mmap over data segment"); +} + diff --git a/pintos-progos/tests/vm/mmap-over-data.ck b/pintos-progos/tests/vm/mmap-over-data.ck new file mode 100644 index 0000000..98770cc --- /dev/null +++ b/pintos-progos/tests/vm/mmap-over-data.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-over-data) begin +(mmap-over-data) open "sample.txt" +(mmap-over-data) try to mmap over data segment +(mmap-over-data) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-over-stk.c b/pintos-progos/tests/vm/mmap-over-stk.c new file mode 100644 index 0000000..4e241e8 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-over-stk.c @@ -0,0 +1,19 @@ +/* Verifies that mapping over the stack segment is disallowed. */ + +#include +#include +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + uintptr_t handle_page = ROUND_DOWN ((uintptr_t) &handle, 4096); + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, (void *) handle_page) == MAP_FAILED, + "try to mmap over stack segment"); +} + diff --git a/pintos-progos/tests/vm/mmap-over-stk.ck b/pintos-progos/tests/vm/mmap-over-stk.ck new file mode 100644 index 0000000..e6880cf --- /dev/null +++ b/pintos-progos/tests/vm/mmap-over-stk.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-over-stk) begin +(mmap-over-stk) open "sample.txt" +(mmap-over-stk) try to mmap over stack segment +(mmap-over-stk) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-overlap.c b/pintos-progos/tests/vm/mmap-overlap.c new file mode 100644 index 0000000..668ae5f --- /dev/null +++ b/pintos-progos/tests/vm/mmap-overlap.c @@ -0,0 +1,20 @@ +/* Verifies that overlapping memory mappings are disallowed. */ + +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char *start = (char *) 0x10000000; + int fd[2]; + + CHECK ((fd[0] = open ("zeros")) > 1, "open \"zeros\" once"); + CHECK (mmap (fd[0], start) != MAP_FAILED, "mmap \"zeros\""); + CHECK ((fd[1] = open ("zeros")) > 1 && fd[0] != fd[1], + "open \"zeros\" again"); + CHECK (mmap (fd[1], start + 4096) == MAP_FAILED, + "try to mmap \"zeros\" again"); +} diff --git a/pintos-progos/tests/vm/mmap-overlap.ck b/pintos-progos/tests/vm/mmap-overlap.ck new file mode 100644 index 0000000..f13801e --- /dev/null +++ b/pintos-progos/tests/vm/mmap-overlap.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-overlap) begin +(mmap-overlap) open "zeros" once +(mmap-overlap) mmap "zeros" +(mmap-overlap) open "zeros" again +(mmap-overlap) try to mmap "zeros" again +(mmap-overlap) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-read.c b/pintos-progos/tests/vm/mmap-read.c new file mode 100644 index 0000000..c0f23a1 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-read.c @@ -0,0 +1,32 @@ +/* Uses a memory mapping to read a file. */ + +#include +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char *actual = (char *) 0x10000000; + int handle; + mapid_t map; + size_t i; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK ((map = mmap (handle, actual)) != MAP_FAILED, "mmap \"sample.txt\""); + + /* Check that data is correct. */ + if (memcmp (actual, sample, strlen (sample))) + fail ("read of mmap'd file reported bad data"); + + /* Verify that data is followed by zeros. */ + for (i = strlen (sample); i < 4096; i++) + if (actual[i] != 0) + fail ("byte %zu of mmap'd region has value %02hhx (should be 0)", + i, actual[i]); + + munmap (map); + close (handle); +} diff --git a/pintos-progos/tests/vm/mmap-read.ck b/pintos-progos/tests/vm/mmap-read.ck new file mode 100644 index 0000000..95ab790 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-read.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-read) begin +(mmap-read) open "sample.txt" +(mmap-read) mmap "sample.txt" +(mmap-read) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-remove.c b/pintos-progos/tests/vm/mmap-remove.c new file mode 100644 index 0000000..5f7444d --- /dev/null +++ b/pintos-progos/tests/vm/mmap-remove.c @@ -0,0 +1,43 @@ +/* Deletes and closes file that is mapped into memory + and verifies that it can still be read through the mapping. */ + +#include +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char *actual = (char *) 0x10000000; + int handle; + mapid_t map; + size_t i; + + /* Map file. */ + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK ((map = mmap (handle, actual)) != MAP_FAILED, "mmap \"sample.txt\""); + + /* Close file and delete it. */ + close (handle); + CHECK (remove ("sample.txt"), "remove \"sample.txt\""); + CHECK (open ("sample.txt") == -1, "try to open \"sample.txt\""); + + /* Create a new file in hopes of overwriting data from the old + one, in case the file system has incorrectly freed the + file's data. */ + CHECK (create ("another", 4096 * 10), "create \"another\""); + + /* Check that mapped data is correct. */ + if (memcmp (actual, sample, strlen (sample))) + fail ("read of mmap'd file reported bad data"); + + /* Verify that data is followed by zeros. */ + for (i = strlen (sample); i < 4096; i++) + if (actual[i] != 0) + fail ("byte %zu of mmap'd region has value %02hhx (should be 0)", + i, actual[i]); + + munmap (map); +} diff --git a/pintos-progos/tests/vm/mmap-remove.ck b/pintos-progos/tests/vm/mmap-remove.ck new file mode 100644 index 0000000..d3cc938 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-remove.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-remove) begin +(mmap-remove) open "sample.txt" +(mmap-remove) mmap "sample.txt" +(mmap-remove) remove "sample.txt" +(mmap-remove) try to open "sample.txt" +(mmap-remove) create "another" +(mmap-remove) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-shuffle.c b/pintos-progos/tests/vm/mmap-shuffle.c new file mode 100644 index 0000000..29921ad --- /dev/null +++ b/pintos-progos/tests/vm/mmap-shuffle.c @@ -0,0 +1,38 @@ +/* Creates a 128 kB file and repeatedly shuffles data in it + through a memory mapping. */ + +#include +#include +#include +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +#define SIZE (128 * 1024) + +static char *buf = (char *) 0x10000000; + +void +test_main (void) +{ + size_t i; + int handle; + + /* Create file, mmap. */ + CHECK (create ("buffer", SIZE), "create \"buffer\""); + CHECK ((handle = open ("buffer")) > 1, "open \"buffer\""); + CHECK (mmap (handle, buf) != MAP_FAILED, "mmap \"buffer\""); + + /* Initialize. */ + for (i = 0; i < SIZE; i++) + buf[i] = i * 257; + msg ("init: cksum=%lu", cksum (buf, SIZE)); + + /* Shuffle repeatedly. */ + for (i = 0; i < 10; i++) + { + shuffle (buf, SIZE, 1); + msg ("shuffle %zu: cksum=%lu", i, cksum (buf, SIZE)); + } +} diff --git a/pintos-progos/tests/vm/mmap-shuffle.ck b/pintos-progos/tests/vm/mmap-shuffle.ck new file mode 100644 index 0000000..c158301 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-shuffle.ck @@ -0,0 +1,47 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::cksum; +use tests::lib; + +my ($init, @shuffle); +if (1) { + # Use precalculated values. + $init = 3115322833; + @shuffle = (1691062564, 1973575879, 1647619479, 96566261, 3885786467, + 3022003332, 3614934266, 2704001777, 735775156, 1864109763); +} else { + # Recalculate values. + my ($buf) = ""; + for my $i (0...128 * 1024 - 1) { + $buf .= chr (($i * 257) & 0xff); + } + $init = cksum ($buf); + + random_init (0); + for my $i (1...10) { + $buf = shuffle ($buf, length ($buf), 1); + push (@shuffle, cksum ($buf)); + } +} + +check_expected (IGNORE_EXIT_CODES => 1, [< +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char *actual[2] = {(char *) 0x10000000, (char *) 0x20000000}; + size_t i; + int handle[2]; + + for (i = 0; i < 2; i++) + { + CHECK ((handle[i] = open ("sample.txt")) > 1, + "open \"sample.txt\" #%zu", i); + CHECK (mmap (handle[i], actual[i]) != MAP_FAILED, + "mmap \"sample.txt\" #%zu at %p", i, (void *) actual[i]); + } + + for (i = 0; i < 2; i++) + CHECK (!memcmp (actual[i], sample, strlen (sample)), + "compare mmap'd file %zu against data", i); +} diff --git a/pintos-progos/tests/vm/mmap-twice.ck b/pintos-progos/tests/vm/mmap-twice.ck new file mode 100644 index 0000000..05e9724 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-twice.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-twice) begin +(mmap-twice) open "sample.txt" #0 +(mmap-twice) mmap "sample.txt" #0 at 0x10000000 +(mmap-twice) open "sample.txt" #1 +(mmap-twice) mmap "sample.txt" #1 at 0x20000000 +(mmap-twice) compare mmap'd file 0 against data +(mmap-twice) compare mmap'd file 1 against data +(mmap-twice) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-unmap.c b/pintos-progos/tests/vm/mmap-unmap.c new file mode 100644 index 0000000..d35a79e --- /dev/null +++ b/pintos-progos/tests/vm/mmap-unmap.c @@ -0,0 +1,23 @@ +/* Maps and unmaps a file and verifies that the mapped region is + inaccessible afterward. */ + +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +#define ACTUAL ((void *) 0x10000000) + +void +test_main (void) +{ + int handle; + mapid_t map; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK ((map = mmap (handle, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\""); + + munmap (map); + + fail ("unmapped memory is readable (%d)", *(int *) ACTUAL); +} diff --git a/pintos-progos/tests/vm/mmap-unmap.ck b/pintos-progos/tests/vm/mmap-unmap.ck new file mode 100644 index 0000000..119658c --- /dev/null +++ b/pintos-progos/tests/vm/mmap-unmap.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::vm::process_death; + +check_process_death ('mmap-unmap'); diff --git a/pintos-progos/tests/vm/mmap-write.c b/pintos-progos/tests/vm/mmap-write.c new file mode 100644 index 0000000..46e8043 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-write.c @@ -0,0 +1,32 @@ +/* Writes to a file through a mapping, and unmaps the file, + then reads the data in the file back using the read system + call to verify. */ + +#include +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +#define ACTUAL ((void *) 0x10000000) + +void +test_main (void) +{ + int handle; + mapid_t map; + char buf[1024]; + + /* Write file via mmap. */ + CHECK (create ("sample.txt", strlen (sample)), "create \"sample.txt\""); + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK ((map = mmap (handle, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\""); + memcpy (ACTUAL, sample, strlen (sample)); + munmap (map); + + /* Read back via read(). */ + read (handle, buf, strlen (sample)); + CHECK (!memcmp (buf, sample, strlen (sample)), + "compare read data against written data"); + close (handle); +} diff --git a/pintos-progos/tests/vm/mmap-write.ck b/pintos-progos/tests/vm/mmap-write.ck new file mode 100644 index 0000000..d2c9cc5 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-write.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-write) begin +(mmap-write) create "sample.txt" +(mmap-write) open "sample.txt" +(mmap-write) mmap "sample.txt" +(mmap-write) compare read data against written data +(mmap-write) end +EOF +pass; diff --git a/pintos-progos/tests/vm/mmap-zero.c b/pintos-progos/tests/vm/mmap-zero.c new file mode 100644 index 0000000..368b759 --- /dev/null +++ b/pintos-progos/tests/vm/mmap-zero.c @@ -0,0 +1,27 @@ +/* Tries to map a zero-length file, which may or may not work but + should not terminate the process or crash. + Then dereferences the address that we tried to map, + and the process must be terminated with -1 exit code. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char *data = (char *) 0x7f000000; + int handle; + + CHECK (create ("empty", 0), "create empty file \"empty\""); + CHECK ((handle = open ("empty")) > 1, "open \"empty\""); + + /* Calling mmap() might succeed or fail. We don't care. */ + msg ("mmap \"empty\""); + mmap (handle, data); + + /* Regardless of whether the call worked, *data should cause + the process to be terminated. */ + fail ("unmapped memory is readable (%d)", *data); +} + diff --git a/pintos-progos/tests/vm/mmap-zero.ck b/pintos-progos/tests/vm/mmap-zero.ck new file mode 100644 index 0000000..6033d5d --- /dev/null +++ b/pintos-progos/tests/vm/mmap-zero.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(mmap-zero) begin +(mmap-zero) create empty file "empty" +(mmap-zero) open "empty" +(mmap-zero) mmap "empty" +mmap-zero: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/vm/page-linear.c b/pintos-progos/tests/vm/page-linear.c new file mode 100644 index 0000000..652a47b --- /dev/null +++ b/pintos-progos/tests/vm/page-linear.c @@ -0,0 +1,44 @@ +/* Encrypts, then decrypts, 2 MB of memory and verifies that the + values are as they should be. */ + +#include +#include "tests/arc4.h" +#include "tests/lib.h" +#include "tests/main.h" + +#define SIZE (2 * 1024 * 1024) + +static char buf[SIZE]; + +void +test_main (void) +{ + struct arc4 arc4; + size_t i; + + /* Initialize to 0x5a. */ + msg ("initialize"); + memset (buf, 0x5a, sizeof buf); + + /* Check that it's all 0x5a. */ + msg ("read pass"); + for (i = 0; i < SIZE; i++) + if (buf[i] != 0x5a) + fail ("byte %zu != 0x5a", i); + + /* Encrypt zeros. */ + msg ("read/modify/write pass one"); + arc4_init (&arc4, "foobar", 6); + arc4_crypt (&arc4, buf, SIZE); + + /* Decrypt back to zeros. */ + msg ("read/modify/write pass two"); + arc4_init (&arc4, "foobar", 6); + arc4_crypt (&arc4, buf, SIZE); + + /* Check that it's all 0x5a. */ + msg ("read pass"); + for (i = 0; i < SIZE; i++) + if (buf[i] != 0x5a) + fail ("byte %zu != 0x5a", i); +} diff --git a/pintos-progos/tests/vm/page-linear.ck b/pintos-progos/tests/vm/page-linear.ck new file mode 100644 index 0000000..dcbc884 --- /dev/null +++ b/pintos-progos/tests/vm/page-linear.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(page-linear) begin +(page-linear) initialize +(page-linear) read pass +(page-linear) read/modify/write pass one +(page-linear) read/modify/write pass two +(page-linear) read pass +(page-linear) end +EOF +pass; diff --git a/pintos-progos/tests/vm/page-merge-mm.c b/pintos-progos/tests/vm/page-merge-mm.c new file mode 100644 index 0000000..908c71c --- /dev/null +++ b/pintos-progos/tests/vm/page-merge-mm.c @@ -0,0 +1,8 @@ +#include "tests/main.h" +#include "tests/vm/parallel-merge.h" + +void +test_main (void) +{ + parallel_merge ("child-qsort-mm", 80); +} diff --git a/pintos-progos/tests/vm/page-merge-mm.ck b/pintos-progos/tests/vm/page-merge-mm.ck new file mode 100644 index 0000000..74fa980 --- /dev/null +++ b/pintos-progos/tests/vm/page-merge-mm.ck @@ -0,0 +1,29 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(page-merge-mm) begin +(page-merge-mm) init +(page-merge-mm) sort chunk 0 +(page-merge-mm) sort chunk 1 +(page-merge-mm) sort chunk 2 +(page-merge-mm) sort chunk 3 +(page-merge-mm) sort chunk 4 +(page-merge-mm) sort chunk 5 +(page-merge-mm) sort chunk 6 +(page-merge-mm) sort chunk 7 +(page-merge-mm) wait for child 0 +(page-merge-mm) wait for child 1 +(page-merge-mm) wait for child 2 +(page-merge-mm) wait for child 3 +(page-merge-mm) wait for child 4 +(page-merge-mm) wait for child 5 +(page-merge-mm) wait for child 6 +(page-merge-mm) wait for child 7 +(page-merge-mm) merge +(page-merge-mm) verify +(page-merge-mm) success, buf_idx=1,048,576 +(page-merge-mm) end +EOF +pass; diff --git a/pintos-progos/tests/vm/page-merge-par.c b/pintos-progos/tests/vm/page-merge-par.c new file mode 100644 index 0000000..e7e1609 --- /dev/null +++ b/pintos-progos/tests/vm/page-merge-par.c @@ -0,0 +1,8 @@ +#include "tests/main.h" +#include "tests/vm/parallel-merge.h" + +void +test_main (void) +{ + parallel_merge ("child-sort", 123); +} diff --git a/pintos-progos/tests/vm/page-merge-par.ck b/pintos-progos/tests/vm/page-merge-par.ck new file mode 100644 index 0000000..31f8aa7 --- /dev/null +++ b/pintos-progos/tests/vm/page-merge-par.ck @@ -0,0 +1,29 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(page-merge-par) begin +(page-merge-par) init +(page-merge-par) sort chunk 0 +(page-merge-par) sort chunk 1 +(page-merge-par) sort chunk 2 +(page-merge-par) sort chunk 3 +(page-merge-par) sort chunk 4 +(page-merge-par) sort chunk 5 +(page-merge-par) sort chunk 6 +(page-merge-par) sort chunk 7 +(page-merge-par) wait for child 0 +(page-merge-par) wait for child 1 +(page-merge-par) wait for child 2 +(page-merge-par) wait for child 3 +(page-merge-par) wait for child 4 +(page-merge-par) wait for child 5 +(page-merge-par) wait for child 6 +(page-merge-par) wait for child 7 +(page-merge-par) merge +(page-merge-par) verify +(page-merge-par) success, buf_idx=1,048,576 +(page-merge-par) end +EOF +pass; diff --git a/pintos-progos/tests/vm/page-merge-seq.c b/pintos-progos/tests/vm/page-merge-seq.c new file mode 100644 index 0000000..12e3880 --- /dev/null +++ b/pintos-progos/tests/vm/page-merge-seq.c @@ -0,0 +1,137 @@ +/* Generates about 1 MB of random data that is then divided into + 16 chunks. A separate subprocess sorts each chunk in + sequence. Then we merge the chunks and verify that the result + is what it should be. */ + +#include +#include "tests/arc4.h" +#include "tests/lib.h" +#include "tests/main.h" + +/* This is the max file size for an older version of the Pintos + file system that had 126 direct blocks each pointing to a + single disk sector. We could raise it now. */ +#define CHUNK_SIZE (126 * 512) +#define CHUNK_CNT 16 /* Number of chunks. */ +#define DATA_SIZE (CHUNK_CNT * CHUNK_SIZE) /* Buffer size. */ + +unsigned char buf1[DATA_SIZE], buf2[DATA_SIZE]; +size_t histogram[256]; + +/* Initialize buf1 with random data, + then count the number of instances of each value within it. */ +static void +init (void) +{ + struct arc4 arc4; + size_t i; + + msg ("init"); + + arc4_init (&arc4, "foobar", 6); + arc4_crypt (&arc4, buf1, sizeof buf1); + for (i = 0; i < sizeof buf1; i++) + histogram[buf1[i]]++; +} + +/* Sort each chunk of buf1 using a subprocess. */ +static void +sort_chunks (void) +{ + size_t i; + + create ("buffer", CHUNK_SIZE); + for (i = 0; i < CHUNK_CNT; i++) + { + pid_t child; + int handle; + + msg ("sort chunk %zu", i); + + /* Write this chunk to a file. */ + quiet = true; + CHECK ((handle = open ("buffer")) > 1, "open \"buffer\""); + write (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); + close (handle); + + /* Sort with subprocess. */ + CHECK ((child = exec ("child-sort buffer")) != -1, + "exec \"child-sort buffer\""); + CHECK (wait (child) == 123, "wait for child-sort"); + + /* Read chunk back from file. */ + CHECK ((handle = open ("buffer")) > 1, "open \"buffer\""); + read (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); + close (handle); + + quiet = false; + } +} + +/* Merge the sorted chunks in buf1 into a fully sorted buf2. */ +static void +merge (void) +{ + unsigned char *mp[CHUNK_CNT]; + size_t mp_left; + unsigned char *op; + size_t i; + + msg ("merge"); + + /* Initialize merge pointers. */ + mp_left = CHUNK_CNT; + for (i = 0; i < CHUNK_CNT; i++) + mp[i] = buf1 + CHUNK_SIZE * i; + + /* Merge. */ + op = buf2; + while (mp_left > 0) + { + /* Find smallest value. */ + size_t min = 0; + for (i = 1; i < mp_left; i++) + if (*mp[i] < *mp[min]) + min = i; + + /* Append value to buf2. */ + *op++ = *mp[min]; + + /* Advance merge pointer. + Delete this chunk from the set if it's emptied. */ + if ((++mp[min] - buf1) % CHUNK_SIZE == 0) + mp[min] = mp[--mp_left]; + } +} + +static void +verify (void) +{ + size_t buf_idx; + size_t hist_idx; + + msg ("verify"); + + buf_idx = 0; + for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram; + hist_idx++) + { + while (histogram[hist_idx]-- > 0) + { + if (buf2[buf_idx] != hist_idx) + fail ("bad value %d in byte %zu", buf2[buf_idx], buf_idx); + buf_idx++; + } + } + + msg ("success, buf_idx=%'zu", buf_idx); +} + +void +test_main (void) +{ + init (); + sort_chunks (); + merge (); + verify (); +} diff --git a/pintos-progos/tests/vm/page-merge-seq.ck b/pintos-progos/tests/vm/page-merge-seq.ck new file mode 100644 index 0000000..d78f69d --- /dev/null +++ b/pintos-progos/tests/vm/page-merge-seq.ck @@ -0,0 +1,29 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(page-merge-seq) begin +(page-merge-seq) init +(page-merge-seq) sort chunk 0 +(page-merge-seq) sort chunk 1 +(page-merge-seq) sort chunk 2 +(page-merge-seq) sort chunk 3 +(page-merge-seq) sort chunk 4 +(page-merge-seq) sort chunk 5 +(page-merge-seq) sort chunk 6 +(page-merge-seq) sort chunk 7 +(page-merge-seq) sort chunk 8 +(page-merge-seq) sort chunk 9 +(page-merge-seq) sort chunk 10 +(page-merge-seq) sort chunk 11 +(page-merge-seq) sort chunk 12 +(page-merge-seq) sort chunk 13 +(page-merge-seq) sort chunk 14 +(page-merge-seq) sort chunk 15 +(page-merge-seq) merge +(page-merge-seq) verify +(page-merge-seq) success, buf_idx=1,032,192 +(page-merge-seq) end +EOF +pass; diff --git a/pintos-progos/tests/vm/page-merge-stk.c b/pintos-progos/tests/vm/page-merge-stk.c new file mode 100644 index 0000000..5eb1069 --- /dev/null +++ b/pintos-progos/tests/vm/page-merge-stk.c @@ -0,0 +1,8 @@ +#include "tests/main.h" +#include "tests/vm/parallel-merge.h" + +void +test_main (void) +{ + parallel_merge ("child-qsort", 72); +} diff --git a/pintos-progos/tests/vm/page-merge-stk.ck b/pintos-progos/tests/vm/page-merge-stk.ck new file mode 100644 index 0000000..c5bc1ae --- /dev/null +++ b/pintos-progos/tests/vm/page-merge-stk.ck @@ -0,0 +1,29 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(page-merge-stk) begin +(page-merge-stk) init +(page-merge-stk) sort chunk 0 +(page-merge-stk) sort chunk 1 +(page-merge-stk) sort chunk 2 +(page-merge-stk) sort chunk 3 +(page-merge-stk) sort chunk 4 +(page-merge-stk) sort chunk 5 +(page-merge-stk) sort chunk 6 +(page-merge-stk) sort chunk 7 +(page-merge-stk) wait for child 0 +(page-merge-stk) wait for child 1 +(page-merge-stk) wait for child 2 +(page-merge-stk) wait for child 3 +(page-merge-stk) wait for child 4 +(page-merge-stk) wait for child 5 +(page-merge-stk) wait for child 6 +(page-merge-stk) wait for child 7 +(page-merge-stk) merge +(page-merge-stk) verify +(page-merge-stk) success, buf_idx=1,048,576 +(page-merge-stk) end +EOF +pass; diff --git a/pintos-progos/tests/vm/page-parallel.c b/pintos-progos/tests/vm/page-parallel.c new file mode 100644 index 0000000..9d619e0 --- /dev/null +++ b/pintos-progos/tests/vm/page-parallel.c @@ -0,0 +1,21 @@ +/* Runs 4 child-linear processes at once. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +#define CHILD_CNT 4 + +void +test_main (void) +{ + pid_t children[CHILD_CNT]; + int i; + + for (i = 0; i < CHILD_CNT; i++) + CHECK ((children[i] = exec ("child-linear")) != -1, + "exec \"child-linear\""); + + for (i = 0; i < CHILD_CNT; i++) + CHECK (wait (children[i]) == 0x42, "wait for child %d", i); +} diff --git a/pintos-progos/tests/vm/page-parallel.ck b/pintos-progos/tests/vm/page-parallel.ck new file mode 100644 index 0000000..90c14ef --- /dev/null +++ b/pintos-progos/tests/vm/page-parallel.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(page-parallel) begin +(page-parallel) exec "child-linear" +(page-parallel) exec "child-linear" +(page-parallel) exec "child-linear" +(page-parallel) exec "child-linear" +(page-parallel) wait for child 0 +(page-parallel) wait for child 1 +(page-parallel) wait for child 2 +(page-parallel) wait for child 3 +(page-parallel) end +EOF +pass; diff --git a/pintos-progos/tests/vm/page-shuffle.c b/pintos-progos/tests/vm/page-shuffle.c new file mode 100644 index 0000000..095a9da --- /dev/null +++ b/pintos-progos/tests/vm/page-shuffle.c @@ -0,0 +1,30 @@ +/* Shuffles a 128 kB data buffer 10 times, printing the checksum + after each time. */ + +#include +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +#define SIZE (128 * 1024) + +static char buf[SIZE]; + +void +test_main (void) +{ + size_t i; + + /* Initialize. */ + for (i = 0; i < sizeof buf; i++) + buf[i] = i * 257; + msg ("init: cksum=%lu", cksum (buf, sizeof buf)); + + /* Shuffle repeatedly. */ + for (i = 0; i < 10; i++) + { + shuffle (buf, sizeof buf, 1); + msg ("shuffle %zu: cksum=%lu", i, cksum (buf, sizeof buf)); + } +} diff --git a/pintos-progos/tests/vm/page-shuffle.ck b/pintos-progos/tests/vm/page-shuffle.ck new file mode 100644 index 0000000..6447d38 --- /dev/null +++ b/pintos-progos/tests/vm/page-shuffle.ck @@ -0,0 +1,44 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::cksum; +use tests::lib; + +my ($init, @shuffle); +if (1) { + # Use precalculated values. + $init = 3115322833; + @shuffle = (1691062564, 1973575879, 1647619479, 96566261, 3885786467, + 3022003332, 3614934266, 2704001777, 735775156, 1864109763); +} else { + # Recalculate values. + my ($buf) = ""; + for my $i (0...128 * 1024 - 1) { + $buf .= chr (($i * 257) & 0xff); + } + $init = cksum ($buf); + + random_init (0); + for my $i (1...10) { + $buf = shuffle ($buf, length ($buf), 1); + push (@shuffle, cksum ($buf)); + } +} + +check_expected (IGNORE_EXIT_CODES => 1, [< +#include +#include "tests/arc4.h" +#include "tests/lib.h" +#include "tests/main.h" + +#define CHUNK_SIZE (128 * 1024) +#define CHUNK_CNT 8 /* Number of chunks. */ +#define DATA_SIZE (CHUNK_CNT * CHUNK_SIZE) /* Buffer size. */ + +unsigned char buf1[DATA_SIZE], buf2[DATA_SIZE]; +size_t histogram[256]; + +/* Initialize buf1 with random data, + then count the number of instances of each value within it. */ +static void +init (void) +{ + struct arc4 arc4; + size_t i; + + msg ("init"); + + arc4_init (&arc4, "foobar", 6); + arc4_crypt (&arc4, buf1, sizeof buf1); + for (i = 0; i < sizeof buf1; i++) + histogram[buf1[i]]++; +} + +/* Sort each chunk of buf1 using SUBPROCESS, + which is expected to return EXIT_STATUS. */ +static void +sort_chunks (const char *subprocess, int exit_status) +{ + pid_t children[CHUNK_CNT]; + size_t i; + + for (i = 0; i < CHUNK_CNT; i++) + { + char fn[128]; + char cmd[128]; + int handle; + + msg ("sort chunk %zu", i); + + /* Write this chunk to a file. */ + snprintf (fn, sizeof fn, "buf%zu", i); + create (fn, CHUNK_SIZE); + quiet = true; + CHECK ((handle = open (fn)) > 1, "open \"%s\"", fn); + write (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); + close (handle); + + /* Sort with subprocess. */ + snprintf (cmd, sizeof cmd, "%s %s", subprocess, fn); + CHECK ((children[i] = exec (cmd)) != -1, "exec \"%s\"", cmd); + quiet = false; + } + + for (i = 0; i < CHUNK_CNT; i++) + { + char fn[128]; + int handle; + + CHECK (wait (children[i]) == exit_status, "wait for child %zu", i); + + /* Read chunk back from file. */ + quiet = true; + snprintf (fn, sizeof fn, "buf%zu", i); + CHECK ((handle = open (fn)) > 1, "open \"%s\"", fn); + read (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); + close (handle); + quiet = false; + } +} + +/* Merge the sorted chunks in buf1 into a fully sorted buf2. */ +static void +merge (void) +{ + unsigned char *mp[CHUNK_CNT]; + size_t mp_left; + unsigned char *op; + size_t i; + + msg ("merge"); + + /* Initialize merge pointers. */ + mp_left = CHUNK_CNT; + for (i = 0; i < CHUNK_CNT; i++) + mp[i] = buf1 + CHUNK_SIZE * i; + + /* Merge. */ + op = buf2; + while (mp_left > 0) + { + /* Find smallest value. */ + size_t min = 0; + for (i = 1; i < mp_left; i++) + if (*mp[i] < *mp[min]) + min = i; + + /* Append value to buf2. */ + *op++ = *mp[min]; + + /* Advance merge pointer. + Delete this chunk from the set if it's emptied. */ + if ((++mp[min] - buf1) % CHUNK_SIZE == 0) + mp[min] = mp[--mp_left]; + } +} + +static void +verify (void) +{ + size_t buf_idx; + size_t hist_idx; + + msg ("verify"); + + buf_idx = 0; + for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram; + hist_idx++) + { + while (histogram[hist_idx]-- > 0) + { + if (buf2[buf_idx] != hist_idx) + fail ("bad value %d in byte %zu", buf2[buf_idx], buf_idx); + buf_idx++; + } + } + + msg ("success, buf_idx=%'zu", buf_idx); +} + +void +parallel_merge (const char *child_name, int exit_status) +{ + init (); + sort_chunks (child_name, exit_status); + merge (); + verify (); +} diff --git a/pintos-progos/tests/vm/parallel-merge.h b/pintos-progos/tests/vm/parallel-merge.h new file mode 100644 index 0000000..a6b6431 --- /dev/null +++ b/pintos-progos/tests/vm/parallel-merge.h @@ -0,0 +1,6 @@ +#ifndef TESTS_VM_PARALLEL_MERGE +#define TESTS_VM_PARALLEL_MERGE 1 + +void parallel_merge (const char *child_name, int exit_status); + +#endif /* tests/vm/parallel-merge.h */ diff --git a/pintos-progos/tests/vm/process_death.pm b/pintos-progos/tests/vm/process_death.pm new file mode 100644 index 0000000..52039a1 --- /dev/null +++ b/pintos-progos/tests/vm/process_death.pm @@ -0,0 +1,22 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; + +sub check_process_death { + my ($proc_name) = @_; + our ($test); + my (@output) = read_text_file ("$test.output"); + + common_checks ("run", @output); + @output = get_core_output ("run", @output); + fail "First line of output is not `($proc_name) begin' message.\n" + if $output[0] ne "($proc_name) begin"; + fail "Output missing '$proc_name: exit(-1)' message.\n" + if !grep ("$proc_name: exit(-1)" eq $_, @output); + fail "Output contains '($proc_name) end' message.\n" + if grep (/\($proc_name\) end/, @output); + pass; +} + +1; diff --git a/pintos-progos/tests/vm/pt-bad-addr.c b/pintos-progos/tests/vm/pt-bad-addr.c new file mode 100644 index 0000000..3ca4084 --- /dev/null +++ b/pintos-progos/tests/vm/pt-bad-addr.c @@ -0,0 +1,11 @@ +/* Accesses a bad address. + The process must be terminated with -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + fail ("bad addr read as %d", *(int *) 0x04000000); +} diff --git a/pintos-progos/tests/vm/pt-bad-addr.ck b/pintos-progos/tests/vm/pt-bad-addr.ck new file mode 100644 index 0000000..09ea039 --- /dev/null +++ b/pintos-progos/tests/vm/pt-bad-addr.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::vm::process_death; + +check_process_death ('pt-bad-addr'); diff --git a/pintos-progos/tests/vm/pt-bad-read.c b/pintos-progos/tests/vm/pt-bad-read.c new file mode 100644 index 0000000..ee791ff --- /dev/null +++ b/pintos-progos/tests/vm/pt-bad-read.c @@ -0,0 +1,16 @@ +/* Reads from a file into a bad address. + The process must be terminated with -1 exit code. */ + +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + read (handle, (char *) &handle - 4096, 1); + fail ("survived reading data into bad address"); +} diff --git a/pintos-progos/tests/vm/pt-bad-read.ck b/pintos-progos/tests/vm/pt-bad-read.ck new file mode 100644 index 0000000..1f96bb4 --- /dev/null +++ b/pintos-progos/tests/vm/pt-bad-read.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(pt-bad-read) begin +(pt-bad-read) open "sample.txt" +pt-bad-read: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/vm/pt-big-stk-obj.c b/pintos-progos/tests/vm/pt-big-stk-obj.c new file mode 100644 index 0000000..6b630ec --- /dev/null +++ b/pintos-progos/tests/vm/pt-big-stk-obj.c @@ -0,0 +1,20 @@ +/* Allocates and writes to a 64 kB object on the stack. + This must succeed. */ + +#include +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char stk_obj[65536]; + struct arc4 arc4; + + arc4_init (&arc4, "foobar", 6); + memset (stk_obj, 0, sizeof stk_obj); + arc4_crypt (&arc4, stk_obj, sizeof stk_obj); + msg ("cksum: %lu", cksum (stk_obj, sizeof stk_obj)); +} diff --git a/pintos-progos/tests/vm/pt-big-stk-obj.ck b/pintos-progos/tests/vm/pt-big-stk-obj.ck new file mode 100644 index 0000000..eb5853a --- /dev/null +++ b/pintos-progos/tests/vm/pt-big-stk-obj.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(pt-big-stk-obj) begin +(pt-big-stk-obj) cksum: 3256410166 +(pt-big-stk-obj) end +EOF +pass; diff --git a/pintos-progos/tests/vm/pt-grow-bad.c b/pintos-progos/tests/vm/pt-grow-bad.c new file mode 100644 index 0000000..d4beba2 --- /dev/null +++ b/pintos-progos/tests/vm/pt-grow-bad.c @@ -0,0 +1,14 @@ +/* Read from an address 4,096 bytes below the stack pointer. + The process must be terminated with -1 exit code. */ + +#include +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile ("movl -4096(%esp), %eax"); +} diff --git a/pintos-progos/tests/vm/pt-grow-bad.ck b/pintos-progos/tests/vm/pt-grow-bad.ck new file mode 100644 index 0000000..4c0ab8a --- /dev/null +++ b/pintos-progos/tests/vm/pt-grow-bad.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(pt-grow-bad) begin +pt-grow-bad: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/vm/pt-grow-pusha.c b/pintos-progos/tests/vm/pt-grow-pusha.c new file mode 100644 index 0000000..f9762a5 --- /dev/null +++ b/pintos-progos/tests/vm/pt-grow-pusha.c @@ -0,0 +1,20 @@ +/* Expand the stack by 32 bytes all at once using the PUSHA + instruction. + This must succeed. */ + +#include +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile + ("movl %%esp, %%eax;" /* Save a copy of the stack pointer. */ + "andl $0xfffff000, %%esp;" /* Move stack pointer to bottom of page. */ + "pushal;" /* Push 32 bytes on stack at once. */ + "movl %%eax, %%esp" /* Restore copied stack pointer. */ + : : : "eax"); /* Tell GCC we destroyed eax. */ +} diff --git a/pintos-progos/tests/vm/pt-grow-pusha.ck b/pintos-progos/tests/vm/pt-grow-pusha.ck new file mode 100644 index 0000000..5000966 --- /dev/null +++ b/pintos-progos/tests/vm/pt-grow-pusha.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(pt-grow-pusha) begin +(pt-grow-pusha) end +EOF +pass; diff --git a/pintos-progos/tests/vm/pt-grow-stack.c b/pintos-progos/tests/vm/pt-grow-stack.c new file mode 100644 index 0000000..0997a00 --- /dev/null +++ b/pintos-progos/tests/vm/pt-grow-stack.c @@ -0,0 +1,20 @@ +/* Demonstrate that the stack can grow. + This must succeed. */ + +#include +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char stack_obj[4096]; + struct arc4 arc4; + + arc4_init (&arc4, "foobar", 6); + memset (stack_obj, 0, sizeof stack_obj); + arc4_crypt (&arc4, stack_obj, sizeof stack_obj); + msg ("cksum: %lu", cksum (stack_obj, sizeof stack_obj)); +} diff --git a/pintos-progos/tests/vm/pt-grow-stack.ck b/pintos-progos/tests/vm/pt-grow-stack.ck new file mode 100644 index 0000000..1e669db --- /dev/null +++ b/pintos-progos/tests/vm/pt-grow-stack.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(pt-grow-stack) begin +(pt-grow-stack) cksum: 3424492700 +(pt-grow-stack) end +EOF +pass; diff --git a/pintos-progos/tests/vm/pt-grow-stk-sc.c b/pintos-progos/tests/vm/pt-grow-stk-sc.c new file mode 100644 index 0000000..3efbb5f --- /dev/null +++ b/pintos-progos/tests/vm/pt-grow-stk-sc.c @@ -0,0 +1,32 @@ +/* This test checks that the stack is properly extended even if + the first access to a stack location occurs inside a system + call. + + From Godmar Back. */ + +#include +#include +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + int slen = strlen (sample); + char buf2[65536]; + + /* Write file via write(). */ + CHECK (create ("sample.txt", slen), "create \"sample.txt\""); + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (write (handle, sample, slen) == slen, "write \"sample.txt\""); + close (handle); + + /* Read back via read(). */ + CHECK ((handle = open ("sample.txt")) > 1, "2nd open \"sample.txt\""); + CHECK (read (handle, buf2 + 32768, slen) == slen, "read \"sample.txt\""); + + CHECK (!memcmp (sample, buf2 + 32768, slen), "compare written data against read data"); + close (handle); +} diff --git a/pintos-progos/tests/vm/pt-grow-stk-sc.ck b/pintos-progos/tests/vm/pt-grow-stk-sc.ck new file mode 100644 index 0000000..23d3b02 --- /dev/null +++ b/pintos-progos/tests/vm/pt-grow-stk-sc.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(pt-grow-stk-sc) begin +(pt-grow-stk-sc) create "sample.txt" +(pt-grow-stk-sc) open "sample.txt" +(pt-grow-stk-sc) write "sample.txt" +(pt-grow-stk-sc) 2nd open "sample.txt" +(pt-grow-stk-sc) read "sample.txt" +(pt-grow-stk-sc) compare written data against read data +(pt-grow-stk-sc) end +EOF +pass; diff --git a/pintos-progos/tests/vm/pt-write-code-2.c b/pintos-progos/tests/vm/pt-write-code-2.c new file mode 100644 index 0000000..83bcc2c --- /dev/null +++ b/pintos-progos/tests/vm/pt-write-code-2.c @@ -0,0 +1,15 @@ +/* Try to write to the code segment using a system call. + The process must be terminated with -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + read (handle, (void *) test_main, 1); + fail ("survived reading data into code segment"); +} diff --git a/pintos-progos/tests/vm/pt-write-code.c b/pintos-progos/tests/vm/pt-write-code.c new file mode 100644 index 0000000..5072cec --- /dev/null +++ b/pintos-progos/tests/vm/pt-write-code.c @@ -0,0 +1,12 @@ +/* Try to write to the code segment. + The process must be terminated with -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + *(int *) test_main = 0; + fail ("writing the code segment succeeded"); +} diff --git a/pintos-progos/tests/vm/pt-write-code.ck b/pintos-progos/tests/vm/pt-write-code.ck new file mode 100644 index 0000000..65610fb --- /dev/null +++ b/pintos-progos/tests/vm/pt-write-code.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::vm::process_death; + +check_process_death ('pt-write-code'); diff --git a/pintos-progos/tests/vm/pt-write-code2.ck b/pintos-progos/tests/vm/pt-write-code2.ck new file mode 100644 index 0000000..69ffc77 --- /dev/null +++ b/pintos-progos/tests/vm/pt-write-code2.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(pt-write-code2) begin +(pt-write-code2) open "sample.txt" +pt-write-code2: exit(-1) +EOF +pass; diff --git a/pintos-progos/tests/vm/qsort.c b/pintos-progos/tests/vm/qsort.c new file mode 100644 index 0000000..922572c --- /dev/null +++ b/pintos-progos/tests/vm/qsort.c @@ -0,0 +1,136 @@ +#include "tests/vm/qsort.h" +#include +#include +#include + +/* Picks a pivot for the quicksort from the SIZE bytes in BUF. */ +static unsigned char +pick_pivot (unsigned char *buf, size_t size) +{ + ASSERT (size >= 1); + return buf[random_ulong () % size]; +} + +/* Checks whether the SIZE bytes in ARRAY are divided into an + initial LEFT_SIZE elements all less than PIVOT followed by + SIZE - LEFT_SIZE elements all greater than or equal to + PIVOT. */ +static bool +is_partitioned (const unsigned char *array, size_t size, + unsigned char pivot, size_t left_size) +{ + size_t i; + + for (i = 0; i < left_size; i++) + if (array[i] >= pivot) + return false; + + for (; i < size; i++) + if (array[i] < pivot) + return false; + + return true; +} + +/* Swaps the bytes at *A and *B. */ +static void +swap (unsigned char *a, unsigned char *b) +{ + unsigned char t = *a; + *a = *b; + *b = t; +} + +/* Partitions ARRAY in-place in an initial run of bytes all less + than PIVOT, followed by a run of bytes all greater than or + equal to PIVOT. Returns the length of the initial run. */ +static size_t +partition (unsigned char *array, size_t size, int pivot) +{ + size_t left_size = size; + unsigned char *first = array; + unsigned char *last = first + left_size; + + for (;;) + { + /* Move FIRST forward to point to first element greater than + PIVOT. */ + for (;;) + { + if (first == last) + { + ASSERT (is_partitioned (array, size, pivot, left_size)); + return left_size; + } + else if (*first >= pivot) + break; + + first++; + } + left_size--; + + /* Move LAST backward to point to last element no bigger + than PIVOT. */ + for (;;) + { + last--; + + if (first == last) + { + ASSERT (is_partitioned (array, size, pivot, left_size)); + return left_size; + } + else if (*last < pivot) + break; + else + left_size--; + } + + /* By swapping FIRST and LAST we extend the starting and + ending sequences that pass and fail, respectively, + PREDICATE. */ + swap (first, last); + first++; + } +} + +/* Returns true if the SIZE bytes in BUF are in nondecreasing + order, false otherwise. */ +static bool +is_sorted (const unsigned char *buf, size_t size) +{ + size_t i; + + for (i = 1; i < size; i++) + if (buf[i - 1] > buf[i]) + return false; + + return true; +} + +/* Sorts the SIZE bytes in BUF into nondecreasing order, using + the quick-sort algorithm. */ +void +qsort_bytes (unsigned char *buf, size_t size) +{ + if (!is_sorted (buf, size)) + { + int pivot = pick_pivot (buf, size); + + unsigned char *left_half = buf; + size_t left_size = partition (buf, size, pivot); + unsigned char *right_half = left_half + left_size; + size_t right_size = size - left_size; + + if (left_size <= right_size) + { + qsort_bytes (left_half, left_size); + qsort_bytes (right_half, right_size); + } + else + { + qsort_bytes (right_half, right_size); + qsort_bytes (left_half, left_size); + } + } +} diff --git a/pintos-progos/tests/vm/qsort.h b/pintos-progos/tests/vm/qsort.h new file mode 100644 index 0000000..61b65f3 --- /dev/null +++ b/pintos-progos/tests/vm/qsort.h @@ -0,0 +1,8 @@ +#ifndef TESTS_VM_QSORT_H +#define TESTS_VM_QSORT_H 1 + +#include + +void qsort_bytes (unsigned char *buf, size_t size); + +#endif /* tests/vm/qsort.h */ diff --git a/pintos-progos/tests/vm/sample.inc b/pintos-progos/tests/vm/sample.inc new file mode 100644 index 0000000..a60a139 --- /dev/null +++ b/pintos-progos/tests/vm/sample.inc @@ -0,0 +1,19 @@ +char sample[] = { + "=== ALL USERS PLEASE NOTE ========================\n" + "\n" + "CAR and CDR now return extra values.\n" + "\n" + "The function CAR now returns two values. Since it has to go to the\n" + "trouble to figure out if the object is carcdr-able anyway, we figured\n" + "you might as well get both halves at once. For example, the following\n" + "code shows how to destructure a cons (SOME-CONS) into its two slots\n" + "(THE-CAR and THE-CDR):\n" + "\n" + " (MULTIPLE-VALUE-BIND (THE-CAR THE-CDR) (CAR SOME-CONS) ...)\n" + "\n" + "For symmetry with CAR, CDR returns a second value which is the CAR of\n" + "the object. In a related change, the functions MAKE-ARRAY and CONS\n" + "have been fixed so they don't allocate any storage except on the\n" + "stack. This should hopefully help people who don't like using the\n" + "garbage collector because it cold boots the machine so often.\n" +}; diff --git a/pintos-progos/tests/vm/sample.txt b/pintos-progos/tests/vm/sample.txt new file mode 100644 index 0000000..c446830 --- /dev/null +++ b/pintos-progos/tests/vm/sample.txt @@ -0,0 +1,17 @@ +=== ALL USERS PLEASE NOTE ======================== + +CAR and CDR now return extra values. + +The function CAR now returns two values. Since it has to go to the +trouble to figure out if the object is carcdr-able anyway, we figured +you might as well get both halves at once. For example, the following +code shows how to destructure a cons (SOME-CONS) into its two slots +(THE-CAR and THE-CDR): + + (MULTIPLE-VALUE-BIND (THE-CAR THE-CDR) (CAR SOME-CONS) ...) + +For symmetry with CAR, CDR returns a second value which is the CAR of +the object. In a related change, the functions MAKE-ARRAY and CONS +have been fixed so they don't allocate any storage except on the +stack. This should hopefully help people who don't like using the +garbage collector because it cold boots the machine so often. diff --git a/pintos-progos/threads/.gitignore b/pintos-progos/threads/.gitignore new file mode 100644 index 0000000..6d5357c --- /dev/null +++ b/pintos-progos/threads/.gitignore @@ -0,0 +1,3 @@ +build +bochsrc.txt +bochsout.txt diff --git a/pintos-progos/threads/Make.vars b/pintos-progos/threads/Make.vars new file mode 100644 index 0000000..310c240 --- /dev/null +++ b/pintos-progos/threads/Make.vars @@ -0,0 +1,7 @@ +# -*- makefile -*- + +kernel.bin: DEFINES = +KERNEL_SUBDIRS = threads devices lib lib/kernel $(TEST_SUBDIRS) +TEST_SUBDIRS = tests/threads +GRADING_FILE = $(SRCDIR)/tests/threads/Grading +SIMULATOR = --bochs diff --git a/pintos-progos/threads/Makefile b/pintos-progos/threads/Makefile new file mode 100644 index 0000000..34c10aa --- /dev/null +++ b/pintos-progos/threads/Makefile @@ -0,0 +1 @@ +include ../Makefile.kernel diff --git a/pintos-progos/threads/flags.h b/pintos-progos/threads/flags.h new file mode 100644 index 0000000..5654ac7 --- /dev/null +++ b/pintos-progos/threads/flags.h @@ -0,0 +1,8 @@ +#ifndef THREADS_FLAGS_H +#define THREADS_FLAGS_H + +/* EFLAGS Register. */ +#define FLAG_MBS 0x00000002 /* Must be set. */ +#define FLAG_IF 0x00000200 /* Interrupt Flag. */ + +#endif /* threads/flags.h */ diff --git a/pintos-progos/threads/init.c b/pintos-progos/threads/init.c new file mode 100644 index 0000000..d8feacd --- /dev/null +++ b/pintos-progos/threads/init.c @@ -0,0 +1,453 @@ +#include "threads/init.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "devices/kbd.h" +#include "devices/input.h" +#include "devices/serial.h" +#include "devices/shutdown.h" +#include "devices/timer.h" +#include "devices/vga.h" +#include "devices/rtc.h" +#include "threads/interrupt.h" +#include "threads/io.h" +#include "threads/loader.h" +#include "threads/malloc.h" +#include "threads/palloc.h" +#include "threads/pte.h" +#include "threads/thread.h" +#ifdef USERPROG +#include "userprog/process.h" +#include "userprog/exception.h" +#include "userprog/gdt.h" +#include "userprog/syscall.h" +#include "userprog/tss.h" +#else +#include "tests/threads/tests.h" +#endif +#ifdef FILESYS +#include "devices/block.h" +#include "devices/ide.h" +#include "filesys/filesys.h" +#include "filesys/fsutil.h" +#endif + + +/* Page directory with kernel mappings only. */ +uint32_t *init_page_dir; + +#ifdef FILESYS +/* -f: Format the file system? */ +static bool format_filesys; + +/* -filesys, -scratch, -swap: Names of block devices to use, + overriding the defaults. */ +static const char *filesys_bdev_name; +static const char *scratch_bdev_name; +#ifdef VM +static const char *swap_bdev_name; +#endif +#endif /* FILESYS */ + +/* -kernel-test: Run kernel test instead of user program */ +static bool kernel_test = false; + +/* provide weak kernel test definition if no test is available */ +void run_test (const char *param); +__attribute__((weak)) +void +run_test (const char *param UNUSED) +{ + printf("No kernel test linked into kernel\n"); +} + + +/* -ul: Maximum number of pages to put into palloc's user pool. */ +static size_t user_page_limit = SIZE_MAX; + +static void bss_init (void); +static void paging_init (void); + +static char **read_command_line (void); +static char **parse_options (char **argv); +static void run_actions (char **argv); +static void usage (void); + +#ifdef FILESYS +static void locate_block_devices (void); +static void locate_block_device (enum block_type, const char *name); +#endif + +int main (void) NO_RETURN; + +/* Pintos main program. */ +int +main (void) +{ + char **argv; + + /* Clear BSS. */ + bss_init (); + + /* Break command line into arguments and parse options. */ + argv = read_command_line (); + argv = parse_options (argv); + + /* Initialize ourselves as a thread so we can use locks, + then enable console locking. */ + thread_init (); + console_init (); + + /* Greet user. */ + printf ("Pintos booting with %'"PRIu32" kB RAM...\n", + init_ram_pages * PGSIZE / 1024); + + /* Initialize memory system. */ + palloc_init (user_page_limit); + malloc_init (); + paging_init (); + + /* Segmentation. */ +#ifdef USERPROG + tss_init (); + gdt_init (); +#endif + + /* Initialize interrupt handlers. */ + intr_init (); + timer_init (); + kbd_init (); + input_init (); +#ifdef USERPROG + exception_init (); + syscall_init (); +#endif + + /* Start thread scheduler and enable interrupts. */ + thread_start (); + serial_init_queue (); + timer_calibrate (); + +#ifdef FILESYS + /* Initialize file system. */ + ide_init (); + locate_block_devices (); + /* kernel tests do not need filesystem */ + if (!kernel_test) + filesys_init (format_filesys); +#endif + + printf ("Boot complete.\n"); + + /* Run actions specified on kernel command line. */ + run_actions (argv); + + /* Finish up. */ + shutdown (); + thread_exit (); +} + +/* Clear the "BSS", a segment that should be initialized to + zeros. It isn't actually stored on disk or zeroed by the + kernel loader, so we have to zero it ourselves. + + The start and end of the BSS segment is recorded by the + linker as _start_bss and _end_bss. See kernel.lds. */ +static void +bss_init (void) +{ + extern char _start_bss, _end_bss; + memset (&_start_bss, 0, &_end_bss - &_start_bss); +} + +/* Populates the base page directory and page table with the + kernel virtual mapping, and then sets up the CPU to use the + new page directory. Points init_page_dir to the page + directory it creates. */ +static void +paging_init (void) +{ + uint32_t *pd, *pt; + size_t page; + extern char _start, _end_kernel_text; + + pd = init_page_dir = palloc_get_page (PAL_ASSERT | PAL_ZERO); + pt = NULL; + for (page = 0; page < init_ram_pages; page++) + { + uintptr_t paddr = page * PGSIZE; + char *vaddr = ptov (paddr); + size_t pde_idx = pd_no (vaddr); + size_t pte_idx = pt_no (vaddr); + bool in_kernel_text = &_start <= vaddr && vaddr < &_end_kernel_text; + + if (pd[pde_idx] == 0) + { + pt = palloc_get_page (PAL_ASSERT | PAL_ZERO); + pd[pde_idx] = pde_create (pt); + } + + pt[pte_idx] = pte_create_kernel (vaddr, !in_kernel_text); + } + + /* Store the physical address of the page directory into CR3 + aka PDBR (page directory base register). This activates our + new page tables immediately. See [IA32-v2a] "MOV--Move + to/from Control Registers" and [IA32-v3a] 3.7.5 "Base Address + of the Page Directory". */ + asm volatile ("movl %0, %%cr3" : : "r" (vtop (init_page_dir))); +} + +/* Breaks the kernel command line into words and returns them as + an argv-like array. */ +static char ** +read_command_line (void) +{ + static char *argv[LOADER_ARGS_LEN / 2 + 1]; + char *p, *end; + int argc; + int i; + + argc = *(uint32_t *) ptov (LOADER_ARG_CNT); + p = ptov (LOADER_ARGS); + end = p + LOADER_ARGS_LEN; + for (i = 0; i < argc; i++) + { + if (p >= end) + PANIC ("command line arguments overflow"); + + argv[i] = p; + p += strnlen (p, end - p) + 1; + } + argv[argc] = NULL; + + /* Print kernel command line. */ + printf ("Kernel command line:"); + for (i = 0; i < argc; i++) + if (strchr (argv[i], ' ') == NULL) + printf (" %s", argv[i]); + else + printf (" '%s'", argv[i]); + printf ("\n"); + + return argv; +} + +/* Parses options in ARGV[] + and returns the first non-option argument. */ +static char ** +parse_options (char **argv) +{ + for (; *argv != NULL && **argv == '-'; argv++) + { + char *save_ptr; + char *name = strtok_r (*argv, "=", &save_ptr); + char *value = strtok_r (NULL, "", &save_ptr); + +#ifndef USERPROG + kernel_test = true; +#endif + if (!strcmp (name, "-h")) + usage (); + else if (!strcmp (name, "-q")) + shutdown_configure (SHUTDOWN_POWER_OFF); + else if (!strcmp (name, "-r")) + shutdown_configure (SHUTDOWN_REBOOT); +#ifdef FILESYS + else if (!strcmp (name, "-f")) + format_filesys = true; + else if (!strcmp (name, "-filesys")) + filesys_bdev_name = value; + else if (!strcmp (name, "-scratch")) + scratch_bdev_name = value; +#ifdef VM + else if (!strcmp (name, "-swap")) + swap_bdev_name = value; +#endif +#endif + else if (!strcmp (name, "-rs")) + random_init (atoi (value)); + else if (!strcmp (name, "-mlfqs")) + thread_mlfqs = true; +#ifdef USERPROG + else if (!strcmp (name, "-kernel-test")) + kernel_test = true; + else if (!strcmp (name, "-ul")) + user_page_limit = atoi (value); +#endif + else + PANIC ("unknown option `%s' (use -h for help)", name); + } + + /* Initialize the random number generator based on the system + time. This has no effect if an "-rs" option was specified. + + When running under Bochs, this is not enough by itself to + get a good seed value, because the pintos script sets the + initial time to a predictable value, not to the local time, + for reproducibility. To fix this, give the "-r" option to + the pintos script to request real-time execution. */ + random_init (rtc_get_time ()); + + return argv; +} + +/* Runs the task specified in ARGV[1]. */ +static void +run_task (char **argv) +{ + const char *task = argv[1]; + + printf ("Executing '%s':\n", task); +#ifdef USERPROG + if (kernel_test) + run_test (task); + else + process_wait (process_execute (task)); +#else + run_test (task); +#endif + printf ("Execution of '%s' complete.\n", task); +} + +/* Executes all of the actions specified in ARGV[] + up to the null pointer sentinel. */ +static void +run_actions (char **argv) +{ + /* An action. */ + struct action + { + char *name; /* Action name. */ + int argc; /* # of args, including action name. */ + void (*function) (char **argv); /* Function to execute action. */ + }; + + /* Table of supported actions. */ + static const struct action actions[] = + { + {"run", 2, run_task}, +#ifdef FILESYS + {"ls", 1, fsutil_ls}, + {"cat", 2, fsutil_cat}, + {"rm", 2, fsutil_rm}, + {"extract", 1, fsutil_extract}, + {"append", 2, fsutil_append}, +#endif + {NULL, 0, NULL}, + }; + + while (*argv != NULL) + { + const struct action *a; + int i; + + /* Find action name. */ + for (a = actions; ; a++) + if (a->name == NULL) + PANIC ("unknown action `%s' (use -h for help)", *argv); + else if (!strcmp (*argv, a->name)) + break; + + /* Check for required arguments. */ + for (i = 1; i < a->argc; i++) + if (argv[i] == NULL) + PANIC ("action `%s' requires %d argument(s)", *argv, a->argc - 1); + + /* Invoke action and advance. */ + a->function (argv); + argv += a->argc; + } + +} + +/* Prints a kernel command line help message and powers off the + machine. */ +static void +usage (void) +{ + printf ("\nCommand line syntax: [OPTION...] [ACTION...]\n" + "Options must precede actions.\n" + "Actions are executed in the order specified.\n" + "\nAvailable actions:\n" +#ifdef USERPROG + " run 'PROG [ARG...]' Run PROG and wait for it to complete.\n" +#else + " run TEST Run TEST.\n" +#endif +#ifdef FILESYS + " ls List files in the root directory.\n" + " cat FILE Print FILE to the console.\n" + " rm FILE Delete FILE.\n" + "Use these actions indirectly via `pintos' -g and -p options:\n" + " extract Untar from scratch device into file system.\n" + " append FILE Append FILE to tar file on scratch device.\n" +#endif + "\nOptions:\n" + " -h Print this help message and power off.\n" + " -q Power off VM after actions or on panic.\n" + " -r Reboot after actions.\n" +#ifdef FILESYS + " -f Format file system device during startup.\n" + " -filesys=BDEV Use BDEV for file system instead of default.\n" + " -scratch=BDEV Use BDEV for scratch instead of default.\n" +#ifdef VM + " -swap=BDEV Use BDEV for swap instead of default.\n" +#endif +#endif + " -rs=SEED Set random number seed to SEED.\n" + " -mlfqs Use multi-level feedback queue scheduler.\n" +#ifdef USERPROG + " -ul=COUNT Limit user memory to COUNT pages.\n" +#endif + ); + shutdown_power_off (); +} + +#ifdef FILESYS +/* Figure out what block devices to cast in the various Pintos roles. */ +static void +locate_block_devices (void) +{ + locate_block_device (BLOCK_FILESYS, filesys_bdev_name); + locate_block_device (BLOCK_SCRATCH, scratch_bdev_name); +#ifdef VM + locate_block_device (BLOCK_SWAP, swap_bdev_name); +#endif +} + +/* Figures out what block device to use for the given ROLE: the + block device with the given NAME, if NAME is non-null, + otherwise the first block device in probe order of type + ROLE. */ +static void +locate_block_device (enum block_type role, const char *name) +{ + struct block *block = NULL; + + if (name != NULL) + { + block = block_get_by_name (name); + if (block == NULL) + PANIC ("No such block device \"%s\"", name); + } + else + { + for (block = block_first (); block != NULL; block = block_next (block)) + if (block_type (block) == role) + break; + } + + if (block != NULL) + { + printf ("%s: using %s\n", block_type_name (role), block_name (block)); + block_set_role (role, block); + } +} +#endif diff --git a/pintos-progos/threads/init.h b/pintos-progos/threads/init.h new file mode 100644 index 0000000..8a3df90 --- /dev/null +++ b/pintos-progos/threads/init.h @@ -0,0 +1,12 @@ +#ifndef THREADS_INIT_H +#define THREADS_INIT_H + +#include +#include +#include +#include + +/* Page directory with kernel mappings only. */ +extern uint32_t *init_page_dir; + +#endif /* threads/init.h */ diff --git a/pintos-progos/threads/interrupt.c b/pintos-progos/threads/interrupt.c new file mode 100644 index 0000000..e3b90dc --- /dev/null +++ b/pintos-progos/threads/interrupt.c @@ -0,0 +1,438 @@ +#include "threads/interrupt.h" +#include +#include +#include +#include +#include "threads/flags.h" +#include "threads/intr-stubs.h" +#include "threads/io.h" +#include "threads/thread.h" +#include "threads/vaddr.h" +#include "devices/timer.h" + +/* Programmable Interrupt Controller (PIC) registers. + A PC has two PICs, called the master and slave PICs, with the + slave attached ("cascaded") to the master IRQ line 2. */ +#define PIC0_CTRL 0x20 /* Master PIC control register address. */ +#define PIC0_DATA 0x21 /* Master PIC data register address. */ +#define PIC1_CTRL 0xa0 /* Slave PIC control register address. */ +#define PIC1_DATA 0xa1 /* Slave PIC data register address. */ + +/* Number of x86 interrupts. */ +#define INTR_CNT 256 + +/* The Interrupt Descriptor Table (IDT). The format is fixed by + the CPU. See [IA32-v3a] sections 5.10 "Interrupt Descriptor + Table (IDT)", 5.11 "IDT Descriptors", 5.12.1.2 "Flag Usage By + Exception- or Interrupt-Handler Procedure". */ +static uint64_t idt[INTR_CNT]; + +/* Interrupt handler functions for each interrupt. */ +static intr_handler_func *intr_handlers[INTR_CNT]; + +/* Names for each interrupt, for debugging purposes. */ +static const char *intr_names[INTR_CNT]; + +/* Number of unexpected interrupts for each vector. An + unexpected interrupt is one that has no registered handler. */ +static unsigned int unexpected_cnt[INTR_CNT]; + +/* External interrupts are those generated by devices outside the + CPU, such as the timer. External interrupts run with + interrupts turned off, so they never nest, nor are they ever + pre-empted. Handlers for external interrupts also may not + sleep, although they may invoke intr_yield_on_return() to + request that a new process be scheduled just before the + interrupt returns. */ +static bool in_external_intr; /* Are we processing an external interrupt? */ +static bool yield_on_return; /* Should we yield on interrupt return? */ + +/* Programmable Interrupt Controller helpers. */ +static void pic_init (void); +static void pic_end_of_interrupt (int irq); + +/* Interrupt Descriptor Table helpers. */ +static uint64_t make_intr_gate (void (*) (void), int dpl); +static uint64_t make_trap_gate (void (*) (void), int dpl); +static inline uint64_t make_idtr_operand (uint16_t limit, void *base); + +/* Interrupt handlers. */ +void intr_handler (struct intr_frame *args); +static void unexpected_interrupt (const struct intr_frame *); + +/* Returns the current interrupt status. */ +enum intr_level +intr_get_level (void) +{ + uint32_t flags; + + /* Push the flags register on the processor stack, then pop the + value off the stack into `flags'. See [IA32-v2b] "PUSHF" + and "POP" and [IA32-v3a] 5.8.1 "Masking Maskable Hardware + Interrupts". */ + asm volatile ("pushfl; popl %0" : "=g" (flags)); + + return flags & FLAG_IF ? INTR_ON : INTR_OFF; +} + +/* Enables or disables interrupts as specified by LEVEL and + returns the previous interrupt status. */ +enum intr_level +intr_set_level (enum intr_level level) +{ + return level == INTR_ON ? intr_enable () : intr_disable (); +} + +/* Enables interrupts and returns the previous interrupt status. */ +enum intr_level +intr_enable (void) +{ + enum intr_level old_level = intr_get_level (); + ASSERT (!intr_context ()); + + /* Enable interrupts by setting the interrupt flag. + + See [IA32-v2b] "STI" and [IA32-v3a] 5.8.1 "Masking Maskable + Hardware Interrupts". */ + asm volatile ("sti"); + + return old_level; +} + +/* Disables interrupts and returns the previous interrupt status. */ +enum intr_level +intr_disable (void) +{ + enum intr_level old_level = intr_get_level (); + + /* Disable interrupts by clearing the interrupt flag. + See [IA32-v2b] "CLI" and [IA32-v3a] 5.8.1 "Masking Maskable + Hardware Interrupts". */ + asm volatile ("cli" : : : "memory"); + + return old_level; +} + +/* Initializes the interrupt system. */ +void +intr_init (void) +{ + uint64_t idtr_operand; + int i; + + /* Initialize interrupt controller. */ + pic_init (); + + /* Initialize IDT. */ + for (i = 0; i < INTR_CNT; i++) + idt[i] = make_intr_gate (intr_stubs[i], 0); + + /* Load IDT register. + See [IA32-v2a] "LIDT" and [IA32-v3a] 5.10 "Interrupt + Descriptor Table (IDT)". */ + idtr_operand = make_idtr_operand (sizeof idt - 1, idt); + asm volatile ("lidt %0" : : "m" (idtr_operand)); + + /* Initialize intr_names. */ + for (i = 0; i < INTR_CNT; i++) + intr_names[i] = "unknown"; + intr_names[0] = "#DE Divide Error"; + intr_names[1] = "#DB Debug Exception"; + intr_names[2] = "NMI Interrupt"; + intr_names[3] = "#BP Breakpoint Exception"; + intr_names[4] = "#OF Overflow Exception"; + intr_names[5] = "#BR BOUND Range Exceeded Exception"; + intr_names[6] = "#UD Invalid Opcode Exception"; + intr_names[7] = "#NM Device Not Available Exception"; + intr_names[8] = "#DF Double Fault Exception"; + intr_names[9] = "Coprocessor Segment Overrun"; + intr_names[10] = "#TS Invalid TSS Exception"; + intr_names[11] = "#NP Segment Not Present"; + intr_names[12] = "#SS Stack Fault Exception"; + intr_names[13] = "#GP General Protection Exception"; + intr_names[14] = "#PF Page-Fault Exception"; + intr_names[16] = "#MF x87 FPU Floating-Point Error"; + intr_names[17] = "#AC Alignment Check Exception"; + intr_names[18] = "#MC Machine-Check Exception"; + intr_names[19] = "#XF SIMD Floating-Point Exception"; +} + +/* Registers interrupt VEC_NO to invoke HANDLER with descriptor + privilege level DPL. Names the interrupt NAME for debugging + purposes. The interrupt handler will be invoked with + interrupt status set to LEVEL. */ +static void +register_handler (uint8_t vec_no, int dpl, enum intr_level level, + intr_handler_func *handler, const char *name) +{ + ASSERT (intr_handlers[vec_no] == NULL); + if (level == INTR_ON) + idt[vec_no] = make_trap_gate (intr_stubs[vec_no], dpl); + else + idt[vec_no] = make_intr_gate (intr_stubs[vec_no], dpl); + intr_handlers[vec_no] = handler; + intr_names[vec_no] = name; +} + +/* Registers external interrupt VEC_NO to invoke HANDLER, which + is named NAME for debugging purposes. The handler will + execute with interrupts disabled. */ +void +intr_register_ext (uint8_t vec_no, intr_handler_func *handler, + const char *name) +{ + ASSERT (vec_no >= 0x20 && vec_no <= 0x2f); + register_handler (vec_no, 0, INTR_OFF, handler, name); +} + +/* Registers internal interrupt VEC_NO to invoke HANDLER, which + is named NAME for debugging purposes. The interrupt handler + will be invoked with interrupt status LEVEL. + + The handler will have descriptor privilege level DPL, meaning + that it can be invoked intentionally when the processor is in + the DPL or lower-numbered ring. In practice, DPL==3 allows + user mode to invoke the interrupts and DPL==0 prevents such + invocation. Faults and exceptions that occur in user mode + still cause interrupts with DPL==0 to be invoked. See + [IA32-v3a] sections 4.5 "Privilege Levels" and 4.8.1.1 + "Accessing Nonconforming Code Segments" for further + discussion. */ +void +intr_register_int (uint8_t vec_no, int dpl, enum intr_level level, + intr_handler_func *handler, const char *name) +{ + ASSERT (vec_no < 0x20 || vec_no > 0x2f); + register_handler (vec_no, dpl, level, handler, name); +} + +/* Returns true during processing of an external interrupt + and false at all other times. */ +bool +intr_context (void) +{ + return in_external_intr; +} + +/* During processing of an external interrupt, directs the + interrupt handler to yield to a new process just before + returning from the interrupt. May not be called at any other + time. */ +void +intr_yield_on_return (void) +{ + ASSERT (intr_context ()); + yield_on_return = true; +} + +/* 8259A Programmable Interrupt Controller. */ + +/* Initializes the PICs. Refer to [8259A] for details. + + By default, interrupts 0...15 delivered by the PICs will go to + interrupt vectors 0...15. Those vectors are also used for CPU + traps and exceptions, so we reprogram the PICs so that + interrupts 0...15 are delivered to interrupt vectors 32...47 + (0x20...0x2f) instead. */ +static void +pic_init (void) +{ + /* Mask all interrupts on both PICs. */ + outb (PIC0_DATA, 0xff); + outb (PIC1_DATA, 0xff); + + /* Initialize master. */ + outb (PIC0_CTRL, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */ + outb (PIC0_DATA, 0x20); /* ICW2: line IR0...7 -> irq 0x20...0x27. */ + outb (PIC0_DATA, 0x04); /* ICW3: slave PIC on line IR2. */ + outb (PIC0_DATA, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */ + + /* Initialize slave. */ + outb (PIC1_CTRL, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */ + outb (PIC1_DATA, 0x28); /* ICW2: line IR0...7 -> irq 0x28...0x2f. */ + outb (PIC1_DATA, 0x02); /* ICW3: slave ID is 2. */ + outb (PIC1_DATA, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */ + + /* Unmask all interrupts. */ + outb (PIC0_DATA, 0x00); + outb (PIC1_DATA, 0x00); +} + +/* Sends an end-of-interrupt signal to the PIC for the given IRQ. + If we don't acknowledge the IRQ, it will never be delivered to + us again, so this is important. */ +static void +pic_end_of_interrupt (int irq) +{ + ASSERT (irq >= 0x20 && irq < 0x30); + + /* Acknowledge master PIC. */ + outb (0x20, 0x20); + + /* Acknowledge slave PIC if this is a slave interrupt. */ + if (irq >= 0x28) + outb (0xa0, 0x20); +} + +/* Creates an gate that invokes FUNCTION. + + The gate has descriptor privilege level DPL, meaning that it + can be invoked intentionally when the processor is in the DPL + or lower-numbered ring. In practice, DPL==3 allows user mode + to call into the gate and DPL==0 prevents such calls. Faults + and exceptions that occur in user mode still cause gates with + DPL==0 to be invoked. See [IA32-v3a] sections 4.5 "Privilege + Levels" and 4.8.1.1 "Accessing Nonconforming Code Segments" + for further discussion. + + TYPE must be either 14 (for an interrupt gate) or 15 (for a + trap gate). The difference is that entering an interrupt gate + disables interrupts, but entering a trap gate does not. See + [IA32-v3a] section 5.12.1.2 "Flag Usage By Exception- or + Interrupt-Handler Procedure" for discussion. */ +static uint64_t +make_gate (void (*function) (void), int dpl, int type) +{ + uint32_t e0, e1; + + ASSERT (function != NULL); + ASSERT (dpl >= 0 && dpl <= 3); + ASSERT (type >= 0 && type <= 15); + + e0 = (((uint32_t) function & 0xffff) /* Offset 15:0. */ + | (SEL_KCSEG << 16)); /* Target code segment. */ + + e1 = (((uint32_t) function & 0xffff0000) /* Offset 31:16. */ + | (1 << 15) /* Present. */ + | ((uint32_t) dpl << 13) /* Descriptor privilege level. */ + | (0 << 12) /* System. */ + | ((uint32_t) type << 8)); /* Gate type. */ + + return e0 | ((uint64_t) e1 << 32); +} + +/* Creates an interrupt gate that invokes FUNCTION with the given + DPL. */ +static uint64_t +make_intr_gate (void (*function) (void), int dpl) +{ + return make_gate (function, dpl, 14); +} + +/* Creates a trap gate that invokes FUNCTION with the given + DPL. */ +static uint64_t +make_trap_gate (void (*function) (void), int dpl) +{ + return make_gate (function, dpl, 15); +} + +/* Returns a descriptor that yields the given LIMIT and BASE when + used as an operand for the LIDT instruction. */ +static inline uint64_t +make_idtr_operand (uint16_t limit, void *base) +{ + return limit | ((uint64_t) (uint32_t) base << 16); +} + +/* Interrupt handlers. */ + +/* Handler for all interrupts, faults, and exceptions. This + function is called by the assembly language interrupt stubs in + intr-stubs.S. FRAME describes the interrupt and the + interrupted thread's registers. */ +void +intr_handler (struct intr_frame *frame) +{ + bool external; + intr_handler_func *handler; + + /* External interrupts are special. + We only handle one at a time (so interrupts must be off) + and they need to be acknowledged on the PIC (see below). + An external interrupt handler cannot sleep. */ + external = frame->vec_no >= 0x20 && frame->vec_no < 0x30; + if (external) + { + ASSERT (intr_get_level () == INTR_OFF); + ASSERT (!intr_context ()); + + in_external_intr = true; + yield_on_return = false; + } + + /* Invoke the interrupt's handler. */ + handler = intr_handlers[frame->vec_no]; + if (handler != NULL) + handler (frame); + else if (frame->vec_no == 0x27 || frame->vec_no == 0x2f) + { + /* There is no handler, but this interrupt can trigger + spuriously due to a hardware fault or hardware race + condition. Ignore it. */ + } + else + unexpected_interrupt (frame); + + /* Complete the processing of an external interrupt. */ + if (external) + { + ASSERT (intr_get_level () == INTR_OFF); + ASSERT (intr_context ()); + + in_external_intr = false; + pic_end_of_interrupt (frame->vec_no); + + if (yield_on_return) + thread_yield (); + } +} + +/* Handles an unexpected interrupt with interrupt frame F. An + unexpected interrupt is one that has no registered handler. */ +static void +unexpected_interrupt (const struct intr_frame *f) +{ + /* Count the number so far. */ + unsigned int n = ++unexpected_cnt[f->vec_no]; + + /* If the number is a power of 2, print a message. This rate + limiting means that we get information about an uncommon + unexpected interrupt the first time and fairly often after + that, but one that occurs many times will not overwhelm the + console. */ + if ((n & (n - 1)) == 0) + printf ("Unexpected interrupt %#04x (%s)\n", + f->vec_no, intr_names[f->vec_no]); +} + +/* Dumps interrupt frame F to the console, for debugging. */ +void +intr_dump_frame (const struct intr_frame *f) +{ + uint32_t cr2; + + /* Store current value of CR2 into `cr2'. + CR2 is the linear address of the last page fault. + See [IA32-v2a] "MOV--Move to/from Control Registers" and + [IA32-v3a] 5.14 "Interrupt 14--Page Fault Exception + (#PF)". */ + asm ("movl %%cr2, %0" : "=r" (cr2)); + + printf ("Interrupt %#04x (%s) at eip=%p\n", + f->vec_no, intr_names[f->vec_no], f->eip); + printf (" cr2=%08"PRIx32" error=%08"PRIx32"\n", cr2, f->error_code); + printf (" eax=%08"PRIx32" ebx=%08"PRIx32" ecx=%08"PRIx32" edx=%08"PRIx32"\n", + f->eax, f->ebx, f->ecx, f->edx); + printf (" esi=%08"PRIx32" edi=%08"PRIx32" esp=%08"PRIx32" ebp=%08"PRIx32"\n", + f->esi, f->edi, (uint32_t) f->esp, f->ebp); + printf (" cs=%04"PRIx16" ds=%04"PRIx16" es=%04"PRIx16" ss=%04"PRIx16"\n", + f->cs, f->ds, f->es, f->ss); +} + +/* Returns the name of interrupt VEC. */ +const char * +intr_name (uint8_t vec) +{ + return intr_names[vec]; +} diff --git a/pintos-progos/threads/interrupt.h b/pintos-progos/threads/interrupt.h new file mode 100644 index 0000000..d43e06d --- /dev/null +++ b/pintos-progos/threads/interrupt.h @@ -0,0 +1,70 @@ +#ifndef THREADS_INTERRUPT_H +#define THREADS_INTERRUPT_H + +#include +#include + +/* Interrupts on or off? */ +enum intr_level + { + INTR_OFF, /* Interrupts disabled. */ + INTR_ON /* Interrupts enabled. */ + }; + +enum intr_level intr_get_level (void); +enum intr_level intr_set_level (enum intr_level); +enum intr_level intr_enable (void); +enum intr_level intr_disable (void); + +/* Interrupt stack frame. */ +struct intr_frame + { + /* Pushed by intr_entry in intr-stubs.S. + These are the interrupted task's saved registers. */ + uint32_t edi; /* Saved EDI. */ + uint32_t esi; /* Saved ESI. */ + uint32_t ebp; /* Saved EBP. */ + uint32_t esp_dummy; /* Not used. */ + uint32_t ebx; /* Saved EBX. */ + uint32_t edx; /* Saved EDX. */ + uint32_t ecx; /* Saved ECX. */ + uint32_t eax; /* Saved EAX. */ + uint16_t gs, :16; /* Saved GS segment register. */ + uint16_t fs, :16; /* Saved FS segment register. */ + uint16_t es, :16; /* Saved ES segment register. */ + uint16_t ds, :16; /* Saved DS segment register. */ + + /* Pushed by intrNN_stub in intr-stubs.S. */ + uint32_t vec_no; /* Interrupt vector number. */ + + /* Sometimes pushed by the CPU, + otherwise for consistency pushed as 0 by intrNN_stub. + The CPU puts it just under `eip', but we move it here. */ + uint32_t error_code; /* Error code. */ + + /* Pushed by intrNN_stub in intr-stubs.S. + This frame pointer eases interpretation of backtraces. */ + void *frame_pointer; /* Saved EBP (frame pointer). */ + + /* Pushed by the CPU. + These are the interrupted task's saved registers. */ + void (*eip) (void); /* Next instruction to execute. */ + uint16_t cs, :16; /* Code segment for eip. */ + uint32_t eflags; /* Saved CPU flags. */ + void *esp; /* Saved stack pointer. */ + uint16_t ss, :16; /* Data segment for esp. */ + }; + +typedef void intr_handler_func (struct intr_frame *); + +void intr_init (void); +void intr_register_ext (uint8_t vec, intr_handler_func *, const char *name); +void intr_register_int (uint8_t vec, int dpl, enum intr_level, + intr_handler_func *, const char *name); +bool intr_context (void); +void intr_yield_on_return (void); + +void intr_dump_frame (const struct intr_frame *); +const char *intr_name (uint8_t vec); + +#endif /* threads/interrupt.h */ diff --git a/pintos-progos/threads/intr-stubs.S b/pintos-progos/threads/intr-stubs.S new file mode 100644 index 0000000..adb674e --- /dev/null +++ b/pintos-progos/threads/intr-stubs.S @@ -0,0 +1,203 @@ +#include "threads/loader.h" + + .text + +/* Main interrupt entry point. + + An internal or external interrupt starts in one of the + intrNN_stub routines, which push the `struct intr_frame' + frame_pointer, error_code, and vec_no members on the stack, + then jump here. + + We save the rest of the `struct intr_frame' members to the + stack, set up some registers as needed by the kernel, and then + call intr_handler(), which actually handles the interrupt. + + We "fall through" to intr_exit to return from the interrupt. +*/ +.func intr_entry +intr_entry: + /* Save caller's registers. */ + pushl %ds + pushl %es + pushl %fs + pushl %gs + pushal + + /* Set up kernel environment. */ + cld /* String instructions go upward. */ + mov $SEL_KDSEG, %eax /* Initialize segment registers. */ + mov %eax, %ds + mov %eax, %es + leal 56(%esp), %ebp /* Set up frame pointer. */ + + /* Call interrupt handler. */ + pushl %esp +.globl intr_handler + call intr_handler + addl $4, %esp +.endfunc + +/* Interrupt exit. + + Restores the caller's registers, discards extra data on the + stack, and returns to the caller. + + This is a separate function because it is called directly when + we launch a new user process (see start_process() in + userprog/process.c). */ +.globl intr_exit +.func intr_exit +intr_exit: + /* Restore caller's registers. */ + popal + popl %gs + popl %fs + popl %es + popl %ds + + /* Discard `struct intr_frame' vec_no, error_code, + frame_pointer members. */ + addl $12, %esp + + /* Return to caller. */ + iret +.endfunc + +/* Interrupt stubs. + + This defines 256 fragments of code, named `intr00_stub' + through `intrff_stub', each of which is used as the entry + point for the corresponding interrupt vector. It also puts + the address of each of these functions in the correct spot in + `intr_stubs', an array of function pointers. + + Most of the stubs do this: + + 1. Push %ebp on the stack (frame_pointer in `struct intr_frame'). + + 2. Push 0 on the stack (error_code). + + 3. Push the interrupt number on the stack (vec_no). + + The CPU pushes an extra "error code" on the stack for a few + interrupts. Because we want %ebp to be where the error code + is, we follow a different path: + + 1. Push a duplicate copy of the error code on the stack. + + 2. Replace the original copy of the error code by %ebp. + + 3. Push the interrupt number on the stack. */ + + .data +.globl intr_stubs +intr_stubs: + +/* This implements steps 1 and 2, described above, in the common + case where we just push a 0 error code. */ +#define zero \ + pushl %ebp; \ + pushl $0 + +/* This implements steps 1 and 2, described above, in the case + where the CPU already pushed an error code. */ +#define REAL \ + pushl (%esp); \ + movl %ebp, 4(%esp) + +/* Emits a stub for interrupt vector NUMBER. + TYPE is `zero', for the case where we push a 0 error code, + or `REAL', if the CPU pushes an error code for us. */ +#define STUB(NUMBER, TYPE) \ + .text; \ +.func intr##NUMBER##_stub; \ +intr##NUMBER##_stub: \ + TYPE; \ + push $0x##NUMBER; \ + jmp intr_entry; \ +.endfunc; \ + \ + .data; \ + .long intr##NUMBER##_stub; + +/* All the stubs. */ +STUB(00, zero) STUB(01, zero) STUB(02, zero) STUB(03, zero) +STUB(04, zero) STUB(05, zero) STUB(06, zero) STUB(07, zero) +STUB(08, REAL) STUB(09, zero) STUB(0a, REAL) STUB(0b, REAL) +STUB(0c, zero) STUB(0d, REAL) STUB(0e, REAL) STUB(0f, zero) + +STUB(10, zero) STUB(11, REAL) STUB(12, zero) STUB(13, zero) +STUB(14, zero) STUB(15, zero) STUB(16, zero) STUB(17, zero) +STUB(18, REAL) STUB(19, zero) STUB(1a, REAL) STUB(1b, REAL) +STUB(1c, zero) STUB(1d, REAL) STUB(1e, REAL) STUB(1f, zero) + +STUB(20, zero) STUB(21, zero) STUB(22, zero) STUB(23, zero) +STUB(24, zero) STUB(25, zero) STUB(26, zero) STUB(27, zero) +STUB(28, zero) STUB(29, zero) STUB(2a, zero) STUB(2b, zero) +STUB(2c, zero) STUB(2d, zero) STUB(2e, zero) STUB(2f, zero) + +STUB(30, zero) STUB(31, zero) STUB(32, zero) STUB(33, zero) +STUB(34, zero) STUB(35, zero) STUB(36, zero) STUB(37, zero) +STUB(38, zero) STUB(39, zero) STUB(3a, zero) STUB(3b, zero) +STUB(3c, zero) STUB(3d, zero) STUB(3e, zero) STUB(3f, zero) + +STUB(40, zero) STUB(41, zero) STUB(42, zero) STUB(43, zero) +STUB(44, zero) STUB(45, zero) STUB(46, zero) STUB(47, zero) +STUB(48, zero) STUB(49, zero) STUB(4a, zero) STUB(4b, zero) +STUB(4c, zero) STUB(4d, zero) STUB(4e, zero) STUB(4f, zero) + +STUB(50, zero) STUB(51, zero) STUB(52, zero) STUB(53, zero) +STUB(54, zero) STUB(55, zero) STUB(56, zero) STUB(57, zero) +STUB(58, zero) STUB(59, zero) STUB(5a, zero) STUB(5b, zero) +STUB(5c, zero) STUB(5d, zero) STUB(5e, zero) STUB(5f, zero) + +STUB(60, zero) STUB(61, zero) STUB(62, zero) STUB(63, zero) +STUB(64, zero) STUB(65, zero) STUB(66, zero) STUB(67, zero) +STUB(68, zero) STUB(69, zero) STUB(6a, zero) STUB(6b, zero) +STUB(6c, zero) STUB(6d, zero) STUB(6e, zero) STUB(6f, zero) + +STUB(70, zero) STUB(71, zero) STUB(72, zero) STUB(73, zero) +STUB(74, zero) STUB(75, zero) STUB(76, zero) STUB(77, zero) +STUB(78, zero) STUB(79, zero) STUB(7a, zero) STUB(7b, zero) +STUB(7c, zero) STUB(7d, zero) STUB(7e, zero) STUB(7f, zero) + +STUB(80, zero) STUB(81, zero) STUB(82, zero) STUB(83, zero) +STUB(84, zero) STUB(85, zero) STUB(86, zero) STUB(87, zero) +STUB(88, zero) STUB(89, zero) STUB(8a, zero) STUB(8b, zero) +STUB(8c, zero) STUB(8d, zero) STUB(8e, zero) STUB(8f, zero) + +STUB(90, zero) STUB(91, zero) STUB(92, zero) STUB(93, zero) +STUB(94, zero) STUB(95, zero) STUB(96, zero) STUB(97, zero) +STUB(98, zero) STUB(99, zero) STUB(9a, zero) STUB(9b, zero) +STUB(9c, zero) STUB(9d, zero) STUB(9e, zero) STUB(9f, zero) + +STUB(a0, zero) STUB(a1, zero) STUB(a2, zero) STUB(a3, zero) +STUB(a4, zero) STUB(a5, zero) STUB(a6, zero) STUB(a7, zero) +STUB(a8, zero) STUB(a9, zero) STUB(aa, zero) STUB(ab, zero) +STUB(ac, zero) STUB(ad, zero) STUB(ae, zero) STUB(af, zero) + +STUB(b0, zero) STUB(b1, zero) STUB(b2, zero) STUB(b3, zero) +STUB(b4, zero) STUB(b5, zero) STUB(b6, zero) STUB(b7, zero) +STUB(b8, zero) STUB(b9, zero) STUB(ba, zero) STUB(bb, zero) +STUB(bc, zero) STUB(bd, zero) STUB(be, zero) STUB(bf, zero) + +STUB(c0, zero) STUB(c1, zero) STUB(c2, zero) STUB(c3, zero) +STUB(c4, zero) STUB(c5, zero) STUB(c6, zero) STUB(c7, zero) +STUB(c8, zero) STUB(c9, zero) STUB(ca, zero) STUB(cb, zero) +STUB(cc, zero) STUB(cd, zero) STUB(ce, zero) STUB(cf, zero) + +STUB(d0, zero) STUB(d1, zero) STUB(d2, zero) STUB(d3, zero) +STUB(d4, zero) STUB(d5, zero) STUB(d6, zero) STUB(d7, zero) +STUB(d8, zero) STUB(d9, zero) STUB(da, zero) STUB(db, zero) +STUB(dc, zero) STUB(dd, zero) STUB(de, zero) STUB(df, zero) + +STUB(e0, zero) STUB(e1, zero) STUB(e2, zero) STUB(e3, zero) +STUB(e4, zero) STUB(e5, zero) STUB(e6, zero) STUB(e7, zero) +STUB(e8, zero) STUB(e9, zero) STUB(ea, zero) STUB(eb, zero) +STUB(ec, zero) STUB(ed, zero) STUB(ee, zero) STUB(ef, zero) + +STUB(f0, zero) STUB(f1, zero) STUB(f2, zero) STUB(f3, zero) +STUB(f4, zero) STUB(f5, zero) STUB(f6, zero) STUB(f7, zero) +STUB(f8, zero) STUB(f9, zero) STUB(fa, zero) STUB(fb, zero) +STUB(fc, zero) STUB(fd, zero) STUB(fe, zero) STUB(ff, zero) diff --git a/pintos-progos/threads/intr-stubs.h b/pintos-progos/threads/intr-stubs.h new file mode 100644 index 0000000..9ceba15 --- /dev/null +++ b/pintos-progos/threads/intr-stubs.h @@ -0,0 +1,19 @@ +#ifndef THREADS_INTR_STUBS_H +#define THREADS_INTR_STUBS_H + +/* Interrupt stubs. + + These are little snippets of code in intr-stubs.S, one for + each of the 256 possible x86 interrupts. Each one does a + little bit of stack manipulation, then jumps to intr_entry(). + See intr-stubs.S for more information. + + This array points to each of the interrupt stub entry points + so that intr_init() can easily find them. */ +typedef void intr_stub_func (void); +extern intr_stub_func *intr_stubs[256]; + +/* Interrupt return path. */ +void intr_exit (void); + +#endif /* threads/intr-stubs.h */ diff --git a/pintos-progos/threads/io.h b/pintos-progos/threads/io.h new file mode 100644 index 0000000..30d52da --- /dev/null +++ b/pintos-progos/threads/io.h @@ -0,0 +1,115 @@ +#ifndef THREADS_IO_H +#define THREADS_IO_H + +#include +#include + +/* Reads and returns a byte from PORT. */ +static inline uint8_t +inb (uint16_t port) +{ + /* See [IA32-v2a] "IN". */ + uint8_t data; + asm volatile ("inb %w1, %b0" : "=a" (data) : "Nd" (port)); + return data; +} + +/* Reads CNT bytes from PORT, one after another, and stores them + into the buffer starting at ADDR. */ +static inline void +insb (uint16_t port, void *addr, size_t cnt) +{ + /* See [IA32-v2a] "INS". */ + asm volatile ("rep insb" : "+D" (addr), "+c" (cnt) : "d" (port) : "memory"); +} + +/* Reads and returns 16 bits from PORT. */ +static inline uint16_t +inw (uint16_t port) +{ + uint16_t data; + /* See [IA32-v2a] "IN". */ + asm volatile ("inw %w1, %w0" : "=a" (data) : "Nd" (port)); + return data; +} + +/* Reads CNT 16-bit (halfword) units from PORT, one after + another, and stores them into the buffer starting at ADDR. */ +static inline void +insw (uint16_t port, void *addr, size_t cnt) +{ + /* See [IA32-v2a] "INS". */ + asm volatile ("rep insw" : "+D" (addr), "+c" (cnt) : "d" (port) : "memory"); +} + +/* Reads and returns 32 bits from PORT. */ +static inline uint32_t +inl (uint16_t port) +{ + /* See [IA32-v2a] "IN". */ + uint32_t data; + asm volatile ("inl %w1, %0" : "=a" (data) : "Nd" (port)); + return data; +} + +/* Reads CNT 32-bit (word) units from PORT, one after another, + and stores them into the buffer starting at ADDR. */ +static inline void +insl (uint16_t port, void *addr, size_t cnt) +{ + /* See [IA32-v2a] "INS". */ + asm volatile ("rep insl" : "+D" (addr), "+c" (cnt) : "d" (port) : "memory"); +} + +/* Writes byte DATA to PORT. */ +static inline void +outb (uint16_t port, uint8_t data) +{ + /* See [IA32-v2b] "OUT". */ + asm volatile ("outb %b0, %w1" : : "a" (data), "Nd" (port)); +} + +/* Writes to PORT each byte of data in the CNT-byte buffer + starting at ADDR. */ +static inline void +outsb (uint16_t port, const void *addr, size_t cnt) +{ + /* See [IA32-v2b] "OUTS". */ + asm volatile ("rep outsb" : "+S" (addr), "+c" (cnt) : "d" (port)); +} + +/* Writes the 16-bit DATA to PORT. */ +static inline void +outw (uint16_t port, uint16_t data) +{ + /* See [IA32-v2b] "OUT". */ + asm volatile ("outw %w0, %w1" : : "a" (data), "Nd" (port)); +} + +/* Writes to PORT each 16-bit unit (halfword) of data in the + CNT-halfword buffer starting at ADDR. */ +static inline void +outsw (uint16_t port, const void *addr, size_t cnt) +{ + /* See [IA32-v2b] "OUTS". */ + asm volatile ("rep outsw" : "+S" (addr), "+c" (cnt) : "d" (port)); +} + +/* Writes the 32-bit DATA to PORT. */ +static inline void +outl (uint16_t port, uint32_t data) +{ + /* See [IA32-v2b] "OUT". */ + asm volatile ("outl %0, %w1" : : "a" (data), "Nd" (port)); +} + +/* Writes to PORT each 32-bit unit (word) of data in the CNT-word + buffer starting at ADDR. */ +static inline void +outsl (uint16_t port, const void *addr, size_t cnt) +{ + /* See [IA32-v2b] "OUTS". */ + asm volatile ("rep outsl" : "+S" (addr), "+c" (cnt) : "d" (port)); +} + +#endif /* threads/io.h */ diff --git a/pintos-progos/threads/kernel.lds.S b/pintos-progos/threads/kernel.lds.S new file mode 100644 index 0000000..19082d5 --- /dev/null +++ b/pintos-progos/threads/kernel.lds.S @@ -0,0 +1,30 @@ +#include "threads/loader.h" + +OUTPUT_FORMAT("elf32-i386") +OUTPUT_ARCH("i386") +ENTRY(start) /* Kernel starts at "start" symbol. */ +SECTIONS +{ + /* Specify the kernel base address. */ + _start = LOADER_PHYS_BASE + LOADER_KERN_BASE; + + /* Make room for the ELF headers. */ + . = _start + SIZEOF_HEADERS; + + /* Kernel starts with code, followed by read-only data and writable data. */ + .text : { *(.start) *(.text) } = 0x90 + .rodata : { *(.rodata) *(.rodata.*) + . = ALIGN(0x1000); + _end_kernel_text = .; } + .data : { *(.data) + _signature = .; LONG(0xaa55aa55) } + + /* BSS (zero-initialized data) is after everything else. */ + _start_bss = .; + .bss : { *(.bss) } + _end_bss = .; + + _end = .; + + ASSERT (_end - _start <= 512K, "Kernel image is too big.") +} diff --git a/pintos-progos/threads/loader.S b/pintos-progos/threads/loader.S new file mode 100644 index 0000000..dd87ea1 --- /dev/null +++ b/pintos-progos/threads/loader.S @@ -0,0 +1,263 @@ +#include "threads/loader.h" + +#### Kernel loader. + +#### This code should be stored in the first sector of a hard disk. +#### When the BIOS runs, it loads this code at physical address +#### 0x7c00-0x7e00 (512 bytes) and jumps to the beginning of it, +#### in real mode. The loader loads the kernel into memory and jumps +#### to its entry point, which is the start function in start.S. +#### +#### The BIOS passes in the drive that the loader was read from as +#### DL, with floppy drives numbered 0x00, 0x01, ... and hard drives +#### numbered 0x80, 0x81, ... We want to support booting a kernel on +#### a different drive from the loader, so we don't take advantage of +#### this. + +# Runs in real mode, which is a 16-bit segment. + .code16 + +# Set up segment registers. +# Set stack to grow downward from 60 kB (after boot, the kernel +# continues to use this stack for its initial thread). + + sub %ax, %ax + mov %ax, %ds + mov %ax, %ss + mov $0xf000, %esp + +# Configure serial port so we can report progress without connected VGA. +# See [IntrList] for details. + sub %dx, %dx # Serial port 0. + mov $0xe3, %al # 9600 bps, N-8-1. + # AH is already 0 (Initialize Port). + int $0x14 # Destroys AX. + + call puts + .string "PiLo" + +#### Read the partition table on each system hard disk and scan for a +#### partition of type 0x20, which is the type that we use for a +#### Pintos kernel. +#### +#### Read [Partitions] for a description of the partition table format +#### that we parse. +#### +#### We print out status messages to show the disk and partition being +#### scanned, e.g. hda1234 as we scan four partitions on the first +#### hard disk. + + mov $0x80, %dl # Hard disk 0. +read_mbr: + sub %ebx, %ebx # Sector 0. + mov $0x2000, %ax # Use 0x20000 for buffer. + mov %ax, %es + call read_sector + jc no_such_drive + + # Print hd[a-z]. + call puts + .string " hd" + mov %dl, %al + add $'a' - 0x80, %al + call putc + + # Check for MBR signature--if not present, it's not a + # partitioned hard disk. + cmpw $0xaa55, %es:510 + jne next_drive + + mov $446, %si # Offset of partition table entry 1. + mov $'1', %al +check_partition: + # Is it an unused partition? + cmpl $0, %es:(%si) + je next_partition + + # Print [1-4]. + call putc + + # Is it a Pintos kernel partition? + cmpb $0x20, %es:4(%si) + jne next_partition + + # Is it a bootable partition? + cmpb $0x80, %es:(%si) + je load_kernel + +next_partition: + # No match for this partition, go on to the next one. + add $16, %si # Offset to next partition table entry. + inc %al + cmp $510, %si + jb check_partition + +next_drive: + # No match on this drive, go on to the next one. + inc %dl + jnc read_mbr + +no_such_drive: +no_boot_partition: + # Didn't find a Pintos kernel partition anywhere, give up. + call puts + .string "\rNot found\r" + + # Notify BIOS that boot failed. See [IntrList]. + int $0x18 + +#### We found a kernel. The kernel's drive is in DL. The partition +#### table entry for the kernel's partition is at ES:SI. Our job now +#### is to read the kernel from disk and jump to its start address. + +load_kernel: + call puts + .string "\rLoading" + + # Figure out number of sectors to read. A Pintos kernel is + # just an ELF format object, which doesn't have an + # easy-to-read field to identify its own size (see [ELF1]). + # But we limit Pintos kernels to 512 kB for other reasons, so + # it's easy enough to just read the entire contents of the + # partition or 512 kB from disk, whichever is smaller. + mov %es:12(%si), %ecx # EBP = number of sectors + cmp $1024, %ecx # Cap size at 512 kB + jbe 1f + mov $1024, %cx +1: + + mov %es:8(%si), %ebx # EBX = first sector + mov $0x2000, %ax # Start load address: 0x20000 + +next_sector: + # Read one sector into memory. + mov %ax, %es # ES:0000 -> load address + call read_sector + jc read_failed + + # Print '.' as progress indicator once every 16 sectors == 8 kB. + test $15, %bl + jnz 1f + call puts + .string "." +1: + + # Advance memory pointer and disk sector. + add $0x20, %ax + inc %bx + loop next_sector + + call puts + .string "\r" + +#### Transfer control to the kernel that we loaded. We read the start +#### address out of the ELF header (see [ELF1]) and convert it from a +#### 32-bit linear address into a 16:16 segment:offset address for +#### real mode, then jump to the converted address. The 80x86 doesn't +#### have an instruction to jump to an absolute segment:offset kept in +#### registers, so in fact we store the address in a temporary memory +#### location, then jump indirectly through that location. To save 4 +#### bytes in the loader, we reuse 4 bytes of the loader's code for +#### this temporary pointer. + + mov $0x2000, %ax + mov %ax, %es + mov %es:0x18, %dx + mov %dx, start + movw $0x2000, start + 2 + ljmp *start + +read_failed: +start: + # Disk sector read failed. + call puts +1: .string "\rBad read\r" + + # Notify BIOS that boot failed. See [IntrList]. + int $0x18 + +#### Print string subroutine. To save space in the loader, this +#### subroutine takes its null-terminated string argument from the +#### code stream just after the call, and then returns to the byte +#### just after the terminating null. This subroutine preserves all +#### general-purpose registers. + +puts: xchg %si, %ss:(%esp) + push %ax +next_char: + mov %cs:(%si), %al + inc %si + test %al, %al + jz 1f + call putc + jmp next_char +1: pop %ax + xchg %si, %ss:(%esp) + ret + +#### Character output subroutine. Prints the character in AL to the +#### VGA display and serial port 0, using BIOS services (see +#### [IntrList]). Preserves all general-purpose registers. +#### +#### If called upon to output a carriage return, this subroutine +#### automatically supplies the following line feed. + +putc: pusha + +1: sub %bh, %bh # Page 0. + mov $0x0e, %ah # Teletype output service. + int $0x10 + + mov $0x01, %ah # Serial port output service. + sub %dx, %dx # Serial port 0. +2: int $0x14 # Destroys AH. + test $0x80, %ah # Output timed out? + jz 3f + movw $0x9090, 2b # Turn "int $0x14" above into NOPs. + +3: + cmp $'\r', %al + jne popa_ret + mov $'\n', %al + jmp 1b + +#### Sector read subroutine. Takes a drive number in DL (0x80 = hard +#### disk 0, 0x81 = hard disk 1, ...) and a sector number in EBX, and +#### reads the specified sector into memory at ES:0000. Returns with +#### carry set on error, clear otherwise. Preserves all +#### general-purpose registers. + +read_sector: + pusha + sub %ax, %ax + push %ax # LBA sector number [48:63] + push %ax # LBA sector number [32:47] + push %ebx # LBA sector number [0:31] + push %es # Buffer segment + push %ax # Buffer offset (always 0) + push $1 # Number of sectors to read + push $16 # Packet size + mov $0x42, %ah # Extended read + mov %sp, %si # DS:SI -> packet + int $0x13 # Error code in CF + popa # Pop 16 bytes, preserve flags +popa_ret: + popa + ret # Error code still in CF + +#### Command-line arguments and their count. +#### This is written by the `pintos' utility and read by the kernel. +#### The loader itself does not do anything with the command line. + .org LOADER_ARG_CNT - LOADER_BASE + .fill LOADER_ARG_CNT_LEN, 1, 0 + + .org LOADER_ARGS - LOADER_BASE + .fill LOADER_ARGS_LEN, 1, 0 + +#### Partition table. + .org LOADER_PARTS - LOADER_BASE + .fill LOADER_PARTS_LEN, 1, 0 + +#### Boot-sector signature for BIOS inspection. + .org LOADER_SIG - LOADER_BASE + .word 0xaa55 diff --git a/pintos-progos/threads/loader.h b/pintos-progos/threads/loader.h new file mode 100644 index 0000000..1bfe111 --- /dev/null +++ b/pintos-progos/threads/loader.h @@ -0,0 +1,40 @@ +#ifndef THREADS_LOADER_H +#define THREADS_LOADER_H + +/* Constants fixed by the PC BIOS. */ +#define LOADER_BASE 0x7c00 /* Physical address of loader's base. */ +#define LOADER_END 0x7e00 /* Physical address of end of loader. */ + +/* Physical address of kernel base. */ +#define LOADER_KERN_BASE 0x20000 /* 128 kB. */ + +/* Kernel virtual address at which all physical memory is mapped. + Must be aligned on a 4 MB boundary. */ +#define LOADER_PHYS_BASE 0xc0000000 /* 3 GB. */ + +/* Important loader physical addresses. */ +#define LOADER_SIG (LOADER_END - LOADER_SIG_LEN) /* 0xaa55 BIOS signature. */ +#define LOADER_PARTS (LOADER_SIG - LOADER_PARTS_LEN) /* Partition table. */ +#define LOADER_ARGS (LOADER_PARTS - LOADER_ARGS_LEN) /* Command-line args. */ +#define LOADER_ARG_CNT (LOADER_ARGS - LOADER_ARG_CNT_LEN) /* Number of args. */ + +/* Sizes of loader data structures. */ +#define LOADER_SIG_LEN 2 +#define LOADER_PARTS_LEN 64 +#define LOADER_ARGS_LEN 128 +#define LOADER_ARG_CNT_LEN 4 + +/* GDT selectors defined by loader. + More selectors are defined by userprog/gdt.h. */ +#define SEL_NULL 0x00 /* Null selector. */ +#define SEL_KCSEG 0x08 /* Kernel code selector. */ +#define SEL_KDSEG 0x10 /* Kernel data selector. */ + +#ifndef __ASSEMBLER__ +#include + +/* Amount of physical memory, in 4 kB pages. */ +extern uint32_t init_ram_pages; +#endif + +#endif /* threads/loader.h */ diff --git a/pintos-progos/threads/malloc.c b/pintos-progos/threads/malloc.c new file mode 100644 index 0000000..f6f803b --- /dev/null +++ b/pintos-progos/threads/malloc.c @@ -0,0 +1,294 @@ +#include "threads/malloc.h" +#include +#include +#include +#include +#include +#include +#include "threads/palloc.h" +#include "threads/synch.h" +#include "threads/vaddr.h" + +/* A simple implementation of malloc(). + + The size of each request, in bytes, is rounded up to a power + of 2 and assigned to the "descriptor" that manages blocks of + that size. The descriptor keeps a list of free blocks. If + the free list is nonempty, one of its blocks is used to + satisfy the request. + + Otherwise, a new page of memory, called an "arena", is + obtained from the page allocator (if none is available, + malloc() returns a null pointer). The new arena is divided + into blocks, all of which are added to the descriptor's free + list. Then we return one of the new blocks. + + When we free a block, we add it to its descriptor's free list. + But if the arena that the block was in now has no in-use + blocks, we remove all of the arena's blocks from the free list + and give the arena back to the page allocator. + + We can't handle blocks bigger than 2 kB using this scheme, + because they're too big to fit in a single page with a + descriptor. We handle those by allocating contiguous pages + with the page allocator and sticking the allocation size at + the beginning of the allocated block's arena header. */ + +/* Descriptor. */ +struct desc + { + size_t block_size; /* Size of each element in bytes. */ + size_t blocks_per_arena; /* Number of blocks in an arena. */ + struct list free_list; /* List of free blocks. */ + struct lock lock; /* Lock. */ + }; + +/* Magic number for detecting arena corruption. */ +#define ARENA_MAGIC 0x9a548eed + +/* Arena. */ +struct arena + { + unsigned magic; /* Always set to ARENA_MAGIC. */ + struct desc *desc; /* Owning descriptor, null for big block. */ + size_t free_cnt; /* Free blocks; pages in big block. */ + }; + +/* Free block. */ +struct block + { + struct list_elem free_elem; /* Free list element. */ + }; + +/* Our set of descriptors. */ +static struct desc descs[10]; /* Descriptors. */ +static size_t desc_cnt; /* Number of descriptors. */ + +static struct arena *block_to_arena (struct block *); +static struct block *arena_to_block (struct arena *, size_t idx); + +/* Initializes the malloc() descriptors. */ +void +malloc_init (void) +{ + size_t block_size; + + for (block_size = 16; block_size < PGSIZE / 2; block_size *= 2) + { + struct desc *d = &descs[desc_cnt++]; + ASSERT (desc_cnt <= sizeof descs / sizeof *descs); + d->block_size = block_size; + d->blocks_per_arena = (PGSIZE - sizeof (struct arena)) / block_size; + list_init (&d->free_list); + lock_init (&d->lock); + } +} + +/* Obtains and returns a new block of at least SIZE bytes. + Returns a null pointer if memory is not available. */ +void * +malloc (size_t size) +{ + struct desc *d; + struct block *b; + struct arena *a; + + /* A null pointer satisfies a request for 0 bytes. */ + if (size == 0) + return NULL; + + /* Find the smallest descriptor that satisfies a SIZE-byte + request. */ + for (d = descs; d < descs + desc_cnt; d++) + if (d->block_size >= size) + break; + if (d == descs + desc_cnt) + { + /* SIZE is too big for any descriptor. + Allocate enough pages to hold SIZE plus an arena. */ + size_t page_cnt = DIV_ROUND_UP (size + sizeof *a, PGSIZE); + a = palloc_get_multiple (0, page_cnt); + if (a == NULL) + return NULL; + + /* Initialize the arena to indicate a big block of PAGE_CNT + pages, and return it. */ + a->magic = ARENA_MAGIC; + a->desc = NULL; + a->free_cnt = page_cnt; + return a + 1; + } + + lock_acquire (&d->lock); + + /* If the free list is empty, create a new arena. */ + if (list_empty (&d->free_list)) + { + size_t i; + + /* Allocate a page. */ + a = palloc_get_page (0); + if (a == NULL) + { + lock_release (&d->lock); + return NULL; + } + + /* Initialize arena and add its blocks to the free list. */ + a->magic = ARENA_MAGIC; + a->desc = d; + a->free_cnt = d->blocks_per_arena; + for (i = 0; i < d->blocks_per_arena; i++) + { + struct block *b = arena_to_block (a, i); + list_push_back (&d->free_list, &b->free_elem); + } + } + + /* Get a block from free list and return it. */ + b = list_entry (list_pop_front (&d->free_list), struct block, free_elem); + a = block_to_arena (b); + a->free_cnt--; + lock_release (&d->lock); + return b; +} + +/* Allocates and return A times B bytes initialized to zeroes. + Returns a null pointer if memory is not available. */ +void * +calloc (size_t a, size_t b) +{ + void *p; + size_t size; + + /* Calculate block size and make sure it fits in size_t. */ + size = a * b; + if (size < a || size < b) + return NULL; + + /* Allocate and zero memory. */ + p = malloc (size); + if (p != NULL) + memset (p, 0, size); + + return p; +} + +/* Returns the number of bytes allocated for BLOCK. */ +static size_t +block_size (void *block) +{ + struct block *b = block; + struct arena *a = block_to_arena (b); + struct desc *d = a->desc; + + return d != NULL ? d->block_size : PGSIZE * a->free_cnt - pg_ofs (block); +} + +/* Attempts to resize OLD_BLOCK to NEW_SIZE bytes, possibly + moving it in the process. + If successful, returns the new block; on failure, returns a + null pointer. + A call with null OLD_BLOCK is equivalent to malloc(NEW_SIZE). + A call with zero NEW_SIZE is equivalent to free(OLD_BLOCK). */ +void * +realloc (void *old_block, size_t new_size) +{ + if (new_size == 0) + { + free (old_block); + return NULL; + } + else + { + void *new_block = malloc (new_size); + if (old_block != NULL && new_block != NULL) + { + size_t old_size = block_size (old_block); + size_t min_size = new_size < old_size ? new_size : old_size; + memcpy (new_block, old_block, min_size); + free (old_block); + } + return new_block; + } +} + +/* Frees block P, which must have been previously allocated with + malloc(), calloc(), or realloc(). */ +void +free (void *p) +{ + if (p != NULL) + { + struct block *b = p; + struct arena *a = block_to_arena (b); + struct desc *d = a->desc; + + if (d != NULL) + { + /* It's a normal block. We handle it here. */ + +#ifndef NDEBUG + /* Clear the block to help detect use-after-free bugs. */ + memset (b, 0xcc, d->block_size); +#endif + + lock_acquire (&d->lock); + + /* Add block to free list. */ + list_push_front (&d->free_list, &b->free_elem); + + /* If the arena is now entirely unused, free it. */ + if (++a->free_cnt >= d->blocks_per_arena) + { + size_t i; + + ASSERT (a->free_cnt == d->blocks_per_arena); + for (i = 0; i < d->blocks_per_arena; i++) + { + struct block *b = arena_to_block (a, i); + list_remove (&b->free_elem); + } + palloc_free_page (a); + } + + lock_release (&d->lock); + } + else + { + /* It's a big block. Free its pages. */ + palloc_free_multiple (a, a->free_cnt); + return; + } + } +} + +/* Returns the arena that block B is inside. */ +static struct arena * +block_to_arena (struct block *b) +{ + struct arena *a = pg_round_down (b); + + /* Check that the arena is valid. */ + ASSERT (a != NULL); + ASSERT (a->magic == ARENA_MAGIC); + + /* Check that the block is properly aligned for the arena. */ + ASSERT (a->desc == NULL + || (pg_ofs (b) - sizeof *a) % a->desc->block_size == 0); + ASSERT (a->desc != NULL || pg_ofs (b) == sizeof *a); + + return a; +} + +/* Returns the (IDX - 1)'th block within arena A. */ +static struct block * +arena_to_block (struct arena *a, size_t idx) +{ + ASSERT (a != NULL); + ASSERT (a->magic == ARENA_MAGIC); + ASSERT (idx < a->desc->blocks_per_arena); + return (struct block *) ((uint8_t *) a + + sizeof *a + + idx * a->desc->block_size); +} diff --git a/pintos-progos/threads/malloc.h b/pintos-progos/threads/malloc.h new file mode 100644 index 0000000..bc55d36 --- /dev/null +++ b/pintos-progos/threads/malloc.h @@ -0,0 +1,13 @@ +#ifndef THREADS_MALLOC_H +#define THREADS_MALLOC_H + +#include +#include + +void malloc_init (void); +void *malloc (size_t) __attribute__ ((malloc)); +void *calloc (size_t, size_t) __attribute__ ((malloc)); +void *realloc (void *, size_t); +void free (void *); + +#endif /* threads/malloc.h */ diff --git a/pintos-progos/threads/palloc.c b/pintos-progos/threads/palloc.c new file mode 100644 index 0000000..5c7ef2f --- /dev/null +++ b/pintos-progos/threads/palloc.c @@ -0,0 +1,199 @@ +#include "threads/palloc.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "threads/loader.h" +#include "threads/synch.h" +#include "threads/vaddr.h" + +/* Page allocator. Hands out memory in page-size (or + page-multiple) chunks. See malloc.h for an allocator that + hands out smaller chunks. + + System memory is divided into two "pools" called the kernel + and user pools. The user pool is for user (virtual) memory + pages, the kernel pool for everything else. The idea here is + that the kernel needs to have memory for its own operations + even if user processes are swapping like mad. + + By default, half of system RAM is given to the kernel pool and + half to the user pool. That should be huge overkill for the + kernel pool, but that's just fine for demonstration purposes. */ + +/* A memory pool. */ +struct pool + { + struct lock lock; /* Mutual exclusion. */ + struct bitmap *used_map; /* Bitmap of free pages. */ + uint8_t *base; /* Base of pool. */ + }; + +/* Two pools: one for kernel data, one for user pages. */ +static struct pool kernel_pool, user_pool; + +static void init_pool (struct pool *, void *base, size_t page_cnt, + const char *name); +static bool page_from_pool (const struct pool *, void *page); + +/* Initializes the page allocator. At most USER_PAGE_LIMIT + pages are put into the user pool. */ +void +palloc_init (size_t user_page_limit) +{ + /* Free memory starts at 1 MB and runs to the end of RAM. */ + uint8_t *free_start = ptov (1024 * 1024); + uint8_t *free_end = ptov (init_ram_pages * PGSIZE); + size_t free_pages = (free_end - free_start) / PGSIZE; + size_t user_pages = free_pages / 2; + size_t kernel_pages; + if (user_pages > user_page_limit) + user_pages = user_page_limit; + kernel_pages = free_pages - user_pages; + + /* Give half of memory to kernel, half to user. */ + init_pool (&kernel_pool, free_start, kernel_pages, "kernel pool"); + init_pool (&user_pool, free_start + kernel_pages * PGSIZE, + user_pages, "user pool"); +} + +/* Obtains and returns a group of PAGE_CNT contiguous free pages. + If PAL_USER is set, the pages are obtained from the user pool, + otherwise from the kernel pool. If PAL_ZERO is set in FLAGS, + then the pages are filled with zeros. If too few pages are + available, returns a null pointer, unless PAL_ASSERT is set in + FLAGS, in which case the kernel panics. */ +void * +palloc_get_multiple (enum palloc_flags flags, size_t page_cnt) +{ + struct pool *pool = flags & PAL_USER ? &user_pool : &kernel_pool; + void *pages; + size_t page_idx; + + if (page_cnt == 0) + return NULL; + + lock_acquire (&pool->lock); + page_idx = bitmap_scan_and_flip (pool->used_map, 0, page_cnt, false); + lock_release (&pool->lock); + + if (page_idx != BITMAP_ERROR) + pages = pool->base + PGSIZE * page_idx; + else + pages = NULL; + + if (pages != NULL) + { + if (flags & PAL_ZERO) + memset (pages, 0, PGSIZE * page_cnt); + } + else + { + if (flags & PAL_ASSERT) + PANIC ("palloc_get: out of pages"); + } + + return pages; +} + +/* Obtains a single free page and returns its kernel virtual + address. + If PAL_USER is set, the page is obtained from the user pool, + otherwise from the kernel pool. If PAL_ZERO is set in FLAGS, + then the page is filled with zeros. If no pages are + available, returns a null pointer, unless PAL_ASSERT is set in + FLAGS, in which case the kernel panics. */ +void * +palloc_get_page (enum palloc_flags flags) +{ + return palloc_get_multiple (flags, 1); +} + +/* Frees the PAGE_CNT pages starting at PAGES. */ +void +palloc_free_multiple (void *pages, size_t page_cnt) +{ + struct pool *pool; + size_t page_idx; + + ASSERT (pg_ofs (pages) == 0); + if (pages == NULL || page_cnt == 0) + return; + + if (page_from_pool (&kernel_pool, pages)) + pool = &kernel_pool; + else if (page_from_pool (&user_pool, pages)) + pool = &user_pool; + else + NOT_REACHED (); + + page_idx = pg_no (pages) - pg_no (pool->base); + +#ifndef NDEBUG + memset (pages, 0xcc, PGSIZE * page_cnt); +#endif + + ASSERT (bitmap_all (pool->used_map, page_idx, page_cnt)); + bitmap_set_multiple (pool->used_map, page_idx, page_cnt, false); +} + +/* Frees the page at PAGE. */ +void +palloc_free_page (void *page) +{ + palloc_free_multiple (page, 1); +} + +/* List all used pages */ +void +palloc_dump_used_pages () +{ + struct pool *pool; + int pool_index; + for (pool_index = 0; pool_index < 2; pool_index++) { + pool = (pool_index == 0) ? &kernel_pool : &user_pool; + printf("%s Pool at %p\n", (pool_index == 0) ? "kernel" : "user", pool->base); + size_t start = 0, index; + while ((index = bitmap_scan(pool->used_map,start,1,true)) != BITMAP_ERROR) { + printf(" - %p\n",pool->base + (PGSIZE * index)); + start = index + 1; + } + } +} + +/* Initializes pool P as starting at START and ending at END, + naming it NAME for debugging purposes. */ +static void +init_pool (struct pool *p, void *base, size_t page_cnt, const char *name) +{ + /* We'll put the pool's used_map at its base. + Calculate the space needed for the bitmap + and subtract it from the pool's size. */ + size_t bm_pages = DIV_ROUND_UP (bitmap_buf_size (page_cnt), PGSIZE); + if (bm_pages > page_cnt) + PANIC ("Not enough memory in %s for bitmap.", name); + page_cnt -= bm_pages; + + printf ("%zu pages available in %s.\n", page_cnt, name); + + /* Initialize the pool. */ + lock_init (&p->lock); + p->used_map = bitmap_create_in_buf (page_cnt, base, bm_pages * PGSIZE); + p->base = base + bm_pages * PGSIZE; +} + +/* Returns true if PAGE was allocated from POOL, + false otherwise. */ +static bool +page_from_pool (const struct pool *pool, void *page) +{ + size_t page_no = pg_no (page); + size_t start_page = pg_no (pool->base); + size_t end_page = start_page + bitmap_size (pool->used_map); + + return page_no >= start_page && page_no < end_page; +} diff --git a/pintos-progos/threads/palloc.h b/pintos-progos/threads/palloc.h new file mode 100644 index 0000000..d7f4e0b --- /dev/null +++ b/pintos-progos/threads/palloc.h @@ -0,0 +1,20 @@ +#ifndef THREADS_PALLOC_H +#define THREADS_PALLOC_H + +#include + +/* How to allocate pages. */ +enum palloc_flags + { + PAL_ASSERT = 001, /* Panic on failure. */ + PAL_ZERO = 002, /* Zero page contents. */ + PAL_USER = 004 /* User page. */ + }; +void palloc_init (size_t user_page_limit); +void *palloc_get_page (enum palloc_flags); +void *palloc_get_multiple (enum palloc_flags, size_t page_cnt); +void palloc_free_page (void *); +void palloc_free_multiple (void *, size_t page_cnt); +void palloc_dump_used_pages (void); + +#endif /* threads/palloc.h */ diff --git a/pintos-progos/threads/pte.h b/pintos-progos/threads/pte.h new file mode 100644 index 0000000..1660727 --- /dev/null +++ b/pintos-progos/threads/pte.h @@ -0,0 +1,107 @@ +#ifndef THREADS_PTE_H +#define THREADS_PTE_H + +#include "threads/vaddr.h" + +/* Functions and macros for working with x86 hardware page + tables. + + See vaddr.h for more generic functions and macros for virtual + addresses. + + Virtual addresses are structured as follows: + + 31 22 21 12 11 0 + +----------------------+----------------------+----------------------+ + | Page Directory Index | Page Table Index | Page Offset | + +----------------------+----------------------+----------------------+ +*/ + +/* Page table index (bits 12:21). */ +#define PTSHIFT PGBITS /* First page table bit. */ +#define PTBITS 10 /* Number of page table bits. */ +#define PTSPAN (1 << PTBITS << PGBITS) /* Bytes covered by a page table. */ +#define PTMASK BITMASK(PTSHIFT, PTBITS) /* Page table bits (12:21). */ + +/* Page directory index (bits 22:31). */ +#define PDSHIFT (PTSHIFT + PTBITS) /* First page directory bit. */ +#define PDBITS 10 /* Number of page dir bits. */ +#define PDMASK BITMASK(PDSHIFT, PDBITS) /* Page directory bits (22:31). */ + +/* Obtains page table index from a virtual address. */ +static inline unsigned pt_no (const void *va) { + return ((uintptr_t) va & PTMASK) >> PTSHIFT; +} + +/* Obtains page directory index from a virtual address. */ +static inline uintptr_t pd_no (const void *va) { + return (uintptr_t) va >> PDSHIFT; +} + +/* Page directory and page table entries. + + For more information see the section on page tables in the + Pintos reference guide chapter, or [IA32-v3a] 3.7.6 + "Page-Directory and Page-Table Entries". + + PDEs and PTEs share a common format: + + 31 12 11 0 + +------------------------------------+------------------------+ + | Physical Address | Flags | + +------------------------------------+------------------------+ + + In a PDE, the physical address points to a page table. + In a PTE, the physical address points to a data or code page. + The important flags are listed below. + When a PDE or PTE is not "present", the other flags are + ignored. + A PDE or PTE that is initialized to 0 will be interpreted as + "not present", which is just fine. */ +#define PTE_FLAGS 0x00000fff /* Flag bits. */ +#define PTE_ADDR 0xfffff000 /* Address bits. */ +#define PTE_AVL 0x00000e00 /* Bits available for OS use. */ +#define PTE_P 0x1 /* 1=present, 0=not present. */ +#define PTE_W 0x2 /* 1=read/write, 0=read-only. */ +#define PTE_U 0x4 /* 1=user/kernel, 0=kernel only. */ +#define PTE_A 0x20 /* 1=accessed, 0=not acccessed. */ +#define PTE_D 0x40 /* 1=dirty, 0=not dirty (PTEs only). */ + +/* Returns a PDE that points to page table PT. */ +static inline uint32_t pde_create (uint32_t *pt) { + ASSERT (pg_ofs (pt) == 0); + return vtop (pt) | PTE_U | PTE_P | PTE_W; +} + +/* Returns a pointer to the page table that page directory entry + PDE, which must "present", points to. */ +static inline uint32_t *pde_get_pt (uint32_t pde) { + ASSERT (pde & PTE_P); + return ptov (pde & PTE_ADDR); +} + +/* Returns a PTE that points to PAGE. + The PTE's page is readable. + If WRITABLE is true then it will be writable as well. + The page will be usable only by ring 0 code (the kernel). */ +static inline uint32_t pte_create_kernel (void *page, bool writable) { + ASSERT (pg_ofs (page) == 0); + return vtop (page) | PTE_P | (writable ? PTE_W : 0); +} + +/* Returns a PTE that points to PAGE. + The PTE's page is readable. + If WRITABLE is true then it will be writable as well. + The page will be usable by both user and kernel code. */ +static inline uint32_t pte_create_user (void *page, bool writable) { + return pte_create_kernel (page, writable) | PTE_U; +} + +/* Returns a pointer to the page that page table entry PTE points + to. */ +static inline void *pte_get_page (uint32_t pte) { + return ptov (pte & PTE_ADDR); +} + +#endif /* threads/pte.h */ + diff --git a/pintos-progos/threads/start.S b/pintos-progos/threads/start.S new file mode 100644 index 0000000..29ffa7a --- /dev/null +++ b/pintos-progos/threads/start.S @@ -0,0 +1,204 @@ + #include "threads/loader.h" + +#### Kernel startup code. + +#### The loader (in loader.S) loads the kernel at physical address +#### 0x20000 (128 kB) and jumps to "start", defined here. This code +#### switches from real mode to 32-bit protected mode and calls +#### main(). + +/* Flags in control register 0. */ +#define CR0_PE 0x00000001 /* Protection Enable. */ +#define CR0_EM 0x00000004 /* (Floating-point) Emulation. */ +#define CR0_PG 0x80000000 /* Paging. */ +#define CR0_WP 0x00010000 /* Write-Protect enable in kernel mode. */ + + .section .start + +# The following code runs in real mode, which is a 16-bit code segment. + .code16 + +.func start +.globl start +start: + +# The loader called into us with CS = 0x2000, SS = 0x0000, ESP = 0xf000, +# but we should initialize the other segment registers. + + mov $0x2000, %ax + mov %ax, %ds + mov %ax, %es + +# Set string instructions to go upward. + cld + +#### Get memory size, via interrupt 15h function 88h (see [IntrList]), +#### which returns AX = (kB of physical memory) - 1024. This only +#### works for memory sizes <= 65 MB, which should be fine for our +#### purposes. We cap memory at 64 MB because that's all we prepare +#### page tables for, below. + + movb $0x88, %ah + int $0x15 + addl $1024, %eax # Total kB memory + cmp $0x10000, %eax # Cap at 64 MB + jbe 1f + mov $0x10000, %eax +1: shrl $2, %eax # Total 4 kB pages + addr32 movl %eax, init_ram_pages - LOADER_PHYS_BASE - 0x20000 + +#### Enable A20. Address line 20 is tied low when the machine boots, +#### which prevents addressing memory about 1 MB. This code fixes it. + +# Poll status register while busy. + +1: inb $0x64, %al + testb $0x2, %al + jnz 1b + +# Send command for writing output port. + + movb $0xd1, %al + outb %al, $0x64 + +# Poll status register while busy. + +1: inb $0x64, %al + testb $0x2, %al + jnz 1b + +# Enable A20 line. + + movb $0xdf, %al + outb %al, $0x60 + +# Poll status register while busy. + +1: inb $0x64, %al + testb $0x2, %al + jnz 1b + +#### Create temporary page directory and page table and set page +#### directory base register. + +# Create page directory at 0xf000 (60 kB) and fill with zeroes. + mov $0xf00, %ax + mov %ax, %es + subl %eax, %eax + subl %edi, %edi + movl $0x400, %ecx + rep stosl + +# Add PDEs to point to page tables for the first 64 MB of RAM. +# Also add identical PDEs starting at LOADER_PHYS_BASE. +# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries" +# for a description of the bits in %eax. + + movl $0x10007, %eax + movl $0x11, %ecx + subl %edi, %edi +1: movl %eax, %es:(%di) + movl %eax, %es:LOADER_PHYS_BASE >> 20(%di) + addw $4, %di + addl $0x1000, %eax + loop 1b + +# Set up page tables for one-to-map linear to physical map for the +# first 64 MB of RAM. +# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries" +# for a description of the bits in %eax. + + movw $0x1000, %ax + movw %ax, %es + movl $0x7, %eax + movl $0x4000, %ecx + subl %edi, %edi +1: movl %eax, %es:(%di) + addw $4, %di + addl $0x1000, %eax + loop 1b + +# Set page directory base register. + + movl $0xf000, %eax + movl %eax, %cr3 + +#### Switch to protected mode. + +# First, disable interrupts. We won't set up the IDT until we get +# into C code, so any interrupt would blow us away. + + cli + +# Protected mode requires a GDT, so point the GDTR to our GDT. +# We need a data32 prefix to ensure that all 32 bits of the GDT +# descriptor are loaded (default is to load only 24 bits). +# The CPU doesn't need an addr32 prefix but ELF doesn't do 16-bit +# relocations. + + data32 addr32 lgdt gdtdesc - LOADER_PHYS_BASE - 0x20000 + +# Then we turn on the following bits in CR0: +# PE (Protect Enable): this turns on protected mode. +# PG (Paging): turns on paging. +# WP (Write Protect): if unset, ring 0 code ignores +# write-protect bits in page tables (!). +# EM (Emulation): forces floating-point instructions to trap. +# We don't support floating point. + + movl %cr0, %eax + orl $CR0_PE | CR0_PG | CR0_WP | CR0_EM, %eax + movl %eax, %cr0 + +# We're now in protected mode in a 16-bit segment. The CPU still has +# the real-mode code segment cached in %cs's segment descriptor. We +# need to reload %cs, and the easiest way is to use a far jump. +# Because we're not running in a 32-bit segment the data32 prefix is +# needed to jump to a 32-bit offset in the target segment. + + data32 ljmp $SEL_KCSEG, $1f + +# We're now in protected mode in a 32-bit segment. +# Let the assembler know. + + .code32 + +# Reload all the other segment registers and the stack pointer to +# point into our new GDT. + +1: mov $SEL_KDSEG, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + mov %ax, %ss + addl $LOADER_PHYS_BASE, %esp + movl $0, %ebp # Null-terminate main()'s backtrace + +#### Call main(). + + call main + +# main() shouldn't ever return. If it does, spin. + +1: jmp 1b +.endfunc + +#### GDT + + .align 8 +gdt: + .quad 0x0000000000000000 # Null segment. Not used by CPU. + .quad 0x00cf9a000000ffff # System code, base 0, limit 4 GB. + .quad 0x00cf92000000ffff # System data, base 0, limit 4 GB. + +gdtdesc: + .word gdtdesc - gdt - 1 # Size of the GDT, minus 1 byte. + .long gdt # Address of the GDT. + +#### Physical memory size in 4 kB pages. This is exported to the rest +#### of the kernel. +.globl init_ram_pages +init_ram_pages: + .long 0 + diff --git a/pintos-progos/threads/switch.S b/pintos-progos/threads/switch.S new file mode 100644 index 0000000..feca86c --- /dev/null +++ b/pintos-progos/threads/switch.S @@ -0,0 +1,65 @@ +#include "threads/switch.h" + +#### struct thread *switch_threads (struct thread *cur, struct thread *next); +#### +#### Switches from CUR, which must be the running thread, to NEXT, +#### which must also be running switch_threads(), returning CUR in +#### NEXT's context. +#### +#### This function works by assuming that the thread we're switching +#### into is also running switch_threads(). Thus, all it has to do is +#### preserve a few registers on the stack, then switch stacks and +#### restore the registers. As part of switching stacks we record the +#### current stack pointer in CUR's thread structure. + +.globl switch_threads +.func switch_threads +switch_threads: + # Save caller's register state. + # + # Note that the SVR4 ABI allows us to destroy %eax, %ecx, %edx, + # but requires us to preserve %ebx, %ebp, %esi, %edi. See + # [SysV-ABI-386] pages 3-11 and 3-12 for details. + # + # This stack frame must match the one set up by thread_create() + # in size. + pushl %ebx + pushl %ebp + pushl %esi + pushl %edi + + # Get offsetof (struct thread, stack). +.globl thread_stack_ofs + mov thread_stack_ofs, %edx + + # Save current stack pointer to old thread's stack, if any. + movl SWITCH_CUR(%esp), %eax + movl %esp, (%eax,%edx,1) + + # Restore stack pointer from new thread's stack. + movl SWITCH_NEXT(%esp), %ecx + movl (%ecx,%edx,1), %esp + + # Restore caller's register state. + popl %edi + popl %esi + popl %ebp + popl %ebx + ret +.endfunc + +.globl switch_entry +.func switch_entry +switch_entry: + # Discard switch_threads() arguments. + addl $8, %esp + + # Call thread_schedule_tail(prev). + pushl %eax +.globl thread_schedule_tail + call thread_schedule_tail + addl $4, %esp + + # Start thread proper. + ret +.endfunc diff --git a/pintos-progos/threads/switch.h b/pintos-progos/threads/switch.h new file mode 100644 index 0000000..cc156b6 --- /dev/null +++ b/pintos-progos/threads/switch.h @@ -0,0 +1,39 @@ +#ifndef THREADS_SWITCH_H +#define THREADS_SWITCH_H + +#ifndef __ASSEMBLER__ +/* switch_thread()'s stack frame. */ +struct switch_threads_frame + { + uint32_t edi; /* 0: Saved %edi. */ + uint32_t esi; /* 4: Saved %esi. */ + uint32_t ebp; /* 8: Saved %ebp. */ + uint32_t ebx; /* 12: Saved %ebx. */ + void (*eip) (void); /* 16: Return address. */ + struct thread *cur; /* 20: switch_threads()'s CUR argument. */ + struct thread *next; /* 24: switch_threads()'s NEXT argument. */ + }; + +/* Switches from CUR, which must be the running thread, to NEXT, + which must also be running switch_threads(), returning CUR in + NEXT's context. */ +struct thread *switch_threads (struct thread *cur, struct thread *next); + +/* Stack frame for switch_entry(). */ +struct switch_entry_frame + { + void (*eip) (void); + }; + +void switch_entry (void); + +/* Pops the CUR and NEXT arguments off the stack, for use in + initializing threads. */ +void switch_thunk (void); +#endif + +/* Offsets used by switch.S. */ +#define SWITCH_CUR 20 +#define SWITCH_NEXT 24 + +#endif /* threads/switch.h */ diff --git a/pintos-progos/threads/synch.c b/pintos-progos/threads/synch.c new file mode 100644 index 0000000..317c68a --- /dev/null +++ b/pintos-progos/threads/synch.c @@ -0,0 +1,338 @@ +/* This file is derived from source code for the Nachos + instructional operating system. The Nachos copyright notice + is reproduced in full below. */ + +/* Copyright (c) 1992-1996 The Regents of the University of California. + All rights reserved. + + Permission to use, copy, modify, and distribute this software + and its documentation for any purpose, without fee, and + without written agreement is hereby granted, provided that the + above copyright notice and the following two paragraphs appear + in all copies of this software. + + IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO + ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE + AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA + HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" + BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO + PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR + MODIFICATIONS. +*/ + +#include "threads/synch.h" +#include +#include +#include "threads/interrupt.h" +#include "threads/thread.h" + +/* Initializes semaphore SEMA to VALUE. A semaphore is a + nonnegative integer along with two atomic operators for + manipulating it: + + - down or "P": wait for the value to become positive, then + decrement it. + + - up or "V": increment the value (and wake up one waiting + thread, if any). */ +void +sema_init (struct semaphore *sema, unsigned value) +{ + ASSERT (sema != NULL); + + sema->value = value; + list_init (&sema->waiters); +} + +/* Down or "P" operation on a semaphore. Waits for SEMA's value + to become positive and then atomically decrements it. + + This function may sleep, so it must not be called within an + interrupt handler. This function may be called with + interrupts disabled, but if it sleeps then the next scheduled + thread will probably turn interrupts back on. */ +void +sema_down (struct semaphore *sema) +{ + enum intr_level old_level; + + ASSERT (sema != NULL); + ASSERT (!intr_context ()); + + old_level = intr_disable (); + while (sema->value == 0) + { + list_push_back (&sema->waiters, &thread_current ()->elem); + thread_block (); + } + sema->value--; + intr_set_level (old_level); +} + +/* Down or "P" operation on a semaphore, but only if the + semaphore is not already 0. Returns true if the semaphore is + decremented, false otherwise. + + This function may be called from an interrupt handler. */ +bool +sema_try_down (struct semaphore *sema) +{ + enum intr_level old_level; + bool success; + + ASSERT (sema != NULL); + + old_level = intr_disable (); + if (sema->value > 0) + { + sema->value--; + success = true; + } + else + success = false; + intr_set_level (old_level); + + return success; +} + +/* Up or "V" operation on a semaphore. Increments SEMA's value + and wakes up one thread of those waiting for SEMA, if any. + + This function may be called from an interrupt handler. */ +void +sema_up (struct semaphore *sema) +{ + enum intr_level old_level; + + ASSERT (sema != NULL); + + old_level = intr_disable (); + if (!list_empty (&sema->waiters)) + thread_unblock (list_entry (list_pop_front (&sema->waiters), + struct thread, elem)); + sema->value++; + intr_set_level (old_level); +} + +static void sema_test_helper (void *sema_); + +/* Self-test for semaphores that makes control "ping-pong" + between a pair of threads. Insert calls to printf() to see + what's going on. */ +void +sema_self_test (void) +{ + struct semaphore sema[2]; + int i; + + printf ("Testing semaphores..."); + sema_init (&sema[0], 0); + sema_init (&sema[1], 0); + thread_create ("sema-test", PRI_DEFAULT, sema_test_helper, &sema); + for (i = 0; i < 10; i++) + { + sema_up (&sema[0]); + sema_down (&sema[1]); + } + printf ("done.\n"); +} + +/* Thread function used by sema_self_test(). */ +static void +sema_test_helper (void *sema_) +{ + struct semaphore *sema = sema_; + int i; + + for (i = 0; i < 10; i++) + { + sema_down (&sema[0]); + sema_up (&sema[1]); + } +} + +/* Initializes LOCK. A lock can be held by at most a single + thread at any given time. Our locks are not "recursive", that + is, it is an error for the thread currently holding a lock to + try to acquire that lock. + + A lock is a specialization of a semaphore with an initial + value of 1. The difference between a lock and such a + semaphore is twofold. First, a semaphore can have a value + greater than 1, but a lock can only be owned by a single + thread at a time. Second, a semaphore does not have an owner, + meaning that one thread can "down" the semaphore and then + another one "up" it, but with a lock the same thread must both + acquire and release it. When these restrictions prove + onerous, it's a good sign that a semaphore should be used, + instead of a lock. */ +void +lock_init (struct lock *lock) +{ + ASSERT (lock != NULL); + + lock->holder = NULL; + sema_init (&lock->semaphore, 1); +} + +/* Acquires LOCK, sleeping until it becomes available if + necessary. The lock must not already be held by the current + thread. + + This function may sleep, so it must not be called within an + interrupt handler. This function may be called with + interrupts disabled, but interrupts will be turned back on if + we need to sleep. */ +void +lock_acquire (struct lock *lock) +{ + ASSERT (lock != NULL); + ASSERT (!intr_context ()); + ASSERT (!lock_held_by_current_thread (lock)); + + sema_down (&lock->semaphore); + lock->holder = thread_current (); +} + +/* Tries to acquires LOCK and returns true if successful or false + on failure. The lock must not already be held by the current + thread. + + This function will not sleep, so it may be called within an + interrupt handler. */ +bool +lock_try_acquire (struct lock *lock) +{ + bool success; + + ASSERT (lock != NULL); + ASSERT (!lock_held_by_current_thread (lock)); + + success = sema_try_down (&lock->semaphore); + if (success) + lock->holder = thread_current (); + return success; +} + +/* Releases LOCK, which must be owned by the current thread. + + An interrupt handler cannot acquire a lock, so it does not + make sense to try to release a lock within an interrupt + handler. */ +void +lock_release (struct lock *lock) +{ + ASSERT (lock != NULL); + ASSERT (lock_held_by_current_thread (lock)); + + lock->holder = NULL; + sema_up (&lock->semaphore); +} + +/* Returns true if the current thread holds LOCK, false + otherwise. (Note that testing whether some other thread holds + a lock would be racy.) */ +bool +lock_held_by_current_thread (const struct lock *lock) +{ + ASSERT (lock != NULL); + + return lock->holder == thread_current (); +} + +/* One semaphore in a list. */ +struct semaphore_elem + { + struct list_elem elem; /* List element. */ + struct semaphore semaphore; /* This semaphore. */ + }; + +/* Initializes condition variable COND. A condition variable + allows one piece of code to signal a condition and cooperating + code to receive the signal and act upon it. */ +void +cond_init (struct condition *cond) +{ + ASSERT (cond != NULL); + + list_init (&cond->waiters); +} + +/* Atomically releases LOCK and waits for COND to be signaled by + some other piece of code. After COND is signaled, LOCK is + reacquired before returning. LOCK must be held before calling + this function. + + The monitor implemented by this function is "Mesa" style, not + "Hoare" style, that is, sending and receiving a signal are not + an atomic operation. Thus, typically the caller must recheck + the condition after the wait completes and, if necessary, wait + again. + + A given condition variable is associated with only a single + lock, but one lock may be associated with any number of + condition variables. That is, there is a one-to-many mapping + from locks to condition variables. + + This function may sleep, so it must not be called within an + interrupt handler. This function may be called with + interrupts disabled, but interrupts will be turned back on if + we need to sleep. */ +void +cond_wait (struct condition *cond, struct lock *lock) +{ + struct semaphore_elem waiter; + + ASSERT (cond != NULL); + ASSERT (lock != NULL); + ASSERT (!intr_context ()); + ASSERT (lock_held_by_current_thread (lock)); + + sema_init (&waiter.semaphore, 0); + list_push_back (&cond->waiters, &waiter.elem); + lock_release (lock); + sema_down (&waiter.semaphore); + lock_acquire (lock); +} + +/* If any threads are waiting on COND (protected by LOCK), then + this function signals one of them to wake up from its wait. + LOCK must be held before calling this function. + + An interrupt handler cannot acquire a lock, so it does not + make sense to try to signal a condition variable within an + interrupt handler. */ +void +cond_signal (struct condition *cond, struct lock *lock UNUSED) +{ + ASSERT (cond != NULL); + ASSERT (lock != NULL); + ASSERT (!intr_context ()); + ASSERT (lock_held_by_current_thread (lock)); + + if (!list_empty (&cond->waiters)) + sema_up (&list_entry (list_pop_front (&cond->waiters), + struct semaphore_elem, elem)->semaphore); +} + +/* Wakes up all threads, if any, waiting on COND (protected by + LOCK). LOCK must be held before calling this function. + + An interrupt handler cannot acquire a lock, so it does not + make sense to try to signal a condition variable within an + interrupt handler. */ +void +cond_broadcast (struct condition *cond, struct lock *lock) +{ + ASSERT (cond != NULL); + ASSERT (lock != NULL); + + while (!list_empty (&cond->waiters)) + cond_signal (cond, lock); +} diff --git a/pintos-progos/threads/synch.h b/pintos-progos/threads/synch.h new file mode 100644 index 0000000..a19e88b --- /dev/null +++ b/pintos-progos/threads/synch.h @@ -0,0 +1,51 @@ +#ifndef THREADS_SYNCH_H +#define THREADS_SYNCH_H + +#include +#include + +/* A counting semaphore. */ +struct semaphore + { + unsigned value; /* Current value. */ + struct list waiters; /* List of waiting threads. */ + }; + +void sema_init (struct semaphore *, unsigned value); +void sema_down (struct semaphore *); +bool sema_try_down (struct semaphore *); +void sema_up (struct semaphore *); +void sema_self_test (void); + +/* Lock. */ +struct lock + { + struct thread *holder; /* Thread holding lock (for debugging). */ + struct semaphore semaphore; /* Binary semaphore controlling access. */ + }; + +void lock_init (struct lock *); +void lock_acquire (struct lock *); +bool lock_try_acquire (struct lock *); +void lock_release (struct lock *); +bool lock_held_by_current_thread (const struct lock *); + +/* Condition variable. */ +struct condition + { + struct list waiters; /* List of waiting threads. */ + }; + +void cond_init (struct condition *); +void cond_wait (struct condition *, struct lock *); +void cond_signal (struct condition *, struct lock *); +void cond_broadcast (struct condition *, struct lock *); + +/* Optimization barrier. + + The compiler will not reorder operations across an + optimization barrier. See "Optimization Barriers" in the + reference guide for more information.*/ +#define barrier() asm volatile ("" : : : "memory") + +#endif /* threads/synch.h */ diff --git a/pintos-progos/threads/thread.c b/pintos-progos/threads/thread.c new file mode 100644 index 0000000..845f059 --- /dev/null +++ b/pintos-progos/threads/thread.c @@ -0,0 +1,594 @@ +#include "threads/thread.h" +#include +#include +#include +#include +#include +#include "threads/flags.h" +#include "threads/interrupt.h" +#include "threads/intr-stubs.h" +#include "threads/palloc.h" +#include "threads/switch.h" +#include "threads/synch.h" +#include "threads/vaddr.h" +#ifdef USERPROG +#include "userprog/process.h" +#endif + +/* Random value for struct thread's `magic' member. + Used to detect stack overflow. See the big comment at the top + of thread.h for details. */ +#define THREAD_MAGIC 0xcd6abf4b + +/* List of processes in THREAD_READY state, that is, processes + that are ready to run but not actually running. */ +static struct list ready_list; + +/* List of all processes. Processes are added to this list + when they are first scheduled and removed when they exit. */ +static struct list all_list; + +/* Idle thread. */ +static struct thread *idle_thread; + +/* Initial thread, the thread running init.c:main(). */ +static struct thread *initial_thread; + +/* Lock used by allocate_tid(). */ +static struct lock tid_lock; + +/* Stack frame for kernel_thread(). */ +struct kernel_thread_frame + { + void *eip; /* Return address. */ + thread_func *function; /* Function to call. */ + void *aux; /* Auxiliary data for function. */ + }; + +/* Statistics. */ +static long long idle_ticks; /* # of timer ticks spent idle. */ +static long long kernel_ticks; /* # of timer ticks in kernel threads. */ +static long long user_ticks; /* # of timer ticks in user programs. */ + +/* Scheduling. */ +#define TIME_SLICE 4 /* # of timer ticks to give each thread. */ +static unsigned thread_ticks; /* # of timer ticks since last yield. */ + +/* If false (default), use round-robin scheduler. + If true, use multi-level feedback queue scheduler. + Controlled by kernel command-line option "-o mlfqs". */ +bool thread_mlfqs; + +static void kernel_thread (thread_func *, void *aux); + +static void idle (void *aux UNUSED); +static struct thread *running_thread (void); +static struct thread *next_thread_to_run (void); +static void init_thread (struct thread *, const char *name, int priority); +static bool is_thread (struct thread *) UNUSED; +static void *alloc_frame (struct thread *, size_t size); +static void schedule (void); +void thread_schedule_tail (struct thread *prev); +static tid_t allocate_tid (void); + +/* Initializes the threading system by transforming the code + that's currently running into a thread. This can't work in + general and it is possible in this case only because loader.S + was careful to put the bottom of the stack at a page boundary. + + Also initializes the run queue and the tid lock. + + After calling this function, be sure to initialize the page + allocator before trying to create any threads with + thread_create(). + + It is not safe to call thread_current() until this function + finishes. */ +void +thread_init (void) +{ + ASSERT (intr_get_level () == INTR_OFF); + + lock_init (&tid_lock); + list_init (&ready_list); + list_init (&all_list); + +#ifdef USERPROG + process_init (); +#endif + + /* Set up a thread structure for the running thread. */ + initial_thread = running_thread (); + init_thread (initial_thread, "main", PRI_DEFAULT); + initial_thread->status = THREAD_RUNNING; + initial_thread->tid = allocate_tid (); +} + +/* Starts preemptive thread scheduling by enabling interrupts. + Also creates the idle thread. */ +void +thread_start (void) +{ + /* Create the idle thread. */ + struct semaphore idle_started; + sema_init (&idle_started, 0); + thread_create ("idle", PRI_MIN, idle, &idle_started); + + /* Start preemptive thread scheduling. */ + intr_enable (); + + /* Wait for the idle thread to initialize idle_thread. */ + sema_down (&idle_started); +} + +/* Called by the timer interrupt handler at each timer tick. + Thus, this function runs in an external interrupt context. */ +void +thread_tick (void) +{ + struct thread *t = thread_current (); + + /* Update statistics. */ + if (t == idle_thread) + idle_ticks++; +#ifdef USERPROG + else if (t->pagedir != NULL) + user_ticks++; +#endif + else + kernel_ticks++; + + /* Enforce preemption. */ + if (++thread_ticks >= TIME_SLICE) + intr_yield_on_return (); +} + +/* Prints thread statistics. */ +void +thread_print_stats (void) +{ + printf ("Thread: %lld idle ticks, %lld kernel ticks, %lld user ticks\n", + idle_ticks, kernel_ticks, user_ticks); +} + +/* Creates a new kernel thread named NAME with the given initial + PRIORITY, which executes FUNCTION passing AUX as the argument, + and adds it to the ready queue. Returns the thread identifier + for the new thread, or TID_ERROR if creation fails. + + If thread_start() has been called, then the new thread may be + scheduled before thread_create() returns. It could even exit + before thread_create() returns. Contrariwise, the original + thread may run for any amount of time before the new thread is + scheduled. Use a semaphore or some other form of + synchronization if you need to ensure ordering. + + The code provided sets the new thread's `priority' member to + PRIORITY, but no actual priority scheduling is implemented. + Priority scheduling is the goal of Problem 1-3. */ +tid_t +thread_create (const char *name, int priority, + thread_func *function, void *aux) +{ + struct thread *t; + struct kernel_thread_frame *kf; + struct switch_entry_frame *ef; + struct switch_threads_frame *sf; + tid_t tid; + enum intr_level old_level; + + ASSERT (function != NULL); + + /* Allocate thread. */ + t = palloc_get_page (PAL_ZERO); + if (t == NULL) + return TID_ERROR; + + /* Initialize thread. */ + init_thread (t, name, priority); + tid = t->tid = allocate_tid (); + + /* Prepare thread for first run by initializing its stack. + Do this atomically so intermediate values for the 'stack' + member cannot be observed. */ + old_level = intr_disable (); + + /* Stack frame for kernel_thread(). */ + kf = alloc_frame (t, sizeof *kf); + kf->eip = NULL; + kf->function = function; + kf->aux = aux; + + /* Stack frame for switch_entry(). */ + ef = alloc_frame (t, sizeof *ef); + ef->eip = (void (*) (void)) kernel_thread; + + /* Stack frame for switch_threads(). */ + sf = alloc_frame (t, sizeof *sf); + sf->eip = switch_entry; + sf->ebp = 0; + + intr_set_level (old_level); + + /* Add to run queue. */ + thread_unblock (t); + + return tid; +} + +/* Puts the current thread to sleep. It will not be scheduled + again until awoken by thread_unblock(). + + This function must be called with interrupts turned off. It + is usually a better idea to use one of the synchronization + primitives in synch.h. */ +void +thread_block (void) +{ + ASSERT (!intr_context ()); + ASSERT (intr_get_level () == INTR_OFF); + + thread_current ()->status = THREAD_BLOCKED; + schedule (); +} + +/* Transitions a blocked thread T to the ready-to-run state. + This is an error if T is not blocked. (Use thread_yield() to + make the running thread ready.) + + This function does not preempt the running thread. This can + be important: if the caller had disabled interrupts itself, + it may expect that it can atomically unblock a thread and + update other data. */ +void +thread_unblock (struct thread *t) +{ + enum intr_level old_level; + + ASSERT (is_thread (t)); + + old_level = intr_disable (); + ASSERT (t->status == THREAD_BLOCKED); + list_push_back (&ready_list, &t->elem); + t->status = THREAD_READY; + intr_set_level (old_level); +} + +/* Returns the name of the running thread. */ +const char * +thread_name (void) +{ + return thread_current ()->name; +} + +/* Returns the running thread. + This is running_thread() plus a couple of sanity checks. + See the big comment at the top of thread.h for details. */ +struct thread * +thread_current (void) +{ + struct thread *t = running_thread (); + + /* Make sure T is really a thread. + If either of these assertions fire, then your thread may + have overflowed its stack. Each thread has less than 4 kB + of stack, so a few big automatic arrays or moderate + recursion can cause stack overflow. */ + ASSERT (is_thread (t)); + ASSERT (t->status == THREAD_RUNNING); + + return t; +} + +/* Returns the running thread's tid. */ +tid_t +thread_tid (void) +{ + return thread_current ()->tid; +} + +/* Deschedules the current thread and destroys it. Never + returns to the caller. */ +void +thread_exit (void) +{ + ASSERT (!intr_context ()); + +#ifdef USERPROG + process_exit (); +#endif + + /* Remove thread from all threads list, set our status to dying, + and schedule another process. That process will destroy us + when it calls thread_schedule_tail(). */ + intr_disable (); + list_remove (&thread_current()->allelem); + thread_current ()->status = THREAD_DYING; + schedule (); + NOT_REACHED (); +} + +/* Yields the CPU. The current thread is not put to sleep and + may be scheduled again immediately at the scheduler's whim. */ +void +thread_yield (void) +{ + struct thread *cur = thread_current (); + enum intr_level old_level; + + ASSERT (!intr_context ()); + + old_level = intr_disable (); + if (cur != idle_thread) + list_push_back (&ready_list, &cur->elem); + cur->status = THREAD_READY; + schedule (); + intr_set_level (old_level); +} + +/* Invoke function 'func' on all threads, passing along 'aux'. + This function must be called with interrupts off. */ +void +thread_foreach (thread_action_func *func, void *aux) +{ + struct list_elem *e; + + ASSERT (intr_get_level () == INTR_OFF); + + for (e = list_begin (&all_list); e != list_end (&all_list); + e = list_next (e)) + { + struct thread *t = list_entry (e, struct thread, allelem); + func (t, aux); + } +} + +/* Sets the current thread's priority to NEW_PRIORITY. */ +void +thread_set_priority (int new_priority) +{ + thread_current ()->priority = new_priority; +} + +/* Returns the current thread's priority. */ +int +thread_get_priority (void) +{ + return thread_current ()->priority; +} + +/* Sets the current thread's nice value to NICE. */ +void +thread_set_nice (int nice UNUSED) +{ + /* Not yet implemented. */ +} + +/* Returns the current thread's nice value. */ +int +thread_get_nice (void) +{ + /* Not yet implemented. */ + return 0; +} + +/* Returns 100 times the system load average. */ +int +thread_get_load_avg (void) +{ + /* Not yet implemented. */ + return 0; +} + +/* Returns 100 times the current thread's recent_cpu value. */ +int +thread_get_recent_cpu (void) +{ + /* Not yet implemented. */ + return 0; +} + +/* Idle thread. Executes when no other thread is ready to run. + + The idle thread is initially put on the ready list by + thread_start(). It will be scheduled once initially, at which + point it initializes idle_thread, "up"s the semaphore passed + to it to enable thread_start() to continue, and immediately + blocks. After that, the idle thread never appears in the + ready list. It is returned by next_thread_to_run() as a + special case when the ready list is empty. */ +static void +idle (void *idle_started_ UNUSED) +{ + struct semaphore *idle_started = idle_started_; + idle_thread = thread_current (); + sema_up (idle_started); + + for (;;) + { + /* Let someone else run. */ + intr_disable (); + thread_block (); + + /* Re-enable interrupts and wait for the next one. + + The `sti' instruction disables interrupts until the + completion of the next instruction, so these two + instructions are executed atomically. This atomicity is + important; otherwise, an interrupt could be handled + between re-enabling interrupts and waiting for the next + one to occur, wasting as much as one clock tick worth of + time. + + See [IA32-v2a] "HLT", [IA32-v2b] "STI", and [IA32-v3a] + 7.11.1 "HLT Instruction". */ + asm volatile ("sti; hlt" : : : "memory"); + } +} + +/* Function used as the basis for a kernel thread. */ +static void +kernel_thread (thread_func *function, void *aux) +{ + ASSERT (function != NULL); + + intr_enable (); /* The scheduler runs with interrupts off. */ + function (aux); /* Execute the thread function. */ + thread_exit (); /* If function() returns, kill the thread. */ +} + +/* Returns the running thread. */ +struct thread * +running_thread (void) +{ + uint32_t *esp; + + /* Copy the CPU's stack pointer into `esp', and then round that + down to the start of a page. Because `struct thread' is + always at the beginning of a page and the stack pointer is + somewhere in the middle, this locates the curent thread. */ + asm ("mov %%esp, %0" : "=g" (esp)); + return pg_round_down (esp); +} + +/* Returns true if T appears to point to a valid thread. */ +static bool +is_thread (struct thread *t) +{ + return t != NULL && t->magic == THREAD_MAGIC; +} + +/* Does basic initialization of T as a blocked thread named + NAME. */ +static void +init_thread (struct thread *t, const char *name, int priority) +{ + ASSERT (t != NULL); + ASSERT (PRI_MIN <= priority && priority <= PRI_MAX); + ASSERT (name != NULL); + + memset (t, 0, sizeof *t); + t->status = THREAD_BLOCKED; + strlcpy (t->name, name, sizeof t->name); + t->stack = (uint8_t *) t + PGSIZE; + t->priority = priority; + t->magic = THREAD_MAGIC; + list_push_back (&all_list, &t->allelem); +#ifdef USERPROG + list_init(&t->children); +#endif +} + +/* Allocates a SIZE-byte frame at the top of thread T's stack and + returns a pointer to the frame's base. */ +static void * +alloc_frame (struct thread *t, size_t size) +{ + /* Stack data is always allocated in word-size units. */ + ASSERT (is_thread (t)); + ASSERT (size % sizeof (uint32_t) == 0); + + t->stack -= size; + return t->stack; +} + +/* Chooses and returns the next thread to be scheduled. Should + return a thread from the run queue, unless the run queue is + empty. (If the running thread can continue running, then it + will be in the run queue.) If the run queue is empty, return + idle_thread. */ +static struct thread * +next_thread_to_run (void) +{ + if (list_empty (&ready_list)) + return idle_thread; + else + return list_entry (list_pop_front (&ready_list), struct thread, elem); +} + +/* Completes a thread switch by activating the new thread's page + tables, and, if the previous thread is dying, destroying it. + + At this function's invocation, we just switched from thread + PREV, the new thread is already running, and interrupts are + still disabled. This function is normally invoked by + thread_schedule() as its final action before returning, but + the first time a thread is scheduled it is called by + switch_entry() (see switch.S). + + It's not safe to call printf() until the thread switch is + complete. In practice that means that printf()s should be + added at the end of the function. + + After this function and its caller returns, the thread switch + is complete. */ +void +thread_schedule_tail (struct thread *prev) +{ + struct thread *cur = running_thread (); + + ASSERT (intr_get_level () == INTR_OFF); + + /* Mark us as running. */ + cur->status = THREAD_RUNNING; + + /* Start new time slice. */ + thread_ticks = 0; + +#ifdef USERPROG + /* Activate the new address space. */ + process_activate (); +#endif + + /* If the thread we switched from is dying, destroy its struct + thread. This must happen late so that thread_exit() doesn't + pull out the rug under itself. (We don't free + initial_thread because its memory was not obtained via + palloc().) */ + if (prev != NULL && prev->status == THREAD_DYING && prev != initial_thread) + { + ASSERT (prev != cur); + palloc_free_page (prev); + } +} + +/* Schedules a new process. At entry, interrupts must be off and + the running process's state must have been changed from + running to some other state. This function finds another + thread to run and switches to it. + + It's not safe to call printf() until thread_schedule_tail() + has completed. */ +static void +schedule (void) +{ + struct thread *cur = running_thread (); + struct thread *next = next_thread_to_run (); + struct thread *prev = NULL; + + ASSERT (intr_get_level () == INTR_OFF); + ASSERT (cur->status != THREAD_RUNNING); + ASSERT (is_thread (next)); + + if (cur != next) + prev = switch_threads (cur, next); + thread_schedule_tail (prev); +} + +/* Returns a tid to use for a new thread. */ +static tid_t +allocate_tid (void) +{ + static tid_t next_tid = 1; + tid_t tid; + + lock_acquire (&tid_lock); + tid = next_tid++; + lock_release (&tid_lock); + + return tid; +} + +/* Offset of `stack' member within `struct thread'. + Used by switch.S, which can't figure it out on its own. */ +uint32_t thread_stack_ofs = offsetof (struct thread, stack); diff --git a/pintos-progos/threads/thread.h b/pintos-progos/threads/thread.h new file mode 100644 index 0000000..eebf42c --- /dev/null +++ b/pintos-progos/threads/thread.h @@ -0,0 +1,144 @@ +#ifndef THREADS_THREAD_H +#define THREADS_THREAD_H + +#include +#include +#include +#include "threads/synch.h" + +/* States in a thread's life cycle. */ +enum thread_status + { + THREAD_RUNNING, /* Running thread. */ + THREAD_READY, /* Not running but ready to run. */ + THREAD_BLOCKED, /* Waiting for an event to trigger. */ + THREAD_DYING /* About to be destroyed. */ + }; + +/* Thread identifier type. + You can redefine this to whatever type you like. */ +typedef int tid_t; +#define TID_ERROR ((tid_t) -1) /* Error value for tid_t. */ + +/* Thread priorities. */ +#define PRI_MIN 0 /* Lowest priority. */ +#define PRI_DEFAULT 31 /* Default priority. */ +#define PRI_MAX 63 /* Highest priority. */ + +/* A kernel thread or user process. + + Each thread structure is stored in its own 4 kB page. The + thread structure itself sits at the very bottom of the page + (at offset 0). The rest of the page is reserved for the + thread's kernel stack, which grows downward from the top of + the page (at offset 4 kB). Here's an illustration: + + 4 kB +---------------------------------+ + | kernel stack | + | | | + | | | + | V | + | grows downward | + | | + | | + | | + | | + | | + | | + | | + | | + +---------------------------------+ + | magic | + | : | + | : | + | name | + | status | + 0 kB +---------------------------------+ + + The upshot of this is twofold: + + 1. First, `struct thread' must not be allowed to grow too + big. If it does, then there will not be enough room for + the kernel stack. Our base `struct thread' is only a + few bytes in size. It probably should stay well under 1 + kB. + + 2. Second, kernel stacks must not be allowed to grow too + large. If a stack overflows, it will corrupt the thread + state. Thus, kernel functions should not allocate large + structures or arrays as non-static local variables. Use + dynamic allocation with malloc() or palloc_get_page() + instead. + + The first symptom of either of these problems will probably be + an assertion failure in thread_current(), which checks that + the `magic' member of the running thread's `struct thread' is + set to THREAD_MAGIC. Stack overflow will normally change this + value, triggering the assertion. */ +/* The `elem' member has a dual purpose. It can be an element in + the run queue (thread.c), or it can be an element in a + semaphore wait list (synch.c). It can be used these two ways + only because they are mutually exclusive: only a thread in the + ready state is on the run queue, whereas only a thread in the + blocked state is on a semaphore wait list. */ +struct thread + { + /* Owned by thread.c. */ + tid_t tid; /* Thread identifier. */ + enum thread_status status; /* Thread state. */ + char name[16]; /* Name (for debugging purposes). */ + uint8_t *stack; /* Saved stack pointer. */ + int priority; /* Priority. */ + struct list_elem allelem; /* List element for all threads list. */ + + /* Shared between thread.c and synch.c. */ + struct list_elem elem; /* List element. */ + +#ifdef USERPROG + /* Owned by userprog/process.c */ + struct process* process; /* Process Structure */ + struct list children; /* Threads can hold processes, but not vice versa */ + uint32_t *pagedir; /* Page directory. */ +#endif + + /* Owned by thread.c. */ + unsigned magic; /* Detects stack overflow. */ + }; + +/* If false (default), use round-robin scheduler. + If true, use multi-level feedback queue scheduler. + Controlled by kernel command-line option "-o mlfqs". */ +extern bool thread_mlfqs; + +void thread_init (void); +void thread_start (void); + +void thread_tick (void); +void thread_print_stats (void); + +typedef void thread_func (void *aux); +tid_t thread_create (const char *name, int priority, thread_func *, void *); + +void thread_block (void); +void thread_unblock (struct thread *); + +struct thread *thread_current (void); +tid_t thread_tid (void); +const char *thread_name (void); + +void thread_exit (void) NO_RETURN; +void thread_yield (void); + +/* Performs some operation on thread t, given auxiliary data AUX. */ +typedef void thread_action_func (struct thread *t, void *aux); +void thread_foreach (thread_action_func *, void *); + +int thread_get_priority (void); +void thread_set_priority (int); + +int thread_get_nice (void); +void thread_set_nice (int); +int thread_get_recent_cpu (void); +int thread_get_load_avg (void); + +#endif /* threads/thread.h */ diff --git a/pintos-progos/threads/vaddr.h b/pintos-progos/threads/vaddr.h new file mode 100644 index 0000000..184c824 --- /dev/null +++ b/pintos-progos/threads/vaddr.h @@ -0,0 +1,89 @@ +#ifndef THREADS_VADDR_H +#define THREADS_VADDR_H + +#include +#include +#include + +#include "threads/loader.h" + +/* Functions and macros for working with virtual addresses. + + See pte.h for functions and macros specifically for x86 + hardware page tables. */ + +#define BITMASK(SHIFT, CNT) (((1ul << (CNT)) - 1) << (SHIFT)) + +/* Page offset (bits 0:12). */ +#define PGSHIFT 0 /* Index of first offset bit. */ +#define PGBITS 12 /* Number of offset bits. */ +#define PGSIZE (1 << PGBITS) /* Bytes in a page. */ +#define PGMASK BITMASK(PGSHIFT, PGBITS) /* Page offset bits (0:12). */ + +/* Offset within a page. */ +static inline unsigned pg_ofs (const void *va) { + return (uintptr_t) va & PGMASK; +} + +/* Virtual page number. */ +static inline uintptr_t pg_no (const void *va) { + return (uintptr_t) va >> PGBITS; +} + +/* Round up to nearest page boundary. */ +static inline void *pg_round_up (const void *va) { + return (void *) (((uintptr_t) va + PGSIZE - 1) & ~PGMASK); +} + +/* Round down to nearest page boundary. */ +static inline void *pg_round_down (const void *va) { + return (void *) ((uintptr_t) va & ~PGMASK); +} + +/* Base address of the 1:1 physical-to-virtual mapping. Physical + memory is mapped starting at this virtual address. Thus, + physical address 0 is accessible at PHYS_BASE, physical + address address 0x1234 at (uint8_t *) PHYS_BASE + 0x1234, and + so on. + + This address also marks the end of user programs' address + space. Up to this point in memory, user programs are allowed + to map whatever they like. At this point and above, the + virtual address space belongs to the kernel. */ +#define PHYS_BASE ((void *) LOADER_PHYS_BASE) + +/* Returns true if VADDR is a user virtual address. */ +static inline bool +is_user_vaddr (const void *vaddr) +{ + return vaddr < PHYS_BASE; +} + +/* Returns true if VADDR is a kernel virtual address. */ +static inline bool +is_kernel_vaddr (const void *vaddr) +{ + return vaddr >= PHYS_BASE; +} + +/* Returns kernel virtual address at which physical address PADDR + is mapped. */ +static inline void * +ptov (uintptr_t paddr) +{ + ASSERT ((void *) paddr < PHYS_BASE); + + return (void *) (paddr + PHYS_BASE); +} + +/* Returns physical address at which kernel virtual address VADDR + is mapped. */ +static inline uintptr_t +vtop (const void *vaddr) +{ + ASSERT (is_kernel_vaddr (vaddr)); + + return (uintptr_t) vaddr - (uintptr_t) PHYS_BASE; +} + +#endif /* threads/vaddr.h */ diff --git a/pintos-progos/userprog/.gitignore b/pintos-progos/userprog/.gitignore new file mode 100644 index 0000000..6d5357c --- /dev/null +++ b/pintos-progos/userprog/.gitignore @@ -0,0 +1,3 @@ +build +bochsrc.txt +bochsout.txt diff --git a/pintos-progos/userprog/Make.vars b/pintos-progos/userprog/Make.vars new file mode 100644 index 0000000..e4dbb08 --- /dev/null +++ b/pintos-progos/userprog/Make.vars @@ -0,0 +1,7 @@ +# -*- makefile -*- + +kernel.bin: DEFINES = -DUSERPROG -DFILESYS +KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys +TEST_SUBDIRS = tests/userprog tests/userprog/no-vm tests/filesys/base +GRADING_FILE = $(SRCDIR)/tests/userprog/Grading +SIMULATOR = --qemu diff --git a/pintos-progos/userprog/Makefile b/pintos-progos/userprog/Makefile new file mode 100644 index 0000000..34c10aa --- /dev/null +++ b/pintos-progos/userprog/Makefile @@ -0,0 +1 @@ +include ../Makefile.kernel diff --git a/pintos-progos/userprog/exception.c b/pintos-progos/userprog/exception.c new file mode 100644 index 0000000..17620ad --- /dev/null +++ b/pintos-progos/userprog/exception.c @@ -0,0 +1,174 @@ +#include "userprog/exception.h" +#include +#include +#include "userprog/gdt.h" +#include "threads/interrupt.h" +#include "threads/thread.h" +#include "threads/vaddr.h" + +/* Number of page faults processed. */ +static long long page_fault_cnt; + +static void kill (struct intr_frame *); +static void page_fault (struct intr_frame *); + +/* Registers handlers for interrupts that can be caused by user + programs. + + In a real Unix-like OS, most of these interrupts would be + passed along to the user process in the form of signals, as + described in [SV-386] 3-24 and 3-25, but we don't implement + signals. Instead, we'll make them simply kill the user + process. + + Page faults are an exception. Here they are treated the same + way as other exceptions, but this will need to change to + implement virtual memory. + + Refer to [IA32-v3a] section 5.15 "Exception and Interrupt + Reference" for a description of each of these exceptions. */ +void +exception_init (void) +{ + /* These exceptions can be raised explicitly by a user program, + e.g. via the INT, INT3, INTO, and BOUND instructions. Thus, + we set DPL==3, meaning that user programs are allowed to + invoke them via these instructions. */ + intr_register_int (3, 3, INTR_ON, kill, "#BP Breakpoint Exception"); + intr_register_int (4, 3, INTR_ON, kill, "#OF Overflow Exception"); + intr_register_int (5, 3, INTR_ON, kill, + "#BR BOUND Range Exceeded Exception"); + + /* These exceptions have DPL==0, preventing user processes from + invoking them via the INT instruction. They can still be + caused indirectly, e.g. #DE can be caused by dividing by + 0. */ + intr_register_int (0, 0, INTR_ON, kill, "#DE Divide Error"); + intr_register_int (1, 0, INTR_ON, kill, "#DB Debug Exception"); + intr_register_int (6, 0, INTR_ON, kill, "#UD Invalid Opcode Exception"); + intr_register_int (7, 0, INTR_ON, kill, + "#NM Device Not Available Exception"); + intr_register_int (11, 0, INTR_ON, kill, "#NP Segment Not Present"); + intr_register_int (12, 0, INTR_ON, kill, "#SS Stack Fault Exception"); + intr_register_int (13, 0, INTR_ON, kill, "#GP General Protection Exception"); + intr_register_int (16, 0, INTR_ON, kill, "#MF x87 FPU Floating-Point Error"); + intr_register_int (19, 0, INTR_ON, kill, + "#XF SIMD Floating-Point Exception"); + + /* Most exceptions can be handled with interrupts turned on. + We need to disable interrupts for page faults because the + fault address is stored in CR2 and needs to be preserved. */ + intr_register_int (14, 0, INTR_OFF, page_fault, "#PF Page-Fault Exception"); +} + +/* Prints exception statistics. */ +void +exception_print_stats (void) +{ + printf ("Exception: %lld page faults\n", page_fault_cnt); +} + +/* Handler for an exception (probably) caused by a user process. */ +static void +kill (struct intr_frame *f) +{ + /* This interrupt is one (probably) caused by a user process. + For example, the process might have tried to access unmapped + virtual memory (a page fault). For now, we simply kill the + user process. Later, we'll want to handle page faults in + the kernel. Real Unix-like operating systems pass most + exceptions back to the process via signals, but we don't + implement them. */ + + /* The interrupt frame's code segment value tells us where the + exception originated. */ + switch (f->cs) + { + case SEL_UCSEG: + /* User's code segment, so it's a user exception, as we + expected. Kill the user process. */ + printf ("%s: dying due to interrupt %#04x (%s).\n", + thread_name (), f->vec_no, intr_name (f->vec_no)); + intr_dump_frame (f); + thread_exit (); + + case SEL_KCSEG: + /* Kernel's code segment, which indicates a kernel bug. + Kernel code shouldn't throw exceptions. (Page faults + may cause kernel exceptions--but they shouldn't arrive + here.) Panic the kernel to make the point. */ + intr_dump_frame (f); + PANIC ("Kernel bug - unexpected interrupt in kernel"); + + default: + /* Some other code segment? Shouldn't happen. Panic the + kernel. */ + printf ("Interrupt %#04x (%s) in unknown segment %04x\n", + f->vec_no, intr_name (f->vec_no), f->cs); + thread_exit (); + } +} + +/* Page fault handler. This is a skeleton that must be filled in + to implement virtual memory. Some solutions to project 2 may + also require modifying this code. + + At entry, the address that faulted is in CR2 (Control Register + 2) and information about the fault, formatted as described in + the PF_* macros in exception.h, is in F's error_code member. The + example code here shows how to parse that information. You + can find more information about both of these in the + description of "Interrupt 14--Page Fault Exception (#PF)" in + [IA32-v3a] section 5.15 "Exception and Interrupt Reference". */ +static void +page_fault (struct intr_frame *f) +{ + bool not_present; /* True: not-present page, false: writing r/o page. */ + bool write; /* True: access was write, false: access was read. */ + bool user; /* True: access by user, false: access by kernel. */ + void *fault_addr; /* Fault address. */ + + /* Obtain faulting address, the virtual address that was + accessed to cause the fault. It may point to code or to + data. It is not necessarily the address of the instruction + that caused the fault (that's f->eip). + See [IA32-v2a] "MOV--Move to/from Control Registers" and + [IA32-v3a] 5.15 "Interrupt 14--Page Fault Exception + (#PF)". */ + asm ("movl %%cr2, %0" : "=r" (fault_addr)); + + /* Turn interrupts back on (they were only off so that we could + be assured of reading CR2 before it changed). */ + intr_enable (); + + /* Count page faults. */ + page_fault_cnt++; + + /* Determine cause. */ + not_present = (f->error_code & PF_P) == 0; + write = (f->error_code & PF_W) != 0; + user = (f->error_code & PF_U) != 0; + + /* To implement virtual memory, adapt the rest of the function + body, adding code that brings in the page to + which fault_addr refers. */ + if (is_user_vaddr(fault_addr)) { + if (! user) { + /* syscall exception; set eax and eip */ + f->eip = (void*)f->eax; + f->eax = 0xFFFFFFFF; + return; + } else { + /* user process access violation */ + thread_exit(); + } + } else { + printf ("Page fault at %p: %s error %s page in %s context.\n", + fault_addr, + not_present ? "not present" : "rights violation", + write ? "writing" : "reading", + user ? "user" : "kernel"); + kill (f); + } +} + diff --git a/pintos-progos/userprog/exception.h b/pintos-progos/userprog/exception.h new file mode 100644 index 0000000..f83e615 --- /dev/null +++ b/pintos-progos/userprog/exception.h @@ -0,0 +1,12 @@ +#ifndef USERPROG_EXCEPTION_H +#define USERPROG_EXCEPTION_H + +/* Page fault error code bits that describe the cause of the exception. */ +#define PF_P 0x1 /* 0: not-present page. 1: access rights violation. */ +#define PF_W 0x2 /* 0: read, 1: write. */ +#define PF_U 0x4 /* 0: kernel, 1: user process. */ + +void exception_init (void); +void exception_print_stats (void); + +#endif /* userprog/exception.h */ diff --git a/pintos-progos/userprog/gdt.c b/pintos-progos/userprog/gdt.c new file mode 100644 index 0000000..e866037 --- /dev/null +++ b/pintos-progos/userprog/gdt.c @@ -0,0 +1,146 @@ +#include "userprog/gdt.h" +#include +#include "userprog/tss.h" +#include "threads/palloc.h" +#include "threads/vaddr.h" + +/* The Global Descriptor Table (GDT). + + The GDT, an x86-specific structure, defines segments that can + potentially be used by all processes in a system, subject to + their permissions. There is also a per-process Local + Descriptor Table (LDT) but that is not used by modern + operating systems. + + Each entry in the GDT, which is known by its byte offset in + the table, identifies a segment. For our purposes only three + types of segments are of interest: code, data, and TSS or + Task-State Segment descriptors. The former two types are + exactly what they sound like. The TSS is used primarily for + stack switching on interrupts. + + For more information on the GDT as used here, refer to + [IA32-v3a] 3.2 "Using Segments" through 3.5 "System Descriptor + Types". */ +static uint64_t gdt[SEL_CNT]; + +/* GDT helpers. */ +static uint64_t make_code_desc (int dpl); +static uint64_t make_data_desc (int dpl); +static uint64_t make_tss_desc (void *laddr); +static uint64_t make_gdtr_operand (uint16_t limit, void *base); + +/* Sets up a proper GDT. The bootstrap loader's GDT didn't + include user-mode selectors or a TSS, but we need both now. */ +void +gdt_init (void) +{ + uint64_t gdtr_operand; + + /* Initialize GDT. */ + gdt[SEL_NULL / sizeof *gdt] = 0; + gdt[SEL_KCSEG / sizeof *gdt] = make_code_desc (0); + gdt[SEL_KDSEG / sizeof *gdt] = make_data_desc (0); + gdt[SEL_UCSEG / sizeof *gdt] = make_code_desc (3); + gdt[SEL_UDSEG / sizeof *gdt] = make_data_desc (3); + gdt[SEL_TSS / sizeof *gdt] = make_tss_desc (tss_get ()); + + /* Load GDTR, TR. See [IA32-v3a] 2.4.1 "Global Descriptor + Table Register (GDTR)", 2.4.4 "Task Register (TR)", and + 6.2.4 "Task Register". */ + gdtr_operand = make_gdtr_operand (sizeof gdt - 1, gdt); + asm volatile ("lgdt %0" : : "m" (gdtr_operand)); + asm volatile ("ltr %w0" : : "q" (SEL_TSS)); +} + +/* System segment or code/data segment? */ +enum seg_class + { + CLS_SYSTEM = 0, /* System segment. */ + CLS_CODE_DATA = 1 /* Code or data segment. */ + }; + +/* Limit has byte or 4 kB page granularity? */ +enum seg_granularity + { + GRAN_BYTE = 0, /* Limit has 1-byte granularity. */ + GRAN_PAGE = 1 /* Limit has 4 kB granularity. */ + }; + +/* Returns a segment descriptor with the given 32-bit BASE and + 20-bit LIMIT (whose interpretation depends on GRANULARITY). + The descriptor represents a system or code/data segment + according to CLASS, and TYPE is its type (whose interpretation + depends on the class). + + The segment has descriptor privilege level DPL, meaning that + it can be used in rings numbered DPL or lower. In practice, + DPL==3 means that user processes can use the segment and + DPL==0 means that only the kernel can use the segment. See + [IA32-v3a] 4.5 "Privilege Levels" for further discussion. */ +static uint64_t +make_seg_desc (uint32_t base, + uint32_t limit, + enum seg_class class, + int type, + int dpl, + enum seg_granularity granularity) +{ + uint32_t e0, e1; + + ASSERT (limit <= 0xfffff); + ASSERT (class == CLS_SYSTEM || class == CLS_CODE_DATA); + ASSERT (type >= 0 && type <= 15); + ASSERT (dpl >= 0 && dpl <= 3); + ASSERT (granularity == GRAN_BYTE || granularity == GRAN_PAGE); + + e0 = ((limit & 0xffff) /* Limit 15:0. */ + | (base << 16)); /* Base 15:0. */ + + e1 = (((base >> 16) & 0xff) /* Base 23:16. */ + | (type << 8) /* Segment type. */ + | (class << 12) /* 0=system, 1=code/data. */ + | (dpl << 13) /* Descriptor privilege. */ + | (1 << 15) /* Present. */ + | (limit & 0xf0000) /* Limit 16:19. */ + | (1 << 22) /* 32-bit segment. */ + | (granularity << 23) /* Byte/page granularity. */ + | (base & 0xff000000)); /* Base 31:24. */ + + return e0 | ((uint64_t) e1 << 32); +} + +/* Returns a descriptor for a readable code segment with base at + 0, a limit of 4 GB, and the given DPL. */ +static uint64_t +make_code_desc (int dpl) +{ + return make_seg_desc (0, 0xfffff, CLS_CODE_DATA, 10, dpl, GRAN_PAGE); +} + +/* Returns a descriptor for a writable data segment with base at + 0, a limit of 4 GB, and the given DPL. */ +static uint64_t +make_data_desc (int dpl) +{ + return make_seg_desc (0, 0xfffff, CLS_CODE_DATA, 2, dpl, GRAN_PAGE); +} + +/* Returns a descriptor for an "available" 32-bit Task-State + Segment with its base at the given linear address, a limit of + 0x67 bytes (the size of a 32-bit TSS), and a DPL of 0. + See [IA32-v3a] 6.2.2 "TSS Descriptor". */ +static uint64_t +make_tss_desc (void *laddr) +{ + return make_seg_desc ((uint32_t) laddr, 0x67, CLS_SYSTEM, 9, 0, GRAN_BYTE); +} + + +/* Returns a descriptor that yields the given LIMIT and BASE when + used as an operand for the LGDT instruction. */ +static uint64_t +make_gdtr_operand (uint16_t limit, void *base) +{ + return limit | ((uint64_t) (uint32_t) base << 16); +} diff --git a/pintos-progos/userprog/gdt.h b/pintos-progos/userprog/gdt.h new file mode 100644 index 0000000..81fe50c --- /dev/null +++ b/pintos-progos/userprog/gdt.h @@ -0,0 +1,15 @@ +#ifndef USERPROG_GDT_H +#define USERPROG_GDT_H + +#include "threads/loader.h" + +/* Segment selectors. + More selectors are defined by the loader in loader.h. */ +#define SEL_UCSEG 0x1B /* User code selector. */ +#define SEL_UDSEG 0x23 /* User data selector. */ +#define SEL_TSS 0x28 /* Task-state segment. */ +#define SEL_CNT 6 /* Number of segments. */ + +void gdt_init (void); + +#endif /* userprog/gdt.h */ diff --git a/pintos-progos/userprog/pagedir.c b/pintos-progos/userprog/pagedir.c new file mode 100644 index 0000000..a6a87b8 --- /dev/null +++ b/pintos-progos/userprog/pagedir.c @@ -0,0 +1,263 @@ +#include "userprog/pagedir.h" +#include +#include +#include +#include "threads/init.h" +#include "threads/pte.h" +#include "threads/palloc.h" + +static uint32_t *active_pd (void); +static void invalidate_pagedir (uint32_t *); + +/* Creates a new page directory that has mappings for kernel + virtual addresses, but none for user virtual addresses. + Returns the new page directory, or a null pointer if memory + allocation fails. */ +uint32_t * +pagedir_create (void) +{ + uint32_t *pd = palloc_get_page (0); + if (pd != NULL) + memcpy (pd, init_page_dir, PGSIZE); + return pd; +} + +/* Destroys page directory PD, freeing all the pages it + references. */ +void +pagedir_destroy (uint32_t *pd) +{ + uint32_t *pde; + + if (pd == NULL) + return; + + ASSERT (pd != init_page_dir); + for (pde = pd; pde < pd + pd_no (PHYS_BASE); pde++) + if (*pde & PTE_P) + { + uint32_t *pt = pde_get_pt (*pde); + uint32_t *pte; + + for (pte = pt; pte < pt + PGSIZE / sizeof *pte; pte++) + if (*pte & PTE_P) + palloc_free_page (pte_get_page (*pte)); + palloc_free_page (pt); + } + palloc_free_page (pd); +} + +/* Returns the address of the page table entry for virtual + address VADDR in page directory PD. + If PD does not have a page table for VADDR, behavior depends + on CREATE. If CREATE is true, then a new page table is + created and a pointer into it is returned. Otherwise, a null + pointer is returned. */ +static uint32_t * +lookup_page (uint32_t *pd, const void *vaddr, bool create) +{ + uint32_t *pt, *pde; + + ASSERT (pd != NULL); + + /* Shouldn't create new kernel virtual mappings. */ + ASSERT (!create || is_user_vaddr (vaddr)); + + /* Check for a page table for VADDR. + If one is missing, create one if requested. */ + pde = pd + pd_no (vaddr); + if (*pde == 0) + { + if (create) + { + pt = palloc_get_page (PAL_ZERO); + if (pt == NULL) + return NULL; + + *pde = pde_create (pt); + } + else + return NULL; + } + + /* Return the page table entry. */ + pt = pde_get_pt (*pde); + return &pt[pt_no (vaddr)]; +} + +/* Adds a mapping in page directory PD from user virtual page + UPAGE to the physical frame identified by kernel virtual + address KPAGE. + UPAGE must not already be mapped. + KPAGE should probably be a page obtained from the user pool + with palloc_get_page(). + If WRITABLE is true, the new page is read/write; + otherwise it is read-only. + Returns true if successful, false if memory allocation + failed. */ +bool +pagedir_set_page (uint32_t *pd, void *upage, void *kpage, bool writable) +{ + uint32_t *pte; + + ASSERT (pg_ofs (upage) == 0); + ASSERT (pg_ofs (kpage) == 0); + ASSERT (is_user_vaddr (upage)); + ASSERT (vtop (kpage) >> PTSHIFT < init_ram_pages); + ASSERT (pd != init_page_dir); + + pte = lookup_page (pd, upage, true); + + if (pte != NULL) + { + ASSERT ((*pte & PTE_P) == 0); + *pte = pte_create_user (kpage, writable); + return true; + } + else + return false; +} + +/* Looks up the physical address that corresponds to user virtual + address UADDR in PD. Returns the kernel virtual address + corresponding to that physical address, or a null pointer if + UADDR is unmapped. */ +void * +pagedir_get_page (uint32_t *pd, const void *uaddr) +{ + uint32_t *pte; + + ASSERT (is_user_vaddr (uaddr)); + + pte = lookup_page (pd, uaddr, false); + if (pte != NULL && (*pte & PTE_P) != 0) + return pte_get_page (*pte) + pg_ofs (uaddr); + else + return NULL; +} + +/* Marks user virtual page UPAGE "not present" in page + directory PD. Later accesses to the page will fault. Other + bits in the page table entry are preserved. + UPAGE need not be mapped. */ +void +pagedir_clear_page (uint32_t *pd, void *upage) +{ + uint32_t *pte; + + ASSERT (pg_ofs (upage) == 0); + ASSERT (is_user_vaddr (upage)); + + pte = lookup_page (pd, upage, false); + if (pte != NULL && (*pte & PTE_P) != 0) + { + *pte &= ~PTE_P; + invalidate_pagedir (pd); + } +} + +/* Returns true if the PTE for virtual page VPAGE in PD is dirty, + that is, if the page has been modified since the PTE was + installed. + Returns false if PD contains no PTE for VPAGE. */ +bool +pagedir_is_dirty (uint32_t *pd, const void *vpage) +{ + uint32_t *pte = lookup_page (pd, vpage, false); + return pte != NULL && (*pte & PTE_D) != 0; +} + +/* Set the dirty bit to DIRTY in the PTE for virtual page VPAGE + in PD. */ +void +pagedir_set_dirty (uint32_t *pd, const void *vpage, bool dirty) +{ + uint32_t *pte = lookup_page (pd, vpage, false); + if (pte != NULL) + { + if (dirty) + *pte |= PTE_D; + else + { + *pte &= ~(uint32_t) PTE_D; + invalidate_pagedir (pd); + } + } +} + +/* Returns true if the PTE for virtual page VPAGE in PD has been + accessed recently, that is, between the time the PTE was + installed and the last time it was cleared. Returns false if + PD contains no PTE for VPAGE. */ +bool +pagedir_is_accessed (uint32_t *pd, const void *vpage) +{ + uint32_t *pte = lookup_page (pd, vpage, false); + return pte != NULL && (*pte & PTE_A) != 0; +} + +/* Sets the accessed bit to ACCESSED in the PTE for virtual page + VPAGE in PD. */ +void +pagedir_set_accessed (uint32_t *pd, const void *vpage, bool accessed) +{ + uint32_t *pte = lookup_page (pd, vpage, false); + if (pte != NULL) + { + if (accessed) + *pte |= PTE_A; + else + { + *pte &= ~(uint32_t) PTE_A; + invalidate_pagedir (pd); + } + } +} + +/* Loads page directory PD into the CPU's page directory base + register. */ +void +pagedir_activate (uint32_t *pd) +{ + if (pd == NULL) + pd = init_page_dir; + + /* Store the physical address of the page directory into CR3 + aka PDBR (page directory base register). This activates our + new page tables immediately. See [IA32-v2a] "MOV--Move + to/from Control Registers" and [IA32-v3a] 3.7.5 "Base + Address of the Page Directory". */ + asm volatile ("movl %0, %%cr3" : : "r" (vtop (pd)) : "memory"); +} + +/* Returns the currently active page directory. */ +static uint32_t * +active_pd (void) +{ + /* Copy CR3, the page directory base register (PDBR), into + `pd'. + See [IA32-v2a] "MOV--Move to/from Control Registers" and + [IA32-v3a] 3.7.5 "Base Address of the Page Directory". */ + uintptr_t pd; + asm volatile ("movl %%cr3, %0" : "=r" (pd)); + return ptov (pd); +} + +/* Seom page table changes can cause the CPU's translation + lookaside buffer (TLB) to become out-of-sync with the page + table. When this happens, we have to "invalidate" the TLB by + re-activating it. + + This function invalidates the TLB if PD is the active page + directory. (If PD is not active then its entries are not in + the TLB, so there is no need to invalidate anything.) */ +static void +invalidate_pagedir (uint32_t *pd) +{ + if (active_pd () == pd) + { + /* Re-activating PD clears the TLB. See [IA32-v3a] 3.12 + "Translation Lookaside Buffers (TLBs)". */ + pagedir_activate (pd); + } +} diff --git a/pintos-progos/userprog/pagedir.h b/pintos-progos/userprog/pagedir.h new file mode 100644 index 0000000..cd92447 --- /dev/null +++ b/pintos-progos/userprog/pagedir.h @@ -0,0 +1,18 @@ +#ifndef USERPROG_PAGEDIR_H +#define USERPROG_PAGEDIR_H + +#include +#include + +uint32_t *pagedir_create (void); +void pagedir_destroy (uint32_t *pd); +bool pagedir_set_page (uint32_t *pd, void *upage, void *kpage, bool rw); +void *pagedir_get_page (uint32_t *pd, const void *upage); +void pagedir_clear_page (uint32_t *pd, void *upage); +bool pagedir_is_dirty (uint32_t *pd, const void *upage); +void pagedir_set_dirty (uint32_t *pd, const void *upage, bool dirty); +bool pagedir_is_accessed (uint32_t *pd, const void *upage); +void pagedir_set_accessed (uint32_t *pd, const void *upage, bool accessed); +void pagedir_activate (uint32_t *pd); + +#endif /* userprog/pagedir.h */ diff --git a/pintos-progos/userprog/process.c b/pintos-progos/userprog/process.c new file mode 100644 index 0000000..adb6b66 --- /dev/null +++ b/pintos-progos/userprog/process.c @@ -0,0 +1,721 @@ +#include "userprog/process.h" +#include +#include +#include +#include +#include +#include +#include "userprog/gdt.h" +#include "userprog/pagedir.h" +#include "userprog/tss.h" +#include "filesys/directory.h" +#include "filesys/file.h" +#include "filesys/filesys.h" +#include "threads/flags.h" +#include "threads/init.h" +#include "threads/interrupt.h" +#include "threads/palloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "threads/vaddr.h" + +/* data structure to communicate with the thread initializing a new process */ +struct start_aux_data { + char *filename; + struct semaphore startup_sem; + struct thread *parent_thread; + struct process *new_process; +}; + +/* filesystem lock */ +struct lock filesys_lock; + +/* prototypes */ +static thread_func start_process NO_RETURN; +static bool load (char *filename, void (**eip) (void), void **esp); +static bool setup_stack (void **esp); +static bool init_fd_table (struct fd_table * table); + +/* Initialize the filesystem lock */ +void +process_init () +{ + lock_init (&filesys_lock); +} + +/* Get current process (only valid for processes) */ +struct process* +process_current () +{ + ASSERT (thread_current()->process != NULL); + return thread_current()->process; +} + +/* Starts a new thread running a user program loaded from + `filename`. + The new thread may be scheduled (and may even exit) + before process_execute() returns. Returns the new process's + thread id, or TID_ERROR if the thread cannot be created. + + In the first assignment, you should change this to function to + + process_execute (const char *cmd) + + and support command strings such as "echo A B C". You + will also need to change `load` and `setup_stack`. */ +tid_t +process_execute (const char *filename) +{ + tid_t tid = TID_ERROR; + char *fn_copy = NULL; + struct start_aux_data *aux_data = NULL; + + /* Setup the auxiliary data for starting up the new process */ + fn_copy = palloc_get_page (0); + aux_data = palloc_get_page (0); + if (aux_data == NULL || fn_copy == NULL) + goto done; + strlcpy (fn_copy, filename, PGSIZE); + aux_data->filename = fn_copy; + aux_data->parent_thread = thread_current (); + aux_data->new_process = NULL; + sema_init (&aux_data->startup_sem, 0); + + /* Create a new thread to execute FILE_NAME. */ + tid = thread_create (fn_copy, PRI_DEFAULT, start_process, aux_data); + if (tid == TID_ERROR) + goto done; + + /* wait for startup */ + sema_down (&aux_data->startup_sem); + if (aux_data->new_process == NULL) { + tid = TID_ERROR; + goto done; + } + /* register child process */ + list_push_back (&thread_current()->children, + &aux_data->new_process->parentelem); + + done: + palloc_free_page (fn_copy); + palloc_free_page (aux_data); + return tid; +} + +/* A thread function that loads a user process and starts it + running. */ +static void +start_process (void *aux) +{ + struct intr_frame if_; + struct start_aux_data *aux_data = (struct start_aux_data*) aux; + struct thread *thread = thread_current (); + + /* Initialize Process */ + struct process *process = palloc_get_page (PAL_ZERO); + if (process == NULL) + goto signal; + sema_init (&process->exit_sem, 0); + lock_init (&process->exit_lock); + process->exit_status = -1; + if (! init_fd_table (&process->fd_table)) + goto signal; + + /* register process */ + process->thread_id = thread->tid; + process->parent_tid = aux_data->parent_thread->tid; + thread->process = process; + + /* Initialize interrupt frame and load executable. */ + memset (&if_, 0, sizeof if_); + if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG; + if_.cs = SEL_UCSEG; + if_.eflags = FLAG_IF | FLAG_MBS; + if (! load (aux_data->filename, &if_.eip, &if_.esp)) { + thread->process = NULL; + } else { + aux_data->new_process = thread->process; + } + + signal: + /* Signal the parent process that loading is finished */ + sema_up (&aux_data->startup_sem); /* transfer ownership of aux_data */ + + /* If process startup failed, quit. */ + if (thread->process == NULL) { + if (process != NULL) { + if (process->fd_table.fds != NULL) + palloc_free_page (process->fd_table.fds); + palloc_free_page (process); + } + thread_exit (); + } + + /* Start the user process by simulating a return from an + interrupt, implemented by intr_exit (in + threads/intr-stubs.S). Because intr_exit takes all of its + arguments on the stack in the form of a `struct intr_frame', + we just point the stack pointer (%esp) to our stack frame + and jump to it. */ + asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (&if_) : "memory"); + NOT_REACHED (); +} + +/* Waits for thread TID to die and returns its exit status. If + it was terminated by the kernel (i.e. killed due to an + exception), returns -1. If TID is invalid or if it was not a + child of the calling process, or if process_wait() has already + been successfully called for the given TID, returns -1 + immediately, without waiting. */ +int +process_wait (tid_t child_tid) +{ + struct thread *cur = thread_current (); + struct process *child = NULL; + + /* iterate over child processes */ + struct list_elem *e = list_head (&cur->children); + while ((e = list_next (e)) != list_end (&cur->children)) { + struct process* t = list_entry (e, struct process, parentelem); + if (t->thread_id == child_tid) { + list_remove (e); + child = t; + break; + } + } + if (child == NULL) { + return -1; + } + sema_down (&child->exit_sem); + int exit_status = child->exit_status; + palloc_free_page (child); + return exit_status; +} + +/* Free the current process's resources. */ +void +process_exit (void) +{ + struct thread *thread = thread_current (); + ASSERT (thread != NULL); + + /* remove (and if necessary clean up) child processes */ + struct list_elem *e = list_head (&thread->children); + while ((e = list_next (e)) != list_end (&thread->children)) { + struct process *p = list_entry (e, struct process, parentelem); + bool process_dying; + lock_acquire (&p->exit_lock); + process_dying = p->parent_tid < 0; + p->parent_tid = -1; + lock_release (&p->exit_lock); + if (process_dying) + palloc_free_page (p); + } + + if (thread->process == NULL) + return; /* not a process, nothing else left to do */ + + struct process *proc = thread->process; + uint32_t *pd; + + printf ("%s: exit(%d)\n", thread->name, proc->exit_status); + + /* close executable, allow write */ + if (proc->executable != NULL) { + lock_acquire (&filesys_lock); + file_close (proc->executable); + lock_release (&filesys_lock); + } + + int fd; + for (fd = 2; fd <= proc->fd_table.fd_max; fd++) { + process_close_file (fd); + } + palloc_free_page (proc->fd_table.fds); + + /* Destroy the current process's page directory and switch back + to the kernel-only page directory. */ + pd = thread->pagedir; + if (pd != NULL) { + /* Correct ordering here is crucial. We must set + cur->pagedir to NULL before switching page directories, + so that a timer interrupt can't switch back to the + process page directory. We must activate the base page + directory before destroying the process's page + directory, or our active page directory will be one + that's been freed (and cleared). */ + thread->pagedir = NULL; + pagedir_activate (NULL); + pagedir_destroy (pd); + } + + /* Destroy the process structure if the parent is not alive + * any more. Atomic test and set would be sufficient here. + */ + bool parent_dead = false; + lock_acquire (&proc->exit_lock); + parent_dead = proc->parent_tid < 0; + proc->parent_tid = -1; + lock_release (&proc->exit_lock); + if (parent_dead) { + palloc_free_page (proc); + } else { + sema_up (&proc->exit_sem); + } +} + +/* Sets up the CPU for running user code in the current + thread. + This function is called on every context switch. */ +void +process_activate (void) +{ + struct thread *t = thread_current (); + /* Activate thread's page tables. */ + pagedir_activate (t->pagedir); + + /* Set thread's kernel stack for use in processing + interrupts. */ + tss_update (); +} + +/* We load ELF binaries. The following definitions are taken + from the ELF specification, [ELF1], more-or-less verbatim. */ + +/* ELF types. See [ELF1] 1-2. */ +typedef uint32_t Elf32_Word, Elf32_Addr, Elf32_Off; +typedef uint16_t Elf32_Half; + +/* For use with ELF types in printf(). */ +#define PE32Wx PRIx32 /* Print Elf32_Word in hexadecimal. */ +#define PE32Ax PRIx32 /* Print Elf32_Addr in hexadecimal. */ +#define PE32Ox PRIx32 /* Print Elf32_Off in hexadecimal. */ +#define PE32Hx PRIx16 /* Print Elf32_Half in hexadecimal. */ + +/* Executable header. See [ELF1] 1-4 to 1-8. + This appears at the very beginning of an ELF binary. */ +struct Elf32_Ehdr +{ + unsigned char e_ident[16]; + Elf32_Half e_type; + Elf32_Half e_machine; + Elf32_Word e_version; + Elf32_Addr e_entry; + Elf32_Off e_phoff; + Elf32_Off e_shoff; + Elf32_Word e_flags; + Elf32_Half e_ehsize; + Elf32_Half e_phentsize; + Elf32_Half e_phnum; + Elf32_Half e_shentsize; + Elf32_Half e_shnum; + Elf32_Half e_shstrndx; +}; + +/* Program header. See [ELF1] 2-2 to 2-4. + There are e_phnum of these, starting at file offset e_phoff + (see [ELF1] 1-6). */ +struct Elf32_Phdr +{ + Elf32_Word p_type; + Elf32_Off p_offset; + Elf32_Addr p_vaddr; + Elf32_Addr p_paddr; + Elf32_Word p_filesz; + Elf32_Word p_memsz; + Elf32_Word p_flags; + Elf32_Word p_align; +}; + +/* Values for p_type. See [ELF1] 2-3. */ +#define PT_NULL 0 /* Ignore. */ +#define PT_LOAD 1 /* Loadable segment. */ +#define PT_DYNAMIC 2 /* Dynamic linking info. */ +#define PT_INTERP 3 /* Name of dynamic loader. */ +#define PT_NOTE 4 /* Auxiliary info. */ +#define PT_SHLIB 5 /* Reserved. */ +#define PT_PHDR 6 /* Program header table. */ +#define PT_STACK 0x6474e551 /* Stack segment. */ + +/* Flags for p_flags. See [ELF3] 2-3 and 2-4. */ +#define PF_X 1 /* Executable. */ +#define PF_W 2 /* Writable. */ +#define PF_R 4 /* Readable. */ + +static bool validate_segment (const struct Elf32_Phdr *, struct file *); +static bool load_segment (struct file *file, off_t ofs, uint8_t *upage, + uint32_t read_bytes, uint32_t zero_bytes, + bool writable); + +/* Loads an ELF executable from file_name (the first word of + cmd) into the current thread. + Stores the executable's entry point into *EIP + and its initial stack pointer into *ESP. + Returns true if successful, false otherwise. */ +bool +load (char *file_name, void (**eip) (void), void **esp) +{ + struct thread *t = thread_current (); + struct Elf32_Ehdr ehdr; + struct file *file = NULL; + off_t file_ofs; + bool success = false; + int i; + + /* Allocate and activate page directory. */ + t->pagedir = pagedir_create (); + if (t->pagedir == NULL) + return false; + process_activate (); + + /* Coarse grained filesystem lock for loading */ + lock_acquire (&filesys_lock); + + /* Open executable file. */ + file = filesys_open (file_name); + if (file == NULL) + goto done; + + /* Deny writes to the file during loading */ + file_deny_write (file); + + /* Read and verify executable header. */ + if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr + || memcmp (ehdr.e_ident, "\177ELF\1\1\1", 7) + || ehdr.e_type != 2 + || ehdr.e_machine != 3 + || ehdr.e_version != 1 + || ehdr.e_phentsize != sizeof (struct Elf32_Phdr) + || ehdr.e_phnum > 1024) + { + printf ("load: %s: error loading executable\n", file_name); + goto done; + } + + /* Read program headers. */ + file_ofs = ehdr.e_phoff; + for (i = 0; i < ehdr.e_phnum; i++) + { + struct Elf32_Phdr phdr; + + if (file_ofs < 0 || file_ofs > file_length (file)) + goto done; + file_seek (file, file_ofs); + + if (file_read (file, &phdr, sizeof phdr) != sizeof phdr) + goto done; + file_ofs += sizeof phdr; + if (phdr.p_vaddr < PGSIZE) + continue; /* Ignore build-id segment */ + switch (phdr.p_type) + { + case PT_NULL: + case PT_NOTE: + case PT_PHDR: + case PT_STACK: + default: + /* Ignore this segment. */ + break; + case PT_DYNAMIC: + case PT_INTERP: + case PT_SHLIB: + goto done; + case PT_LOAD: + if (phdr.p_vaddr == 0) + break; // Ignore the .note.gnu.build-i segment + if (validate_segment (&phdr, file)) + { + bool writable = (phdr.p_flags & PF_W) != 0; + uint32_t file_page = phdr.p_offset & ~PGMASK; + uint32_t mem_page = phdr.p_vaddr & ~PGMASK; + uint32_t page_offset = phdr.p_vaddr & PGMASK; + uint32_t read_bytes, zero_bytes; + if (phdr.p_filesz > 0) + { + /* Normal segment. + Read initial part from disk and zero the rest. */ + read_bytes = page_offset + phdr.p_filesz; + zero_bytes = (ROUND_UP (page_offset + phdr.p_memsz, PGSIZE) + - read_bytes); + } + else + { + /* Entirely zero. + Don't read anything from disk. */ + read_bytes = 0; + zero_bytes = ROUND_UP (page_offset + phdr.p_memsz, PGSIZE); + } + if (!load_segment (file, file_page, (void *) mem_page, + read_bytes, zero_bytes, writable)) + goto done; + } + else + goto done; + break; + } + } + + /* Set up stack. */ + if (!setup_stack (esp)) + goto done; + + /* Start address. */ + *eip = (void (*) (void)) ehdr.e_entry; + + success = true; + + done: + /* We arrive here whether the load is successful or not. */ + if (success) { + process_current()->executable = file; + } else { + file_close (file); + } + lock_release (&filesys_lock); + return success; +} + +/* load() helpers. */ + +static bool install_page (void *upage, void *kpage, bool writable); + +/* Checks whether PHDR describes a valid, loadable segment in + FILE and returns true if so, false otherwise. */ +static bool +validate_segment (const struct Elf32_Phdr *phdr, struct file *file) +{ + /* p_offset and p_vaddr must have the same page offset. */ + if ((phdr->p_offset & PGMASK) != (phdr->p_vaddr & PGMASK)) + return false; + + /* p_offset must point within FILE. */ + if (phdr->p_offset > (Elf32_Off) file_length (file)) + return false; + + /* p_memsz must be at least as big as p_filesz. */ + if (phdr->p_memsz < phdr->p_filesz) + return false; + + /* The segment must not be empty. */ + if (phdr->p_memsz == 0) + return false; + + /* The virtual memory region must both start and end within the + user address space range. */ + if (!is_user_vaddr ((void *) phdr->p_vaddr)) + return false; + if (!is_user_vaddr ((void *) (phdr->p_vaddr + phdr->p_memsz))) + return false; + + /* The region cannot "wrap around" across the kernel virtual + address space. */ + if (phdr->p_vaddr + phdr->p_memsz < phdr->p_vaddr) + return false; + + /* Disallow mapping page 0. + Not only is it a bad idea to map page 0, but if we allowed + it then user code that passed a null pointer to system calls + could quite likely panic the kernel by way of null pointer + assertions in memcpy(), etc. */ + if (phdr->p_vaddr < PGSIZE) + return false; + + /* It's okay. */ + return true; +} + +/* Loads a segment starting at offset OFS in FILE at address + UPAGE. In total, READ_BYTES + ZERO_BYTES bytes of virtual + memory are initialized, as follows: + + - READ_BYTES bytes at UPAGE must be read from FILE + starting at offset OFS. + + - ZERO_BYTES bytes at UPAGE + READ_BYTES must be zeroed. + + The pages initialized by this function must be writable by the + user process if WRITABLE is true, read-only otherwise. + + Return true if successful, false if a memory allocation error + or disk read error occurs. */ +static bool +load_segment (struct file *file, off_t ofs, uint8_t *upage, + uint32_t read_bytes, uint32_t zero_bytes, bool writable) +{ + ASSERT ((read_bytes + zero_bytes) % PGSIZE == 0); + ASSERT (pg_ofs (upage) == 0); + ASSERT (ofs % PGSIZE == 0); + + file_seek (file, ofs); + while (read_bytes > 0 || zero_bytes > 0) + { + /* Calculate how to fill this page. + We will read PAGE_READ_BYTES bytes from FILE + and zero the final PAGE_ZERO_BYTES bytes. */ + size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE; + size_t page_zero_bytes = PGSIZE - page_read_bytes; + + /* Get a page of memory. */ + uint8_t *kpage = palloc_get_page (PAL_USER); + if (kpage == NULL) + return false; + + /* Load this page. */ + if (file_read (file, kpage, page_read_bytes) != (int) page_read_bytes) + { + palloc_free_page (kpage); + return false; + } + memset (kpage + page_read_bytes, 0, page_zero_bytes); + + /* Add the page to the process's address space. */ + if (!install_page (upage, kpage, writable)) + { + palloc_free_page (kpage); + return false; + } + + /* Advance. */ + read_bytes -= page_read_bytes; + zero_bytes -= page_zero_bytes; + upage += PGSIZE; + } + return true; +} + +/* Create a minimal stack by mapping a zeroed page at the top of + user virtual memory. + You will implement this function in the Project 0. + Consider using `hex_dump` for debugging purposes */ +static bool +setup_stack (void **esp) +{ + uint8_t *kpage = NULL; + + kpage = palloc_get_page (PAL_USER | PAL_ZERO); + if (kpage == NULL) + return false; + + if (! install_page (((uint8_t *) PHYS_BASE) - PGSIZE, kpage, true)) { + palloc_free_page (kpage); + return false; + } + + /* Currently we assume that 'argc = 0' */ + *esp = PHYS_BASE - 12; + + return true; +} + +/* Adds a mapping from user virtual address UPAGE to kernel + virtual address KPAGE to the page table. + If WRITABLE is true, the user process may modify the page; + otherwise, it is read-only. + UPAGE must not already be mapped. + KPAGE should probably be a page obtained from the user pool + with palloc_get_page(). + Returns true on success, false if UPAGE is already mapped or + if memory allocation fails. */ +static bool +install_page (void *upage, void *kpage, bool writable) +{ + struct thread *t = thread_current (); + + /* Verify that there's not already a page at that virtual + address, then map our page there. */ + return (pagedir_get_page (t->pagedir, upage) == NULL + && pagedir_set_page (t->pagedir, upage, kpage, writable)); +} + +static +bool +init_fd_table (struct fd_table *table) +{ + table->fds = palloc_get_page (PAL_ZERO); + if (table->fds == NULL) + return false; + table->fd_cap = PGSIZE / sizeof (table->fds[0]); + table->fd_free = 2; + table->fd_max = 1; + return true; +} + +/* Open the file with the given name; returns + a file descriptor for this file if successful, + and a negative value otherwise */ +int +process_open_file (const char* fname) +{ + struct fd_table *fdt = &process_current()->fd_table; + if (fdt->fd_free >= fdt->fd_cap) + return -1; + + lock_acquire (&filesys_lock); + struct file *f = filesys_open (fname); + lock_release (&filesys_lock); + + if (f == NULL) + return -1; + + int fd = fdt->fd_free++; + fdt->fds[fd] = f; + + /* update index of free/max file descriptor index*/ + if (fd > fdt->fd_max) fdt->fd_max = fd; + while (fdt->fds[fdt->fd_free] != NULL) { + fdt->fd_free++; + if (fdt->fd_free >= fdt->fd_cap) + break; + } + return fd; +} + +/* Get the file associated with the given file + descriptor; return NULL if no file is associated + with the given descriptor */ +struct file* +process_get_file (int fd) +{ + struct fd_table *fdt = &process_current()->fd_table; + if (fd < 2 || fd >= fdt->fd_cap || ! fdt->fds[fd]) + return NULL; + return fdt->fds[fd]; +} + +/* Acquire global lock for the filesystem */ +void process_lock_filesys (void) +{ + lock_acquire (&filesys_lock); +} + +/* Release global filesystem lock */ +void process_unlock_filesys (void) +{ + lock_release (&filesys_lock); +} + +/* Close the file associated with the given file + descriptor; returns true if close was successful */ +bool +process_close_file (int fd) +{ + struct file *file = process_get_file (fd); + if (file == NULL) + return false; + + lock_acquire (&filesys_lock); + file_close (file); + lock_release (&filesys_lock); + + struct fd_table *fdt = &process_current()->fd_table; + fdt->fds[fd] = NULL; + + /* update index of free/max file descriptor index*/ + if (fd < fdt->fd_free) fdt->fd_free = fd; + while (fdt->fds[fdt->fd_max] == NULL) { + fdt->fd_max--; + if (fdt->fd_max < 2) + break; + } + return true; +} diff --git a/pintos-progos/userprog/process.h b/pintos-progos/userprog/process.h new file mode 100644 index 0000000..1609801 --- /dev/null +++ b/pintos-progos/userprog/process.h @@ -0,0 +1,47 @@ +#ifndef USERPROG_PROCESS_H +#define USERPROG_PROCESS_H + +#include "threads/thread.h" + +/* In the current implementation, the capacity is fixed to 1024 (PGSIZE/4) */ +struct fd_table { + struct file** fds; + int fd_free; /* lowest-index free FD table entry */ + int fd_max; /* highest-index used FD table entry */ + int fd_cap; /* FD table capacity */ +}; + +struct process { + /* process tree */ + tid_t thread_id; + tid_t parent_tid; + struct list_elem parentelem; /* Owned by parent */ + + /* communication with parent process */ + struct semaphore exit_sem; + struct lock exit_lock; + int exit_status; + + /* files */ + struct file *executable; /* Loaded executable, if any. */ + struct fd_table fd_table; /* File descriptor table */ + + /* Owned by syscall.c */ + void* syscall_buffer; + size_t syscall_buffer_page_cnt; +}; + +void process_init (void); +struct process* process_current (void); +tid_t process_execute (const char *file_name); +int process_wait (tid_t); +void process_exit (void); +void process_activate (void); + +int process_open_file(const char* fname); +struct file* process_get_file(int fd); +void process_lock_filesys (void); +void process_unlock_filesys (void); +bool process_close_file(int fd); + +#endif /* userprog/process.h */ diff --git a/pintos-progos/userprog/syscall.c b/pintos-progos/userprog/syscall.c new file mode 100644 index 0000000..f8e0197 --- /dev/null +++ b/pintos-progos/userprog/syscall.c @@ -0,0 +1,563 @@ +#include +#include +#include "devices/input.h" +#include "devices/shutdown.h" +#include "filesys/file.h" +#include "filesys/filesys.h" +#include "filesys/inode.h" +#include "lib/string.h" +#include "threads/interrupt.h" +#include "threads/palloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "threads/vaddr.h" +#include "userprog/pagedir.h" +#include "userprog/process.h" +#include "userprog/syscall.h" + +#define STACK_SLOT_SIZE sizeof(int) + +/* Prototypes for Utilities */ +static int get_user (const uint8_t *uaddr); +static bool put_user (uint8_t *udst, uint8_t byte); +static void* memcpy_from_user (void *kaddr, void *uaddr, size_t bytes); +static void* memcpy_to_user (void *kaddr, void *addr, size_t bytes); +static void* copy_string_arg (void *usp, bool *segfault); +static void free_string_arg_buf (void *kbuf); + +/* Reads a byte at user virtual address UADDR. + UADDR must be below PHYS_BASE. + Returns the byte value if successful, -1 if a segfault + occurred. */ +static int +get_user (const uint8_t *uaddr) +{ + int result; + asm ("movl $1f, %0; " /* save eip in eax */ + "movzbl %1, %0; " /* read byte from user memory into eax */ + "1:" /* continue here on page fault, with eax set to -1 */ + : "=&a" (result) : "m" (*uaddr)); + return result; +} + +/* Writes BYTE to user address UDST. + UDST must be below PHYS_BASE. + Returns true if successful, false if a segfault occurred. */ +static bool +put_user (uint8_t *udst, uint8_t byte) +{ + int error_code; + asm ("movl $1f, %0;" /* save EIP in EAX */ + "movb %b2, %1;" /* write byte to user memory */ + "1:" /* continue here on page fault, with eax set to -1 */ + : "=&a" (error_code), "=m" (*udst) : "q" (byte)); + return error_code != -1; +} + +/* Copy bytes from user space; returns NULL if a segfault + occured, and kaddr otherwise */ +static void* +memcpy_from_user (void *kaddr, void *uaddr, size_t bytes) +{ + uint8_t* kp = kaddr; + size_t i; + if (! is_user_vaddr (uaddr) || + ! is_user_vaddr (uaddr + bytes - 1)) + return false; + for (i=0;iesp; + + /* The system call number and the arguments are on the stack */ + if (! copy_from_user (&syscall_nr,sp)) + goto fail; + switch (syscall_nr) { + case SYS_HALT: fp = syscall_halt; break; + case SYS_EXIT: fp = syscall_exit; break; + case SYS_EXEC: fp = syscall_exec; break; + case SYS_WAIT: fp = syscall_wait; break; + case SYS_CREATE: fp = syscall_create; break; + case SYS_REMOVE: fp = syscall_remove; break; + case SYS_OPEN: fp = syscall_open; break; + case SYS_FILESIZE: fp = syscall_filesize; break; + case SYS_READ: fp = syscall_read; break; + case SYS_WRITE: fp = syscall_write; break; + case SYS_SEEK: fp = syscall_seek; break; + case SYS_TELL: fp = syscall_tell; break; + case SYS_CLOSE: fp = syscall_close; break; + default: + goto fail; + } + result = fp (sp, &segfault); + if (segfault) + goto fail; + f->eax = result; + return; + + fail: + process_current()->exit_status = -1; + thread_exit (); +} + +/* Shutdown machine */ +static int +syscall_halt (void *sp UNUSED, bool *segfault UNUSED) +{ + shutdown (); + NOT_REACHED (); +} + +/* Exit current process with given exit code */ +static int +syscall_exit (void *sp, bool *segfault) +{ + int exit_status; + if (! copy_from_user (&exit_status, STACK_ADDR (sp,1))) { + *segfault = true; + return -1; + } + process_current()->exit_status = exit_status; + thread_exit (); + NOT_REACHED (); +} + +/* Spawn new process executing the supplied command */ +static int +syscall_exec (void *sp, bool *segfault) +{ + char *kbuf; + int result = TID_ERROR; + if ((kbuf = copy_string_arg (STACK_ADDR (sp, 1), segfault)) != NULL) { + result = process_execute (kbuf); + free_string_arg_buf (kbuf); + } + return result; +} + +/* Wait until specified process exits */ +static int +syscall_wait (void *sp, bool *segfault) +{ + tid_t arg; + if (! copy_from_user (&arg, STACK_ADDR (sp,1))) { + *segfault = true; + return 0; + } + return process_wait (arg); +} + +/* Create a new file with given initial size */ +static int +syscall_create (void *sp, bool *segfault) +{ + bool success = false; + char *fname; + int initial_size; + + if (! copy_from_user (&initial_size, STACK_ADDR (sp,2))) { + *segfault = true; + return false; + } + if ((fname = copy_string_arg (STACK_ADDR (sp, 1), segfault)) == NULL) + return false; + + process_lock_filesys (); + success = filesys_create (fname, initial_size); + process_unlock_filesys (); + free_string_arg_buf (fname); + return success; +} + +/* Remove name file, returns true if successful */ +static int +syscall_remove (void *sp, bool *segfault) +{ + bool success; + char *fname; + + if ((fname = copy_string_arg (STACK_ADDR (sp, 1), segfault)) == NULL) + return false; + process_lock_filesys (); + success = filesys_remove (fname); + process_unlock_filesys (); + free_string_arg_buf (fname); + return (int)success; +} + +/* Open file, returning non-negative file descriptor if successful */ +static int +syscall_open (void *sp, bool *segfault) +{ + char *fname; + int fd; + if ((fname = copy_string_arg (STACK_ADDR (sp, 1), segfault)) == NULL) + return false; + fd = process_open_file (fname); + free_string_arg_buf (fname); + return fd; +} + +/* Return size of file described by file descriptor */ +static int +syscall_filesize (void *sp, bool *segfault) +{ + int fd; + struct file *f; + int size; + + if (! copy_from_user (&fd, STACK_ADDR (sp,1))) { + *segfault = true; + return -1; + } + if ((f = process_get_file (fd)) == NULL) + return -1; + process_lock_filesys (); + size = inode_length (file_get_inode (f)); + process_unlock_filesys (); + return size; +} + +/* Read bytes from the file referenced by the given file + descriptor into the supplied user space buffer, returning + number of bytes read. */ +static int +syscall_read (void *sp, bool *segfault) +{ + int fd; + uint8_t *user_buffer; + size_t size, bytes_to_read; + + /* get arguments */ + if (! copy_from_user (&fd, STACK_ADDR (sp,1)) || + ! copy_from_user (&user_buffer, STACK_ADDR (sp, 2)) || + ! copy_from_user (&size, STACK_ADDR (sp,3))) { + *segfault = true; + return -1; + } + + /* ensure buffer is in user space */ + if (! is_user_vaddr (user_buffer) || + ! is_user_vaddr (user_buffer + size - 1)) { + *segfault = true; + return -1; + } + + bytes_to_read = size; + /* handle stdin */ + if (fd == STDIN_FILENO) { + char c; + while (bytes_to_read--) { + c = input_getc (); + if (! put_user (user_buffer++, c)) { + *segfault = true; + return -1; + } + } + return size; + } + /* get file */ + struct file *file = process_get_file (fd); + if (file == NULL) + return -1; + + char *kbuf = palloc_get_page (0); + if (kbuf == NULL) + return -1; + + /* read loop */ + do { + int bytes_read; + int blocksize = bytes_to_read; + if (bytes_to_read > PGSIZE) + blocksize = PGSIZE; + + /* read bytes */ + process_lock_filesys (); + bytes_read = file_read (file, kbuf, blocksize); + process_unlock_filesys (); + + /* Stop when EOF has been reached */ + if (bytes_read == 0) + break; + bytes_to_read -= bytes_read; + if (! memcpy_to_user (user_buffer, kbuf, bytes_read)) { + *segfault = true; + break; + } + user_buffer += bytes_read; + } while (bytes_to_read > 0); + + palloc_free_page (kbuf); + return size - bytes_to_read; +} + +/* Write bytes from user buffer into the specified + file, returning number of bytes written. */ +static int +syscall_write (void *sp, bool *segfault) +{ + int fd; + size_t size, bytes_to_write; + char *user_buffer; + + /* get arguments */ + if (! copy_from_user (&fd, STACK_ADDR (sp,1)) || + ! copy_from_user (&user_buffer, STACK_ADDR (sp, 2)) || + ! copy_from_user (&size, STACK_ADDR (sp,3))) { + *segfault = true; + return -1; + } + + /* ensure buffer is in user space */ + if (! is_user_vaddr (user_buffer) || + ! is_user_vaddr (user_buffer + size - 1)) { + *segfault = true; + return -1; + } + + /* get file handle */ + struct file *file = NULL; + if (fd != STDOUT_FILENO) { + file = process_get_file (fd); + if (file == NULL) + return -1; + } + + /* allocate kernel buffer */ + char *kbuf = palloc_get_page (0); + if (kbuf == NULL) + return -1; + + /* write loop */ + bytes_to_write = size; + do { + int blocksize = bytes_to_write; + if (bytes_to_write > PGSIZE) + blocksize = PGSIZE; + if (memcpy_from_user (kbuf, user_buffer, blocksize) == NULL) { + *segfault = true; + break; + } + if (fd == STDOUT_FILENO) { + putbuf (kbuf, blocksize); + bytes_to_write -= blocksize; + } else { + int bytes_written = 0; + int bytes_left_filesys = blocksize; + + process_lock_filesys (); + while (bytes_left_filesys > 0) { + bytes_written = file_write (file, kbuf, bytes_left_filesys); + if (bytes_written <= 0) { + break; + } + bytes_left_filesys -= bytes_written; + } + process_unlock_filesys (); + + if (bytes_written <= 0) + break; + bytes_to_write -= blocksize; + } + user_buffer += blocksize; + } while (bytes_to_write > 0); + + /* return bytes written */ + palloc_free_page (kbuf); + return size - bytes_to_write; +} + +/* Change the position where the next byte will be read or written */ +static int +syscall_seek (void *sp, bool *segfault) +{ + int fd; + off_t new_pos; + + /* get arguments */ + if (! copy_from_user (&fd, STACK_ADDR (sp,1)) || + ! copy_from_user (&new_pos, STACK_ADDR (sp, 2))) { + *segfault = true; + return 0; + } + + /* no way to return something sensible (void function) */ + struct file *file = process_get_file (fd); + if (file == NULL) + return 0; + + process_lock_filesys (); + file_seek (file, new_pos); + process_unlock_filesys (); + return 0; +} + +/* Returns the position of the next byte to be read or written */ +static int +syscall_tell (void *sp, bool *segfault) +{ + int fd; + unsigned r = 0; + + /* get arguments */ + if (! copy_from_user (&fd, STACK_ADDR (sp,1))) { + *segfault = true; + return 0; + } + + /* no way to return something sensible function */ + struct file *file = process_get_file (fd); + if (file == NULL) + return 0; + + process_lock_filesys (); + r = file_tell (file); + process_unlock_filesys (); + return r; +} + +/* Close the given file */ +static int +syscall_close (void *sp, bool *segfault) +{ + int fd; + + /* get arguments */ + if (! copy_from_user (&fd, STACK_ADDR (sp,1))) { + *segfault = true; + return 0; + } + + /* no way to return something sensible function (void) */ + (void) process_close_file (fd); + return 0; +} diff --git a/pintos-progos/userprog/syscall.h b/pintos-progos/userprog/syscall.h new file mode 100644 index 0000000..f7ab2f3 --- /dev/null +++ b/pintos-progos/userprog/syscall.h @@ -0,0 +1,5 @@ +#ifndef USERPROG_SYSCALL_H +#define USERPROG_SYSCALL_H + +void syscall_init (void); +#endif /* userprog/syscall.h */ diff --git a/pintos-progos/userprog/tss.c b/pintos-progos/userprog/tss.c new file mode 100644 index 0000000..f8ed9a9 --- /dev/null +++ b/pintos-progos/userprog/tss.c @@ -0,0 +1,106 @@ +#include "userprog/tss.h" +#include +#include +#include "userprog/gdt.h" +#include "threads/thread.h" +#include "threads/palloc.h" +#include "threads/vaddr.h" + +/* The Task-State Segment (TSS). + + Instances of the TSS, an x86-specific structure, are used to + define "tasks", a form of support for multitasking built right + into the processor. However, for various reasons including + portability, speed, and flexibility, most x86 OSes almost + completely ignore the TSS. We are no exception. + + Unfortunately, there is one thing that can only be done using + a TSS: stack switching for interrupts that occur in user mode. + When an interrupt occurs in user mode (ring 3), the processor + consults the ss0 and esp0 members of the current TSS to + determine the stack to use for handling the interrupt. Thus, + we must create a TSS and initialize at least these fields, and + this is precisely what this file does. + + When an interrupt is handled by an interrupt or trap gate + (which applies to all interrupts we handle), an x86 processor + works like this: + + - If the code interrupted by the interrupt is in the same + ring as the interrupt handler, then no stack switch takes + place. This is the case for interrupts that happen when + we're running in the kernel. The contents of the TSS are + irrelevant for this case. + + - If the interrupted code is in a different ring from the + handler, then the processor switches to the stack + specified in the TSS for the new ring. This is the case + for interrupts that happen when we're in user space. It's + important that we switch to a stack that's not already in + use, to avoid corruption. Because we're running in user + space, we know that the current process's kernel stack is + not in use, so we can always use that. Thus, when the + scheduler switches threads, it also changes the TSS's + stack pointer to point to the new thread's kernel stack. + (The call is in thread_schedule_tail() in thread.c.) + + See [IA32-v3a] 6.2.1 "Task-State Segment (TSS)" for a + description of the TSS. See [IA32-v3a] 5.12.1 "Exception- or + Interrupt-Handler Procedures" for a description of when and + how stack switching occurs during an interrupt. */ +struct tss + { + uint16_t back_link, :16; + void *esp0; /* Ring 0 stack virtual address. */ + uint16_t ss0, :16; /* Ring 0 stack segment selector. */ + void *esp1; + uint16_t ss1, :16; + void *esp2; + uint16_t ss2, :16; + uint32_t cr3; + void (*eip) (void); + uint32_t eflags; + uint32_t eax, ecx, edx, ebx; + uint32_t esp, ebp, esi, edi; + uint16_t es, :16; + uint16_t cs, :16; + uint16_t ss, :16; + uint16_t ds, :16; + uint16_t fs, :16; + uint16_t gs, :16; + uint16_t ldt, :16; + uint16_t trace, bitmap; + }; + +/* Kernel TSS. */ +static struct tss *tss; + +/* Initializes the kernel TSS. */ +void +tss_init (void) +{ + /* Our TSS is never used in a call gate or task gate, so only a + few fields of it are ever referenced, and those are the only + ones we initialize. */ + tss = palloc_get_page (PAL_ASSERT | PAL_ZERO); + tss->ss0 = SEL_KDSEG; + tss->bitmap = 0xdfff; + tss_update (); +} + +/* Returns the kernel TSS. */ +struct tss * +tss_get (void) +{ + ASSERT (tss != NULL); + return tss; +} + +/* Sets the ring 0 stack pointer in the TSS to point to the end + of the thread stack. */ +void +tss_update (void) +{ + ASSERT (tss != NULL); + tss->esp0 = (uint8_t *) thread_current () + PGSIZE; +} diff --git a/pintos-progos/userprog/tss.h b/pintos-progos/userprog/tss.h new file mode 100644 index 0000000..467bd19 --- /dev/null +++ b/pintos-progos/userprog/tss.h @@ -0,0 +1,11 @@ +#ifndef USERPROG_TSS_H +#define USERPROG_TSS_H + +#include + +struct tss; +void tss_init (void); +struct tss *tss_get (void); +void tss_update (void); + +#endif /* userprog/tss.h */ diff --git a/pintos-progos/utils/.gitignore b/pintos-progos/utils/.gitignore new file mode 100644 index 0000000..b96f278 --- /dev/null +++ b/pintos-progos/utils/.gitignore @@ -0,0 +1,3 @@ +setitimer-helper +squish-pty +squish-unix diff --git a/pintos-progos/utils/Makefile b/pintos-progos/utils/Makefile new file mode 100644 index 0000000..46a9124 --- /dev/null +++ b/pintos-progos/utils/Makefile @@ -0,0 +1,11 @@ +all: setitimer-helper squish-pty squish-unix + +CC = gcc +CFLAGS = -Wall -W +LDFLAGS = -lm +setitimer-helper: setitimer-helper.o +squish-pty: squish-pty.o +squish-unix: squish-unix.o + +clean: + rm -f *.o setitimer-helper squish-pty squish-unix diff --git a/pintos-progos/utils/Pintos.pm b/pintos-progos/utils/Pintos.pm new file mode 100644 index 0000000..70df40d --- /dev/null +++ b/pintos-progos/utils/Pintos.pm @@ -0,0 +1,491 @@ +# Pintos helper subroutines. + +# Number of bytes available for the loader at the beginning of the MBR. +# Kernel command-line arguments follow the loader. +our $LOADER_SIZE = 314; + +# Partition types. +my (%role2type) = (KERNEL => 0x20, + FILESYS => 0x21, + SCRATCH => 0x22, + SWAP => 0x23); +my (%type2role) = reverse %role2type; + +# Order of roles within a given disk. +our (@role_order) = qw (KERNEL FILESYS SCRATCH SWAP); + +# Partitions. +# +# Valid keys are KERNEL, FILESYS, SCRATCH, SWAP. Only those +# partitions which are in use are included. +# +# Each value is a reference to a hash. If the partition's contents +# are to be obtained from a file (that will be copied into a new +# virtual disk), then the hash contains: +# +# FILE => name of file from which the partition's contents are copied +# (perhaps "/dev/zero"), +# OFFSET => offset in bytes in FILE, +# BYTES => size in bytes of contents from FILE, +# +# If the partition is taken from a virtual disk directly, then it +# contains the following. The same keys are also filled in once a +# file-based partition has been copied into a new virtual disk: +# +# DISK => name of virtual disk file, +# START => sector offset of start of partition within DISK, +# SECTORS => number of sectors of partition within DISK, which is usually +# greater than round_up (BYTES, 512) due to padding. +our (%parts); + +# set_part($opt, $arg) +# +# For use as a helper function for Getopt::Long::GetOptions to set +# disk sources. +sub set_part { + my ($opt, $arg) = @_; + my ($role, $source) = $opt =~ /^([a-z]+)(?:-([a-z]+))?/ or die; + + $role = uc $role; + $source = 'FILE' if $source eq ''; + + die "can't have two sources for \L$role\E partition" + if exists $parts{$role}; + + do_set_part ($role, $source, $arg); +} + +# do_set_part($role, $source, $arg) +# +# Sets partition $role as coming from $source (one of 'file', 'from', +# or 'size'). $arg is a file name for 'file' or 'from', a size in +# megabytes for 'size'. +sub do_set_part { + my ($role, $source, $arg) = @_; + + my ($p) = $parts{$role} = {}; + if ($source eq 'file') { + if (read_mbr ($arg)) { + print STDERR "warning: $arg looks like a partitioned disk "; + print STDERR "(did you want --$role-from=$arg or --disk=$arg?)\n" + } + + $p->{FILE} = $arg; + $p->{OFFSET} = 0; + $p->{BYTES} = -s $arg; + } elsif ($source eq 'from') { + my (%pt) = read_partition_table ($arg); + my ($sp) = $pt{$role}; + die "$arg: does not contain \L$role\E partition\n" if !defined $sp; + + $p->{FILE} = $arg; + $p->{OFFSET} = $sp->{START} * 512; + $p->{BYTES} = $sp->{SECTORS} * 512; + } elsif ($source eq 'size') { + $arg =~ /^\d+(\.\d+)?|\.\d+$/ or die "$arg: not a valid size in MB\n"; + + $p->{FILE} = "/dev/zero"; + $p->{OFFSET} = 0; + $p->{BYTES} = ceil ($arg * 1024 * 1024); + } else { + die; + } +} + +# set_geometry('HEADS,SPT') +# set_geometry('zip') +# +# For use as a helper function for Getopt::Long::GetOptions to set +# disk geometry. +sub set_geometry { + local ($_) = $_[1]; + if ($_ eq 'zip') { + @geometry{'H', 'S'} = (64, 32); + } else { + @geometry{'H', 'S'} = /^(\d+)[,\s]+(\d+)$/ + or die "bad syntax for geometry\n"; + $geometry{H} <= 255 or die "heads limited to 255\n"; + $geometry{S} <= 63 or die "sectors per track limited to 63\n"; + } +} + +# set_align('bochs|full|none') +# +# For use as a helper function for Getopt::Long::GetOptions to set +# partition alignment. +sub set_align { + $align = $_[1]; + die "unknown alignment type \"$align\"\n" + if $align ne 'bochs' && $align ne 'full' && $align ne 'none'; +} + +# assemble_disk(%args) +# +# Creates a virtual disk $args{DISK} containing the partitions +# described by @args{KERNEL, FILESYS, SCRATCH, SWAP}. +# +# Required arguments: +# DISK => output disk file name +# HANDLE => output file handle (will be closed) +# +# Normally at least one of the following is included: +# KERNEL, FILESYS, SCRATCH, SWAP => {input: +# FILE => file to read, +# OFFSET => byte offset in file, +# BYTES => byte count from file, +# +# output: +# DISK => output disk file name, +# START => sector offset in DISK, +# SECTORS => sector count in DISK}, +# +# Optional arguments: +# ALIGN => 'bochs' (default), 'full', or 'none' +# GEOMETRY => {H => heads, S => sectors per track} (default 16, 63) +# FORMAT => 'partitioned' (default) or 'raw' +# LOADER => $LOADER_SIZE-byte string containing the loader binary +# ARGS => ['arg 1', 'arg 2', ...] +sub assemble_disk { + my (%args) = @_; + + my (%geometry) = $args{GEOMETRY} || (H => 16, S => 63); + + my ($align); # Align partition start, end to cylinder boundary? + my ($pad); # Pad end of disk out to cylinder boundary? + if (!defined ($args{ALIGN}) || $args{ALIGN} eq 'bochs') { + $align = 0; + $pad = 1; + } elsif ($args{ALIGN} eq 'full') { + $align = 1; + $pad = 0; + } elsif ($args{ALIGN} eq 'none') { + $align = $pad = 0; + } else { + die; + } + + my ($format) = $args{FORMAT} || 'partitioned'; + die if $format ne 'partitioned' && $format ne 'raw'; + + # Check that we have apartitions to copy in. + my $part_cnt = grep (defined ($args{$_}), keys %role2type); + die "must have exactly one partition for raw output\n" + if $format eq 'raw' && $part_cnt != 1; + + # Calculate the disk size. + my ($total_sectors) = 0; + if ($format eq 'partitioned') { + $total_sectors += $align ? $geometry{S} : 1; + } + for my $role (@role_order) { + my ($p) = $args{$role}; + next if !defined $p; + + die if $p->{DISK}; + + my ($bytes) = $p->{BYTES}; + my ($start) = $total_sectors; + my ($end) = $start + div_round_up ($bytes, 512); + $end = round_up ($end, cyl_sectors (%geometry)) if $align; + + $p->{DISK} = $args{DISK}; + $p->{START} = $start; + $p->{SECTORS} = $end - $start; + $total_sectors = $end; + } + + # Write the disk. + my ($disk_fn) = $args{DISK}; + my ($disk) = $args{HANDLE}; + if ($format eq 'partitioned') { + # Pack loader into MBR. + my ($loader) = $args{LOADER} || "\xcd\x18"; + my ($mbr) = pack ("a$LOADER_SIZE", $loader); + + $mbr .= make_kernel_command_line (@{$args{ARGS}}); + + # Pack partition table into MBR. + $mbr .= make_partition_table (\%geometry, \%args); + + # Add signature to MBR. + $mbr .= pack ("v", 0xaa55); + + die if length ($mbr) != 512; + write_fully ($disk, $disk_fn, $mbr); + write_zeros ($disk, $disk_fn, 512 * ($geometry{S} - 1)) if $align; + } + for my $role (@role_order) { + my ($p) = $args{$role}; + next if !defined $p; + + my ($source); + my ($fn) = $p->{FILE}; + open ($source, '<', $fn) or die "$fn: open: $!\n"; + if ($p->{OFFSET}) { + sysseek ($source, $p->{OFFSET}, 0) == $p->{OFFSET} + or die "$fn: seek: $!\n"; + } + copy_file ($source, $fn, $disk, $disk_fn, $p->{BYTES}); + close ($source) or die "$fn: close: $!\n"; + + write_zeros ($disk, $disk_fn, $p->{SECTORS} * 512 - $p->{BYTES}); + } + if ($pad) { + my ($pad_sectors) = round_up ($total_sectors, cyl_sectors (%geometry)); + write_zeros ($disk, $disk_fn, ($pad_sectors - $total_sectors) * 512); + } + close ($disk) or die "$disk: close: $!\n"; +} + +# make_partition_table({H => heads, S => sectors}, {KERNEL => ..., ...}) +# +# Creates and returns a partition table for the given partitions and +# disk geometry. +sub make_partition_table { + my ($geometry, $partitions) = @_; + my ($table) = ''; + for my $role (@role_order) { + defined (my $p = $partitions->{$role}) or next; + + my $end = $p->{START} + $p->{SECTORS} - 1; + my $bootable = $role eq 'KERNEL'; + + $table .= pack ("C", $bootable ? 0x80 : 0); # Bootable? + $table .= pack_chs ($p->{START}, $geometry); # CHS of partition start + $table .= pack ("C", $role2type{$role}); # Partition type + $table .= pack_chs($end, $geometry); # CHS of partition end + $table .= pack ("V", $p->{START}); # LBA of partition start + $table .= pack ("V", $p->{SECTORS}); # Length in sectors + die if length ($table) % 16; + } + return pack ("a64", $table); +} + +# make_kernel_command_line(@args) +# +# Returns the raw bytes to write to an MBR at offset $LOADER_SIZE to +# set a Pintos kernel command line. +sub make_kernel_command_line { + my (@args) = @_; + my ($args) = join ('', map ("$_\0", @args)); + die "command line exceeds 128 bytes" if length ($args) > 128; + return pack ("V a128", scalar (@args), $args); +} + +# copy_file($from_handle, $from_file_name, $to_handle, $to_file_name, $size) +# +# Copies $size bytes from $from_handle to $to_handle. +# $from_file_name and $to_file_name are used in error messages. +sub copy_file { + my ($from_handle, $from_file_name, $to_handle, $to_file_name, $size) = @_; + + while ($size > 0) { + my ($chunk_size) = 4096; + $chunk_size = $size if $chunk_size > $size; + $size -= $chunk_size; + + my ($data) = read_fully ($from_handle, $from_file_name, $chunk_size); + write_fully ($to_handle, $to_file_name, $data); + } +} + +# read_fully($handle, $file_name, $bytes) +# +# Reads exactly $bytes bytes from $handle and returns the data read. +# $file_name is used in error messages. +sub read_fully { + my ($handle, $file_name, $bytes) = @_; + my ($data); + my ($read_bytes) = sysread ($handle, $data, $bytes); + die "$file_name: read: $!\n" if !defined $read_bytes; + die "$file_name: unexpected end of file\n" if $read_bytes != $bytes; + return $data; +} + +# write_fully($handle, $file_name, $data) +# +# Write $data to $handle. +# $file_name is used in error messages. +sub write_fully { + my ($handle, $file_name, $data) = @_; + my ($written_bytes) = syswrite ($handle, $data); + die "$file_name: write: $!\n" if !defined $written_bytes; + die "$file_name: short write\n" if $written_bytes != length $data; +} + +sub write_zeros { + my ($handle, $file_name, $size) = @_; + + while ($size > 0) { + my ($chunk_size) = 4096; + $chunk_size = $size if $chunk_size > $size; + $size -= $chunk_size; + + write_fully ($handle, $file_name, "\0" x $chunk_size); + } +} + +# div_round_up($x,$y) +# +# Returns $x / $y, rounded up to the nearest integer. +# $y must be an integer. +sub div_round_up { + my ($x, $y) = @_; + return int ((ceil ($x) + $y - 1) / $y); +} + +# round_up($x, $y) +# +# Returns $x rounded up to the nearest multiple of $y. +# $y must be an integer. +sub round_up { + my ($x, $y) = @_; + return div_round_up ($x, $y) * $y; +} + +# cyl_sectors(H => heads, S => sectors) +# +# Returns the number of sectors in a cylinder of a disk with the given +# geometry. +sub cyl_sectors { + my (%geometry) = @_; + return $geometry{H} * $geometry{S}; +} + +# read_loader($file_name) +# +# Reads and returns the first $LOADER_SIZE bytes in $file_name. +# If $file_name is undefined, tries to find the default loader. +# Makes sure that the loader is a reasonable size. +sub read_loader { + my ($name) = @_; + $name = find_file ("loader.bin") if !defined $name; + die "Cannot find loader\n" if !defined $name; + + my ($handle); + open ($handle, '<', $name) or die "$name: open: $!\n"; + -s $handle == $LOADER_SIZE || -s $handle == 512 + or die "$name: must be exactly $LOADER_SIZE or 512 bytes long\n"; + $loader = read_fully ($handle, $name, $LOADER_SIZE); + close ($handle) or die "$name: close: $!\n"; + return $loader; +} + +# pack_chs($lba, {H => heads, S => sectors}) +# +# Converts logical sector $lba to a 3-byte packed geometrical sector +# in the format used in PC partition tables (see [Partitions]) and +# returns the geometrical sector as a 3-byte string. +sub pack_chs { + my ($lba, $geometry) = @_; + my ($cyl, $head, $sect) = lba_to_chs ($lba, $geometry); + return pack ("CCC", $head, $sect | (($cyl >> 2) & 0xc0), $cyl & 0xff); +} + +# lba_to_chs($lba, {H => heads, S => sectors}) +# +# Returns the geometrical sector corresponding to logical sector $lba +# given the specified geometry. +sub lba_to_chs { + my ($lba, $geometry) = @_; + my ($hpc) = $geometry->{H}; + my ($spt) = $geometry->{S}; + + # Source: + # http://en.wikipedia.org/wiki/CHS_conversion + use integer; + my $cyl = $lba / ($hpc * $spt); + my $temp = $lba % ($hpc * $spt); + my $head = $temp / $spt; + my $sect = $temp % $spt + 1; + + # Source: + # http://www.cgsecurity.org/wiki/Intel_Partition_Table + if ($cyl <= 1023) { + return ($cyl, $head, $sect); + } else { + return (1023, 254, 63); ## or should this be (1023, $hpc, $spt)? + } +} + +# read_mbr($file) +# +# Tries to read an MBR from $file. Returns the 512-byte MBR if +# successful, otherwise numeric 0. +sub read_mbr { + my ($file) = @_; + my ($retval) = 0; + open (FILE, '<', $file) or die "$file: open: $!\n"; + if (-s FILE == 0) { + die "$file: file has zero size\n"; + } elsif (-s FILE >= 512) { + my ($mbr); + sysread (FILE, $mbr, 512) == 512 or die "$file: read: $!\n"; + $retval = $mbr if unpack ("v", substr ($mbr, 510)) == 0xaa55; + } + close (FILE); + return $retval; +} + +# interpret_partition_table($mbr, $disk) +# +# Parses the partition-table in the specified 512-byte $mbr and +# returns the partitions. $disk is used for error messages. +sub interpret_partition_table { + my ($mbr, $disk) = @_; + my (%parts); + for my $i (0...3) { + my ($bootable, $valid, $type, $lba_start, $lba_length) + = unpack ("C X V C x3 V V", substr ($mbr, 446 + 16 * $i, 16)); + next if !$valid; + + (print STDERR "warning: invalid partition entry $i in $disk\n"), + next if $bootable != 0 && $bootable != 0x80; + + my ($role) = $type2role{$type}; + (printf STDERR "warning: non-Pintos partition type 0x%02x in %s\n", + $type, $disk), + next if !defined $role; + + (print STDERR "warning: duplicate \L$role\E partition in $disk\n"), + next if exists $parts{$role}; + + $parts{$role} = {START => $lba_start, + SECTORS => $lba_length}; + } + return %parts; +} + +# find_file($base_name) +# +# Looks for a file named $base_name in a couple of likely spots. If +# found, returns the name; otherwise, returns undef. +sub find_file { + my ($base_name) = @_; + -e && return $_ foreach $base_name, "build/$base_name"; + return undef; +} + +# read_partition_table($file) +# +# Reads a partition table from $file and returns the parsed +# partitions. Dies if partitions can't be read. +sub read_partition_table { + my ($file) = @_; + my ($mbr) = read_mbr ($file); + die "$file: not a partitioned disk\n" if !$mbr; + return interpret_partition_table ($mbr, $file); +} + +# max(@args) +# +# Returns the numerically largest value in @args. +sub max { + my ($max) = $_[0]; + foreach (@_[1..$#_]) { + $max = $_ if $_ > $max; + } + return $max; +} + +1; diff --git a/pintos-progos/utils/backtrace b/pintos-progos/utils/backtrace new file mode 100755 index 0000000..95e422f --- /dev/null +++ b/pintos-progos/utils/backtrace @@ -0,0 +1,106 @@ +#! /usr/bin/perl -w + +use strict; + +# Check command line. +if (grep ($_ eq '-h' || $_ eq '--help', @ARGV)) { + print <<'EOF'; +backtrace, for converting raw addresses into symbolic backtraces +usage: backtrace [BINARY]... ADDRESS... +where BINARY is the binary file or files from which to obtain symbols + and ADDRESS is a raw address to convert to a symbol name. + +If no BINARY is unspecified, the default is the first of kernel.o or +build/kernel.o that exists. If multiple binaries are specified, each +symbol printed is from the first binary that contains a match. + +The ADDRESS list should be taken from the "Call stack:" printed by the +kernel. Read "Backtraces" in the "Debugging Tools" chapter of the +Pintos documentation for more information. +EOF + exit 0; +} +die "backtrace: at least one argument required (use --help for help)\n" + if @ARGV == 0; + +# Drop garbage inserted by kernel. +@ARGV = grep (!/^(call|stack:?|[-+])$/i, @ARGV); +s/\.$// foreach @ARGV; + +# Find binaries. +my (@binaries); +while ($ARGV[0] !~ /^0x/) { + my ($bin) = shift @ARGV; + die "backtrace: $bin: not found (use --help for help)\n" if ! -e $bin; + push (@binaries, $bin); +} +if (!@binaries) { + my ($bin); + if (-e 'kernel.o') { + $bin = 'kernel.o'; + } elsif (-e 'build/kernel.o') { + $bin = 'build/kernel.o'; + } else { + die "backtrace: no binary specified and neither \"kernel.o\" nor \"build/kernel.o\" exists (use --help for help)\n"; + } + push (@binaries, $bin); +} + +# Find addr2line. +my ($a2l) = search_path ("i386-elf-addr2line") || search_path ("addr2line"); +if (!$a2l) { + die "backtrace: neither `i386-elf-addr2line' nor `addr2line' in PATH\n"; +} +sub search_path { + my ($target) = @_; + for my $dir (split (':', $ENV{PATH})) { + my ($file) = "$dir/$target"; + return $file if -e $file; + } + return undef; +} + +# Figure out backtrace. +my (@locs) = map ({ADDR => $_}, @ARGV); +for my $bin (@binaries) { + open (A2L, "$a2l -fe $bin " . join (' ', map ($_->{ADDR}, @locs)) . "|"); + for (my ($i) = 0; ; $i++) { + my ($function, $line); + chomp ($function = $_); + chomp ($line = ); + next if defined $locs[$i]{BINARY}; + + if ($function ne '??' || $line ne '??:0') { + $locs[$i]{FUNCTION} = $function; + $locs[$i]{LINE} = $line; + $locs[$i]{BINARY} = $bin; + } + } + close (A2L); +} + +# Print backtrace. +my ($cur_binary); +for my $loc (@locs) { + if (defined ($loc->{BINARY}) + && @binaries > 1 + && (!defined ($cur_binary) || $loc->{BINARY} ne $cur_binary)) { + $cur_binary = $loc->{BINARY}; + print "In $cur_binary:\n"; + } + + my ($addr) = $loc->{ADDR}; + $addr = sprintf ("0x%08x", hex ($addr)) if $addr =~ /^0x[0-9a-f]+$/i; + + print $addr, ": "; + if (defined ($loc->{BINARY})) { + my ($function) = $loc->{FUNCTION}; + my ($line) = $loc->{LINE}; + $line =~ s/^(\.\.\/)*//; + $line = "..." . substr ($line, -25) if length ($line) > 28; + print "$function ($line)"; + } else { + print "(unknown)"; + } + print "\n"; +} diff --git a/pintos-progos/utils/pintos b/pintos-progos/utils/pintos new file mode 100755 index 0000000..91f73ad --- /dev/null +++ b/pintos-progos/utils/pintos @@ -0,0 +1,955 @@ +#! /usr/bin/perl -w + +use strict; +use POSIX; +use Fcntl; +use File::Temp 'tempfile'; +use Getopt::Long qw(:config bundling); +use Fcntl qw(SEEK_SET SEEK_CUR); + +# Read Pintos.pm from the same directory as this program. +BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; } + +# Command-line options. +our ($start_time) = time (); +our ($sim); # Simulator: bochs, qemu, or player. +our ($debug) = "none"; # Debugger: none, monitor, or gdb. +our ($mem) = 4; # Physical RAM in MB. +our ($serial) = 1; # Use serial port for input and output? +our ($vga); # VGA output: window, terminal, or none. +our ($jitter); # Seed for random timer interrupts, if set. +our ($realtime); # Synchronize timer interrupts with real time? +our ($timeout); # Maximum runtime in seconds, if set. +our ($kill_on_failure); # Abort quickly on test failure? +our ($kernel_test); # Run kernel test instead of user program +our (@puts); # Files to copy into the VM. +our (@gets); # Files to copy out of the VM. +our ($as_ref); # Reference to last addition to @gets or @puts. +our (@kernel_args); # Arguments to pass to kernel. +our (%parts); # Partitions. +our ($make_disk); # Name of disk to create. +our ($tmp_disk) = 1; # Delete $make_disk after run? +our (@disks); # Extra disk images to pass to simulator. +our ($loader_fn); # Bootstrap loader. +our (%geometry); # IDE disk geometry. +our ($align); # Partition alignment. + +parse_command_line (); +prepare_scratch_disk (); +find_disks (); +run_vm (); +finish_scratch_disk (); + +exit 0; + +# Parses the command line. +sub parse_command_line { + usage (0) if @ARGV == 0 || (@ARGV == 1 && $ARGV[0] eq '--help'); + + @kernel_args = @ARGV; + if (grep ($_ eq '--', @kernel_args)) { + @ARGV = (); + while ((my $arg = shift (@kernel_args)) ne '--') { + push (@ARGV, $arg); + } + GetOptions ("sim=s" => sub { set_sim ($_[1]) }, + "bochs" => sub { set_sim ("bochs") }, + "qemu" => sub { set_sim ("qemu") }, + "player" => sub { set_sim ("player") }, + + "debug=s" => sub { set_debug ($_[1]) }, + "no-debug" => sub { set_debug ("none") }, + "monitor" => sub { set_debug ("monitor") }, + "gdb" => sub { set_debug ("gdb") }, + + "m|memory=i" => \$mem, + "j|jitter=i" => sub { set_jitter ($_[1]) }, + "r|realtime" => sub { set_realtime () }, + + "T|timeout=i" => \$timeout, + "k|kill-on-failure" => \$kill_on_failure, + + "v|no-vga" => sub { set_vga ('none'); }, + "s|no-serial" => sub { $serial = 0; }, + "t|terminal" => sub { set_vga ('terminal'); }, + + "kernel-test" => sub { set_kernel_test(); }, + "p|put-file=s" => sub { add_file (\@puts, $_[1]); }, + "g|get-file=s" => sub { add_file (\@gets, $_[1]); }, + "a|as=s" => sub { set_as ($_[1]); }, + + "h|help" => sub { usage (0); }, + + "kernel=s" => \&set_part, + "filesys=s" => \&set_part, + "swap=s" => \&set_part, + + "filesys-size=s" => \&set_part, + "scratch-size=s" => \&set_part, + "swap-size=s" => \&set_part, + + "kernel-from=s" => \&set_part, + "filesys-from=s" => \&set_part, + "swap-from=s" => \&set_part, + + "make-disk=s" => sub { $make_disk = $_[1]; + $tmp_disk = 0; }, + "disk=s" => sub { set_disk ($_[1]); }, + "loader=s" => \$loader_fn, + + "geometry=s" => \&set_geometry, + "align=s" => \&set_align) + or exit 1; + } + + $sim = "bochs" if !defined $sim; + $debug = "none" if !defined $debug; + $vga = exists ($ENV{DISPLAY}) ? "window" : "none" if !defined $vga; + + undef $timeout, print "warning: disabling timeout with --$debug\n" + if defined ($timeout) && $debug ne 'none'; + + print "warning: enabling serial port for -k or --kill-on-failure\n" + if $kill_on_failure && !$serial; + + $align = "bochs", + print STDERR "warning: setting --align=bochs for Bochs support\n" + if $sim eq 'bochs' && defined ($align) && $align eq 'none'; +} + +# usage($exitcode). +# Prints a usage message and exits with $exitcode. +sub usage { + my ($exitcode) = @_; + $exitcode = 1 unless defined $exitcode; + print <<'EOF'; +pintos, a utility for running Pintos in a simulator +Usage: pintos [OPTION...] -- [ARGUMENT...] +where each OPTION is one of the following options + and each ARGUMENT is passed to Pintos kernel verbatim. +Simulator selection: + --bochs (default) Use Bochs as simulator + --qemu Use QEMU as simulator + --player Use VMware Player as simulator +Debugger selection: + --no-debug (default) No debugger + --monitor Debug with simulator's monitor + --gdb Debug with gdb +Display options: (default is both VGA and serial) + -v, --no-vga No VGA display or keyboard + -s, --no-serial No serial input or output + -t, --terminal Display VGA in terminal (Bochs only) +Timing options: (Bochs only) + -j SEED Randomize timer interrupts + -r, --realtime Use realistic, not reproducible, timings +Testing options: + -T, --timeout=N Kill Pintos after N seconds CPU time or N*load_avg + seconds wall-clock time (whichever comes first) + -k, --kill-on-failure Kill Pintos a few seconds after a kernel or user + panic, test failure, or triple fault + --kernel-test Run kernel test, even though user programs are + enabled. +Configuration options: + -m, --mem=N Give Pintos N MB physical RAM (default: 4) +File system commands: + -p, --put-file=HOSTFN Copy HOSTFN into VM, by default under same name + -g, --get-file=GUESTFN Copy GUESTFN out of VM, by default under same name + -a, --as=FILENAME Specifies guest (for -p) or host (for -g) file name +Partition options: (where PARTITION is one of: kernel filesys scratch swap) + --PARTITION=FILE Use a copy of FILE for the given PARTITION + --PARTITION-size=SIZE Create an empty PARTITION of the given SIZE in MB + --PARTITION-from=DISK Use of a copy of the given PARTITION in DISK + (There is no --kernel-size, --scratch, or --scratch-from option.) +Disk configuration options: + --make-disk=DISK Name the new DISK and don't delete it after the run + --disk=DISK Also use existing DISK (may be used multiple times) +Advanced disk configuration options: + --loader=FILE Use FILE as bootstrap loader (default: loader.bin) + --geometry=H,S Use H head, S sector geometry (default: 16,63) + --geometry=zip Use 64 head, 32 sector geometry for USB-ZIP boot + (see http://syslinux.zytor.com/usbkey.php) + --align=bochs Pad out disk to cylinder to support Bochs (default) + --align=full Align partition boundaries to cylinder boundary to + let fdisk guess correct geometry and quiet warnings + --align=none Don't align partitions at all, to save space +Other options: + -h, --help Display this help message. +EOF + exit $exitcode; +} + +# Sets the simulator. +sub set_sim { + my ($new_sim) = @_; + die "--$new_sim conflicts with --$sim\n" + if defined ($sim) && $sim ne $new_sim; + $sim = $new_sim; +} + +# Sets the debugger. +sub set_debug { + my ($new_debug) = @_; + die "--$new_debug conflicts with --$debug\n" + if $debug ne 'none' && $new_debug ne 'none' && $debug ne $new_debug; + $debug = $new_debug; +} + +# Sets VGA output destination. +sub set_vga { + my ($new_vga) = @_; + if (defined ($vga) && $vga ne $new_vga) { + print "warning: conflicting vga display options\n"; + } + $vga = $new_vga; +} + +# Sets randomized timer interrupts. +sub set_jitter { + my ($new_jitter) = @_; + die "--realtime conflicts with --jitter\n" if defined $realtime; + die "different --jitter already defined\n" + if defined $jitter && $jitter != $new_jitter; + $jitter = $new_jitter; +} + +# Sets real-time timer interrupts. +sub set_realtime { + die "--realtime conflicts with --jitter\n" if defined $jitter; + $realtime = 1; +} + +# Sets load to run kernel test instead of user program. +# If user programs are disabled, pintos always runs a kernel test. +sub set_kernel_test { + $kernel_test = 1; +} + +# add_file(\@list, $file) +# +# Adds [$file] to @list, which should be @puts or @gets. +# Sets $as_ref to point to the added element. +sub add_file { + my ($list, $file) = @_; + $as_ref = [$file]; + push (@$list, $as_ref); +} + +# Sets the guest/host name for the previous put/get. +sub set_as { + my ($as) = @_; + die "-a (or --as) is only allowed after -p or -g\n" if !defined $as_ref; + die "Only one -a (or --as) is allowed after -p or -g\n" + if defined $as_ref->[1]; + $as_ref->[1] = $as; +} + +# Sets $disk as a disk to be included in the VM to run. +sub set_disk { + my ($disk) = @_; + + push (@disks, $disk); + + my (%pt) = read_partition_table ($disk); + for my $role (keys %pt) { + die "can't have two sources for \L$role\E partition" + if exists $parts{$role}; + $parts{$role}{DISK} = $disk; + $parts{$role}{START} = $pt{$role}{START}; + $parts{$role}{SECTORS} = $pt{$role}{SECTORS}; + } +} + +# Locates the files used to back each of the virtual disks, +# and creates temporary disks. +sub find_disks { + # Find kernel, if we don't already have one. + if (!exists $parts{KERNEL}) { + my $name = find_file ('kernel.bin'); + die "Cannot find kernel\n" if !defined $name; + do_set_part ('KERNEL', 'file', $name); + } + + # Try to find file system and swap disks, if we don't already have + # partitions. + if (!exists $parts{FILESYS}) { + my $name = find_file ('filesys.dsk'); + set_disk ($name) if defined $name; + } + if (!exists $parts{SWAP}) { + my $name = find_file ('swap.dsk'); + set_disk ($name) if defined $name; + } + + # Warn about (potentially) missing partitions. + if (my ($project) = `pwd` =~ /\b(threads|userprog|vm|filesys)\b/) { + if ((grep ($project eq $_, qw (userprog vm filesys))) + && !defined $parts{FILESYS}) { + print STDERR "warning: it looks like you're running the $project "; + print STDERR "project, but no file system partition is present\n"; + } + if ($project eq 'vm' && !defined $parts{SWAP}) { + print STDERR "warning: it looks like you're running the $project "; + print STDERR "project, but no swap partition is present\n"; + } + } + + # Open disk handle. + my ($handle); + if (!defined $make_disk) { + ($handle, $make_disk) = tempfile (UNLINK => $tmp_disk, + SUFFIX => '.dsk'); + } else { + die "$make_disk: already exists\n" if -e $make_disk; + open ($handle, '>', $make_disk) or die "$make_disk: create: $!\n"; + } + + # Prepare the arguments to pass to the Pintos kernel. + my (@args); + push (@args, '-kernel-test') if $kernel_test; + push (@args, shift (@kernel_args)) + while @kernel_args && $kernel_args[0] =~ /^-/; + push (@args, 'extract') if @puts; + push (@args, @kernel_args); + push (@args, 'append', $_->[0]) foreach @gets; + + # Make disk. + my (%disk); + our (@role_order); + for my $role (@role_order) { + my $p = $parts{$role}; + next if !defined $p; + next if exists $p->{DISK}; + $disk{$role} = $p; + } + $disk{DISK} = $make_disk; + $disk{HANDLE} = $handle; + $disk{ALIGN} = $align; + $disk{GEOMETRY} = %geometry; + $disk{FORMAT} = 'partitioned'; + $disk{LOADER} = read_loader ($loader_fn); + $disk{ARGS} = \@args; + assemble_disk (%disk); + + # Put the disk at the front of the list of disks. + unshift (@disks, $make_disk); + die "can't use more than " . scalar (@disks) . "disks\n" if @disks > 4; +} + +# Prepare the scratch disk for gets and puts. +sub prepare_scratch_disk { + return if !@gets && !@puts; + + my ($p) = $parts{SCRATCH}; + # Create temporary partition and write the files to put to it, + # then write an end-of-archive marker. + my ($part_handle, $part_fn) = tempfile (UNLINK => 1, SUFFIX => '.part'); + put_scratch_file ($_->[0], defined $_->[1] ? $_->[1] : $_->[0], + $part_handle, $part_fn) + foreach @puts; + write_fully ($part_handle, $part_fn, "\0" x 1024); + + # Make sure the scratch disk is big enough to get big files + # and at least as big as any requested size. + my ($size) = round_up (max (@gets * 1024 * 1024, $p->{BYTES} || 0), 512); + extend_file ($part_handle, $part_fn, $size); + close ($part_handle); + + if (exists $p->{DISK}) { + # Copy the scratch partition to the disk. + die "$p->{DISK}: scratch partition too small\n" + if $p->{SECTORS} * 512 < $size; + + my ($disk_handle); + open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n"; + open ($disk_handle, '+<', $p->{DISK}) or die "$p->{DISK}: open: $!\n"; + my ($start) = $p->{START} * 512; + sysseek ($disk_handle, $start, SEEK_SET) == $start + or die "$p->{DISK}: seek: $!\n"; + copy_file ($part_handle, $part_fn, $disk_handle, $p->{DISK}, $size); + close ($disk_handle) or die "$p->{DISK}: close: $!\n"; + close ($part_handle) or die "$part_fn: close: $!\n"; + } else { + # Set $part_fn as the source for the scratch partition. + do_set_part ('SCRATCH', 'file', $part_fn); + } +} + +# Read "get" files from the scratch disk. +sub finish_scratch_disk { + return if !@gets; + + # Open scratch partition. + my ($p) = $parts{SCRATCH}; + my ($part_handle); + my ($part_fn) = $p->{DISK}; + open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n"; + sysseek ($part_handle, $p->{START} * 512, SEEK_SET) == $p->{START} * 512 + or die "$part_fn: seek: $!\n"; + + # Read each file. + # If reading fails, delete that file and all subsequent files, but + # don't die with an error, because that's a guest error not a host + # error. (If we do exit with an error code, it fouls up the + # grading process.) Instead, just make sure that the host file(s) + # we were supposed to retrieve is unlinked. + my ($ok) = 1; + my ($part_end) = ($p->{START} + $p->{SECTORS}) * 512; + foreach my $get (@gets) { + my ($name) = defined ($get->[1]) ? $get->[1] : $get->[0]; + if ($ok) { + my ($error) = get_scratch_file ($name, $part_handle, $part_fn); + if (!$error && sysseek ($part_handle, 0, SEEK_CUR) > $part_end) { + $error = "$part_fn: scratch data overflows partition"; + } + if ($error) { + print STDERR "getting $name failed ($error)\n"; + $ok = 0; + } + } + die "$name: unlink: $!\n" if !$ok && !unlink ($name) && !$!{ENOENT}; + } +} + +# mk_ustar_field($number, $size) +# +# Returns $number in a $size-byte numeric field in the format used by +# the standard ustar archive header. +sub mk_ustar_field { + my ($number, $size) = @_; + my ($len) = $size - 1; + my ($out) = sprintf ("%0${len}o", $number) . "\0"; + die "$number: too large for $size-byte octal ustar field\n" + if length ($out) != $size; + return $out; +} + +# calc_ustar_chksum($s) +# +# Calculates and returns the ustar checksum of 512-byte ustar archive +# header $s. +sub calc_ustar_chksum { + my ($s) = @_; + die if length ($s) != 512; + substr ($s, 148, 8, ' ' x 8); + return unpack ("%32a*", $s); +} + +# put_scratch_file($src_file_name, $dst_file_name, +# $disk_handle, $disk_file_name). +# +# Copies $src_file_name into $disk_handle for extraction as +# $dst_file_name. $disk_file_name is used for error messages. +sub put_scratch_file { + my ($src_file_name, $dst_file_name, $disk_handle, $disk_file_name) = @_; + + print "Copying $src_file_name to scratch partition...\n"; + + # ustar format supports up to 100 characters for a file name, and + # even longer names given some common properties, but our code in + # the Pintos kernel only supports at most 99 characters. + die "$dst_file_name: name too long (max 99 characters)\n" + if length ($dst_file_name) > 99; + + # Compose and write ustar header. + stat $src_file_name or die "$src_file_name: stat: $!\n"; + my ($size) = -s _; + my ($header) = (pack ("a100", $dst_file_name) # name + . mk_ustar_field (0644, 8) # mode + . mk_ustar_field (0, 8) # uid + . mk_ustar_field (0, 8) # gid + . mk_ustar_field ($size, 12) # size + . mk_ustar_field (1136102400, 12) # mtime + . (' ' x 8) # chksum + . '0' # typeflag + . ("\0" x 100) # linkname + . "ustar\0" # magic + . "00" # version + . "root" . ("\0" x 28) # uname + . "root" . ("\0" x 28) # gname + . "\0" x 8 # devmajor + . "\0" x 8 # devminor + . ("\0" x 155)) # prefix + . "\0" x 12; # pad to 512 bytes + substr ($header, 148, 8) = mk_ustar_field (calc_ustar_chksum ($header), 8); + write_fully ($disk_handle, $disk_file_name, $header); + + # Copy file data. + my ($put_handle); + sysopen ($put_handle, $src_file_name, O_RDONLY) + or die "$src_file_name: open: $!\n"; + copy_file ($put_handle, $src_file_name, $disk_handle, $disk_file_name, + $size); + die "$src_file_name: changed size while being read\n" + if $size != -s $put_handle; + close ($put_handle); + + # Round up disk data to beginning of next sector. + write_fully ($disk_handle, $disk_file_name, "\0" x (512 - $size % 512)) + if $size % 512; +} + +# get_scratch_file($get_file_name, $disk_handle, $disk_file_name) +# +# Copies from $disk_handle to $get_file_name (which is created). +# $disk_file_name is used for error messages. +# Returns 1 if successful, 0 on failure. +sub get_scratch_file { + my ($get_file_name, $disk_handle, $disk_file_name) = @_; + + print "Copying $get_file_name out of $disk_file_name...\n"; + + # Read ustar header sector. + my ($header) = read_fully ($disk_handle, $disk_file_name, 512); + return "scratch disk tar archive ends unexpectedly" + if $header eq ("\0" x 512); + + # Verify magic numbers. + return "corrupt ustar signature" if substr ($header, 257, 6) ne "ustar\0"; + return "invalid ustar version" if substr ($header, 263, 2) ne '00'; + + # Verify checksum. + my ($chksum) = oct (unpack ("Z*", substr ($header, 148, 8))); + my ($correct_chksum) = calc_ustar_chksum ($header); + return "checksum mismatch" if $chksum != $correct_chksum; + + # Get type. + my ($typeflag) = substr ($header, 156, 1); + return "not a regular file" if $typeflag ne '0' && $typeflag ne "\0"; + + # Get size. + my ($size) = oct (unpack ("Z*", substr ($header, 124, 12))); + return "bad size $size\n" if $size < 0; + + # Copy file data. + my ($get_handle); + sysopen ($get_handle, $get_file_name, O_WRONLY | O_CREAT, 0666) + or die "$get_file_name: create: $!\n"; + copy_file ($disk_handle, $disk_file_name, $get_handle, $get_file_name, + $size); + close ($get_handle); + + # Skip forward in disk up to beginning of next sector. + read_fully ($disk_handle, $disk_file_name, 512 - $size % 512) + if $size % 512; + + return 0; +} + +# Running simulators. + +# Runs the selected simulator. +sub run_vm { + if ($sim eq 'bochs') { + run_bochs (); + } elsif ($sim eq 'qemu') { + run_qemu (); + } elsif ($sim eq 'player') { + run_player (); + } else { + die "unknown simulator `$sim'\n"; + } +} + +# Runs Bochs. +sub run_bochs { + # Select Bochs binary based on the chosen debugger. + my ($bin) = $debug eq 'monitor' ? 'bochs-dbg' : 'bochs'; + + my ($squish_pty); + if ($serial) { + $squish_pty = find_in_path ("squish-pty"); + print "warning: can't find squish-pty, so terminal input will fail\n" + if !defined $squish_pty; + } + + # Write bochsrc.txt configuration file. + open (BOCHSRC, ">", "bochsrc.txt") or die "bochsrc.txt: create: $!\n"; + print BOCHSRC < 2; + print_bochs_disk_line ("ata0-master", $disks[0]); + print_bochs_disk_line ("ata0-slave", $disks[1]); + print_bochs_disk_line ("ata1-master", $disks[2]); + print_bochs_disk_line ("ata1-slave", $disks[3]); + if ($vga ne 'terminal') { + if ($serial) { + my $mode = defined ($squish_pty) ? "term" : "file"; + print BOCHSRC "com1: enabled=1, mode=$mode, dev=/dev/stdout\n"; + } + print BOCHSRC "display_library: nogui\n" if $vga eq 'none'; + } else { + print BOCHSRC "display_library: term\n"; + } + close (BOCHSRC); + + # Compose Bochs command line. + my (@cmd) = ($bin, '-q'); + unshift (@cmd, $squish_pty) if defined $squish_pty; + push (@cmd, '-j', $jitter) if defined $jitter; + + # Run Bochs. + print join (' ', @cmd), "\n"; + my ($exit) = xsystem (@cmd); + if (WIFEXITED ($exit)) { + # Bochs exited normally. + # Ignore the exit code; Bochs normally exits with status 1, + # which is weird. + } elsif (WIFSIGNALED ($exit)) { + die "Bochs died with signal ", WTERMSIG ($exit), "\n"; + } else { + die "Bochs died: code $exit\n"; + } +} + +sub print_bochs_disk_line { + my ($device, $disk) = @_; + if (defined $disk) { + my (%geom) = disk_geometry ($disk); + print BOCHSRC "$device: type=disk, path=$disk, mode=flat, "; + print BOCHSRC "cylinders=$geom{C}, heads=$geom{H}, spt=$geom{S}, "; + print BOCHSRC "translation=none\n"; + } +} + +# Runs QEMU. +sub run_qemu { + print "warning: qemu doesn't support --terminal\n" + if $vga eq 'terminal'; + print "warning: qemu doesn't support jitter\n" + if defined $jitter; + my (@cmd) = ('qemu'); +# push (@cmd, '-no-kqemu'); + push (@cmd, '-hda', $disks[0]) if defined $disks[0]; + push (@cmd, '-hdb', $disks[1]) if defined $disks[1]; + push (@cmd, '-hdc', $disks[2]) if defined $disks[2]; + push (@cmd, '-hdd', $disks[3]) if defined $disks[3]; + push (@cmd, '-m', $mem); + push (@cmd, '-net', 'none'); + push (@cmd, '-nographic') if $vga eq 'none'; + push (@cmd, '-serial', 'stdio') if $serial && $vga ne 'none'; + push (@cmd, '-S') if $debug eq 'monitor'; + push (@cmd, '-s', '-S') if $debug eq 'gdb'; + push (@cmd, '-monitor', 'null') if $vga eq 'none' && $debug eq 'none'; + run_command (@cmd); +} + +# player_unsup($flag) +# +# Prints a message that $flag is unsupported by VMware Player. +sub player_unsup { + my ($flag) = @_; + print "warning: no support for $flag with VMware Player\n"; +} + +# Runs VMware Player. +sub run_player { + player_unsup ("--$debug") if $debug ne 'none'; + player_unsup ("--no-vga") if $vga eq 'none'; + player_unsup ("--terminal") if $vga eq 'terminal'; + player_unsup ("--jitter") if defined $jitter; + player_unsup ("--timeout"), undef $timeout if defined $timeout; + player_unsup ("--kill-on-failure"), undef $kill_on_failure + if defined $kill_on_failure; + + $mem = round_up ($mem, 4); # Memory must be multiple of 4 MB. + + open (VMX, ">", "pintos.vmx") or die "pintos.vmx: create: $!\n"; + chmod 0777 & ~umask, "pintos.vmx"; + print VMX <", $pln) or die "$pln: create: $!\n"; + print PLN < $size / 512, + C => $cylinders, + H => 16, + S => 63); +} + +# Subprocess utilities. + +# run_command(@args) +# +# Runs xsystem(@args). +# Also prints the command it's running and checks that it succeeded. +sub run_command { + print join (' ', @_), "\n"; + die "command failed\n" if xsystem (@_); +} + +# xsystem(@args) +# +# Creates a subprocess via exec(@args) and waits for it to complete. +# Relays common signals to the subprocess. +# If $timeout is set then the subprocess will be killed after that long. +sub xsystem { + # QEMU turns off local echo and does not restore it if killed by a signal. + # We compensate by restoring it ourselves. + my $cleanup = sub {}; + if (isatty (0)) { + my $termios = POSIX::Termios->new; + $termios->getattr (0); + $cleanup = sub { $termios->setattr (0, &POSIX::TCSANOW); } + } + + # Create pipe for filtering output. + pipe (my $in, my $out) or die "pipe: $!\n" if $kill_on_failure; + + my ($pid) = fork; + if (!defined ($pid)) { + # Fork failed. + die "fork: $!\n"; + } elsif (!$pid) { + # Running in child process. + dup2 (fileno ($out), STDOUT_FILENO) or die "dup2: $!\n" + if $kill_on_failure; + exec_setitimer (@_); + } else { + # Running in parent process. + close $out if $kill_on_failure; + + my ($cause); + local $SIG{ALRM} = sub { timeout ($pid, $cause, $cleanup); }; + local $SIG{INT} = sub { relay_signal ($pid, "INT", $cleanup); }; + local $SIG{TERM} = sub { relay_signal ($pid, "TERM", $cleanup); }; + alarm ($timeout * get_load_average () + 1) if defined ($timeout); + + if ($kill_on_failure) { + # Filter output. + my ($buf) = ""; + my ($boots) = 0; + local ($|) = 1; + for (;;) { + if (waitpid ($pid, WNOHANG) != 0) { + # Subprocess died. Pass through any remaining data. + print $buf while sysread ($in, $buf, 4096) > 0; + last; + } + + # Read and print out pipe data. + my ($len) = length ($buf); + waitpid ($pid, 0), last + if sysread ($in, $buf, 4096, $len) <= 0; + print substr ($buf, $len); + + # Remove full lines from $buf and scan them for keywords. + while ((my $idx = index ($buf, "\n")) >= 0) { + local $_ = substr ($buf, 0, $idx + 1, ''); + next if defined ($cause); + if (/(Kernel PANIC|User process ABORT)/ ) { + $cause = "\L$1\E"; + alarm (5); + } elsif (/Pintos booting/ && ++$boots > 1) { + $cause = "triple fault"; + alarm (5); + } elsif (/FAILED/) { + $cause = "test failure"; + alarm (5); + } + } + } + } else { + waitpid ($pid, 0); + } + alarm (0); + &$cleanup (); + + if (WIFSIGNALED ($?) && WTERMSIG ($?) == SIGVTALRM ()) { + seek (STDOUT, 0, 2); + print "\nTIMEOUT after $timeout seconds of host CPU time\n"; + exit 0; + } + + return $?; + } +} + +# relay_signal($pid, $signal, &$cleanup) +# +# Relays $signal to $pid and then reinvokes it for us with the default +# handler. Also cleans up temporary files and invokes $cleanup. +sub relay_signal { + my ($pid, $signal, $cleanup) = @_; + kill $signal, $pid; + eval { File::Temp::cleanup() }; # Not defined in old File::Temp. + &$cleanup (); + $SIG{$signal} = 'DEFAULT'; + kill $signal, getpid (); +} + +# timeout($pid, $cause, &$cleanup) +# +# Interrupts $pid and dies with a timeout error message, +# after invoking $cleanup. +sub timeout { + my ($pid, $cause, $cleanup) = @_; + kill "INT", $pid; + waitpid ($pid, 0); + &$cleanup (); + seek (STDOUT, 0, 2); + if (!defined ($cause)) { + my ($load_avg) = `uptime` =~ /(load average:.*)$/i; + print "\nTIMEOUT after ", time () - $start_time, + " seconds of wall-clock time"; + print " - $load_avg" if defined $load_avg; + print "\n"; + } else { + print "Simulation terminated due to $cause.\n"; + } + exit 0; +} + +# Returns the system load average over the last minute. +# If the load average is less than 1.0 or cannot be determined, returns 1.0. +sub get_load_average { + my ($avg) = `uptime` =~ /load average:\s*([^,]+),/; + return $avg >= 1.0 ? $avg : 1.0; +} + +# Calls setitimer to set a timeout, then execs what was passed to us. +sub exec_setitimer { + if (defined $timeout) { + if ($ ge 5.8.0) { + eval " + use Time::HiRes qw(setitimer ITIMER_VIRTUAL); + setitimer (ITIMER_VIRTUAL, $timeout, 0); + "; + } else { + { exec ("setitimer-helper", $timeout, @_); }; + exit 1 if !$!{ENOENT}; + print STDERR "warning: setitimer-helper is not installed, so ", + "CPU time limit will not be enforced\n"; + } + } + exec (@_); + exit (1); +} + +sub SIGVTALRM { + use Config; + my $i = 0; + foreach my $name (split(' ', $Config{sig_name})) { + return $i if $name eq 'VTALRM'; + $i++; + } + return 0; +} + +# find_in_path ($program) +# +# Searches for $program in $ENV{PATH}. +# Returns $program if found, otherwise undef. +sub find_in_path { + my ($program) = @_; + -x "$_/$program" and return $program foreach split (':', $ENV{PATH}); + return; +} diff --git a/pintos-progos/utils/pintos-gdb b/pintos-progos/utils/pintos-gdb new file mode 100755 index 0000000..9c9555b --- /dev/null +++ b/pintos-progos/utils/pintos-gdb @@ -0,0 +1,21 @@ +#! /bin/sh + +# Path to GDB macros file. Customize for your site. +PINTOS_SRC="$(dirname $(dirname $(which pintos-gdb)))" +GDBMACROS="${PINTOS_SRC}/misc/gdb-macros" + +# Choose correct GDB. +if command -v i386-elf-gdb >/dev/null 2>&1; then + GDB=i386-elf-gdb +else + GDB=gdb +fi + +# Run GDB. +if test -f "$GDBMACROS"; then + exec $GDB -x "$GDBMACROS" "$@" +else + echo "*** $GDBMACROS does not exist ***" + echo "*** Pintos GDB macros will not be available ***" + exec $GDB "$@" +fi diff --git a/pintos-progos/utils/pintos-mkdisk b/pintos-progos/utils/pintos-mkdisk new file mode 100755 index 0000000..87b1563 --- /dev/null +++ b/pintos-progos/utils/pintos-mkdisk @@ -0,0 +1,134 @@ +#! /usr/bin/perl + +use strict; +use warnings; +use POSIX; +use Getopt::Long qw(:config bundling); +use Fcntl 'SEEK_SET'; + +# Read Pintos.pm from the same directory as this program. +BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; } + +our ($disk_fn); # Output disk file name. +our (%parts); # Partitions. +our ($format); # "partitioned" (default) or "raw" +our (%geometry); # IDE disk geometry. +our ($align); # Align partitions on cylinders? +our ($loader_fn); # File name of loader. +our ($include_loader); # Include loader? +our (@kernel_args); # Kernel arguments. + +if (grep ($_ eq '--', @ARGV)) { + @kernel_args = @ARGV; + @ARGV = (); + while ((my $arg = shift (@kernel_args)) ne '--') { + push (@ARGV, $arg); + } +} + +GetOptions ("h|help" => sub { usage (0); }, + + "kernel=s" => \&set_part, + "filesys=s" => \&set_part, + "scratch=s" => \&set_part, + "swap=s" => \&set_part, + + "filesys-size=s" => \&set_part, + "scratch-size=s" => \&set_part, + "swap-size=s" => \&set_part, + + "kernel-from=s" => \&set_part, + "filesys-from=s" => \&set_part, + "scratch-from=s" => \&set_part, + "swap-from=s" => \&set_part, + + "format=s" => \$format, + "loader:s" => \&set_loader, + "no-loader" => \&set_no_loader, + "geometry=s" => \&set_geometry, + "align=s" => \&set_align) + or exit 1; +usage (1) if @ARGV != 1; + +$disk_fn = $ARGV[0]; +die "$disk_fn: already exists\n" if -e $disk_fn; + +# Sets the loader to copy to the MBR. +sub set_loader { + die "can't specify both --loader and --no-loader\n" + if defined ($include_loader) && !$include_loader; + $include_loader = 1; + $loader_fn = $_[1] if $_[1] ne ''; +} + +# Disables copying a loader to the MBR. +sub set_no_loader { + die "can't specify both --loader and --no-loader\n" + if defined ($include_loader) && $include_loader; + $include_loader = 0; +} + +# Figure out whether to include a loader. +$include_loader = exists ($parts{KERNEL}) && $format eq 'partitioned' + if !defined ($include_loader); +die "can't write loader to raw disk\n" if $include_loader && $format eq 'raw'; +die "can't write command-line arguments without --loader or --kernel\n" + if @kernel_args && !$include_loader; +print STDERR "warning: --loader only makes sense without --kernel " + . "if this disk will be used to load a kernel from another disk\n" + if $include_loader && !exists ($parts{KERNEL}); + +# Open disk. +my ($disk_handle); +open ($disk_handle, '>', $disk_fn) or die "$disk_fn: create: $!\n"; + +# Read loader. +my ($loader); +$loader = read_loader ($loader_fn) if $include_loader; + +# Write disk. +my (%disk) = %parts; +$disk{DISK} = $disk_fn; +$disk{HANDLE} = $disk_handle; +$disk{ALIGN} = $align; +$disk{GEOMETRY} = %geometry; +$disk{FORMAT} = $format; +$disk{LOADER} = $loader; +$disk{ARGS} = \@kernel_args; +assemble_disk (%disk); + +# Done. +exit 0; + +sub usage { + print <<'EOF'; +pintos-mkdisk, a utility for creating Pintos virtual disks +Usage: pintos-mkdisk [OPTIONS] DISK [-- ARGUMENT...] +where DISK is the virtual disk to create, + each ARGUMENT is inserted into the command line written to DISK, + and each OPTION is one of the following options. +Partition options: (where PARTITION is one of: kernel filesys scratch swap) + --PARTITION=FILE Use a copy of FILE for the given PARTITION + --PARTITION-size=SIZE Create an empty PARTITION of the given SIZE in MB + --PARTITION-from=DISK Use of a copy of the given PARTITION in DISK + (There is no --kernel-size option.) +Output disk options: + --format=partitioned Write partition table to output (default) + --format=raw Do not write partition table to output + (Pintos can only use partitioned disks.) +Partitioned format output options: + --loader[=FILE] Get bootstrap loader from FILE (default: loader.bin + if --kernel option is specified, empty otherwise) + --no-loader Do not include a bootstrap loader + --geometry=H,S Use H head, S sector geometry (default: 16, 63) + --geometry=zip Use 64 head, 32 sector geometry for USB-ZIP boot + per http://syslinux.zytor.com/usbkey.php + --align=bochs Round size to cylinder for Bochs support (default) + --align=full Align partition boundaries to cylinder boundary to + let fdisk guess correct geometry and quiet warnings + --align=none Don't align partitions at all, to save space +Other options: + -h, --help Display this help message. +EOF + exit ($_[0]); +} diff --git a/pintos-progos/utils/pintos-set-cmdline b/pintos-progos/utils/pintos-set-cmdline new file mode 100644 index 0000000..8c8f702 --- /dev/null +++ b/pintos-progos/utils/pintos-set-cmdline @@ -0,0 +1,42 @@ +#! /usr/bin/perl -w + +use strict; +use Fcntl 'SEEK_SET'; + +# Read Pintos.pm from the same directory as this program. +BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; } + +# Get command-line arguments. +usage (0) if @ARGV == 1 && $ARGV[0] eq '--help'; +usage (1) if @ARGV < 2 || $ARGV[1] ne '--'; +my ($disk, undef, @kernel_args) = @ARGV; + +# Open disk. +my ($handle); +open ($handle, '+<', $disk) or die "$disk: open: $!\n"; + +# Check that it's a partitioned disk with a Pintos loader. +my ($buffer) = read_fully ($handle, $disk, 512); +unpack ("x510 v", $buffer) == 0xaa55 or die "$disk: not a partitioned disk\n"; +$buffer =~ /Pintos/ or die "$disk: does not contain Pintos loader\n"; + +# Write the command line. +our ($LOADER_SIZE); +sysseek ($handle, $LOADER_SIZE, SEEK_SET) == $LOADER_SIZE + or die "$disk: seek: $!\n"; +write_fully ($handle, $disk, make_kernel_command_line (@kernel_args)); + +# Close disk. +close ($handle) or die "$disk: close: $!\n"; + +exit 0; + +sub usage { + print <<'EOF'; +pintos-set-cmdline, a utility for changing the command line in Pintos disks +Usage: pintos-set-cmdline DISK -- [ARGUMENT...] +where DISK is a bootable disk containing a Pintos loader + and each ARGUMENT is inserted into the command line written to DISK. +EOF + exit ($_[0]); +} diff --git a/pintos-progos/utils/setitimer-helper.c b/pintos-progos/utils/setitimer-helper.c new file mode 100644 index 0000000..772d736 --- /dev/null +++ b/pintos-progos/utils/setitimer-helper.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int +main (int argc, char *argv[]) +{ + const char *program_name = argv[0]; + double timeout; + + if (argc < 3) + { + fprintf (stderr, + "setitimer-helper: runs a program with a virtual CPU limit\n" + "usage: %s TIMEOUT PROGRAM [ARG...]\n" + " where TIMEOUT is the virtual CPU limit, in seconds,\n" + " and remaining arguments specify the program to run\n" + " and its argument.\n", + program_name); + return EXIT_FAILURE; + } + + timeout = strtod (argv[1], NULL); + if (timeout >= 0.0 && timeout < LONG_MAX) + { + struct itimerval it; + + it.it_interval.tv_sec = 0; + it.it_interval.tv_usec = 0; + it.it_value.tv_sec = timeout; + it.it_value.tv_usec = (timeout - floor (timeout)) * 1000000; + if (setitimer (ITIMER_VIRTUAL, &it, NULL) < 0) + fprintf (stderr, "%s: setitimer: %s\n", + program_name, strerror (errno)); + } + else + fprintf (stderr, "%s: invalid timeout value \"%s\"\n", + program_name, argv[1]); + + execvp (argv[2], &argv[2]); + fprintf (stderr, "%s: couldn't exec \"%s\": %s\n", + program_name, argv[2], strerror (errno)); + return EXIT_FAILURE; +} diff --git a/pintos-progos/utils/squish-pty.c b/pintos-progos/utils/squish-pty.c new file mode 100644 index 0000000..c8375a5 --- /dev/null +++ b/pintos-progos/utils/squish-pty.c @@ -0,0 +1,355 @@ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void +fail_io (const char *msg, ...) + __attribute__ ((noreturn)) + __attribute__ ((format (printf, 1, 2))); + +/* Prints MSG, formatting as with printf(), + plus an error message based on errno, + and exits. */ +static void +fail_io (const char *msg, ...) +{ + va_list args; + + va_start (args, msg); + vfprintf (stderr, msg, args); + va_end (args); + + if (errno != 0) + fprintf (stderr, ": %s", strerror (errno)); + putc ('\n', stderr); + exit (EXIT_FAILURE); +} + +/* If FD is a terminal, configures it for noncanonical input mode + with VMIN and VTIME set as indicated. + If FD is not a terminal, has no effect. */ +static void +make_noncanon (int fd, int vmin, int vtime) +{ + if (isatty (fd)) + { + struct termios termios; + if (tcgetattr (fd, &termios) < 0) + fail_io ("tcgetattr"); + termios.c_lflag &= ~(ICANON | ECHO); + termios.c_cc[VMIN] = vmin; + termios.c_cc[VTIME] = vtime; + if (tcsetattr (fd, TCSANOW, &termios) < 0) + fail_io ("tcsetattr"); + } +} + +/* Make FD non-blocking if NONBLOCKING is true, + or blocking if NONBLOCKING is false. */ +static void +make_nonblocking (int fd, bool nonblocking) +{ + int flags = fcntl (fd, F_GETFL); + if (flags < 0) + fail_io ("fcntl"); + if (nonblocking) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + if (fcntl (fd, F_SETFL, flags) < 0) + fail_io ("fcntl"); +} + +/* Handle a read or write on *FD, which is the pty if FD_IS_PTY + is true, that returned end-of-file or error indication RETVAL. + The system call is named CALL, for use in error messages. + Returns true if processing may continue, false if we're all + done. */ +static bool +handle_error (ssize_t retval, int *fd, bool fd_is_pty, const char *call) +{ + if (fd_is_pty) + { + if (retval < 0) + { + if (errno == EIO) + { + /* Slave side of pty has been closed. */ + return false; + } + else + fail_io (call); + } + else + return true; + } + else + { + if (retval == 0) + { + close (*fd); + *fd = -1; + return true; + } + else + fail_io (call); + } +} + +/* Copies data from stdin to PTY and from PTY to stdout until no + more data can be read or written. */ +static void +relay (int pty, int dead_child_fd) +{ + struct pipe + { + int in, out; + char buf[BUFSIZ]; + size_t size, ofs; + bool active; + }; + struct pipe pipes[2]; + + /* Make PTY, stdin, and stdout non-blocking. */ + make_nonblocking (pty, true); + make_nonblocking (STDIN_FILENO, true); + make_nonblocking (STDOUT_FILENO, true); + + /* Configure noncanonical mode on PTY and stdin to avoid + waiting for end-of-line. We want to minimize context + switching on PTY (for efficiency) and minimize latency on + stdin to avoid a laggy user experience. */ + make_noncanon (pty, 16, 1); + make_noncanon (STDIN_FILENO, 1, 0); + + memset (pipes, 0, sizeof pipes); + pipes[0].in = STDIN_FILENO; + pipes[0].out = pty; + pipes[1].in = pty; + pipes[1].out = STDOUT_FILENO; + + while (pipes[0].in != -1 || pipes[1].in != -1) + { + fd_set read_fds, write_fds; + int retval; + int i; + + FD_ZERO (&read_fds); + FD_ZERO (&write_fds); + for (i = 0; i < 2; i++) + { + struct pipe *p = &pipes[i]; + + /* Don't do anything with the stdin->pty pipe until we + have some data for the pty->stdout pipe. If we get + too eager, Bochs will throw away our input. */ + if (i == 0 && !pipes[1].active) + continue; + + if (p->in != -1 && p->size + p->ofs < sizeof p->buf) + FD_SET (p->in, &read_fds); + if (p->out != -1 && p->size > 0) + FD_SET (p->out, &write_fds); + } + FD_SET (dead_child_fd, &read_fds); + + do + { + retval = select (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL); + } + while (retval < 0 && errno == EINTR); + if (retval < 0) + fail_io ("select"); + + if (FD_ISSET (dead_child_fd, &read_fds)) + { + /* Child died. Do final relaying. */ + struct pipe *p = &pipes[1]; + if (p->out == -1) + return; + make_nonblocking (STDOUT_FILENO, false); + for (;;) + { + ssize_t n; + + /* Write buffer. */ + while (p->size > 0) + { + n = write (p->out, p->buf + p->ofs, p->size); + if (n < 0) + fail_io ("write"); + else if (n == 0) + fail_io ("zero-length write"); + p->ofs += n; + p->size -= n; + } + p->ofs = 0; + + p->size = n = read (p->in, p->buf, sizeof p->buf); + if (n <= 0) + return; + } + } + + for (i = 0; i < 2; i++) + { + struct pipe *p = &pipes[i]; + if (p->in != -1 && FD_ISSET (p->in, &read_fds)) + { + ssize_t n = read (p->in, p->buf + p->ofs + p->size, + sizeof p->buf - p->ofs - p->size); + if (n > 0) + { + p->active = true; + p->size += n; + if (p->size == BUFSIZ && p->ofs != 0) + { + memmove (p->buf, p->buf + p->ofs, p->size); + p->ofs = 0; + } + } + else if (!handle_error (n, &p->in, p->in == pty, "read")) + return; + } + if (p->out != -1 && FD_ISSET (p->out, &write_fds)) + { + ssize_t n = write (p->out, p->buf + p->ofs, p->size); + if (n > 0) + { + p->ofs += n; + p->size -= n; + if (p->size == 0) + p->ofs = 0; + } + else if (!handle_error (n, &p->out, p->out == pty, "write")) + return; + } + } + } +} + +static int dead_child_fd; + +static void +sigchld_handler (int signo __attribute__ ((unused))) +{ + if (write (dead_child_fd, "", 1) < 0) + _exit (1); +} + +int +main (int argc __attribute__ ((unused)), char *argv[]) +{ + int master, slave; + char *name; + pid_t pid; + struct sigaction sa; + int pipe_fds[2]; + struct itimerval zero_itimerval, old_itimerval; + + if (argc < 2) + { + fprintf (stderr, + "usage: squish-pty COMMAND [ARG]...\n" + "Squishes both stdin and stdout into a single pseudoterminal,\n" + "which is passed as stdout to run the specified COMMAND.\n"); + return EXIT_FAILURE; + } + + /* Open master side of pty and get ready to open slave. */ + master = open ("/dev/ptmx", O_RDWR | O_NOCTTY); + if (master < 0) + fail_io ("open \"/dev/ptmx\""); + if (grantpt (master) < 0) + fail_io ("grantpt"); + if (unlockpt (master) < 0) + fail_io ("unlockpt"); + + /* Open slave side of pty. */ + name = ptsname (master); + if (name == NULL) + fail_io ("ptsname"); + slave = open (name, O_RDWR); + if (slave < 0) + fail_io ("open \"%s\"", name); + + /* System V implementations need STREAMS configuration for the + slave. */ + if (isastream (slave)) + { + if (ioctl (slave, I_PUSH, "ptem") < 0 + || ioctl (slave, I_PUSH, "ldterm") < 0) + fail_io ("ioctl"); + } + + /* Arrange to get notified when a child dies, by writing a byte + to a pipe fd. We really want to use pselect() and + sigprocmask(), but Solaris 2.7 doesn't have it. */ + if (pipe (pipe_fds) < 0) + fail_io ("pipe"); + dead_child_fd = pipe_fds[1]; + + memset (&sa, 0, sizeof sa); + sa.sa_handler = sigchld_handler; + sigemptyset (&sa.sa_mask); + sa.sa_flags = SA_RESTART; + if (sigaction (SIGCHLD, &sa, NULL) < 0) + fail_io ("sigaction"); + + /* Save the virtual interval timer, which might have been set + by the process that ran us. It really should be applied to + our child process. */ + memset (&zero_itimerval, 0, sizeof zero_itimerval); + if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, &old_itimerval) < 0) + fail_io ("setitimer"); + + pid = fork (); + if (pid < 0) + fail_io ("fork"); + else if (pid != 0) + { + /* Running in parent process. */ + int status; + close (slave); + relay (master, pipe_fds[0]); + + /* If the subprocess has died, die in the same fashion. + In particular, dying from SIGVTALRM tells the pintos + script that we ran out of CPU time. */ + if (waitpid (pid, &status, WNOHANG) > 0) + { + if (WIFEXITED (status)) + return WEXITSTATUS (status); + else if (WIFSIGNALED (status)) + raise (WTERMSIG (status)); + } + return 0; + } + else + { + /* Running in child process. */ + if (setitimer (ITIMER_VIRTUAL, &old_itimerval, NULL) < 0) + fail_io ("setitimer"); + if (dup2 (slave, STDOUT_FILENO) < 0) + fail_io ("dup2"); + if (close (pipe_fds[0]) < 0 || close (pipe_fds[1]) < 0 + || close (slave) < 0 || close (master) < 0) + fail_io ("close"); + execvp (argv[1], argv + 1); + fail_io ("exec"); + } +} diff --git a/pintos-progos/utils/squish-unix.c b/pintos-progos/utils/squish-unix.c new file mode 100644 index 0000000..805205b --- /dev/null +++ b/pintos-progos/utils/squish-unix.c @@ -0,0 +1,338 @@ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void +fail_io (const char *msg, ...) + __attribute__ ((noreturn)) + __attribute__ ((format (printf, 1, 2))); + +/* Prints MSG, formatting as with printf(), + plus an error message based on errno, + and exits. */ +static void +fail_io (const char *msg, ...) +{ + va_list args; + + va_start (args, msg); + vfprintf (stderr, msg, args); + va_end (args); + + if (errno != 0) + fprintf (stderr, ": %s", strerror (errno)); + putc ('\n', stderr); + exit (EXIT_FAILURE); +} + +/* If FD is a terminal, configures it for noncanonical input mode + with VMIN and VTIME set as indicated. + If FD is not a terminal, has no effect. */ +static void +make_noncanon (int fd, int vmin, int vtime) +{ + if (isatty (fd)) + { + struct termios termios; + if (tcgetattr (fd, &termios) < 0) + fail_io ("tcgetattr"); + termios.c_lflag &= ~(ICANON | ECHO); + termios.c_cc[VMIN] = vmin; + termios.c_cc[VTIME] = vtime; + if (tcsetattr (fd, TCSANOW, &termios) < 0) + fail_io ("tcsetattr"); + } +} + +/* Make FD non-blocking if NONBLOCKING is true, + or blocking if NONBLOCKING is false. */ +static void +make_nonblocking (int fd, bool nonblocking) +{ + int flags = fcntl (fd, F_GETFL); + if (flags < 0) + fail_io ("fcntl"); + if (nonblocking) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + if (fcntl (fd, F_SETFL, flags) < 0) + fail_io ("fcntl"); +} + +/* Handle a read or write on *FD, which is the socket if + FD_IS_SOCK is true, that returned end-of-file or error + indication RETVAL. The system call is named CALL, for use in + error messages. Returns true if processing may continue, + false if we're all done. */ +static bool +handle_error (ssize_t retval, int *fd, bool fd_is_sock, const char *call) +{ + if (retval == 0) + { + if (fd_is_sock) + return false; + else + { + *fd = -1; + return true; + } + } + else + fail_io (call); +} + +/* Copies data from stdin to SOCK and from SOCK to stdout until no + more data can be read or written. */ +static void +relay (int sock) +{ + struct pipe + { + int in, out; + char buf[BUFSIZ]; + size_t size, ofs; + bool active; + }; + struct pipe pipes[2]; + + /* In case stdin is a file, go back to the beginning. + This allows replaying the input on reset. */ + lseek (STDIN_FILENO, 0, SEEK_SET); + + /* Make SOCK, stdin, and stdout non-blocking. */ + make_nonblocking (sock, true); + make_nonblocking (STDIN_FILENO, true); + make_nonblocking (STDOUT_FILENO, true); + + /* Configure noncanonical mode on stdin to avoid waiting for + end-of-line. */ + make_noncanon (STDIN_FILENO, 1, 0); + + memset (pipes, 0, sizeof pipes); + pipes[0].in = STDIN_FILENO; + pipes[0].out = sock; + pipes[1].in = sock; + pipes[1].out = STDOUT_FILENO; + + while (pipes[0].in != -1 || pipes[1].in != -1 + || (pipes[1].size && pipes[1].out != -1)) + { + fd_set read_fds, write_fds; + sigset_t empty_set; + int retval; + int i; + + FD_ZERO (&read_fds); + FD_ZERO (&write_fds); + for (i = 0; i < 2; i++) + { + struct pipe *p = &pipes[i]; + + /* Don't do anything with the stdin->sock pipe until we + have some data for the sock->stdout pipe. If we get + too eager, vmplayer will throw away our input. */ + if (i == 0 && !pipes[1].active) + continue; + + if (p->in != -1 && p->size + p->ofs < sizeof p->buf) + FD_SET (p->in, &read_fds); + if (p->out != -1 && p->size > 0) + FD_SET (p->out, &write_fds); + } + sigemptyset (&empty_set); + retval = pselect (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL, + &empty_set); + if (retval < 0) + { + if (errno == EINTR) + { + /* Child died. Do final relaying. */ + struct pipe *p = &pipes[1]; + if (p->out == -1) + exit (0); + make_nonblocking (STDOUT_FILENO, false); + for (;;) + { + ssize_t n; + + /* Write buffer. */ + while (p->size > 0) + { + n = write (p->out, p->buf + p->ofs, p->size); + if (n < 0) + fail_io ("write"); + else if (n == 0) + fail_io ("zero-length write"); + p->ofs += n; + p->size -= n; + } + p->ofs = 0; + + p->size = n = read (p->in, p->buf, sizeof p->buf); + if (n <= 0) + exit (0); + } + } + fail_io ("select"); + } + + for (i = 0; i < 2; i++) + { + struct pipe *p = &pipes[i]; + if (p->in != -1 && FD_ISSET (p->in, &read_fds)) + { + ssize_t n = read (p->in, p->buf + p->ofs + p->size, + sizeof p->buf - p->ofs - p->size); + if (n > 0) + { + p->active = true; + p->size += n; + if (p->size == BUFSIZ && p->ofs != 0) + { + memmove (p->buf, p->buf + p->ofs, p->size); + p->ofs = 0; + } + } + else if (!handle_error (n, &p->in, p->in == sock, "read")) + return; + } + if (p->out != -1 && FD_ISSET (p->out, &write_fds)) + { + ssize_t n = write (p->out, p->buf + p->ofs, p->size); + if (n > 0) + { + p->ofs += n; + p->size -= n; + if (p->size == 0) + p->ofs = 0; + } + else if (!handle_error (n, &p->out, p->out == sock, "write")) + return; + } + } + } +} + +static void +sigchld_handler (int signo __attribute__ ((unused))) +{ + /* Nothing to do. */ +} + +int +main (int argc __attribute__ ((unused)), char *argv[]) +{ + pid_t pid; + struct itimerval zero_itimerval; + struct sockaddr_un sun; + sigset_t sigchld_set; + int sock; + + if (argc < 3) + { + fprintf (stderr, + "usage: squish-unix SOCKET COMMAND [ARG]...\n" + "Squishes both stdin and stdout into a single Unix domain\n" + "socket named SOCKET, and runs COMMAND as a subprocess.\n"); + return EXIT_FAILURE; + } + + /* Create socket. */ + sock = socket (PF_LOCAL, SOCK_STREAM, 0); + if (sock < 0) + fail_io ("socket"); + + /* Configure socket. */ + sun.sun_family = AF_LOCAL; + strncpy (sun.sun_path, argv[1], sizeof sun.sun_path); + sun.sun_path[sizeof sun.sun_path - 1] = '\0'; + if (unlink (sun.sun_path) < 0 && errno != ENOENT) + fail_io ("unlink"); + if (bind (sock, (struct sockaddr *) &sun, + (offsetof (struct sockaddr_un, sun_path) + + strlen (sun.sun_path) + 1)) < 0) + fail_io ("bind"); + + /* Listen on socket. */ + if (listen (sock, 1) < 0) + fail_io ("listen"); + + /* Block SIGCHLD and set up a handler for it. */ + sigemptyset (&sigchld_set); + sigaddset (&sigchld_set, SIGCHLD); + if (sigprocmask (SIG_BLOCK, &sigchld_set, NULL) < 0) + fail_io ("sigprocmask"); + if (signal (SIGCHLD, sigchld_handler) == SIG_ERR) + fail_io ("signal"); + + /* Save the virtual interval timer, which might have been set + by the process that ran us. It really should be applied to + our child process. */ + memset (&zero_itimerval, 0, sizeof zero_itimerval); + if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, NULL) < 0) + fail_io ("setitimer"); + + pid = fork (); + if (pid < 0) + fail_io ("fork"); + else if (pid != 0) + { + /* Running in parent process. */ + make_nonblocking (sock, true); + for (;;) + { + fd_set read_fds; + sigset_t empty_set; + int retval; + int conn; + + /* Wait for connection. */ + FD_ZERO (&read_fds); + FD_SET (sock, &read_fds); + sigemptyset (&empty_set); + retval = pselect (sock + 1, &read_fds, NULL, NULL, NULL, &empty_set); + if (retval < 0) + { + if (errno == EINTR) + break; + fail_io ("select"); + } + + /* Accept connection. */ + conn = accept (sock, NULL, NULL); + if (conn < 0) + fail_io ("accept"); + + /* Relay connection. */ + relay (conn); + close (conn); + } + return 0; + } + else + { + /* Running in child process. */ + if (close (sock) < 0) + fail_io ("close"); + execvp (argv[2], argv + 2); + fail_io ("exec"); + } +} diff --git a/pintos-progos/vm/.gitignore b/pintos-progos/vm/.gitignore new file mode 100644 index 0000000..6d5357c --- /dev/null +++ b/pintos-progos/vm/.gitignore @@ -0,0 +1,3 @@ +build +bochsrc.txt +bochsout.txt diff --git a/pintos-progos/vm/Make.vars b/pintos-progos/vm/Make.vars new file mode 100644 index 0000000..e3c33a7 --- /dev/null +++ b/pintos-progos/vm/Make.vars @@ -0,0 +1,7 @@ +# -*- makefile -*- + +kernel.bin: DEFINES = -DUSERPROG -DFILESYS -DVM +KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys vm +TEST_SUBDIRS = tests/userprog tests/vm tests/filesys/base +GRADING_FILE = $(SRCDIR)/tests/vm/Grading +SIMULATOR = --qemu diff --git a/pintos-progos/vm/Makefile b/pintos-progos/vm/Makefile new file mode 100644 index 0000000..34c10aa --- /dev/null +++ b/pintos-progos/vm/Makefile @@ -0,0 +1 @@ +include ../Makefile.kernel diff --git a/proj0.txt b/proj0.txt new file mode 100644 index 0000000..d77cdb9 --- /dev/null +++ b/proj0.txt @@ -0,0 +1,106 @@ + +--------------------+ + | OS Development | + | PROJECT 0: INTRO | + | DESIGN DOCUMENT | + +--------------------+ + +---- GROUP ---- + +>> Fill in the names and email addresses of your group members. + +FirstName LastName +FirstName LastName +FirstName LastName + +---- PRELIMINARIES ---- + +>> If you have any preliminary comments on your submission, notes for the +>> TAs, or extra credit, please give them here. + +>> Please cite any offline or online sources you consulted while +>> preparing your submission, other than the Pintos documentation, course +>> text, lecture notes, and course staff. + + ALARM CLOCK + =========== + +---- DATA STRUCTURES ---- + +>> A1: Copy here the declaration of each new or changed `struct' or +>> `struct' member, global or static variable, `typedef', or +>> enumeration. Identify the purpose of each in 25 words or less. + +---- ALGORITHMS ---- + +>> A2: Briefly describe what happens in a call to timer_sleep(), +>> including the effects of the timer interrupt handler. + +>> A3: What steps are taken to minimize the amount of time spent in +>> the timer interrupt handler? + +---- SYNCHRONIZATION ---- + +>> A4: How are race conditions avoided when multiple threads call +>> timer_sleep() simultaneously? + +>> A5: How are race conditions avoided when a timer interrupt occurs +>> during a call to timer_sleep()? + +---- RATIONALE ---- + +>> A6: Why did you choose this design? In what ways is it superior to +>> another design you considered? + + ARGUMENT PASSING + ================ + +---- DATA STRUCTURES ---- + +>> A1: Copy here the declaration of each new or changed `struct' or +>> `struct' member, global or static variable, `typedef', or +>> enumeration. Identify the purpose of each in 25 words or less. + +---- ALGORITHMS ---- + +>> A2: Briefly describe how you implemented argument parsing. How do +>> you arrange for the elements of argv[] to be in the right order? +>> How do you avoid overflowing the stack page? + +---- RATIONALE ---- + +>> A3: Why does Pintos implement strtok_r() but not strtok()? + +strtok() uses global data, so it is unsafe in threaded programs such as kernels. + +>> A4: In Pintos, the kernel separates commands into a executable name +>> and arguments. In Unix-like systems, the shell does this +>> separation. Identify at least two advantages of the Unix approach. + + + + SURVEY QUESTIONS + ================ + +Answering these questions is optional, but it will help us improve the +course in future quarters. Feel free to tell us anything you +want--these questions are just to spur your thoughts. You may also +choose to respond anonymously in the course evaluations at the end of +the quarter. + +>> In your opinion, was this assignment, or any one of the three problems +>> in it, too easy or too hard? Did it take too long or too little time? + +>> Did you find that working on a particular part of the assignment gave +>> you greater insight into some aspect of OS design? + +>> Is there some particular fact or hint we should give students in +>> future quarters to help them solve the problems? Conversely, did you +>> find any of our guidance to be misleading? + +>> Do you have any suggestions for the TAs to more effectively assist +>> students, either for future quarters or the remaining projects? + +>> Any other comments? + + + -- cgit v1.2.3