From 4ebf33908a571a7cde93fe618902b044e3633cdf Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sun, 19 Jun 2005 03:20:25 +0000 Subject: [PATCH] Make tests public. Rewrite most tests. Add tests. Major revisions to documentation and assignments. Implement good solutions to all assignments. Rewrite pintos script, fsutils, and Pintos command line parsing to support put, get of multiple files. New pintos-mkdisk script for what pintos doesn't do anymore. Make backtrace more friendly. Major revisions to base file system. Get rid of names passed to synch primitives. Break intr_register() into intr_register_ext() and intr_register_int(). Revise bitmap code. Add console_locked_by_current_thread() and use it. munmap() has void return value (in lib/user/syscall.c). Update expected tools versions. Document use of qemu. Add realloc() function to malloc implementation. Add is_user_vaddr() and is_kernel_vaddr(), use in mmu.h. Add sema_try_down(), lock_try_acquire(). Add memory barrier. Extend default time slice to 4 ticks and calculate time slices properly (don't just switch whenever timer_ticks() % 4 == 0). Fix writing to palloc'd memory without checking against null in pagedir_create(). Invalidate TLB when we mark pages not accessed or not dirty. Revise other pagedir code. Make load() less nasty-looking. --- AUTHORS | 19 +- LICENSE | 61 +- Makefile | 6 +- TODO | 72 +- doc/44bsd.texi | 347 ++ doc/Makefile | 16 +- doc/debug.texi | 117 +- doc/devel.texi | 10 +- doc/doc.texi | 181 +- doc/filesys.texi | 566 +-- doc/filesys.tmpl | 145 + doc/intro.texi | 480 +- doc/mlfqs.texi | 401 -- doc/pintos.css | 14 +- doc/pintos.texi | 5 +- doc/references.texi | 11 +- doc/sample.tmpl | 103 + doc/standards.texi | 73 +- doc/texi2html | 25 +- doc/threads.texi | 879 ++-- doc/threads.tmpl | 157 + doc/tour.texi | 832 ++-- doc/userprog.texi | 1285 +++--- doc/userprog.tmpl | 134 + doc/vm.texi | 766 ++-- doc/vm.tmpl | 148 + grading/Makefile | 11 - grading/filesys/.cvsignore | 48 - grading/filesys/Make.progs | 59 - grading/filesys/Makefile | 9 - grading/filesys/dir-empty-name.exp | 3 - grading/filesys/dir-mk-tree.c | 14 - grading/filesys/dir-rm-root.exp | 4 - grading/filesys/fsmain.c | 12 - grading/filesys/grow-create.c | 3 - grading/filesys/grow-create.exp | 5 - grading/filesys/grow-dir-lg.c | 4 - grading/filesys/grow-root-lg.c | 3 - grading/filesys/grow-root-sm.c | 3 - grading/filesys/grow-seq-lg.c | 3 - grading/filesys/grow-seq-sm.c | 3 - grading/filesys/grow-too-big.exp | 7 - grading/filesys/lg-create.c | 3 - grading/filesys/lg-create.exp | 5 - grading/filesys/lg-full.c | 3 - grading/filesys/lg-random.c | 4 - grading/filesys/lg-seq-block.c | 4 - grading/filesys/lg-seq-random.c | 3 - grading/filesys/lib/.cvsignore | 1 - grading/filesys/lib/user/.cvsignore | 1 - grading/filesys/main.c | 10 - grading/filesys/mk-tree.h | 6 - grading/filesys/review.txt | 61 - grading/filesys/run-tests | 129 - grading/filesys/sm-create.c | 3 - grading/filesys/sm-create.exp | 5 - grading/filesys/sm-full.c | 3 - grading/filesys/sm-full.exp | 8 - grading/filesys/sm-random.c | 4 - grading/filesys/sm-random.exp | 7 - grading/filesys/sm-seq-block.c | 4 - grading/filesys/sm-seq-block.exp | 8 - grading/filesys/sm-seq-random.c | 3 - grading/filesys/sm-seq-random.exp | 8 - grading/filesys/syn-read.h | 2 - grading/filesys/tests.txt | 57 - grading/lib/.cvsignore | 1 - grading/lib/Algorithm/Diff.pm | 1713 ------- grading/lib/Pintos/Grading.pm | 851 ---- grading/lib/cksum.h | 8 - grading/threads/alarm-multiple.c | 154 - grading/threads/mlfqs.c | 130 - grading/threads/priority-donate-multiple.c | 86 - grading/threads/priority-donate-multiple.exp | 13 - grading/threads/priority-donate-nest.c | 103 - grading/threads/priority-donate-nest.exp | 13 - grading/threads/priority-donate-one.c | 74 - grading/threads/priority-donate-one.exp | 11 - grading/threads/priority-preempt.exp | 9 - grading/threads/review.txt | 57 - grading/threads/run-tests | 288 -- grading/threads/tests.txt | 25 - grading/userprog/.cvsignore | 65 - grading/userprog/Make.base | 37 - grading/userprog/Make.tests | 54 - grading/userprog/Makefile | 107 - grading/userprog/args-argc.c | 10 - grading/userprog/args-argc.exp | 2 - grading/userprog/args-argv0.c | 10 - grading/userprog/args-argv0.exp | 2 - grading/userprog/args-argvn.c | 9 - grading/userprog/args-argvn.exp | 2 - grading/userprog/args-dbl-space.c | 33 - grading/userprog/args-dbl-space.exp | 2 - grading/userprog/args-multiple.c | 35 - grading/userprog/args-multiple.exp | 2 - grading/userprog/args-single.c | 25 - grading/userprog/args-single.exp | 2 - grading/userprog/child-arg.c | 25 - grading/userprog/child-bad.c | 11 - grading/userprog/child-close.c | 20 - grading/userprog/child-simple.c | 8 - grading/userprog/close-bad-fd.c | 11 - grading/userprog/close-normal.c | 15 - grading/userprog/close-normal.exp | 3 - grading/userprog/close-stdin.c | 11 - grading/userprog/close-stdin.exp | 6 - grading/userprog/close-stdout.c | 11 - grading/userprog/close-twice.c | 16 - grading/userprog/close-twice.exp | 6 - grading/userprog/create-bad-ptr.c | 11 - grading/userprog/create-bad-ptr.exp | 6 - grading/userprog/create-bound.c | 23 - grading/userprog/create-bound.exp | 4 - grading/userprog/create-empty.c | 11 - grading/userprog/create-empty.exp | 6 - grading/userprog/create-exists.c | 16 - grading/userprog/create-exists.exp | 6 - grading/userprog/create-long.c | 17 - grading/userprog/create-long.exp | 4 - grading/userprog/create-normal.c | 11 - grading/userprog/create-normal.exp | 4 - grading/userprog/create-null.c | 11 - grading/userprog/create-null.exp | 6 - grading/userprog/exec-arg.c | 11 - grading/userprog/exec-arg.exp | 5 - grading/userprog/exec-bad-ptr.c | 11 - grading/userprog/exec-missing.c | 12 - grading/userprog/exec-once.c | 11 - grading/userprog/exit.c | 11 - grading/userprog/exit.exp | 2 - grading/userprog/halt.c | 11 - grading/userprog/halt.exp | 1 - grading/userprog/lib/.cvsignore | 1 - grading/userprog/lib/user/.cvsignore | 1 - grading/userprog/mkmf | 64 - grading/userprog/multi-child-fd.c | 35 - grading/userprog/multi-child-fd.exp | 12 - grading/userprog/multi-parent-fd.c | 35 - grading/userprog/multi-recurse.c | 34 - grading/userprog/null.S | 5 - grading/userprog/null.exp | 1 - grading/userprog/open-bad-ptr.c | 11 - grading/userprog/open-boundary.c | 26 - grading/userprog/open-boundary.exp | 3 - grading/userprog/open-empty.c | 14 - grading/userprog/open-empty.exp | 3 - grading/userprog/open-missing.c | 14 - grading/userprog/open-missing.exp | 3 - grading/userprog/open-normal.c | 14 - grading/userprog/open-normal.exp | 3 - grading/userprog/open-null.c | 11 - grading/userprog/open-null.exp | 6 - grading/userprog/open-twice.c | 22 - grading/userprog/open-twice.exp | 3 - grading/userprog/patches/00random.patch | 96 - grading/userprog/prep-disk | 66 - grading/userprog/read-bad-fd.c | 12 - grading/userprog/read-bad-fd.exp | 6 - grading/userprog/read-bad-ptr.c | 18 - grading/userprog/read-bad-ptr.exp | 6 - grading/userprog/read-boundary.c | 41 - grading/userprog/read-boundary.exp | 3 - grading/userprog/read-normal.c | 28 - grading/userprog/read-normal.exp | 3 - grading/userprog/read-stdout.c | 12 - grading/userprog/read-stdout.exp | 6 - grading/userprog/read-zero.c | 24 - grading/userprog/read-zero.exp | 3 - grading/userprog/review.txt | 64 - grading/userprog/run-tests | 173 - grading/userprog/sample.inc | 6 - grading/userprog/sample.txt | 4 - grading/userprog/sc-bad-arg.exp | 2 - grading/userprog/sc-bad-sp.c | 11 - grading/userprog/sc-bad-sp.exp | 2 - grading/userprog/sc-boundary.c | 26 - grading/userprog/sc-boundary.exp | 2 - grading/userprog/tests.txt | 96 - grading/userprog/wait-bad-pid.c | 11 - grading/userprog/wait-killed.c | 11 - grading/userprog/wait-simple.c | 11 - grading/userprog/wait-twice.c | 14 - grading/userprog/write-bad-fd.c | 12 - grading/userprog/write-bad-ptr.c | 18 - grading/userprog/write-bad-ptr.exp | 6 - grading/userprog/write-boundary.c | 38 - grading/userprog/write-boundary.exp | 3 - grading/userprog/write-normal.c | 26 - grading/userprog/write-normal.exp | 3 - grading/userprog/write-stdin.c | 12 - grading/userprog/write-stdin.exp | 6 - grading/userprog/write-zero.c | 22 - grading/userprog/write-zero.exp | 3 - grading/vm/.cvsignore | 26 - grading/vm/Make.progs | 29 - grading/vm/Makefile | 26 - grading/vm/Makefile.posix | 45 - grading/vm/child-mm-wrt.c | 44 - grading/vm/child-sort.c | 41 - grading/vm/lib/.cvsignore | 1 - grading/vm/lib/user/.cvsignore | 1 - grading/vm/mmap-close.c | 49 - grading/vm/mmap-close.exp | 2 - grading/vm/mmap-exit.c | 58 - grading/vm/mmap-exit.exp | 6 - grading/vm/mmap-overlap.c | 52 - grading/vm/mmap-overlap.exp | 1 - grading/vm/mmap-read.c | 52 - grading/vm/mmap-read.exp | 2 - grading/vm/mmap-shuffle.c | 98 - grading/vm/mmap-shuffle.exp | 13 - grading/vm/mmap-twice.c | 45 - grading/vm/mmap-twice.exp | 2 - grading/vm/mmap-unmap.c | 43 - grading/vm/mmap-write.c | 57 - grading/vm/mmap-write.exp | 2 - grading/vm/page-linear.c | 39 - grading/vm/page-linear.exp | 5 - grading/vm/page-merge-par.exp | 14 - grading/vm/page-parallel.c | 39 - grading/vm/page-parallel.exp | 8 - grading/vm/page-shuffle.c | 69 - grading/vm/page-shuffle.exp | 13 - grading/vm/patches/00random.patch | 96 - grading/vm/posix-compat.c | 187 - grading/vm/posix-compat.h | 83 - grading/vm/prep-disk | 48 - grading/vm/pt-bad-addr.c | 10 - grading/vm/pt-big-stk-obj.c | 19 - grading/vm/pt-big-stk-obj.exp | 3 - grading/vm/pt-grow-stack.c | 19 - grading/vm/pt-grow-stack.exp | 3 - grading/vm/pt-write-code.c | 11 - grading/vm/review.txt | 50 - grading/vm/run-tests | 86 - grading/vm/tests.txt | 29 - solutions/README | 9 +- solutions/p1-1.patch | 126 - solutions/p1-2.patch | 308 -- solutions/p1.patch | 737 +++ solutions/p2-null.patch | 183 - solutions/p2.patch | 125 +- solutions/p3.patch | 2367 ++++++---- solutions/p4.patch | 4000 +++++++++++++++++ src/LICENSE | 61 +- src/Make.config | 10 +- src/Makefile | 11 +- src/Makefile.build | 45 +- src/Makefile.kernel | 18 +- src/Makefile.userprog | 50 +- src/devices/disk.c | 6 +- src/devices/intq.c | 11 +- src/devices/intq.h | 2 +- src/devices/kbd.c | 4 +- src/devices/serial.c | 4 +- src/devices/timer.c | 7 +- src/{tests/userprog => examples}/.cvsignore | 1 - src/{tests/userprog => examples}/Makefile | 2 +- src/{tests/userprog => examples}/bubsort.c | 0 src/{tests/userprog => examples}/echo.c | 0 src/examples/halt.c | 14 + src/{tests/userprog => examples}/insult.c | 0 src/examples/lib/.cvsignore | 0 src/examples/lib/user/.dummy | 0 src/{tests/userprog => examples}/lineup.c | 0 src/{tests/userprog => examples}/ls.c | 0 src/{tests/userprog => examples}/matmult.c | 0 src/{tests/userprog => examples}/mkdir.c | 0 src/{tests/userprog => examples}/recursor.c | 0 src/{tests/userprog => examples}/shell.c | 0 src/filesys/Make.vars | 15 +- src/filesys/directory.c | 264 +- src/filesys/directory.h | 14 +- src/filesys/file.c | 181 +- src/filesys/file.h | 19 +- src/filesys/filesys.c | 296 +- src/filesys/filesys.h | 9 +- src/filesys/free-map.c | 84 + src/filesys/free-map.h | 17 + src/filesys/fsutil.c | 209 +- src/filesys/fsutil.h | 16 +- src/filesys/inode.c | 342 +- src/filesys/inode.h | 9 +- src/filesys/off_t.h | 4 + src/lib/debug.c | 56 +- src/lib/debug.h | 1 - src/lib/kernel/bitmap.c | 218 +- src/lib/kernel/bitmap.h | 34 +- src/lib/kernel/console.c | 11 +- src/lib/kernel/debug.c | 46 + src/lib/kernel/list.c | 3 + src/lib/kernel/stdio.h | 6 + src/lib/stdio.h | 11 +- src/lib/string.c | 4 - src/lib/user/debug.c | 25 + src/lib/user/normal.lds | 1 + src/lib/user/stdio.h | 7 + src/lib/user/syscall.c | 4 +- src/lib/user/syscall.h | 2 +- src/misc/TODO | 39 - src/misc/bochs-2.1.1.patch | 31 +- src/misc/gcc-3.3.5.patch | 20 - src/misc/gcc-3.3.6.patch | 61 + src/tests/Make.tests | 62 + src/tests/Makefile | 8 - {grading/lib => src/tests}/arc4.c | 2 +- {grading/lib => src/tests}/arc4.h | 7 +- src/tests/arc4.pm | 29 + {grading/lib => src/tests}/cksum.c | 30 +- src/tests/cksum.h | 8 + src/tests/cksum.pm | 87 + src/tests/filesys/base/Make.tests | 16 + .../tests/filesys/base}/child-syn-read.c | 6 +- .../tests/filesys/base}/child-syn-wrt.c | 6 +- .../tests/filesys/base}/full.inc | 3 +- src/tests/filesys/base/lg-create.c | 2 + src/tests/filesys/base/lg-create.ck | 12 + src/tests/filesys/base/lg-full.c | 2 + src/tests/filesys/base/lg-full.ck | 15 + src/tests/filesys/base/lg-random.c | 3 + src/tests/filesys/base/lg-random.ck | 13 + src/tests/filesys/base/lg-seq-block.c | 3 + src/tests/filesys/base/lg-seq-block.ck | 15 + src/tests/filesys/base/lg-seq-random.c | 2 + src/tests/filesys/base/lg-seq-random.ck | 15 + .../tests/filesys/base}/random.inc | 7 +- .../tests/filesys/base}/seq-block.inc | 3 +- .../tests/filesys/base}/seq-random.inc | 3 +- src/tests/filesys/base/sm-create.c | 2 + src/tests/filesys/base/sm-create.ck | 12 + src/tests/filesys/base/sm-full.c | 2 + .../tests/filesys/base/sm-full.ck | 7 + src/tests/filesys/base/sm-random.c | 3 + .../tests/filesys/base/sm-random.ck | 6 + src/tests/filesys/base/sm-seq-block.c | 3 + .../tests/filesys/base/sm-seq-block.ck | 7 + src/tests/filesys/base/sm-seq-random.c | 2 + .../tests/filesys/base/sm-seq-random.ck | 7 + .../tests/filesys/base}/syn-read.c | 7 +- .../tests/filesys/base/syn-read.ck | 6 + src/tests/filesys/base/syn-read.h | 7 + .../tests/filesys/base}/syn-remove.c | 7 +- .../tests/filesys/base/syn-remove.ck | 6 + .../tests/filesys/base}/syn-write.c | 7 +- .../tests/filesys/base/syn-write.ck | 6 + .../tests/filesys/base}/syn-write.h | 5 + {grading => src/tests}/filesys/create.inc | 4 +- src/tests/filesys/extended/Make.tests | 21 + .../tests/filesys/extended}/child-syn-rw.c | 8 +- .../tests/filesys/extended}/dir-empty-name.c | 5 +- src/tests/filesys/extended/dir-empty-name.ck | 9 + .../tests/filesys/extended}/dir-lsdir.c | 5 +- src/tests/filesys/extended/dir-lsdir.ck | 38 + src/tests/filesys/extended/dir-mk-tree.c | 9 + .../tests/filesys/extended/dir-mk-tree.ck | 6 + .../tests/filesys/extended}/dir-mk-vine.c | 5 +- .../tests/filesys/extended/dir-mk-vine.ck | 6 + .../tests/filesys/extended}/dir-mkdir.c | 5 +- .../tests/filesys/extended/dir-mkdir.ck | 6 + .../tests/filesys/extended}/dir-open.c | 5 +- .../tests/filesys/extended/dir-open.ck | 8 +- .../tests/filesys/extended}/dir-over-file.c | 5 +- .../tests/filesys/extended/dir-over-file.ck | 6 + .../tests/filesys/extended}/dir-rm-cwd-cd.c | 5 +- .../tests/filesys/extended/dir-rm-cwd-cd.ck | 8 +- .../tests/filesys/extended}/dir-rm-cwd.c | 5 +- .../tests/filesys/extended/dir-rm-cwd.ck | 6 + .../tests/filesys/extended}/dir-rm-parent.c | 5 +- .../tests/filesys/extended/dir-rm-parent.ck | 6 + .../tests/filesys/extended}/dir-rm-root.c | 5 +- src/tests/filesys/extended/dir-rm-root.ck | 10 + .../tests/filesys/extended}/dir-rm-tree.c | 7 +- .../tests/filesys/extended/dir-rm-tree.ck | 6 + .../tests/filesys/extended}/dir-rm-vine.c | 5 +- .../tests/filesys/extended/dir-rm-vine.ck | 6 + .../tests/filesys/extended}/dir-rmdir.c | 5 +- .../tests/filesys/extended/dir-rmdir.ck | 6 + .../tests/filesys/extended}/dir-under-file.c | 5 +- .../tests/filesys/extended/dir-under-file.ck | 6 + src/tests/filesys/extended/grow-create.c | 2 + src/tests/filesys/extended/grow-create.ck | 12 + src/tests/filesys/extended/grow-dir-lg.c | 3 + .../tests/filesys/extended/grow-dir-lg.ck | 6 + .../tests/filesys/extended}/grow-dir.inc | 4 +- .../tests/filesys/extended}/grow-file-size.c | 7 +- .../tests/filesys/extended/grow-file-size.ck | 7 + src/tests/filesys/extended/grow-root-lg.c | 2 + .../tests/filesys/extended/grow-root-lg.ck | 6 + src/tests/filesys/extended/grow-root-sm.c | 2 + .../tests/filesys/extended/grow-root-sm.ck | 6 + src/tests/filesys/extended/grow-seq-lg.c | 2 + .../tests/filesys/extended/grow-seq-lg.ck | 7 + src/tests/filesys/extended/grow-seq-sm.c | 2 + .../tests/filesys/extended/grow-seq-sm.ck | 7 + .../tests/filesys/extended}/grow-seq.inc | 3 +- .../tests/filesys/extended}/grow-sparse.c | 6 +- .../tests/filesys/extended/grow-sparse.ck | 7 + .../tests/filesys/extended}/grow-tell.c | 7 +- .../tests/filesys/extended/grow-tell.ck | 7 + .../tests/filesys/extended}/grow-too-big.c | 7 +- src/tests/filesys/extended/grow-too-big.ck | 13 + .../tests/filesys/extended}/grow-two-files.c | 17 +- .../tests/filesys/extended/grow-two-files.ck | 8 + .../tests/filesys/extended}/mk-tree.c | 4 +- src/tests/filesys/extended/mk-tree.h | 6 + .../tests/filesys/extended}/syn-rw.c | 8 +- src/tests/filesys/extended/syn-rw.ck | 18 + .../tests/filesys/extended}/syn-rw.h | 5 + src/tests/filesys/seq-test.c | 37 + src/tests/filesys/seq-test.h | 11 + src/tests/{threads => internal}/list.c | 0 src/tests/{threads => internal}/stdio.c | 0 src/tests/{threads => internal}/stdlib.c | 0 grading/filesys/fslib.c => src/tests/lib.c | 139 +- grading/filesys/fslib.h => src/tests/lib.h | 21 +- src/tests/lib.pm | 19 + src/tests/main.c | 15 + src/tests/main.h | 6 + src/tests/make-grade | 53 + src/tests/random.pm | 27 + src/tests/tests.pm | 211 + src/tests/threads/Make.tests | 38 + src/tests/threads/alarm-multiple.ck | 4 + .../tests}/threads/alarm-negative.c | 8 +- src/tests/threads/alarm-negative.ck | 9 + src/tests/threads/alarm-priority.c | 55 + src/tests/threads/alarm-priority.ck | 18 + src/tests/threads/alarm-single.ck | 4 + .../tests/threads/alarm-wait.c | 47 +- {grading => src/tests}/threads/alarm-zero.c | 8 +- src/tests/threads/alarm-zero.ck | 9 + src/tests/threads/alarm.pm | 32 + src/tests/threads/mlfqs-fair-2.ck | 7 + src/tests/threads/mlfqs-fair-20.ck | 7 + src/tests/threads/mlfqs-fair.c | 107 + src/tests/threads/mlfqs-load-1.c | 52 + src/tests/threads/mlfqs-load-1.ck | 15 + src/tests/threads/mlfqs-load-60.c | 56 + src/tests/threads/mlfqs-load-60.ck | 36 + src/tests/threads/mlfqs-load-avg.c | 57 + src/tests/threads/mlfqs-load-avg.ck | 36 + src/tests/threads/mlfqs-nice-10.ck | 7 + src/tests/threads/mlfqs-nice-2.ck | 7 + src/tests/threads/mlfqs-recent-1.c | 43 + src/tests/threads/mlfqs-recent-1.ck | 31 + src/tests/threads/mlfqs.pm | 145 + src/tests/threads/p1-1.c | 158 - src/tests/threads/p1-2.c | 112 - src/tests/threads/p1-3.c | 130 - src/tests/threads/priority-change.c | 27 + src/tests/threads/priority-change.ck | 13 + src/tests/threads/priority-condvar.c | 50 + src/tests/threads/priority-condvar.ck | 38 + src/tests/threads/priority-donate-multiple.c | 74 + src/tests/threads/priority-donate-multiple.ck | 18 + src/tests/threads/priority-donate-nest.c | 91 + src/tests/threads/priority-donate-nest.ck | 18 + src/tests/threads/priority-donate-one.c | 62 + src/tests/threads/priority-donate-one.ck | 16 + .../tests}/threads/priority-fifo.c | 73 +- src/tests/threads/priority-fifo.ck | 40 + src/tests/threads/priority-lower.c | 54 + .../tests}/threads/priority-preempt.c | 26 +- src/tests/threads/priority-preempt.ck | 15 + src/tests/threads/priority-sema.c | 42 + src/tests/threads/priority-sema.ck | 28 + src/tests/threads/tests.c | 96 + src/tests/threads/tests.h | 35 + src/tests/userprog/Make.tests | 128 + src/tests/userprog/args-dbl-space.ck | 14 + src/tests/userprog/args-many.ck | 34 + src/tests/userprog/args-multiple.ck | 16 + src/tests/userprog/args-none.ck | 12 + src/tests/userprog/args-single.ck | 13 + src/tests/userprog/args.c | 20 + src/tests/userprog/boundary.c | 29 + src/tests/userprog/boundary.h | 7 + src/tests/userprog/child-bad.c | 9 + src/tests/userprog/child-close.c | 19 + src/tests/userprog/child-rox.c | 49 + src/tests/userprog/child-simple.c | 11 + src/tests/userprog/close-bad-fd.c | 8 + .../tests/userprog/close-bad-fd.ck | 8 +- src/tests/userprog/close-normal.c | 12 + src/tests/userprog/close-normal.ck | 11 + src/tests/userprog/close-stdin.c | 8 + src/tests/userprog/close-stdin.ck | 12 + src/tests/userprog/close-stdout.c | 8 + .../tests/userprog/close-stdout.ck | 8 +- src/tests/userprog/close-twice.c | 14 + src/tests/userprog/close-twice.ck | 18 + src/tests/userprog/create-bad-ptr.c | 8 + src/tests/userprog/create-bad-ptr.ck | 8 + src/tests/userprog/create-bound.c | 11 + src/tests/userprog/create-bound.ck | 10 + src/tests/userprog/create-empty.c | 8 + src/tests/userprog/create-empty.ck | 13 + src/tests/userprog/create-exists.c | 13 + src/tests/userprog/create-exists.ck | 14 + src/tests/userprog/create-long.c | 14 + src/tests/userprog/create-long.ck | 10 + src/tests/userprog/create-normal.c | 8 + src/tests/userprog/create-normal.ck | 10 + src/tests/userprog/create-null.c | 8 + src/tests/userprog/create-null.ck | 8 + src/tests/userprog/exec-arg.c | 8 + src/tests/userprog/exec-arg.ck | 16 + src/tests/userprog/exec-bad-ptr.c | 8 + .../tests/userprog/exec-bad-ptr.ck | 8 +- src/tests/userprog/exec-missing.c | 9 + .../tests/userprog/exec-missing.ck | 10 +- .../tests}/userprog/exec-multiple.c | 10 +- .../tests/userprog/exec-multiple.ck | 6 + src/tests/userprog/exec-once.c | 9 + .../tests/userprog/exec-once.ck | 6 + src/tests/userprog/exit.c | 9 + src/tests/userprog/exit.ck | 8 + src/tests/userprog/halt.c | 15 +- src/tests/userprog/halt.ck | 15 + src/tests/userprog/multi-child-fd.c | 20 + src/tests/userprog/multi-child-fd.ck | 24 + src/tests/userprog/multi-parent-fd.c | 19 + src/tests/userprog/multi-recurse.c | 33 + .../tests/userprog/multi-recurse.ck | 21 + src/tests/userprog/no-vm/Make.tests | 9 + .../tests/userprog/no-vm}/multi-oom.c | 14 +- src/tests/userprog/no-vm/multi-oom.ck | 43 + src/tests/userprog/null.ck | 7 + src/tests/userprog/open-bad-ptr.c | 10 + .../tests/userprog/open-bad-ptr.ck | 8 +- src/tests/userprog/open-boundary.c | 11 + src/tests/userprog/open-boundary.ck | 10 + src/tests/userprog/open-empty.c | 11 + src/tests/userprog/open-empty.ck | 9 + src/tests/userprog/open-missing.c | 11 + src/tests/userprog/open-missing.ck | 9 + src/tests/userprog/open-normal.c | 11 + src/tests/userprog/open-normal.ck | 9 + src/tests/userprog/open-null.c | 9 + src/tests/userprog/open-null.ck | 12 + src/tests/userprog/open-twice.c | 15 + src/tests/userprog/open-twice.ck | 11 + src/tests/userprog/read-bad-fd.c | 17 + src/tests/userprog/read-bad-fd.ck | 12 + src/tests/userprog/read-bad-ptr.c | 13 + src/tests/userprog/read-bad-ptr.ck | 14 + src/tests/userprog/read-boundary.c | 27 + src/tests/userprog/read-boundary.ck | 10 + src/tests/userprog/read-normal.c | 9 + src/tests/userprog/read-normal.ck | 12 + src/tests/userprog/read-stdout.c | 10 + src/tests/userprog/read-stdout.ck | 12 + src/tests/userprog/read-zero.c | 19 + src/tests/userprog/read-zero.ck | 10 + src/tests/userprog/rox-child.c | 2 + src/tests/userprog/rox-child.ck | 19 + src/tests/userprog/rox-child.inc | 33 + src/tests/userprog/rox-multichild.c | 2 + src/tests/userprog/rox-multichild.ck | 43 + src/tests/userprog/rox-simple.c | 16 + src/tests/userprog/rox-simple.ck | 12 + src/tests/userprog/sample.inc | 6 + src/tests/userprog/sample.txt | 4 + {grading => src/tests}/userprog/sc-bad-arg.c | 11 +- src/tests/userprog/sc-bad-arg.ck | 8 + src/tests/userprog/sc-bad-sp.c | 9 + src/tests/userprog/sc-bad-sp.ck | 8 + src/tests/userprog/sc-boundary-2.c | 18 + src/tests/userprog/sc-boundary-2.ck | 8 + src/tests/userprog/sc-boundary.c | 19 + src/tests/userprog/sc-boundary.ck | 8 + src/tests/userprog/wait-bad-pid.c | 8 + .../tests/userprog/wait-bad-pid.ck | 8 +- src/tests/userprog/wait-killed.c | 9 + .../tests/userprog/wait-killed.ck | 6 + src/tests/userprog/wait-simple.c | 9 + .../tests/userprog/wait-simple.ck | 6 + src/tests/userprog/wait-twice.c | 11 + .../tests/userprog/wait-twice.ck | 7 + src/tests/userprog/write-bad-fd.c | 16 + .../tests/userprog/write-bad-fd.ck | 8 +- src/tests/userprog/write-bad-ptr.c | 13 + src/tests/userprog/write-bad-ptr.ck | 14 + src/tests/userprog/write-boundary.c | 22 + src/tests/userprog/write-boundary.ck | 10 + src/tests/userprog/write-normal.c | 18 + src/tests/userprog/write-normal.ck | 11 + src/tests/userprog/write-stdin.c | 10 + src/tests/userprog/write-stdin.ck | 12 + src/tests/userprog/write-zero.c | 17 + src/tests/userprog/write-zero.ck | 10 + src/tests/vm/Make.tests | 89 + src/tests/vm/child-inherit.c | 12 + {grading => src/tests}/vm/child-linear.c | 15 +- src/tests/vm/child-mm-wrt.c | 19 + src/tests/vm/child-sort.c | 38 + src/tests/vm/mmap-bad-fd.c | 11 + src/tests/vm/mmap-bad-fd.ck | 14 + src/tests/vm/mmap-clean.c | 50 + src/tests/vm/mmap-clean.ck | 15 + src/tests/vm/mmap-close.c | 25 + src/tests/vm/mmap-close.ck | 10 + src/tests/vm/mmap-exit.c | 19 + src/tests/vm/mmap-exit.ck | 16 + src/tests/vm/mmap-inherit.c | 29 + src/tests/vm/mmap-inherit.ck | 15 + src/tests/vm/mmap-misalign.c | 14 + src/tests/vm/mmap-misalign.ck | 10 + src/tests/vm/mmap-null.c | 13 + src/tests/vm/mmap-null.ck | 10 + src/tests/vm/mmap-over-code.c | 17 + src/tests/vm/mmap-over-code.ck | 10 + src/tests/vm/mmap-over-data.c | 19 + src/tests/vm/mmap-over-data.ck | 10 + src/tests/vm/mmap-over-stk.c | 17 + src/tests/vm/mmap-over-stk.ck | 10 + src/tests/vm/mmap-overlap.c | 18 + src/tests/vm/mmap-overlap.ck | 12 + src/tests/vm/mmap-read.c | 30 + src/tests/vm/mmap-read.ck | 10 + src/tests/vm/mmap-remove.c | 40 + src/tests/vm/mmap-remove.ck | 13 + src/tests/vm/mmap-shuffle.c | 35 + src/tests/vm/mmap-shuffle.ck | 46 + src/tests/vm/mmap-twice.c | 25 + src/tests/vm/mmap-twice.ck | 14 + src/tests/vm/mmap-unmap.c | 20 + src/tests/vm/mmap-unmap.ck | 7 + src/tests/vm/mmap-write.c | 28 + src/tests/vm/mmap-write.ck | 12 + src/tests/vm/mmap-zero.c | 22 + src/tests/vm/mmap-zero.ck | 11 + src/tests/vm/page-linear.c | 41 + src/tests/vm/page-linear.ck | 13 + {grading => src/tests}/vm/page-merge-par.c | 82 +- src/tests/vm/page-merge-par.ck | 28 + {grading => src/tests}/vm/page-merge-seq.c | 82 +- .../tests/vm/page-merge-seq.ck | 10 +- src/tests/vm/page-parallel.c | 19 + src/tests/vm/page-parallel.ck | 14 + src/tests/vm/page-shuffle.c | 27 + src/tests/vm/page-shuffle.ck | 43 + src/tests/vm/process_death.pm | 22 + src/tests/vm/pt-bad-addr.c | 8 + src/tests/vm/pt-bad-addr.ck | 7 + src/tests/vm/pt-bad-read.c | 13 + src/tests/vm/pt-bad-read.ck | 9 + src/tests/vm/pt-big-stk-obj.c | 17 + src/tests/vm/pt-big-stk-obj.ck | 9 + src/tests/vm/pt-grow-bad.c | 13 + src/tests/vm/pt-grow-bad.ck | 8 + src/tests/vm/pt-grow-pusha.c | 16 + src/tests/vm/pt-grow-pusha.ck | 8 + src/tests/vm/pt-grow-stack.c | 17 + src/tests/vm/pt-grow-stack.ck | 9 + src/tests/vm/pt-write-code-2.c | 12 + src/tests/vm/pt-write-code.c | 9 + src/tests/vm/pt-write-code.ck | 7 + src/tests/vm/pt-write-code2.ck | 9 + {grading => src/tests}/vm/sample.inc | 0 {grading => src/tests}/vm/sample.txt | 0 src/threads/Make.vars | 7 +- src/threads/flags.h | 2 +- src/threads/init.c | 286 +- src/threads/interrupt.c | 58 +- src/threads/interrupt.h | 5 +- src/threads/loader.S | 31 +- src/threads/loader.h | 17 +- src/threads/malloc.c | 104 +- src/threads/malloc.h | 1 + src/threads/mmu.h | 23 +- src/threads/palloc.c | 7 +- src/threads/synch.c | 118 +- src/threads/synch.h | 19 +- src/threads/test.c | 158 - src/threads/test.h | 8 - src/threads/thread.c | 55 +- src/threads/thread.h | 14 +- src/userprog/Make.vars | 7 +- src/userprog/exception.c | 29 +- src/userprog/pagedir.c | 169 +- src/userprog/pagedir.h | 9 +- src/userprog/process.c | 99 +- src/userprog/syscall.c | 2 +- src/utils/backtrace | 50 +- src/utils/pintos | 950 ++-- src/utils/pintos-mkdisk | 37 + src/vm/Make.vars | 7 +- tests/Makefile | 89 +- 690 files changed, 18400 insertions(+), 15141 deletions(-) create mode 100644 doc/44bsd.texi create mode 100644 doc/filesys.tmpl delete mode 100644 doc/mlfqs.texi create mode 100644 doc/sample.tmpl create mode 100644 doc/threads.tmpl create mode 100644 doc/userprog.tmpl create mode 100644 doc/vm.tmpl delete mode 100644 grading/Makefile delete mode 100644 grading/filesys/.cvsignore delete mode 100644 grading/filesys/Make.progs delete mode 100644 grading/filesys/Makefile delete mode 100644 grading/filesys/dir-empty-name.exp delete mode 100644 grading/filesys/dir-mk-tree.c delete mode 100644 grading/filesys/dir-rm-root.exp delete mode 100644 grading/filesys/fsmain.c delete mode 100644 grading/filesys/grow-create.c delete mode 100644 grading/filesys/grow-create.exp delete mode 100644 grading/filesys/grow-dir-lg.c delete mode 100644 grading/filesys/grow-root-lg.c delete mode 100644 grading/filesys/grow-root-sm.c delete mode 100644 grading/filesys/grow-seq-lg.c delete mode 100644 grading/filesys/grow-seq-sm.c delete mode 100644 grading/filesys/grow-too-big.exp delete mode 100644 grading/filesys/lg-create.c delete mode 100644 grading/filesys/lg-create.exp delete mode 100644 grading/filesys/lg-full.c delete mode 100644 grading/filesys/lg-random.c delete mode 100644 grading/filesys/lg-seq-block.c delete mode 100644 grading/filesys/lg-seq-random.c delete mode 100644 grading/filesys/lib/.cvsignore delete mode 100644 grading/filesys/lib/user/.cvsignore delete mode 100644 grading/filesys/main.c delete mode 100644 grading/filesys/mk-tree.h delete mode 100644 grading/filesys/review.txt delete mode 100755 grading/filesys/run-tests delete mode 100644 grading/filesys/sm-create.c delete mode 100644 grading/filesys/sm-create.exp delete mode 100644 grading/filesys/sm-full.c delete mode 100644 grading/filesys/sm-full.exp delete mode 100644 grading/filesys/sm-random.c delete mode 100644 grading/filesys/sm-random.exp delete mode 100644 grading/filesys/sm-seq-block.c delete mode 100644 grading/filesys/sm-seq-block.exp delete mode 100644 grading/filesys/sm-seq-random.c delete mode 100644 grading/filesys/sm-seq-random.exp delete mode 100644 grading/filesys/syn-read.h delete mode 100644 grading/filesys/tests.txt delete mode 100644 grading/lib/.cvsignore delete mode 100644 grading/lib/Algorithm/Diff.pm delete mode 100644 grading/lib/Pintos/Grading.pm delete mode 100644 grading/lib/cksum.h delete mode 100644 grading/threads/alarm-multiple.c delete mode 100644 grading/threads/mlfqs.c delete mode 100644 grading/threads/priority-donate-multiple.c delete mode 100644 grading/threads/priority-donate-multiple.exp delete mode 100644 grading/threads/priority-donate-nest.c delete mode 100644 grading/threads/priority-donate-nest.exp delete mode 100644 grading/threads/priority-donate-one.c delete mode 100644 grading/threads/priority-donate-one.exp delete mode 100644 grading/threads/priority-preempt.exp delete mode 100644 grading/threads/review.txt delete mode 100755 grading/threads/run-tests delete mode 100644 grading/threads/tests.txt delete mode 100644 grading/userprog/.cvsignore delete mode 100644 grading/userprog/Make.base delete mode 100644 grading/userprog/Make.tests delete mode 100644 grading/userprog/Makefile delete mode 100644 grading/userprog/args-argc.c delete mode 100644 grading/userprog/args-argc.exp delete mode 100644 grading/userprog/args-argv0.c delete mode 100644 grading/userprog/args-argv0.exp delete mode 100644 grading/userprog/args-argvn.c delete mode 100644 grading/userprog/args-argvn.exp delete mode 100644 grading/userprog/args-dbl-space.c delete mode 100644 grading/userprog/args-dbl-space.exp delete mode 100644 grading/userprog/args-multiple.c delete mode 100644 grading/userprog/args-multiple.exp delete mode 100644 grading/userprog/args-single.c delete mode 100644 grading/userprog/args-single.exp delete mode 100644 grading/userprog/child-arg.c delete mode 100644 grading/userprog/child-bad.c delete mode 100644 grading/userprog/child-close.c delete mode 100644 grading/userprog/child-simple.c delete mode 100644 grading/userprog/close-bad-fd.c delete mode 100644 grading/userprog/close-normal.c delete mode 100644 grading/userprog/close-normal.exp delete mode 100644 grading/userprog/close-stdin.c delete mode 100644 grading/userprog/close-stdin.exp delete mode 100644 grading/userprog/close-stdout.c delete mode 100644 grading/userprog/close-twice.c delete mode 100644 grading/userprog/close-twice.exp delete mode 100644 grading/userprog/create-bad-ptr.c delete mode 100644 grading/userprog/create-bad-ptr.exp delete mode 100644 grading/userprog/create-bound.c delete mode 100644 grading/userprog/create-bound.exp delete mode 100644 grading/userprog/create-empty.c delete mode 100644 grading/userprog/create-empty.exp delete mode 100644 grading/userprog/create-exists.c delete mode 100644 grading/userprog/create-exists.exp delete mode 100644 grading/userprog/create-long.c delete mode 100644 grading/userprog/create-long.exp delete mode 100644 grading/userprog/create-normal.c delete mode 100644 grading/userprog/create-normal.exp delete mode 100644 grading/userprog/create-null.c delete mode 100644 grading/userprog/create-null.exp delete mode 100644 grading/userprog/exec-arg.c delete mode 100644 grading/userprog/exec-arg.exp delete mode 100644 grading/userprog/exec-bad-ptr.c delete mode 100644 grading/userprog/exec-missing.c delete mode 100644 grading/userprog/exec-once.c delete mode 100644 grading/userprog/exit.c delete mode 100644 grading/userprog/exit.exp delete mode 100644 grading/userprog/halt.c delete mode 100644 grading/userprog/halt.exp delete mode 100644 grading/userprog/lib/.cvsignore delete mode 100644 grading/userprog/lib/user/.cvsignore delete mode 100755 grading/userprog/mkmf delete mode 100644 grading/userprog/multi-child-fd.c delete mode 100644 grading/userprog/multi-child-fd.exp delete mode 100644 grading/userprog/multi-parent-fd.c delete mode 100644 grading/userprog/multi-recurse.c delete mode 100644 grading/userprog/null.S delete mode 100644 grading/userprog/null.exp delete mode 100644 grading/userprog/open-bad-ptr.c delete mode 100644 grading/userprog/open-boundary.c delete mode 100644 grading/userprog/open-boundary.exp delete mode 100644 grading/userprog/open-empty.c delete mode 100644 grading/userprog/open-empty.exp delete mode 100644 grading/userprog/open-missing.c delete mode 100644 grading/userprog/open-missing.exp delete mode 100644 grading/userprog/open-normal.c delete mode 100644 grading/userprog/open-normal.exp delete mode 100644 grading/userprog/open-null.c delete mode 100644 grading/userprog/open-null.exp delete mode 100644 grading/userprog/open-twice.c delete mode 100644 grading/userprog/open-twice.exp delete mode 100644 grading/userprog/patches/00random.patch delete mode 100755 grading/userprog/prep-disk delete mode 100644 grading/userprog/read-bad-fd.c delete mode 100644 grading/userprog/read-bad-fd.exp delete mode 100644 grading/userprog/read-bad-ptr.c delete mode 100644 grading/userprog/read-bad-ptr.exp delete mode 100644 grading/userprog/read-boundary.c delete mode 100644 grading/userprog/read-boundary.exp delete mode 100644 grading/userprog/read-normal.c delete mode 100644 grading/userprog/read-normal.exp delete mode 100644 grading/userprog/read-stdout.c delete mode 100644 grading/userprog/read-stdout.exp delete mode 100644 grading/userprog/read-zero.c delete mode 100644 grading/userprog/read-zero.exp delete mode 100644 grading/userprog/review.txt delete mode 100755 grading/userprog/run-tests delete mode 100644 grading/userprog/sample.inc delete mode 100644 grading/userprog/sample.txt delete mode 100644 grading/userprog/sc-bad-arg.exp delete mode 100644 grading/userprog/sc-bad-sp.c delete mode 100644 grading/userprog/sc-bad-sp.exp delete mode 100644 grading/userprog/sc-boundary.c delete mode 100644 grading/userprog/sc-boundary.exp delete mode 100644 grading/userprog/tests.txt delete mode 100644 grading/userprog/wait-bad-pid.c delete mode 100644 grading/userprog/wait-killed.c delete mode 100644 grading/userprog/wait-simple.c delete mode 100644 grading/userprog/wait-twice.c delete mode 100644 grading/userprog/write-bad-fd.c delete mode 100644 grading/userprog/write-bad-ptr.c delete mode 100644 grading/userprog/write-bad-ptr.exp delete mode 100644 grading/userprog/write-boundary.c delete mode 100644 grading/userprog/write-boundary.exp delete mode 100644 grading/userprog/write-normal.c delete mode 100644 grading/userprog/write-normal.exp delete mode 100644 grading/userprog/write-stdin.c delete mode 100644 grading/userprog/write-stdin.exp delete mode 100644 grading/userprog/write-zero.c delete mode 100644 grading/userprog/write-zero.exp delete mode 100644 grading/vm/.cvsignore delete mode 100644 grading/vm/Make.progs delete mode 100644 grading/vm/Makefile delete mode 100644 grading/vm/Makefile.posix delete mode 100644 grading/vm/child-mm-wrt.c delete mode 100644 grading/vm/child-sort.c delete mode 100644 grading/vm/lib/.cvsignore delete mode 100644 grading/vm/lib/user/.cvsignore delete mode 100644 grading/vm/mmap-close.c delete mode 100644 grading/vm/mmap-close.exp delete mode 100644 grading/vm/mmap-exit.c delete mode 100644 grading/vm/mmap-exit.exp delete mode 100644 grading/vm/mmap-overlap.c delete mode 100644 grading/vm/mmap-overlap.exp delete mode 100644 grading/vm/mmap-read.c delete mode 100644 grading/vm/mmap-read.exp delete mode 100644 grading/vm/mmap-shuffle.c delete mode 100644 grading/vm/mmap-shuffle.exp delete mode 100644 grading/vm/mmap-twice.c delete mode 100644 grading/vm/mmap-twice.exp delete mode 100644 grading/vm/mmap-unmap.c delete mode 100644 grading/vm/mmap-write.c delete mode 100644 grading/vm/mmap-write.exp delete mode 100644 grading/vm/page-linear.c delete mode 100644 grading/vm/page-linear.exp delete mode 100644 grading/vm/page-merge-par.exp delete mode 100644 grading/vm/page-parallel.c delete mode 100644 grading/vm/page-parallel.exp delete mode 100644 grading/vm/page-shuffle.c delete mode 100644 grading/vm/page-shuffle.exp delete mode 100644 grading/vm/patches/00random.patch delete mode 100644 grading/vm/posix-compat.c delete mode 100644 grading/vm/posix-compat.h delete mode 100755 grading/vm/prep-disk delete mode 100644 grading/vm/pt-bad-addr.c delete mode 100644 grading/vm/pt-big-stk-obj.c delete mode 100644 grading/vm/pt-big-stk-obj.exp delete mode 100644 grading/vm/pt-grow-stack.c delete mode 100644 grading/vm/pt-grow-stack.exp delete mode 100644 grading/vm/pt-write-code.c delete mode 100644 grading/vm/review.txt delete mode 100755 grading/vm/run-tests delete mode 100644 grading/vm/tests.txt delete mode 100644 solutions/p1-1.patch delete mode 100644 solutions/p1-2.patch create mode 100644 solutions/p1.patch delete mode 100644 solutions/p2-null.patch create mode 100644 solutions/p4.patch rename src/{tests/userprog => examples}/.cvsignore (94%) rename src/{tests/userprog => examples}/Makefile (97%) rename src/{tests/userprog => examples}/bubsort.c (100%) rename src/{tests/userprog => examples}/echo.c (100%) create mode 100644 src/examples/halt.c rename src/{tests/userprog => examples}/insult.c (100%) create mode 100644 src/examples/lib/.cvsignore create mode 100644 src/examples/lib/user/.dummy rename src/{tests/userprog => examples}/lineup.c (100%) rename src/{tests/userprog => examples}/ls.c (100%) rename src/{tests/userprog => examples}/matmult.c (100%) rename src/{tests/userprog => examples}/mkdir.c (100%) rename src/{tests/userprog => examples}/recursor.c (100%) rename src/{tests/userprog => examples}/shell.c (100%) create mode 100644 src/filesys/free-map.c create mode 100644 src/filesys/free-map.h create mode 100644 src/lib/kernel/debug.c create mode 100644 src/lib/kernel/stdio.h create mode 100644 src/lib/user/debug.c create mode 100644 src/lib/user/stdio.h delete mode 100644 src/misc/TODO delete mode 100644 src/misc/gcc-3.3.5.patch create mode 100644 src/misc/gcc-3.3.6.patch create mode 100644 src/tests/Make.tests delete mode 100644 src/tests/Makefile rename {grading/lib => src/tests}/arc4.c (97%) rename {grading/lib => src/tests}/arc4.h (78%) create mode 100644 src/tests/arc4.pm rename {grading/lib => src/tests}/cksum.c (90%) create mode 100644 src/tests/cksum.h create mode 100644 src/tests/cksum.pm create mode 100644 src/tests/filesys/base/Make.tests rename {grading/filesys => src/tests/filesys/base}/child-syn-read.c (87%) rename {grading/filesys => src/tests/filesys/base}/child-syn-wrt.c (87%) rename {grading/filesys => src/tests/filesys/base}/full.inc (79%) create mode 100644 src/tests/filesys/base/lg-create.c create mode 100644 src/tests/filesys/base/lg-create.ck create mode 100644 src/tests/filesys/base/lg-full.c create mode 100644 src/tests/filesys/base/lg-full.ck create mode 100644 src/tests/filesys/base/lg-random.c create mode 100644 src/tests/filesys/base/lg-random.ck create mode 100644 src/tests/filesys/base/lg-seq-block.c create mode 100644 src/tests/filesys/base/lg-seq-block.ck create mode 100644 src/tests/filesys/base/lg-seq-random.c create mode 100644 src/tests/filesys/base/lg-seq-random.ck rename {grading/filesys => src/tests/filesys/base}/random.inc (89%) rename {grading/filesys => src/tests/filesys/base}/seq-block.inc (80%) rename {grading/filesys => src/tests/filesys/base}/seq-random.inc (82%) create mode 100644 src/tests/filesys/base/sm-create.c create mode 100644 src/tests/filesys/base/sm-create.ck create mode 100644 src/tests/filesys/base/sm-full.c rename grading/filesys/lg-full.exp => src/tests/filesys/base/sm-full.ck (54%) create mode 100644 src/tests/filesys/base/sm-random.c rename grading/filesys/lg-random.exp => src/tests/filesys/base/sm-random.ck (63%) create mode 100644 src/tests/filesys/base/sm-seq-block.c rename grading/filesys/lg-seq-block.exp => src/tests/filesys/base/sm-seq-block.ck (59%) create mode 100644 src/tests/filesys/base/sm-seq-random.c rename grading/filesys/lg-seq-random.exp => src/tests/filesys/base/sm-seq-random.ck (60%) rename {grading/filesys => src/tests/filesys/base}/syn-read.c (86%) rename grading/filesys/syn-read.exp => src/tests/filesys/base/syn-read.ck (91%) create mode 100644 src/tests/filesys/base/syn-read.h rename {grading/filesys => src/tests/filesys/base}/syn-remove.c (84%) rename grading/filesys/syn-remove.exp => src/tests/filesys/base/syn-remove.ck (68%) rename {grading/filesys => src/tests/filesys/base}/syn-write.c (86%) rename grading/filesys/syn-write.exp => src/tests/filesys/base/syn-write.ck (91%) rename {grading/filesys => src/tests/filesys/base}/syn-write.h (50%) rename {grading => src/tests}/filesys/create.inc (83%) create mode 100644 src/tests/filesys/extended/Make.tests rename {grading/filesys => src/tests/filesys/extended}/child-syn-rw.c (88%) rename {grading/filesys => src/tests/filesys/extended}/dir-empty-name.c (63%) create mode 100644 src/tests/filesys/extended/dir-empty-name.ck rename {grading/filesys => src/tests/filesys/extended}/dir-lsdir.c (51%) create mode 100644 src/tests/filesys/extended/dir-lsdir.ck create mode 100644 src/tests/filesys/extended/dir-mk-tree.c rename grading/filesys/dir-mk-tree.exp => src/tests/filesys/extended/dir-mk-tree.ck (56%) rename {grading/filesys => src/tests/filesys/extended}/dir-mk-vine.c (87%) rename grading/filesys/dir-mk-vine.exp => src/tests/filesys/extended/dir-mk-vine.ck (84%) rename {grading/filesys => src/tests/filesys/extended}/dir-mkdir.c (78%) rename grading/filesys/dir-mkdir.exp => src/tests/filesys/extended/dir-mkdir.ck (51%) rename {grading/filesys => src/tests/filesys/extended}/dir-open.c (86%) rename grading/filesys/dir-open.exp => src/tests/filesys/extended/dir-open.ck (66%) rename {grading/filesys => src/tests/filesys/extended}/dir-over-file.c (71%) rename grading/filesys/dir-over-file.exp => src/tests/filesys/extended/dir-over-file.ck (50%) rename {grading/filesys => src/tests/filesys/extended}/dir-rm-cwd-cd.c (85%) rename grading/filesys/dir-rm-cwd-cd.exp => src/tests/filesys/extended/dir-rm-cwd-cd.ck (76%) rename {grading/filesys => src/tests/filesys/extended}/dir-rm-cwd.c (80%) rename grading/filesys/dir-rm-cwd.exp => src/tests/filesys/extended/dir-rm-cwd.ck (58%) rename {grading/filesys => src/tests/filesys/extended}/dir-rm-parent.c (83%) rename grading/filesys/dir-rm-parent.exp => src/tests/filesys/extended/dir-rm-parent.ck (67%) rename {grading/filesys => src/tests/filesys/extended}/dir-rm-root.c (72%) create mode 100644 src/tests/filesys/extended/dir-rm-root.ck rename {grading/filesys => src/tests/filesys/extended}/dir-rm-tree.c (93%) rename grading/filesys/dir-rm-tree.exp => src/tests/filesys/extended/dir-rm-tree.ck (68%) rename {grading/filesys => src/tests/filesys/extended}/dir-rm-vine.c (92%) rename grading/filesys/dir-rm-vine.exp => src/tests/filesys/extended/dir-rm-vine.ck (90%) rename {grading/filesys => src/tests/filesys/extended}/dir-rmdir.c (76%) rename grading/filesys/dir-rmdir.exp => src/tests/filesys/extended/dir-rmdir.ck (51%) rename {grading/filesys => src/tests/filesys/extended}/dir-under-file.c (71%) rename grading/filesys/dir-under-file.exp => src/tests/filesys/extended/dir-under-file.ck (51%) create mode 100644 src/tests/filesys/extended/grow-create.c create mode 100644 src/tests/filesys/extended/grow-create.ck create mode 100644 src/tests/filesys/extended/grow-dir-lg.c rename grading/filesys/grow-dir-lg.exp => src/tests/filesys/extended/grow-dir-lg.ck (95%) rename {grading/filesys => src/tests/filesys/extended}/grow-dir.inc (89%) rename {grading/filesys => src/tests/filesys/extended}/grow-file-size.c (83%) rename grading/filesys/grow-file-size.exp => src/tests/filesys/extended/grow-file-size.ck (61%) create mode 100644 src/tests/filesys/extended/grow-root-lg.c rename grading/filesys/grow-root-lg.exp => src/tests/filesys/extended/grow-root-lg.ck (95%) create mode 100644 src/tests/filesys/extended/grow-root-sm.c rename grading/filesys/grow-root-sm.exp => src/tests/filesys/extended/grow-root-sm.ck (80%) create mode 100644 src/tests/filesys/extended/grow-seq-lg.c rename grading/filesys/grow-seq-lg.exp => src/tests/filesys/extended/grow-seq-lg.ck (59%) create mode 100644 src/tests/filesys/extended/grow-seq-sm.c rename grading/filesys/grow-seq-sm.exp => src/tests/filesys/extended/grow-seq-sm.ck (59%) rename {grading/filesys => src/tests/filesys/extended}/grow-seq.inc (79%) rename {grading/filesys => src/tests/filesys/extended}/grow-sparse.c (86%) rename grading/filesys/grow-sparse.exp => src/tests/filesys/extended/grow-sparse.ck (62%) rename {grading/filesys => src/tests/filesys/extended}/grow-tell.c (83%) rename grading/filesys/grow-tell.exp => src/tests/filesys/extended/grow-tell.ck (57%) rename {grading/filesys => src/tests/filesys/extended}/grow-too-big.c (84%) create mode 100644 src/tests/filesys/extended/grow-too-big.ck rename {grading/filesys => src/tests/filesys/extended}/grow-two-files.c (78%) rename grading/filesys/grow-two-files.exp => src/tests/filesys/extended/grow-two-files.ck (66%) rename {grading/filesys => src/tests/filesys/extended}/mk-tree.c (95%) create mode 100644 src/tests/filesys/extended/mk-tree.h rename {grading/filesys => src/tests/filesys/extended}/syn-rw.c (87%) create mode 100644 src/tests/filesys/extended/syn-rw.ck rename {grading/filesys => src/tests/filesys/extended}/syn-rw.h (50%) create mode 100644 src/tests/filesys/seq-test.c create mode 100644 src/tests/filesys/seq-test.h rename src/tests/{threads => internal}/list.c (100%) rename src/tests/{threads => internal}/stdio.c (100%) rename src/tests/{threads => internal}/stdlib.c (100%) rename grading/filesys/fslib.c => src/tests/lib.c (75%) rename grading/filesys/fslib.h => src/tests/lib.h (83%) create mode 100644 src/tests/lib.pm create mode 100644 src/tests/main.c create mode 100644 src/tests/main.h create mode 100755 src/tests/make-grade create mode 100644 src/tests/random.pm create mode 100644 src/tests/tests.pm create mode 100644 src/tests/threads/Make.tests create mode 100644 src/tests/threads/alarm-multiple.ck rename {grading => src/tests}/threads/alarm-negative.c (68%) create mode 100644 src/tests/threads/alarm-negative.ck create mode 100644 src/tests/threads/alarm-priority.c create mode 100644 src/tests/threads/alarm-priority.ck create mode 100644 src/tests/threads/alarm-single.ck rename grading/threads/alarm-single.c => src/tests/threads/alarm-wait.c (79%) rename {grading => src/tests}/threads/alarm-zero.c (68%) create mode 100644 src/tests/threads/alarm-zero.ck create mode 100644 src/tests/threads/alarm.pm create mode 100644 src/tests/threads/mlfqs-fair-2.ck create mode 100644 src/tests/threads/mlfqs-fair-20.ck create mode 100644 src/tests/threads/mlfqs-fair.c create mode 100644 src/tests/threads/mlfqs-load-1.c create mode 100644 src/tests/threads/mlfqs-load-1.ck create mode 100644 src/tests/threads/mlfqs-load-60.c create mode 100644 src/tests/threads/mlfqs-load-60.ck create mode 100644 src/tests/threads/mlfqs-load-avg.c create mode 100644 src/tests/threads/mlfqs-load-avg.ck create mode 100644 src/tests/threads/mlfqs-nice-10.ck create mode 100644 src/tests/threads/mlfqs-nice-2.ck create mode 100644 src/tests/threads/mlfqs-recent-1.c create mode 100644 src/tests/threads/mlfqs-recent-1.ck create mode 100644 src/tests/threads/mlfqs.pm delete mode 100644 src/tests/threads/p1-1.c delete mode 100644 src/tests/threads/p1-2.c delete mode 100644 src/tests/threads/p1-3.c create mode 100644 src/tests/threads/priority-change.c create mode 100644 src/tests/threads/priority-change.ck create mode 100644 src/tests/threads/priority-condvar.c create mode 100644 src/tests/threads/priority-condvar.ck create mode 100644 src/tests/threads/priority-donate-multiple.c create mode 100644 src/tests/threads/priority-donate-multiple.ck create mode 100644 src/tests/threads/priority-donate-nest.c create mode 100644 src/tests/threads/priority-donate-nest.ck create mode 100644 src/tests/threads/priority-donate-one.c create mode 100644 src/tests/threads/priority-donate-one.ck rename {grading => src/tests}/threads/priority-fifo.c (65%) create mode 100644 src/tests/threads/priority-fifo.ck create mode 100644 src/tests/threads/priority-lower.c rename {grading => src/tests}/threads/priority-preempt.c (60%) create mode 100644 src/tests/threads/priority-preempt.ck create mode 100644 src/tests/threads/priority-sema.c create mode 100644 src/tests/threads/priority-sema.ck create mode 100644 src/tests/threads/tests.c create mode 100644 src/tests/threads/tests.h create mode 100644 src/tests/userprog/Make.tests create mode 100644 src/tests/userprog/args-dbl-space.ck create mode 100644 src/tests/userprog/args-many.ck create mode 100644 src/tests/userprog/args-multiple.ck create mode 100644 src/tests/userprog/args-none.ck create mode 100644 src/tests/userprog/args-single.ck create mode 100644 src/tests/userprog/args.c create mode 100644 src/tests/userprog/boundary.c create mode 100644 src/tests/userprog/boundary.h create mode 100644 src/tests/userprog/child-bad.c create mode 100644 src/tests/userprog/child-close.c create mode 100644 src/tests/userprog/child-rox.c create mode 100644 src/tests/userprog/child-simple.c create mode 100644 src/tests/userprog/close-bad-fd.c rename grading/userprog/close-bad-fd.exp => src/tests/userprog/close-bad-fd.ck (50%) create mode 100644 src/tests/userprog/close-normal.c create mode 100644 src/tests/userprog/close-normal.ck create mode 100644 src/tests/userprog/close-stdin.c create mode 100644 src/tests/userprog/close-stdin.ck create mode 100644 src/tests/userprog/close-stdout.c rename grading/userprog/close-stdout.exp => src/tests/userprog/close-stdout.ck (50%) create mode 100644 src/tests/userprog/close-twice.c create mode 100644 src/tests/userprog/close-twice.ck create mode 100644 src/tests/userprog/create-bad-ptr.c create mode 100644 src/tests/userprog/create-bad-ptr.ck create mode 100644 src/tests/userprog/create-bound.c create mode 100644 src/tests/userprog/create-bound.ck create mode 100644 src/tests/userprog/create-empty.c create mode 100644 src/tests/userprog/create-empty.ck create mode 100644 src/tests/userprog/create-exists.c create mode 100644 src/tests/userprog/create-exists.ck create mode 100644 src/tests/userprog/create-long.c create mode 100644 src/tests/userprog/create-long.ck create mode 100644 src/tests/userprog/create-normal.c create mode 100644 src/tests/userprog/create-normal.ck create mode 100644 src/tests/userprog/create-null.c create mode 100644 src/tests/userprog/create-null.ck create mode 100644 src/tests/userprog/exec-arg.c create mode 100644 src/tests/userprog/exec-arg.ck create mode 100644 src/tests/userprog/exec-bad-ptr.c rename grading/userprog/exec-bad-ptr.exp => src/tests/userprog/exec-bad-ptr.ck (50%) create mode 100644 src/tests/userprog/exec-missing.c rename grading/userprog/exec-missing.exp => src/tests/userprog/exec-missing.ck (77%) rename {grading => src/tests}/userprog/exec-multiple.c (55%) rename grading/userprog/exec-multiple.exp => src/tests/userprog/exec-multiple.ck (71%) create mode 100644 src/tests/userprog/exec-once.c rename grading/userprog/exec-once.exp => src/tests/userprog/exec-once.ck (51%) create mode 100644 src/tests/userprog/exit.c create mode 100644 src/tests/userprog/exit.ck create mode 100644 src/tests/userprog/halt.ck create mode 100644 src/tests/userprog/multi-child-fd.c create mode 100644 src/tests/userprog/multi-child-fd.ck create mode 100644 src/tests/userprog/multi-parent-fd.c create mode 100644 src/tests/userprog/multi-recurse.c rename grading/userprog/multi-recurse.exp => src/tests/userprog/multi-recurse.ck (61%) create mode 100644 src/tests/userprog/no-vm/Make.tests rename {grading/userprog => src/tests/userprog/no-vm}/multi-oom.c (60%) create mode 100644 src/tests/userprog/no-vm/multi-oom.ck create mode 100644 src/tests/userprog/null.ck create mode 100644 src/tests/userprog/open-bad-ptr.c rename grading/userprog/open-bad-ptr.exp => src/tests/userprog/open-bad-ptr.ck (50%) create mode 100644 src/tests/userprog/open-boundary.c create mode 100644 src/tests/userprog/open-boundary.ck create mode 100644 src/tests/userprog/open-empty.c create mode 100644 src/tests/userprog/open-empty.ck create mode 100644 src/tests/userprog/open-missing.c create mode 100644 src/tests/userprog/open-missing.ck create mode 100644 src/tests/userprog/open-normal.c create mode 100644 src/tests/userprog/open-normal.ck create mode 100644 src/tests/userprog/open-null.c create mode 100644 src/tests/userprog/open-null.ck create mode 100644 src/tests/userprog/open-twice.c create mode 100644 src/tests/userprog/open-twice.ck create mode 100644 src/tests/userprog/read-bad-fd.c create mode 100644 src/tests/userprog/read-bad-fd.ck create mode 100644 src/tests/userprog/read-bad-ptr.c create mode 100644 src/tests/userprog/read-bad-ptr.ck create mode 100644 src/tests/userprog/read-boundary.c create mode 100644 src/tests/userprog/read-boundary.ck create mode 100644 src/tests/userprog/read-normal.c create mode 100644 src/tests/userprog/read-normal.ck create mode 100644 src/tests/userprog/read-stdout.c create mode 100644 src/tests/userprog/read-stdout.ck create mode 100644 src/tests/userprog/read-zero.c create mode 100644 src/tests/userprog/read-zero.ck create mode 100644 src/tests/userprog/rox-child.c create mode 100644 src/tests/userprog/rox-child.ck create mode 100644 src/tests/userprog/rox-child.inc create mode 100644 src/tests/userprog/rox-multichild.c create mode 100644 src/tests/userprog/rox-multichild.ck create mode 100644 src/tests/userprog/rox-simple.c create mode 100644 src/tests/userprog/rox-simple.ck create mode 100644 src/tests/userprog/sample.inc create mode 100644 src/tests/userprog/sample.txt rename {grading => src/tests}/userprog/sc-bad-arg.c (54%) create mode 100644 src/tests/userprog/sc-bad-arg.ck create mode 100644 src/tests/userprog/sc-bad-sp.c create mode 100644 src/tests/userprog/sc-bad-sp.ck create mode 100644 src/tests/userprog/sc-boundary-2.c create mode 100644 src/tests/userprog/sc-boundary-2.ck create mode 100644 src/tests/userprog/sc-boundary.c create mode 100644 src/tests/userprog/sc-boundary.ck create mode 100644 src/tests/userprog/wait-bad-pid.c rename grading/userprog/wait-bad-pid.exp => src/tests/userprog/wait-bad-pid.ck (50%) create mode 100644 src/tests/userprog/wait-killed.c rename grading/userprog/wait-killed.exp => src/tests/userprog/wait-killed.ck (58%) create mode 100644 src/tests/userprog/wait-simple.c rename grading/userprog/wait-simple.exp => src/tests/userprog/wait-simple.ck (59%) create mode 100644 src/tests/userprog/wait-twice.c rename grading/userprog/wait-twice.exp => src/tests/userprog/wait-twice.ck (51%) create mode 100644 src/tests/userprog/write-bad-fd.c rename grading/userprog/write-bad-fd.exp => src/tests/userprog/write-bad-fd.ck (50%) create mode 100644 src/tests/userprog/write-bad-ptr.c create mode 100644 src/tests/userprog/write-bad-ptr.ck create mode 100644 src/tests/userprog/write-boundary.c create mode 100644 src/tests/userprog/write-boundary.ck create mode 100644 src/tests/userprog/write-normal.c create mode 100644 src/tests/userprog/write-normal.ck create mode 100644 src/tests/userprog/write-stdin.c create mode 100644 src/tests/userprog/write-stdin.ck create mode 100644 src/tests/userprog/write-zero.c create mode 100644 src/tests/userprog/write-zero.ck create mode 100644 src/tests/vm/Make.tests create mode 100644 src/tests/vm/child-inherit.c rename {grading => src/tests}/vm/child-linear.c (72%) create mode 100644 src/tests/vm/child-mm-wrt.c create mode 100644 src/tests/vm/child-sort.c create mode 100644 src/tests/vm/mmap-bad-fd.c create mode 100644 src/tests/vm/mmap-bad-fd.ck create mode 100644 src/tests/vm/mmap-clean.c create mode 100644 src/tests/vm/mmap-clean.ck create mode 100644 src/tests/vm/mmap-close.c create mode 100644 src/tests/vm/mmap-close.ck create mode 100644 src/tests/vm/mmap-exit.c create mode 100644 src/tests/vm/mmap-exit.ck create mode 100644 src/tests/vm/mmap-inherit.c create mode 100644 src/tests/vm/mmap-inherit.ck create mode 100644 src/tests/vm/mmap-misalign.c create mode 100644 src/tests/vm/mmap-misalign.ck create mode 100644 src/tests/vm/mmap-null.c create mode 100644 src/tests/vm/mmap-null.ck create mode 100644 src/tests/vm/mmap-over-code.c create mode 100644 src/tests/vm/mmap-over-code.ck create mode 100644 src/tests/vm/mmap-over-data.c create mode 100644 src/tests/vm/mmap-over-data.ck create mode 100644 src/tests/vm/mmap-over-stk.c create mode 100644 src/tests/vm/mmap-over-stk.ck create mode 100644 src/tests/vm/mmap-overlap.c create mode 100644 src/tests/vm/mmap-overlap.ck create mode 100644 src/tests/vm/mmap-read.c create mode 100644 src/tests/vm/mmap-read.ck create mode 100644 src/tests/vm/mmap-remove.c create mode 100644 src/tests/vm/mmap-remove.ck create mode 100644 src/tests/vm/mmap-shuffle.c create mode 100644 src/tests/vm/mmap-shuffle.ck create mode 100644 src/tests/vm/mmap-twice.c create mode 100644 src/tests/vm/mmap-twice.ck create mode 100644 src/tests/vm/mmap-unmap.c create mode 100644 src/tests/vm/mmap-unmap.ck create mode 100644 src/tests/vm/mmap-write.c create mode 100644 src/tests/vm/mmap-write.ck create mode 100644 src/tests/vm/mmap-zero.c create mode 100644 src/tests/vm/mmap-zero.ck create mode 100644 src/tests/vm/page-linear.c create mode 100644 src/tests/vm/page-linear.ck rename {grading => src/tests}/vm/page-merge-par.c (59%) create mode 100644 src/tests/vm/page-merge-par.ck rename {grading => src/tests}/vm/page-merge-seq.c (57%) rename grading/vm/page-merge-seq.exp => src/tests/vm/page-merge-seq.ck (76%) create mode 100644 src/tests/vm/page-parallel.c create mode 100644 src/tests/vm/page-parallel.ck create mode 100644 src/tests/vm/page-shuffle.c create mode 100644 src/tests/vm/page-shuffle.ck create mode 100644 src/tests/vm/process_death.pm create mode 100644 src/tests/vm/pt-bad-addr.c create mode 100644 src/tests/vm/pt-bad-addr.ck create mode 100644 src/tests/vm/pt-bad-read.c create mode 100644 src/tests/vm/pt-bad-read.ck create mode 100644 src/tests/vm/pt-big-stk-obj.c create mode 100644 src/tests/vm/pt-big-stk-obj.ck create mode 100644 src/tests/vm/pt-grow-bad.c create mode 100644 src/tests/vm/pt-grow-bad.ck create mode 100644 src/tests/vm/pt-grow-pusha.c create mode 100644 src/tests/vm/pt-grow-pusha.ck create mode 100644 src/tests/vm/pt-grow-stack.c create mode 100644 src/tests/vm/pt-grow-stack.ck create mode 100644 src/tests/vm/pt-write-code-2.c create mode 100644 src/tests/vm/pt-write-code.c create mode 100644 src/tests/vm/pt-write-code.ck create mode 100644 src/tests/vm/pt-write-code2.ck rename {grading => src/tests}/vm/sample.inc (100%) rename {grading => src/tests}/vm/sample.txt (100%) delete mode 100644 src/threads/test.c delete mode 100644 src/threads/test.h create mode 100755 src/utils/pintos-mkdisk diff --git a/AUTHORS b/AUTHORS index f7b22b9..5f7cf44 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,12 +1,13 @@ -src +src -*- text -*- --- * Most code written by Ben Pfaff . -* The structure and form of this operating system is inspired by the - Nachos system from the University of California, Berkeley. A few of - the source files are more-or-less literal translations of the Nachos - C++ code into C. These files bear the original UCB license notice. +* The original structure and form of this operating system is inspired + by the Nachos system from the University of California, Berkeley. A + few of the source files are more-or-less literal translations of the + Nachos C++ code into C. These files bear the original UCB license + notice. * Some of the source code is derived from code used in the Massachusetts Institute of Technology's 6.828 advanced operating @@ -33,11 +34,7 @@ projects If you're not on this list but should be, please let me know. -* The section on multilevel feedback schedulers in the threads project - is adapted from a handout written by Andrea Arpaci-Dusseau - <"dusseau" at the server cs.wisc.edu>. - * Updates for Pintos by Ben Pfaff . -* Example code in the tour's description of monitors is from Mendel - Roseblum's classroom slides. +* Example code for condition variables is from classroom slides + originally by Dawson Engler and updated by Mendel Roseblum. diff --git a/LICENSE b/LICENSE index 30e57e7..d6e8392 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,34 @@ -Code derived from Nachos is subject to the following license: +Most of Pintos is subject to the following license: + + Copyright 2004 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. @@ -24,8 +54,8 @@ Code derived from Nachos is subject to the following license: MODIFICATIONS. */ -Code derived from MIT's 6.828 course code is subject to the following -license: +Also, code derived from MIT's 6.828 course code is subject to the +following license: /* * Copyright (C) 1997 Massachusetts Institute of Technology @@ -62,28 +92,3 @@ license: * holders listed in the AUTHORS file. The rest of this file is covered by * the copyright notices, if any, listed below. */ - -Other code, and modifications to the above code made for this project, -is subject to the following license: - -Copyright 2004 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. diff --git a/Makefile b/Makefile index d1eab01..2c374e7 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ -SUBDIRS = src grading doc tests +CLEAN_SUBDIRS = src doc tests all:: - @echo "This makefile has only 'clean' targets." + @echo "This makefile has only 'clean' and 'check' targets." clean:: - for d in $(SUBDIRS); do $(MAKE) -C $$d $@; done + for d in $(CLEAN_SUBDIRS); do $(MAKE) -C $$d $@; done distclean:: clean find . -name '*~' -exec rm '{}' \; diff --git a/TODO b/TODO index e16216c..3d3207c 100644 --- a/TODO +++ b/TODO @@ -1,45 +1,34 @@ -*- text -*- -* Test pintos with GSX. +* Put time limits on tests. -* Need targets for checking single tests or single subdirs +* `make grade' - - `make check' should say how to get more information +* We need better and more example programs. - - `make recheck', `make clean-check' + - Need an mmap example program as a replacement for the crappy mmap FAQ + question. -* Need to put time limits on tests. +* Give advice on how to use `diff' to find out what has changed -* Add a way for students to prints messages ignored by the autograder. +* pintos script doesn't (always?) delete temp disks - - Ignore text between /* and */. +* GNU make 3.80 or later is required. - - Allow for a comment on `exit' lines? +* Finish writing tour. - . Get rid of LOAD_ERROR() macro by calling thread_exit() directly. -* Improve automatic interpretation of exception messages. -* We need better and more example programs. - - Need an mmap example program as a replacement for the crappy mmap FAQ - question. - - How about `diff' and `cmp' programs? +* process_death test needs improvement -* Threads: +* Internal tests. - - mlfqs tests suck. They aren't even correct, e.g. the amarv - submission from win0405 is graded incorrectly. +* Improve automatic interpretation of exception messages. * Userprog project: - - Don't emphasize that stuff needs to be copied from user space to - kernel space. Instead, emphasize validation and suggest that - copying is a common solution and that it will be necessary in - project 3 and in real OSes. Also revise the grading criteria to - match. - - Mark read-only pages as actually read-only in the page table. Or, since this was consistently rated as the easiest project by the students, require them to do it. @@ -50,41 +39,21 @@ Alternately we could just remove the synchronization on pid selection and check that students fix it. -* VM project: - - - Discuss the perils of mixing dirty bits between kernel and user virtual - memory. - * Filesys project: - - Increase maximum disk size from 8 MB to something that actually - requires doubly indirect nodes. There is a negative pressure here - from the bitmap object--perhaps we need a specialized bitmap that - doesn't have to be all in-memory at once. - - Alternatively, shrink the inode size. - - - Add option to disable buffer cache. - - Need a better way to measure performance improvement of buffer cache. Some students reported that their system was slower with cache--likely, Bochs doesn't simulate a disk with a realistic speed. - - Clarify effect of remove(cwd). - * Documentation: - - Finish writing tour. - - Add "Digging Deeper" sections that describe the nitty-gritty x86 details for the benefit of those interested. - Add explanations of what "real" OSes do to give students some perspective. - - GNU make 3.80 or later is required. - * Assignments: - Add extra credit: @@ -98,20 +67,3 @@ . opendir/readdir/closedir . everything needed for getcwd() - -* Tests: - - - The threads, userprog, vm test source files could use - factorization and cleanup along the lines of fslib in the filesys - tests. - - - The p1-4.c testcase needs significant tuning. Currently it takes - too long (especially when SHOW_PROGRESS is turned on) and doesn't - show significant improvement. - -* Code: - - - Need an optimization barrier and an explanation of it in the - documentation. - - - Need to check the wait system call more thoroughly. diff --git a/doc/44bsd.texi b/doc/44bsd.texi new file mode 100644 index 0000000..84ddf7f --- /dev/null +++ b/doc/44bsd.texi @@ -0,0 +1,347 @@ +@node 4.4BSD Scheduler, Coding Standards, References, Top +@appendix 4.4@acronym{BSD} Scheduler + +@iftex +@macro tm{TEX} +@math{\TEX\} +@end macro +@macro nm{TXT} +@end macro +@macro am{TEX, TXT} +@math{\TEX\} +@end macro +@end iftex + +@ifnottex +@macro tm{TEX} +@end macro +@macro nm{TXT} +@w{\TXT\} +@end macro +@macro am{TEX, TXT} +@w{\TXT\} +@end macro +@end ifnottex + +@ifhtml +@macro math{TXT} +\TXT\ +@end macro +@end ifhtml + +@macro m{MATH} +@am{\MATH\, \MATH\} +@end macro + +The goal of a general-purpose scheduler is to balance threads' different +scheduling needs. Threads that perform a lot of I/O require a fast +response time to keep input and output devices busy, but need little CPU +time. On the other hand, compute-bound threads need to receive a lot of +CPU time to finish their work, but have no requirement for fast response +time. Other threads lie somewhere in between, with periods of I/O +punctuated by periods of computation, and thus have requirements that +vary over time. A well-designed scheduler can often accommodate threads +with all these requirements simultaneously. + +For project 1, you must implement the scheduler described in this +appendix. Our scheduler resembles the one described in @bibref{4.4BSD}, +which is one example of a @dfn{multilevel feedback queue} scheduler. +This type of scheduler maintains several queues of ready-to-run threads, +where each queue holds threads with a different priority. At any given +time, the scheduler chooses a thread from the highest-priority non-empty +queue. If the highest-priority queue contains multiple threads, then +they run in ``round robin'' order. + +@menu +* Thread Niceness:: +* Calculating Priority:: +* Calculating recent_cpu:: +* Calculating load_avg:: +* Fixed-Point Real Arithmetic:: +@end menu + +@node Thread Niceness +@section Niceness + +Thread priority is dynamically determined by the scheduler using a +formula given below. However, each thread also has a relatively static +@dfn{nice} value between -20 and 20 that determines how ``nice'' the +thread should be to other threads. A @var{nice} of zero does not affect +thread priority. A positive @var{nice} increases the numeric priority +of a thread, decreasing its effective priority, and causes it to give up +some CPU time it would otherwise receive. On the other hand, a negative +@var{nice} tends to take away CPU time from other threads. + +The initial thread starts with a @var{nice} value of zero. Other +threads start with a @var{nice} value inherited from their parent +thread. You +must implement these functions, for which we have provided skeleton +definitions in @file{threads/thread.c}. + +@deftypefun int thread_get_nice (void) +Returns the current thread's @var{nice} value. +@end deftypefun + +@deftypefun void thread_set_nice (int @var{new_nice}) +Sets the current thread's @var{nice} value to @var{new_nice} and +recalculates the thread's priority based on the new value +(@pxref{Calculating Priority}). If the running thread no longer has the +highest priority, yields. +@end deftypefun + +@node Calculating Priority +@section Calculating Priority + +Our scheduler has 64 priorities and thus 64 ready queues, numbered 0 +(@code{PRI_MIN}) through 63 (@code{PRI_MAX}). Lower numbers correspond +to @emph{higher} priorities, so that priority 0 is the highest priority +and priority 63 is the lowest. Thread priority is calculated initially +at thread initialization. It is also recalculated once every fourth +clock tick, for every thread. In either case, it is determined by +the formula + +@center @t{@var{priority} = (@var{recent_cpu} / 4) + (@var{nice} * 2)}, + +@noindent where @var{recent_cpu} is an estimate of the CPU time the +thread has used recently (see below) and @var{nice} is the thread's +@var{nice} value. The coefficients @math{1/4} and 2 on @var{recent_cpu} +and @var{nice}, respectively, have been found to work well in practice +but lack deeper meaning. The calculated @var{priority} is always +adjusted to lie in the valid range @code{PRI_MIN} to @code{PRI_MAX}. + +This formula gives a thread that has received CPU +time recently lower priority for being reassigned the CPU the next +time the scheduler runs. This is key to preventing starvation: a +thread that has not received any CPU time recently will have a +@var{recent_cpu} of 0, which barring a high @var{nice} value should +ensure that it receives CPU time soon. + +@node Calculating recent_cpu +@section Calculating @var{recent_cpu} + +We wish @var{recent_cpu} to measure how much CPU time each process has +received ``recently.'' Furthermore, as a refinement, more recent CPU +time should be weighted more heavily than less recent CPU time. One +approach would use an array of @var{n} elements to +track the CPU time received in each of the last @var{n} seconds. +However, this approach requires O(@var{n}) space per thread and +O(@var{n}) time per calculation of a new weighted average. + +Instead, we use a @dfn{exponentially weighted moving average}, which +takes this general form: + +@center @tm{x(0) = f(0),}@nm{x(0) = f(0),} +@center @tm{x(t) = ax(t-1) + (1-a)f(t),}@nm{x(t) = a*x(t-1) + f(t),} +@center @tm{a = k/(k+1),}@nm{a = k/(k+1),} + +@noindent where @math{x(t)} is the moving average at integer time @am{t +\ge 0, t >= 0}, @math{f(t)} is the function being averaged, and @math{k +> 0} controls the rate of decay. We can iterate the formula over a few +steps as follows: + +@center @math{x(1) = f(1)}, +@center @am{x(2) = af(1) + f(2), x(2) = a*f(1) + f(2)}, +@center @am{\vdots, ...} +@center @am{x(5) = a^4f(1) + a^3f(2) + a^2f(3) + af(4) + f(5), x(5) = a**4*f(1) + a**3*f(2) + a**2*f(3) + a*f(4) + f(5)}. + +@noindent The value of @math{f(t)} has a weight of 1 at time @math{t}, a +weight of @math{a} at time @math{t+1}, @am{a^2, a**2} at time +@math{t+2}, and so on. We can also relate @math{x(t)} to @math{k}: +@math{f(t)} has a weight of approximately @math{1/e} at time @math{t+k}, +approximately @am{1/e^2, 1/e**2} at time @am{t+2k, t+2*k}, and so on. +From the opposite direction, @math{f(t)} decays to weight @math{w} at +@am{t = \log_aw, t = ln(w)/ln(a)}. + +The initial value of @var{recent_cpu} is 0 in the first thread +created, or the parent's value in other new threads. Each time a timer +interrupt occurs, @var{recent_cpu} is incremented by 1 for the running +thread only. In addition, once per second the value of @var{recent_cpu} +is recalculated for every thread (whether running, ready, or blocked), +using this formula: + +@center @t{@var{recent_cpu} = (2*@var{load_avg})/(2*@var{load_avg} + 1) * @var{recent_cpu} + @var{nice}}, + +@noindent where @var{load_avg} is a moving average of the number of +threads ready to run (see below). If @var{load_avg} is 1, indicating +that a single thread, on average, is competing for the CPU, then the +current value of @var{recent_cpu} decays to a weight of .1 in +@am{\log_{2/3}.1 \approx 6, ln(2/3)/ln(.1) = approx. 6} seconds; if +@var{load_avg} is 2, then decay to a weight of .1 takes @am{\log_{3/4}.1 +\approx 8, ln(3/4)/ln(.1) = approx. 8} seconds. The effect is that +@var{recent_cpu} estimates the amount of CPU time the thread has +received ``recently,'' with the rate of decay inversely proportional to +the number of threads competing for the CPU. + +Because of assumptions made by some of the tests, @var{recent_cpu} must +be updated exactly when the system tick counter reaches a multiple of a +second, that is, when @code{timer_ticks () % TIMER_FREQ == 0}, and not +at any other time. + +Take note that @var{recent_cpu} can be a negative quantity for a thread +with a negative @var{nice} value. Negative values of @var{recent_cpu} +are not changed to 0. + +You must implement @func{thread_get_recent_cpu}, for which there is a +skeleton in @file{threads/thread.c}. + +@deftypefun int thread_get_recent_cpu (void) +Returns 100 times the current thread's @var{recent_cpu} value, rounded +to the nearest integer. +@end deftypefun + +@node Calculating load_avg +@section Calculating @var{load_avg} + +Finally, @var{load_avg}, often known as the system load average, +estimates the average number of threads ready to run over the past +minute. Like @var{recent_cpu}, it is an exponentially weighted moving +average. Unlike @var{priority} and @var{recent_cpu}, @var{load_avg} is +system-wide, not thread-specific. At system boot, it is initialized to +0. Once per second thereafter, it is updated according to the following +formula: + +@center @t{@var{load_avg} = (59/60)*@var{load_avg} + (1/60)*@var{ready_threads}}, + +@noindent where @var{ready_threads} is the number of threads that are +either running or ready to run at time of update (not including the idle +thread). + +Because of assumptions made by some of the tests, @var{load_avg} must be +updated exactly when the system tick counter reaches a multiple of a +second, that is, when @code{timer_ticks () % TIMER_FREQ == 0}, and not +at any other time. + +You must implement @func{thread_get_load_avg}, for which there is a +skeleton in @file{threads/thread.c}. + +@deftypefun int thread_get_load_avg (void) +Returns 100 times the current system load average, rounded to the +nearest integer. +@end deftypefun + +@menu +* Fixed-Point Real Arithmetic:: +@end menu + +@node Fixed-Point Real Arithmetic +@section Fixed-Point Real Arithmetic + +In the formulas above, @var{priority}, @var{nice}, and +@var{ready_threads} are integers, but @var{recent_cpu} and @var{load_avg} +are real numbers. Unfortunately, Pintos does not support floating-point +arithmetic in the kernel, because it would +complicate and slow the kernel. Real kernels often have the same +limitation, for the same reason. This means that calculations on real +quantities must be simulated using integers. This is not +difficult, but many students do not know how to do it. This +section explains the basics. + +The fundamental idea is to treat the rightmost bits of an integer as +representing a fraction. For example, we can designate the lowest 10 +bits of a signed 32-bit integer as fractional bits, so that an integer +@var{x} represents the real number +@iftex +@m{x/2^{10}}. +@end iftex +@ifnottex +@m{x/(2**10)}, where ** represents exponentiation. +@end ifnottex +This is called a 21.10 fixed-point number representation, because there +are 21 bits before the decimal point, 10 bits after it, and one sign +bit.@footnote{Because we are working in binary, the ``decimal'' point +might more correctly be called the ``binary'' point, but the meaning +should be clear.} A number in 21.10 format represents, at maximum, a +value of @am{(2^{31} - 1) / 2^{10} \approx, (2**31 - 1)/(2**10) = +approx.} 2,097,151.999. + +Suppose that we are using a @m{p.q} fixed-point format, and let @am{f = +2^q, f = 2**q}. By the definition above, we can convert an integer or +real number into @m{p.q} format by multiplying with @m{f}. For example, +in 21.10 format the fraction 59/60 used in the calculation of +@var{load_avg}, above, is @am{(59/60)2^{10}, 59/60*(2**10)} = 1,007 +(rounded to nearest). To convert a fixed-point value back to an +integer, divide by @m{f}. (The normal @samp{/} operator in C rounds +down. To round to nearest, add @m{f / 2} before dividing.) + +Many operations on fixed-point numbers are straightforward. Let +@code{x} and @code{y} be fixed-point numbers, and let @code{n} be an +integer. Then the sum of @code{x} and @code{y} is @code{x + y} and +their difference is @code{x - y}. The sum of @code{x} and @code{n} is +@code{x + n * f}; difference, @code{x - n * f}; product, @code{x * n}; +quotient, @code{x / n}. + +Multiplying two fixed-point values has two complications. First, the +decimal point of the result is @m{q} bits too far to the left. Consider +that @am{(59/60)(59/60), (59/60)*(59/60)} should be slightly less than +1, but @tm{1,007\times 1,007}@nm{1,007*1,007} = 1,014,049 is much +greater than @am{2^{10},2**10} = 1,024. Shifting @m{q} bits right, we +get @tm{1,014,049/2^{10}}@nm{1,014,049/(2**10)} = 990, or about 0.97, +the correct answer. Second, the multiplication can overflow even though +the answer is representable. For example, 128 in 21.10 format is +@am{128 \times 2^{10}, 128*(2**10)} = 131,072 and its square @am{128^2, +128**2} = 16,384 is well within the 21.10 range, but @tm{131,072^2 = +2^{34}}@nm{131,072**2 = 2**34}, greater than the maximum signed 32-bit +integer value @am{2^{31} - 1, 2**31 - 1}. An easy solution is to do the +multiplication as a 64-bit operation. The product of @code{x} and +@code{y} is then @code{((int64_t) x) * y / f}. + +Dividing two fixed-point values has the opposite complications. The +decimal point will be too far to the right, which we fix by shifting the +dividend @m{q} bits to the left before the division. The left shift +discards the top @m{q} bits of the dividend, which we can again fix by +doing the division in 64 bits. Thus, the quotient when @code{x} is +divided by @code{y} is @code{((int64_t) x) * f / y}. + +This section has consistently used multiplication or division by @m{f}, +instead of @m{q}-bit shifts, for two reasons. First, multiplication and +division do not have the surprising operator precedence of the C shift +operators. Second, multiplication and division are well-defined on +negative operands, but the C shift operators are not. Take care with +these issues in your implementation. + +The following table summarizes how fixed-point arithmetic operations can +be implemented in C. In the table, @code{x} and @code{y} are +fixed-point numbers, @code{n} is an integer, fixed-point numbers are in +signed @m{p.q} format where @m{p + q = 31}, and @code{f} is @code{1 << +q}: + +@html +
+@end html +@multitable @columnfractions .5 .5 +@item Convert @code{n} to fixed point: +@tab @code{n * f} + +@item Convert @code{x} to integer (rounding down): +@tab @code{x * f} + +@item Convert @code{x} to integer (rounding to nearest): +@tab @code{(x + f / 2) / f} + +@item Add @code{x} and @code{y}: +@tab @code{x + y} + +@item Subtract @code{y} from @code{x}: +@tab @code{x - y} + +@item Add @code{x} and @code{n}: +@tab @code{x + n * f} + +@item Subtract @code{n} from @code{x}: +@tab @code{x - n * f} + +@item Multiply @code{x} by @code{y}: +@tab @code{((int64_t) x) * y / f} + +@item Multiply @code{x} by @code{n}: +@tab @code{x * n} + +@item Divide @code{x} by @code{y}: +@tab @code{((int64_t) x) * f / y} + +@item Divide @code{x} by @code{n}: +@tab @code{x / n} +@end multitable +@html +
+@end html diff --git a/doc/Makefile b/doc/Makefile index 9e918bb..8a4d80e 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,22 +1,22 @@ -TEXIS = pintos.texi intro.texi tour.texi threads.texi mlfqs.texi \ -userprog.texi vm.texi filesys.texi references.texi standards.texi \ -doc.texi devel.texi debug.texi +TEXIS = pintos.texi intro.texi tour.texi threads.texi userprog.texi \ +vm.texi filesys.texi references.texi standards.texi doc.texi \ +sample.tmpl devel.texi debug.texi 44bsd.texi all: pintos.html pintos.info pintos.dvi pintos.ps pintos.pdf -pintos.html: $(TEXIS) mlfqs1.png mlfqs2.png - ./texi2html -toc_file=$@ -split=chapter -nosec_nav -nomenu -init_file pintos-t2h.init -nonumber $< +pintos.html: $(TEXIS) texi2html + ./texi2html -toc_file=$@ -split=chapter -nosec_nav -nomenu -init_file pintos-t2h.init $< pintos.info: $(TEXIS) makeinfo $< -pintos.dvi: $(TEXIS) mlfqs1.eps mlfqs2.eps +pintos.dvi: $(TEXIS) texi2dvi $< -o $@ pintos.ps: pintos.dvi dvips $< -o $@ -pintos.pdf: $(TEXIS) mlfqs1.pdf mlfqs2.pdf +pintos.pdf: $(TEXIS) texi2pdf $< -o $@ %.eps: %.jgr @@ -30,7 +30,7 @@ pintos.pdf: $(TEXIS) mlfqs1.pdf mlfqs2.pdf clean: rm -f *.info *.html *.png - rm -f *.dvi *.pdf *.ps *.log + rm -f *.dvi *.pdf *.ps *.log *~ rm -rf WWW dist: pintos.html pintos.pdf diff --git a/doc/debug.texi b/doc/debug.texi index 389080d..1c6d7a3 100644 --- a/doc/debug.texi +++ b/doc/debug.texi @@ -7,53 +7,56 @@ introduces you to a few of them. @menu * printf:: * ASSERT:: -* UNUSED NO_RETURN NO_INLINE PRINTF_FORMAT:: +* Function and Parameter Attributes:: * Backtraces:: -* i386-elf-gdb:: +* gdb:: * Debugging by Infinite Loop:: * Modifying Bochs:: * Debugging Tips:: @end menu @node printf -@section @code{@code{printf()}} +@section @code{printf()} Don't underestimate the value of @func{printf}. The way @func{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. +an interrupt handler, almost regardless of what locks are held (but see +@ref{printf Reboots} for a counterexample). -@func{printf} isn't useful just because it can print data members. +@func{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 @func{print} with different strings -(e.g.@: @code{"1\n"}, @code{"2\n"}, @dots{}) throughout the pieces of -code you suspect are failing. If you don't even see @code{1} printed, -then something bad happened before that point, if you see @code{1} -but not @code{2}, then something bad happened between those two +(e.g.@: @code{"<1>"}, @code{"<2>"}, @dots{}) throughout the pieces of +code you suspect are failing. If you don't even see @code{<1>} printed, +then something bad happened before that point, if you see @code{<1>} +but not @code{<2>}, then something bad happened between those two points, and so on. Based on what you learn, you can then insert more @func{printf} calls in the new, smaller region of code you suspect. Eventually you can narrow the problem down to a single statement. +@xref{Debugging by Infinite Loop}, for a related technique. @node ASSERT @section @code{ASSERT} Assertions are useful because they can catch problems early, before -they'd otherwise be notices. Pintos provides a macro for assertions -named @code{ASSERT}, defined in @file{}, that you can use for -this purpose. Ideally, each function should begin with a set of +they'd otherwise be noticed. Pintos provides the +@code{ASSERT}, defined in @file{}, for assertions. +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. When an assertion proves untrue, the kernel panics. The panic message should help you to find the problem. See the description of backtraces below for more information. -@node UNUSED NO_RETURN NO_INLINE PRINTF_FORMAT -@section UNUSED, NO_RETURN, NO_INLINE, and PRINTF_FORMAT +@node Function and Parameter Attributes +@section Function and Parameter Attributes These macros defined in @file{} tell the compiler special attributes of a function or function parameter. Their expansions are @@ -78,11 +81,11 @@ backtraces (see below). @end defmac @defmac PRINTF_FORMAT (@var{format}, @var{first}) -Appended to a function prototype to tell the compiler that the -function takes a @func{printf}-like format string as its -@var{format}th argument and that the corresponding value arguments -start at the @var{first}th argument. This lets the compiler tell you -if you pass the wrong argument types. +Appended to a function prototype to tell the compiler that the function +takes a @func{printf}-like format string as the argument numbered +@var{format} (starting from 1) and that the corresponding value +arguments start at the argument numbered @var{first}. This lets the +compiler tell you if you pass the wrong argument types. @end defmac @node Backtraces @@ -92,16 +95,15 @@ 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 @func{debug_backtrace}, prototyped in -@file{}, at any point in your code. +@file{}, to print a backtrace at any point in your code. The addresses in a backtrace are listed as raw hexadecimal numbers, -which are meaningless in themselves. You can translate them into +which are meaningless by themselves. You can translate them into function names and source file line numbers using a tool called -@command{i386-elf-addr2line}.@footnote{If you're using an 80@var{x}86 -system for development, it's probably just called -@command{addr2line}.} +@command{addr2line} (80@var{x}86) or @command{i386-elf-addr2line} +(SPARC). -The output format of @command{i386-elf-addr2line} is not ideal, so +The output format of @command{addr2line} is not ideal, so we've supplied a wrapper for it simply called @command{backtrace}. Give it the name of your @file{kernel.o} as the first argument and the hexadecimal numbers composing the backtrace (including the @samp{0x} @@ -116,6 +118,11 @@ Alternatively, it could be that the @file{kernel.o} you passed to @command{backtrace} does not correspond to the kernel that produced the backtrace. +Sometimes backtraces can be confusing without implying corruption. +Compiler optimizations can cause surprising behavior. For example, when +a function has called another function as its final action (a @dfn{tail +call}), the calling function may not appear in a backtrace at all. + @menu * Backtrace Example:: @end menu @@ -136,11 +143,11 @@ You would then invoke the @command{backtrace} utility like shown below, cutting and pasting the backtrace information into the command line. This assumes that @file{kernel.o} is in the current directory. You would of course enter all of the following on a single shell command -line: +line, even though that would overflow our margins here: @example -backtrace kernel.o 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 -0xc010325a 0x804812c 0x8048a96 0x8048ac8 +backtrace kernel.o 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 +0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8 @end example The backtrace output would then look something like this: @@ -159,14 +166,14 @@ The backtrace output would then look something like this: (You will probably not get the same results if you run the command above on your own kernel binary, because the source code you compiled from is -different from the source code that emitted the panic message.) +different from the source code that panicked.) The first line in the backtrace refers to @func{debug_panic}, the function that implements kernel panics. Because backtraces commonly result from kernel panics, @func{debug_panic} will often be the first function shown in a backtrace. -The second line shows @func{file_seek} to be the function that panicked, +The second line shows @func{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 @file{filesys/file.c} is the assertion @@ -176,6 +183,7 @@ ASSERT (file_ofs >= 0); @end example @noindent +(This line was also cited in the assertion failure message.) Thus, @func{file_seek} panicked because it passed a negative file offset argument. @@ -208,9 +216,9 @@ The results look like this: 0xc010cf67: ?? (??:0) 0xc0102319: ?? (??:0) 0xc010325a: ?? (??:0) -0x804812c: test_main (/home/blp/cs140/pintos/grading/filesys/grow-too-big.c:20) -0x8048a96: main (/home/blp/cs140/pintos/grading/filesys/fsmain.c:10) -0x8048ac8: _start (../../src/lib/user/entry.c:9) +0x804812c: test_main (../../tests/filesys/extended/grow-too-big.c:20) +0x8048a96: main (../../tests/main.c:10) +0x8048ac8: _start (../../lib/user/entry.c:9) @end example Here's an extra tip for anyone who read this far: @command{backtrace} @@ -224,27 +232,27 @@ backtrace kernel.o Call stack: 0xc0106eff 0xc01102fb 0xc010dc22 0xc010cf67 0xc0102319 0xc010325a 0x804812c 0x8048a96 0x8048ac8. @end example -@node i386-elf-gdb -@section @command{i386-elf-gdb} +@node gdb +@section @command{gdb} You can run the Pintos kernel under the supervision of the -@command{i386-elf-gdb} debugger.@footnote{If you're using an -80@var{x}86 system for development, it's probably just called -@command{gdb}.} There are two steps in the process. First, +@command{gdb} (80@var{x}86) or @command{i386-elf-gdb} (SPARC) +debugger. First, start Pintos with the @option{--gdb} option, e.g.@: @command{pintos ---gdb run}. Second, in a second terminal, invoke @command{gdb} on +--gdb -- run mytest}. Second, in a separate terminal, invoke @command{gdb} (or +@command{i386-elf-gdb}) on @file{kernel.o}: @example -i386-elf-gdb kernel.o +gdb kernel.o @end example @noindent and issue the following @command{gdb} command: @example target remote localhost:1234 @end example -At this point, @command{gdb} is connected to Bochs over a local +Now @command{gdb} is connected to the simulator over a local network connection. You can now issue any normal @command{gdb} -commands. If you issue the @samp{c} command, the Bochs BIOS will take +commands. If you issue the @samp{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 @key{Ctrl+C}. If you want @command{gdb} to stop when Pintos starts running, set a breakpoint on @@ -256,8 +264,7 @@ terminal command prompt, or you can view it in Emacs with the command @table @code @item c -Continue execution until the next breakpoint or until @key{Ctrl+C} is -typed. +Continues execution until @key{Ctrl+C} or the next breakpoint. @item break @var{function} @itemx break @var{filename}:@var{linenum} @@ -267,8 +274,8 @@ Sets a breakpoint at the given function, line number, or address. @item p @var{expression} Evaluates the given C expression and prints its value. -If the expression contains a function call, the function will actually -be executed, so be careful. +If the expression contains a function call, that function will actually +be executed. @item l *@var{address} Lists a few lines of code around the given address. @@ -282,13 +289,16 @@ Prints a stack backtrace similar to that output by the Prints the name of the function or variable that occupies the given address. (Use a @samp{0x} prefix to specify an address in hex.) + +@item diassemble @var{function} +Disassembles the specified @var{function}. @end table If you notice other strange behavior while using @command{gdb}, there -are three possibilities. The first is that there is a bug in your -modified Pintos. The second is that there is a bug in Bochs's -interface to @command{gdb} or in @command{gdb} itself. The third is -that there is a bug in the original Pintos code. The first and second +are three possibilities: a bug in your +modified Pintos, a bug in Bochs's +interface to @command{gdb} or in @command{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. @@ -298,6 +308,7 @@ program's symbol table: @example add-symbol-file @var{program} @end example +@noindent where @var{program} is the name of the program's executable (in the host file system, not in the Pintos file system). After this, you should be able to debug the user program the same way you would the kernel, by @@ -312,7 +323,7 @@ name on the @command{gdb} command line, instead of @file{kernel.o}.) @section Debugging by Infinite Loop If you get yourself into a situation where the machine reboots in a -loop, you've probably hit a ``triple fault.'' In such a situation you +loop, that's probably a ``triple fault.'' In such a situation you might not be able to use @func{printf} for debugging, because the reboots might be happening even before everything needed for @func{printf} is initialized. In such a situation, you might want to @@ -326,7 +337,7 @@ possibilities: @item The machine hangs without rebooting. If this happens, you know that the infinite loop is running. That means that whatever caused the -problem must be @emph{after} the place you inserted the infinite loop. +reboot must be @emph{after} the place you inserted the infinite loop. Now move the infinite loop later in the code sequence. @item @@ -363,7 +374,7 @@ suggestions are in the patch file). Finally, run @command{make}. This will compile Bochs and eventually produce a new binary @file{bochs}. To use your @file{bochs} binary with @command{pintos}, put it in your @env{PATH}, and make sure that it is earlier than -@file{/usr/class/cs140/i386/bochs}. +@file{/usr/class/cs140/`uname -m`/bochs}. 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 diff --git a/doc/devel.texi b/doc/devel.texi index 86c47f4..6496b4f 100644 --- a/doc/devel.texi +++ b/doc/devel.texi @@ -23,7 +23,7 @@ In Emacs, use @kbd{M-.} to follow a tag in the current window, @kbd{C-x 4 .} in a new window, or @kbd{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, @kbd{M-0 -M-.} will jump to the next one. To jump back to where you were before +M-.} jumps to the next one. To jump back to where you were before you followed the last tag, use @kbd{M-*}. @node CVS @@ -38,9 +38,9 @@ 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. -CVS is not the best version control system out there. However, it's -free, it's ubiquitous, and it's fairly easy to use. More to the -point, it's already available on the Leland machines you're using for +CVS is not the best version control system out there, but it's +free, it's fairly easy to use, and +it's already available on the Leland machines you're using for the projects. For more information, visit the @uref{https://www.cvshome.org/, , CVS @@ -53,7 +53,7 @@ SourceForge is a web-based system for facilitating software development. It provides you with a version-control system (typically CVS, as described above) and other tools for tracking your software. You can use it to store files, track bugs, and post notes about -development progress. It's also free. You can set up your own +development progress. You can set up your own project in SourceForge at @uref{http://sourceforge.net, , sourceforge.net}. diff --git a/doc/doc.texi b/doc/doc.texi index 44f6e7d..90edb1e 100644 --- a/doc/doc.texi +++ b/doc/doc.texi @@ -1,138 +1,57 @@ @node Project Documentation, Debugging Tools, Coding Standards, Top @appendix Project Documentation -When you submit your projects, you will be expected to also turn in -three files documenting them: @file{README}, @file{DESIGNDOC} and -@file{TESTCASE}. These guidelines explain what we want to see in -those files. - -Your submission should have exactly one of each file, in the -appropriate directory (e.g.@: for Assignment 1, place the files in the -@file{threads} directory). These files must be written in plain text -format (not Microsoft Word, not PDF). We recommend a text width of 65 -characters per line, with a hard limit of 80. If you use tab characters -in your document files, be sure that your text editor's tab width is set -to 8. +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. @menu -* README:: -* DESIGNDOC:: -* TESTCASE:: +* Sample Assignment:: +* Sample Design Document:: @end menu -@node README -@section @file{README} - -This is the easiest of the bunch. It's the document we'll read while -trying to get your assignment to run. First, place all of your names -and Leland IDs (usernames) at the top. Next, you should also explain -any quirks with your program, such as known show-stopper bugs, weird -dependencies, and so on. - -If you added extra credit features to your project, explain them -concisely in the @file{README}. Otherwise, we're likely to miss them. - -@node DESIGNDOC -@section @file{DESIGNDOC} - -This file is our primary tool for assessing your design. Therefore, -you should be certain to explain your design in some decent amount of -detail. As a broad rule of thumb, we should be able to explain what -basic data structures and algorithms you used to solve the problem -after reading the @file{DESIGNDOC}, but without looking at the code. - -The easiest way to present your @file{DESIGNDOC} is to break it down -by parts of the project (e.g.@: for project 1, you can break the -@file{DESIGNDOC} into four parts). For each part, you should describe -the functionality that needs to be added, the data structures and -algorithms used to solve that problem from a high level, and the -motivations for each decision/change you make to the code. Your -@file{DESIGNDOC} needs to explain and justify your design. That is, -we should be able to understand what you did, and why you chose to do -it that way. The ``why'' should be in concrete terms of greater speed, -better space efficiency, cleaner code, or some other real measure of -goodness. - -Things you @emph{don't} need: an explanation of the pre-existing -Pintos code, an explanation of the project spec, justification for the -project (e.g.@: we don't need you to explain to us why filesystems are -important to an operating system), a play-by-play of every change you -made to the system, any other pontificating. (You may laugh at some -of the things listed above, but we've gotten all of them in the past.) -The @file{DESIGNDOC} needs to discuss design decisions and trade-offs -and the rationales behind them. Any other things can be left out, to -save your time and ours. - -Finally, please keep your @file{DESIGNDOC} as short as you can, -without sacrificing key design decisions. You should be brief, yet -complete. We don't want to read novels. - -@node TESTCASE -@section @file{TESTCASE} - -The @file{TESTCASE} file should contain all the information about how -you tested your programs. At minimum, this file should contain the -output from all the tests that we've provided, with any explanations -needed to reproduce the output (arguments to the program, turning -features on the program on or off, and so on). - -Additionally, you should detail all tests you write yourself. You are -expected to write tests for features which our tests don't cover, and -to write some additional stress tests, since our tests will not -necessarily be too strenuous. If you feel that such tests are not -required, you should explain why you feel so. For each test that you -write, explain how we can use them, and show some sample output from a -run. - -Here are some pointers for writing @file{TESTCASE} files: - -@itemize @bullet -@item -Show us that you tested each part of your assignment. - -@item -Clearly state in your @file{TESTCASE} file what each test is supposed -to test. You should be testing not only the common case, but testing -corner cases. Specify what criteria or issue is being tested. - -@item -Make your tests as succinct as possible. - -@item -Your test cases should be placed in a subdirectory called -@file{testcases} within the project directory. So for project 1, they -should be in @file{pintos/src/threads/testcases}. - -@item -Think about what may actually crash your code. - -@item -Think about what the compiler might do to your code. Suppose you write -the following to test your virtual memory implementation's ability to -expand the stack: -@example -int main (void) @{ - int array[4096]; - array[123] = 234; - return 0; -@} -@end example -@noindent The compiler is quite likely to notice that the value that you -write to the array is never used again and thereby decide not to write -it at all. The result is that your test does not test anything at all. -@end itemize - -Your @file{TESTCASE} file is also where you can show us the -improvements that your code makes to the performance of the system. -You should be able to show us ``before'' and ``after'' performance -data, and explain how the data shows the improvement. For example, -for Problem 1-3, you should show us in the @file{TESTCASE} printouts -from a workload for the non-Solaris scheduler and the Solaris -scheduler and explain why the Solaris scheduler is better. - -Finally, we cannot stress enough the importance of being brief and -complete. - -Keep in mind that the quality of your testing is worth 10% of your -project grade. The bulk of this will be determined from the -@file{TESTCASE} file. +@node Sample Assignment +@section Sample Assignment + +Implement @func{thread_join}. + +@deftypefun void thread_join (tid_t @var{tid}) +Blocks the current thread until thread @var{tid} exits. If @var{A} is +the running thread and @var{B} is the argument, then we say that +``@var{A} joins @var{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 @var{A} over time had two children +@var{B} and @var{C} that were stored at the same address, then +@code{thread_join(@var{B})} and @code{thread_join(@var{C})} would be +ambiguous. + +A thread may only join its immediate children. Calling +@func{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 @var{A} has child @var{B} and @var{B} has child @var{C}, +then @var{A} always returns immediately should it try to join @var{C}, +even if @var{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 (@var{A} +joins @var{B}, then @var{B} joins @var{C}), multiple joins (@var{A} +joins @var{B}, then @var{A} joins @var{C}), and so on. +@end deftypefun + +@node Sample Design Document +@section Sample Design Document + +@verbatiminclude sample.tmpl diff --git a/doc/filesys.texi b/doc/filesys.texi index 6ebc4d9..32a6e88 100644 --- a/doc/filesys.texi +++ b/doc/filesys.texi @@ -2,52 +2,46 @@ @chapter Project 4: File Systems In the previous two assignments, you made extensive use of a -filesystem without actually worrying about how it was implemented +file system without actually worrying about how it was implemented underneath. For this last assignment, you will fill in the -implementation of the filesystem. You will be working primarily in +implementation of the file system. You will be working primarily in the @file{filesys} directory. -You should build on the code you wrote for the previous assignments. -However, if you wish, you may turn off your VM features, as they are -not vital to making the filesystem work. (You will need to edit -@file{filesys/Makefile.vars} to fully disable VM.) All of the -functionality needed for project 2 (argument passing, syscalls and -multiprogramming) must work in your filesys submission. +You may build project 4 on top of project 2 or project 3. In either +case, all of the functionality needed for project 2 must work in your +filesys submission. If you build on project 3, then all of the project +3 functionality must work also, and you will need to edit +@file{filesys/Make.vars} to enable VM functionality. A small amount of +extra credit is available if you do build on project 3. -On the other hand, one of the particular charms of working on -operating systems is being able to use what you build, and building -full-featured systems. Therefore, you should strive to make all the -parts work together so that you can run VM and your filesystem at the -same time. Plus, keeping VM is a great way to stress-test your -filesystem implementation. +@menu +* Project 4 Background:: +* Project 4 Requirements:: +* File System FAQ:: +@end menu + +@node Project 4 Background +@section Background @menu * File System New Code:: -* File System Synchronization:: -* Problem 4-1 Indexed Files:: -* Problem 4-2 File Growth:: -* Problem 4-3 Subdirectories:: -* Problem 4-4 Buffer Cache:: -* File System Design Document Requirements:: -* File System FAQ:: @end menu @node File System New Code -@section New Code +@subsection New Code Here are some files that are probably new to you. These are in the @file{filesys} directory except where indicated: @table @file @item fsutil.c -Simple utilities for the filesystem that are accessible from the +Simple utilities for the file system that are accessible from the kernel command line. @item filesys.h @itemx filesys.c -Top-level interface to the file system. Please read the long comment -near the top of @file{filesys.c}, which introduces some details of the -file system code as provided. +Top-level interface to the file system. @xref{Using the File System}, +for an introduction. @item directory.h @itemx directory.c @@ -77,56 +71,40 @@ system has calls that are similar, but not identical, to these. The file system translates these calls into physical disk operations. All the basic functionality is there in the code above, so that the -filesystem is usable right off the bat. In fact, you've been using it +file system is usable from the start, as you've seen in the previous two projects. However, it has severe limitations which you will remove. While most of your work will be in @file{filesys}, you should be prepared for interactions with all previous parts (as usual). -@node File System Synchronization -@section Synchronization +@node Project 4 Requirements +@section Requirements -The provided file system requires external synchronization, that is, -callers must ensure that only one thread can be running in the file -system code at once. Your submission should use a finer-grained -synchronization strategy. You will need to consider synchronization -issues for each type of file system object. The provided code uses the -following strategies: - -@itemize @bullet -@item -The free map and root directory are read each time they are needed for -an operation, and if they are modified, they are written back before the -operation completes. Thus, the free map is always consistent from an -external viewpoint. - -@item -Inodes are immutable in the provided file system, that is, their content -never changes between creation and deletion. Furthermore, only one copy -of an inode's data is maintained in memory at once, even if the file is -open in multiple contexts. - -@item -File data doesn't have to be consistent because it's just not part of -the model. In Unix and many other operating systems, a read of a file -by one process when the file is being written by another process can -show inconsistent results: it can show that none, all, or part of the -write has completed. (However, after the write system call returns to -its caller, all subsequent readers must see the change.) Similarly, -when two threads write to the same part of a file at the same time, -their data may be interleaved. External synchronization of the provided -file system ensures that reads and writes are fully serialized, but your -file system doesn't have to maintain full serialization as long as it -follows the rules above. -@end itemize +@menu +* Project 4 Design Document:: +* Indexed and Extensible Files:: +* Subdirectories:: +* Buffer Cache:: +* File System Synchronization:: +@end menu + +@node Project 4 Design Document +@subsection Design Document -@node Problem 4-1 Indexed Files -@section Problem 4-1: Indexed Files +Before you turn in your project, you must copy @uref{filesys.tmpl, , the +project 4 design document template} into your source tree under the name +@file{pintos/src/filesys/DESIGNDOC} and fill it in. We recommend that +you read the design document template before you start working on the +project. @xref{Project Documentation}, for a sample design document +that goes along with a fictitious project. + +@node Indexed and Extensible Files +@subsection Indexed and Extensible Files The basic file system allocates files as a single extent, making it vulnerable to external fragmentation. Eliminate this problem by -modifying the inode structure. In practice, this probably means using +modifying the on-disk inode structure. In practice, this probably means using an index structure with direct, indirect, and doubly indirect blocks. (You are welcome to choose a different scheme as long as you explain the rationale for it in your design documentation, and as long as it does @@ -138,22 +116,18 @@ stored in one disk sector, limiting the number of block pointers that it can contain. Supporting 8 MB files will require you to implement doubly-indirect blocks. -@node Problem 4-2 File Growth -@section Problem 4-2: File Growth - -Implement extensible files. In the basic file system, the file size -is specified when the file is created. One advantage of this is that -the inode data structure, once created, never changes. In UNIX and -most other file systems, a file is initially created with size 0 and -is then expanded every time a write is made off the end of the file. -Modify the file system to allow this. -Make sure that concurrent accesses to the inode remain properly -synchronized. +An extent-based file can only grow if it is followed by empty space, but +with indexed inodes file growth is possible whenever free space is +available. Implement file growth. In the basic file system, the file +size is specified when the file is created. In UNIX and most other file +systems, a file is initially created with size 0 and is then expanded +every time a write is made off the end of the file. Your file system +must allow this. There should be no predetermined limit on the size of a file, except that a disk cannot exceed the size of the disk (minus metadata). This also applies to the root directory file, which should now be allowed -to expand beyond its initial limit of ten files. +to expand beyond its initial limit of 16 files. The user is allowed to seek beyond the current end-of-file (EOF). The seek itself does not extend the file. Writing at a position past EOF @@ -168,317 +142,261 @@ until they are explicitly written. The latter file systems are said to support ``sparse files.'' You may adopt either allocation strategy in your file system. -@node Problem 4-3 Subdirectories -@section Problem 4-3: Subdirectories +@node Subdirectories +@subsection Subdirectories Implement a hierarchical name space. In the basic file system, all files live in a single directory. Modify this to allow directory -entries to point to files or to other directories. You will need -routines to parse a path name into a sequence of directories, to -change the current working directory, and to list the contents of the -current directory. For performance, allow concurrent updates to -different directories, but use mutual exclusion to ensure that updates -to the same directory are performed atomically (for example, to ensure -that a file is deleted only once). +entries to point to files or to other directories. Make sure that directories can expand beyond their original size just as any other file can. The basic file system has a 14-character limit on file names. You may retain this limit for individual file name components, or may extend -it, at your option. In any case you must allow full path names to be +it, at your option. You must allow full path names to be much longer than 14 characters. The current directory is maintained separately for each process. At -startup, the initial process has the root directory as its current -directory. When one process starts another with the @code{exec} -system call, the child process inherits its parent's current -directory. After that, the two processes' current directories are -independent, so that either changing its own current directory has no -effect on the other. +startup, the initial process's current directory is the root directory. +When one process starts another with the @code{exec} system call, the +child process inherits its parent's current directory. After that, the +two processes' current directories are independent, so that either +changing its own current directory has no effect on the other. Update the existing system calls so that, anywhere a file name is provided by the caller, an absolute or relative path name may used. Update the @code{remove} system call so that it can delete empty directories in addition to regular files. Directories can only be -deleted if they do not contain files or subdirectories. +deleted if they do not contain any files or subdirectories. Implement the following new system calls: -@table @code -@item SYS_chdir -@itemx bool chdir (const char *@var{dir}) -Attempts to change the current working directory of the process to -@var{dir}, which may be either relative or absolute. Returns true if +@deftypefn {System Call} bool chdir (const char *@var{dir}) +Changes the current working directory of the process to +@var{dir}, which may be relative or absolute. Returns true if successful, false on failure. +@end deftypefn -@item SYS_mkdir -@itemx bool mkdir (const char *dir) -Attempts to create the directory named @var{dir}, which may be either +@deftypefn {System Call} bool mkdir (const char *@var{dir}) +Creates the directory named @var{dir}, which may be relative or absolute. Returns true if successful, false on failure. Fails if @var{dir} already exists or if any directory name in @var{dir}, besides the last, does not already exist. That is, @code{mkdir("/a/b/c")} succeeds only if @file{/a/b} already exists and @file{/a/b/c} does not. +@end deftypefn -@item SYS_lsdir -@itemx void lsdir (void) +@deftypefn {System Call} void lsdir (void) Prints a list of files in the current directory to @code{stdout}, one per line, in no particular order. -@end table +@end deftypefn We have provided @command{ls} and @command{mkdir} user programs, which are straightforward once the above syscalls are implemented. In Unix, these are programs rather than built-in shell commands, but -@command{cd} is a shell command. (Why?) +@command{cd} is a shell command. The @code{pintos} @option{put} and @option{get} commands should now accept full path names, assuming that the directories used in the paths have already been created. This should not require any extra effort on your part. -@node Problem 4-4 Buffer Cache -@section Problem 4-4: Buffer Cache +You may support @file{.} and @file{..} for a small amount of extra +credit. + +@node Buffer Cache +@subsection Buffer Cache Modify the file system to keep a cache of file blocks. When a request is made to read or write a block, check to see if it is stored in the cache, and if so, fetch it immediately from the cache without going to -disk. (Otherwise, fetch the block from disk into cache, evicting an -older entry if necessary.) You are limited to a cache no greater than -64 sectors in size. Be sure to choose an intelligent cache -replacement algorithm. Experiment to see what combination of accessed, -dirty, and other information results in the best performance, as -measured by the number of disk accesses. (For example, metadata is -generally more valuable to cache than data.) Document your -replacement algorithm in your design document. - -The provided file system code uses a ``bounce buffer'' in @struct{file} -to translate the disk's sector-by-sector interface into the system call -interface's byte-by-byte interface. It needs per-file buffers because, -without them, there's no other good place to put sector -data.@footnote{The stack is not a good place because large objects -should not be allocated on the stack. A 512-byte sector is pushing the -limit there.} As part of implementing the buffer cache, you should get -rid of these bounce buffers. Instead, copy data into and out of sectors -in the buffer cache directly. You will probably need some -synchronization to prevent sectors from being evicted from the cache -while you are using them. - -In addition to the basic file caching scheme, your implementation -should also include the following enhancements: +disk. Otherwise, fetch the block from disk into cache, evicting an +older entry if necessary. You are limited to a cache no greater than 64 +sectors in size. -@table @b -@item write-behind: -Instead of always immediately writing modified data to disk, dirty -blocks can be kept in the cache and written out sometime later. Your -buffer cache should write behind whenever a block is evicted from the -cache. +Be sure to choose an intelligent cache replacement algorithm. +Experiment to see what combination of accessed, dirty, and other +information results in the best performance, as measured by the number +of disk accesses. For example, metadata is generally more valuable to +cache than data. -@item read-ahead: -Your buffer cache should automatically fetch the next block of a file -into the cache when one block of a file is read, in case that block is -about to be read. -@end table +You can keep a cached copy of the free map permanently in memory if you +like. It doesn't have to count against the cache size. -For each of these three optimizations, design a file I/O workload that -is likely to benefit from the enhancement, explain why you expect it -to perform better than on the original file system implementation, and -demonstrate the performance improvement. +The provided inode code uses a ``bounce buffer'' allocated with +@func{malloc} to translate the disk's sector-by-sector interface into +the system call interface's byte-by-byte interface. You should get rid +of these bounce buffers. Instead, copy data into and out of sectors in +the buffer cache directly. -Note that write-behind makes your filesystem more fragile in the face -of crashes. Therefore, you should -periodically write all cached blocks to disk. If you have -@func{timer_sleep} from the first project working, this is an -excellent application for it. (If you're still using the base +Your implementation should also include the following features: + +@table @b +@item write-behind: +Keep dirty blocks in the cache, instead of immediately writing modified +data to disk. Write dirty blocks to disk whenever they are evicted. +Because write-behind makes your file system more fragile in the face of +crashes, in addition you should periodically write all dirty, cached +blocks back to disk. The cache should also be written back to disk in +@func{filesys_done}, so that halting Pintos flushes the cache. + +If you have @func{timer_sleep} from the first project working, this is +an excellent application for it. If you're still using the base implementation of @func{timer_sleep}, be aware that it busy-waits, which -is not an acceptable solution.) If @func{timer_sleep}'s delays seem too +is not an acceptable solution. If @func{timer_sleep}'s delays seem too short or too long, reread the explanation of the @option{-r} option to @command{pintos} (@pxref{Debugging versus Testing}). -Likewise, read-ahead is only really useful when done asynchronously. -That is, if a process wants disk block 1 from the file, it needs to -block until disk block 1 is read in, but once that read is complete, -control should return to the process immediately while the read -request for disk block 2 is handled asynchronously. In other words, -the process will block to wait for disk block 1, but should not block -waiting for disk block 2. - -When you're implementing this, please make sure you have a scheme for -making any read-ahead and write-behind threads halt when Pintos is -``done'' (when the user program has completed, etc), so that Pintos -will halt normally and the disk contents will be consistent. - -@node File System Design Document Requirements -@section Design Document Requirements +@item read-ahead: +Your buffer cache should automatically fetch the next block of a file +into the cache when one block of a file is read, in case that block is +about to be read. -As always, submit a design document file summarizing your design. Be -sure to cover the following points: +Read-ahead is only really useful when done asynchronously. That means, +if a process requests disk block 1 from the file, it should block until disk +block 1 is read in, but once that read is complete, control should +return to the process immediately. The read-ahead request for disk +block 2 should be handled asynchronously, in the background. +@end table -@itemize @bullet -@item -How did you choose to synchronize file system operations? +@node File System Synchronization +@subsection Synchronization -@item -How did you structure your inodes? How many blocks did you access -directly, via single-indirection, and/or via double-indirection? Why? +The provided file system requires external synchronization, that is, +callers must ensure that only one thread can be running in the file +system code at once. Your submission must adopt a finer-grained +synchronization strategy that does not require external synchronization. +To the extent possible, operations on independent entities should be +independent, so that they do not need to wait on each other. + +Operations on different cache blocks must be independent. In +particular, when I/O is required on a particular block, operations on +other blocks that do not require I/O should proceed without having to +wait for the I/O to complete. + +Multiple processes must be able to access a single file at once. +Multiple reads of a single file must be able to complete without +waiting for one another. When writing to a file does not extend the +file, multiple processes should also be able to write a single file at +once. A read of a file by one process when the file is being written by +another process is allowed to show that none, all, or part of the write +has completed. (However, after the @code{write} system call returns to +its caller, all subsequent readers must see the change.) Similarly, +when two processes simultaneously write to the same part of a file, +their data may be interleaved. -@item -How did you structure your buffer cache? How did you perform a lookup -in the cache? How did you choose elements to evict from the cache? +On the other hand, extending a file and writing data into the new +section must be atomic. Suppose processes A and B both have a given +file open and both are positioned at end-of-file. If A reads and B +writes the file at the same time, A may read all, part, or none of what +B writes. However, A may not read data other than what B writes, e.g.@: +if B's data is all nonzero bytes, A is not allowed to see any zeros. -@item -How and when did you flush the cache? -@end itemize +Operations on different directories should take place concurrently. +Operations on the same directory may wait for one another. @node File System FAQ @section FAQ -@enumerate 1 -@item -@b{What extra credit opportunities are available for this assignment?} - -@itemize @bullet -@item -We'll give out extra credit to groups that implement Unix-style -support for @file{.} and @file{..} in relative paths in their projects. - -@item -We'll give some extra credit if you submit with VM enabled. If you do -this, make sure you show us that you can run multiple programs -concurrently. A particularly good demonstration is running -@file{capitalize} (with a reduced words file that fits comfortably on -your disk, of course). So submit a file system disk that contains a -VM-heavy program like @file{capitalize}, so we can try it out. And also -include the results in your test case file. - -We feel that you will be much more satisfied with your cs140 ``final -product'' if you can get your VM working with your file system. It's -also a great stress test for your FS, but obviously you have to be -pretty confident with your VM if you're going to submit this extra -credit, since you'll still lose points for failing FS-related tests, -even if the problem is in your VM code. - -@item -A point of extra credit can be assigned if a user can recursively -remove directories from the shell command prompt. Note that the -typical semantic is to just fail if a directory is not empty. -@end itemize - -Make sure that you discuss any extra credit in your @file{README} -file. We're likely to miss it if it gets buried in your design -document. - -@item -@b{What exec modes for running Pintos do I absolutely need to -support?} - -You also need to support the @option{-f}, @option{-ci}, @option{-co}, -and @option{-ex} flags individually, and you need to handle them when -they're combined, like this: @samp{pintos -f -ci shell 12345 -ex -"shell"}. Thus, you should be able to treat the above as equivalent to: - -@example -pintos -f -pintos -ci shell 12345 -pintos -ex "shell" -@end example - -If you don't change the filesystem interface, none of this should -require any special effort on your part. They are already implemented -properly in @file{threads/init.c} and @file{filesys/fsutil.c}. - -You must also implement the @option{-q} option and make sure that data -gets flushed out to disk properly when it is used. - -@item -@b{Will you test our file system with a different @code{DISK_SECTOR_SIZE}?} +@table @b +@item How much code will I need to write? + +Here's a summary of our reference solution, produced by the +@command{diffstat} program. The final row gives total lines inserted +and deleted; a changed line counts as both an insertion and a deletion. + +This summary is relative to the Pintos base code, but we started from +the reference solution to project 3. Thus, the reference solution runs +with virtual memory enabled. @xref{Project 3 FAQ}, for the summary +of project 3. + +@verbatim + Makefile.build | 5 + devices/timer.c | 42 ++ + filesys/Make.vars | 6 + filesys/cache.c | 473 +++++++++++++++++++++++++ + filesys/cache.h | 23 + + filesys/directory.c | 99 ++++- + filesys/directory.h | 3 + filesys/file.c | 4 + filesys/filesys.c | 194 +++++++++- + filesys/filesys.h | 5 + filesys/free-map.c | 45 +- + filesys/free-map.h | 4 + filesys/fsutil.c | 8 + filesys/inode.c | 444 ++++++++++++++++++----- + filesys/inode.h | 11 + threads/init.c | 5 + threads/interrupt.c | 2 + threads/thread.c | 32 + + threads/thread.h | 38 +- + userprog/exception.c | 12 + userprog/pagedir.c | 10 + userprog/process.c | 332 +++++++++++++---- + userprog/syscall.c | 582 ++++++++++++++++++++++++++++++- + userprog/syscall.h | 1 + vm/frame.c | 161 ++++++++ + vm/frame.h | 23 + + vm/page.c | 297 +++++++++++++++ + vm/page.h | 50 ++ + vm/swap.c | 85 ++++ + vm/swap.h | 11 + 30 files changed, 2721 insertions(+), 286 deletions(-) +@end verbatim + +@item What extra credit opportunities are available? + +You may implement Unix-style support for @file{.} and @file{..} in +relative paths in their projects. + +You may submit with VM enabled. + +@item Can @code{DISK_SECTOR_SIZE} change? No, @code{DISK_SECTOR_SIZE} is fixed at 512. This is a fixed property of IDE disk hardware. -@item -@b{Will the @struct{inode} take up space on the disk too?} - -Yes. Anything stored in @struct{inode} takes up space on disk, -so you must include this in your calculation of how many entires will -fit in a single disk sector. - -@item -@b{What's the directory separator character?} +@item What's the directory separator character? Forward slash (@samp{/}). -@end enumerate +@end table @menu -* Problem 4-2 File Growth FAQ:: -* Problem 4-3 Subdirectory FAQ:: -* Problem 4-4 Buffer Cache FAQ:: +* Indexed Files FAQ:: +* Subdirectories FAQ:: +* Buffer Cache FAQ:: @end menu -@node Problem 4-2 File Growth FAQ -@subsection Problem 4-2: File Growth FAQ +@node Indexed Files FAQ +@subsection Indexed Files FAQ -@enumerate 1 -@item -@b{What is the largest file size that we are supposed to support?} +@table @b +@item What is the largest file size that we are supposed to support? The disk we create will be 8 MB or smaller. However, individual files will have to be smaller than the disk to accommodate the metadata. -You'll need to consider this when deciding your @struct{inode} -organization. -@end enumerate - -@node Problem 4-3 Subdirectory FAQ -@subsection Problem 4-3: Subdirectory FAQ - -@enumerate 1 -@item -@b{What's the answer to the question in the spec about why -@command{ls} and @command{mkdir} are user programs, while @command{cd} -is a shell command?} - -Each process maintains its own current working directory, so it's much -easier to change the current working directory of the shell process if -@command{cd} is implemented as a shell command rather than as another -user process. In fact, Unix-like systems don't provide any way for -one process to change another process's current working directory. - -@item -@b{When should the @code{lsdir} system call return?} - -The @code{lsdir} system call should not return until after the -directory has been printed. Here's a code fragment, and the desired -output: - -@example -printf ("Start of directory\n"); -lsdir (); -printf ("End of directory\n"); -@end example - -This code should create the following output: +You'll need to consider this when deciding your inode organization. +@end table -@example -Start of directory -@dots{}directory contents@dots{} -End of directory -@end example +@node Subdirectories FAQ +@subsection Subdirectories FAQ -@item -@b{Do we have to implement both absolute and relative pathnames?} +@table @b +@item Why is @command{cd} a shell command? -Yes. Implementing @file{.} and @file{..} is extra credit, though. -@end enumerate +The current directory of each process is independent. A @command{cd} +program could change its own current directory, but that would have no +effect on the shell. In fact, Unix-like systems don't provide any way +for one process to change another process's current working directory. +@end table -@node Problem 4-4 Buffer Cache FAQ -@subsection Problem 4-4: Buffer Cache FAQ +@node Buffer Cache FAQ +@subsection Buffer Cache FAQ -@enumerate 1 -@item -@b{We're limited to a 64-block cache, but can we also keep an -@struct{inode_disk} inside @struct{inode}, the way the provided code -does?} +@table @b +@item Can we keep a @struct{inode_disk} inside @struct{inode}? The goal of the 64-block limit is to bound the amount of cached file system data. If you keep a block of disk data---whether file data or @@ -487,26 +405,24 @@ the 64-block limit. The same rule applies to anything that's ``similar'' to a block of disk data, such as a @struct{inode_disk} without the @code{length} or @code{sector_cnt} members. -You can keep a cached copy of the free map in memory permanently if you -like. It doesn't have to count against the cache size. - That means you'll have to change the way the inode implementation accesses its corresponding on-disk inode right now, since it currently just embeds a @struct{inode_disk} in @struct{inode} and reads the -corresponding sector in from disk when it's created. Keeping extra -copies of inodes would be cheating the 64-block limitation that we place +corresponding sector from disk when it's created. Keeping extra +copies of inodes would subvert the 64-block limitation that we place on your cache. -You can store pointers to inode data in @struct{inode}, if you want, and -you can store some other small amount of information to help you find -the inode when you need it. Similarly, if you want to store one block -of data plus some small amount of metadata for each of your 64 cache -entries, that's fine. - -If you look at @func{inode_byte_to_sector}, it uses the -@struct{inode_disk} directly without having first read in that sector -from wherever it was in the storage hierarchy. This will no longer -work. You will need to change @func{inode_byte_to_sector} so that it -reads the @struct{inode_disk} from the storage hierarchy before using -it. -@end enumerate +You can store a pointer to inode data in @struct{inode}, if you want, +and you can store other information to help you find the inode when you +need it. Similarly, you may store some metadata along each of your 64 +cache entries. + +You can keep a cached copy of the free map permanently in memory if you +like. It doesn't have to count against the cache size. + +@func{byte_to_sector} in @file{filesys/inode.c} uses the +@struct{inode_disk} directly, without first reading that sector from +wherever it was in the storage hierarchy. This will no longer work. +You will need to change @func{inode_byte_to_sector} so that it reads the +@struct{inode_disk} from the storage hierarchy before using it. +@end table diff --git a/doc/filesys.tmpl b/doc/filesys.tmpl new file mode 100644 index 0000000..1b978e9 --- /dev/null +++ b/doc/filesys.tmpl @@ -0,0 +1,145 @@ + +-------------------------+ + | CS 140 | + | PROJECT 4: FILE SYSTEMS | + | 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, and lecture notes. + + INDEXED AND EXTENSIBLE FILES + ============================ + +---- 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. + +>> What is the maximum size of a file supported by your inode structure? + +---- SYNCHRONIZATION ---- + +>> Explain how your code avoids a race if two processes attempt to extend +>> a file at the same time. + +>> Suppose processes A and B both have file F open, both positioned at +>> end-of-file. If A reads and B writes F at the same time, A may read +>> all, part, or none of what B writes. However, A may not read data +>> other than what B writes, e.g. if B writes nonzero data, A is not +>> allowed to see all zeros. Explain how your code avoids this race. + +>> Explain how your synchronization design provides "fairness". File +>> access is "fair" if readers cannot indefinitely block writers or vice +>> versa. That is, many processes reading from a file cannot prevent +>> forever another process from writing the file, and many processes +>> writing to a file cannot prevent another process forever from reading +>> the file. + +---- RATIONALE ---- + +>> Is your inode structure a multilevel index? If so, why did you choose +>> this particular combination of direct, indirect, and doubly indirect +>> blocks? If not, why did you choose an alternative inode structure, +>> and what advantages and disadvantages does your structure have, +>> compared to a multilevel index? + + SUBDIRECTORIES + ============== + +---- 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. + +---- ALGORITHMS ---- + +>> Describe your code for traversing a user-specified path. How do +>> traversals of absolute and relative paths differ? + +---- SYNCHRONIZATION ---- + +>> How do you prevent races on directory entries? For example, only one +>> of two simultaneous attempts to remove a single file should succeed, +>> as should only one of two simultaneous attempts to create a file with +>> the same name, and so on. + +>> Does your implementation allow a directory to be removed if it is in +>> use as a process's current directory? If so, what happens to that +>> process's future file system operations? If not, how do you prevent +>> it? + +---- RATIONALE ---- + +>> Explain why you chose to represent the current directory of a process +>> the way you did. + + BUFFER CACHE + ============ + +---- 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. + +---- ALGORITHMS ---- + +>> Describe how your cache replacement algorithm chooses a cache block to +>> evict. + +>> Describe your implementation of write-behind. + +>> Describe your implementation of read-ahead. + +---- SYNCHRONIZATION ---- + +>> When one process is actively reading or writing data in a buffer cache +>> block, how are other processes prevented from evicting that block? + +>> During the eviction of a block from the cache, how are other processes +>> prevented from attempting to access the block? + +---- RATIONALE ---- + +>> Describe a file workload likely to benefit from buffer caching, and +>> workloads likely to benefit from read-ahead and write-behind. + + 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 in future quarters? + +>> Any other comments? diff --git a/doc/intro.texi b/doc/intro.texi index 1c6584f..e1eefab 100644 --- a/doc/intro.texi +++ b/doc/intro.texi @@ -8,61 +8,63 @@ these in a very simple way. In the Pintos projects, you and your project team will strengthen its support in all three of these areas. You will also add a virtual memory implementation. -Pintos could, theoretically, run on a regular IBM-compatible PC. As -much fun as it might be, it is impractical to supply every student in -CS 140 with his or her own PC. Therefore, we will run Pintos projects -in a PC simulator, that is, a program that simulates an 80@var{x}86 -CPU and its peripheral devices well enough that unmodified operating +Pintos could, theoretically, run on a regular IBM-compatible PC. +Unfortunately, it is impractical to supply every CS 140 student +a dedicated PC for use with Pintos. Therefore, we will run Pintos projects +in a system simulator, that is, a program that simulates an 80@var{x}86 +CPU and its peripheral devices accurately enough that unmodified operating systems and software can run under it. In class we will use the -@uref{http://bochs.sourceforge.net, , Bochs} simulator. Pintos has -also been tested within @uref{http://fabrice.bellard.free.fr/qemu/, , -qemu} and +@uref{http://bochs.sourceforge.net, , Bochs} and +@uref{http://fabrice.bellard.free.fr/qemu/, , +qemu} simulators. Pintos has also been tested with @uref{http://www.vmware.com/products/server/gsx_features.html, , VMware GSX Server}. These projects are hard. CS 140 has a reputation of taking a lot of -time, and deservedly so. We will do what we can to ease the pain, -such as providing a lot of support material, but to some extent there -is simply some amount of hard work that needs to be done. Because -Pintos is a new system, it is also possible that some parts of the -projects are not only difficult, but more difficult than they should -be. 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. +time, and 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 proceed to any of the +should read the entire chapter before you start work on any of the projects. @menu * Getting Started:: -* Pintos License:: -* Pintos Trivia:: +* Grading:: +* License:: +* Acknowledgements:: +* Trivia:: @end menu @node Getting Started @section Getting Started To get started, you'll have to log into a machine that Pintos can be -built on. For the purposes of CS 140, our ``officially supported'' -Pintos development machines are the Sun Solaris machines managed by +built on. The CS140 ``officially supported'' +Pintos development machines are the machines in Sweet Hall managed by Stanford ITSS, as described on the -@uref{http://www.stanford.edu/dept/itss/services/cluster/environs/sweet/, -, ITSS webpage}. We will test your code on these machines, and the -instructions given here assume this environment. However, Pintos and -its supporting tools are portable enough that it should build ``out of -the box'' in other environments, including the Linux machines managed -by ITSS. +@uref{http://www.stanford.edu/services/cluster/environs/sweet/, , ITSS +webpage}. You may use the Solaris or Linux machines. We will test your +code on these machines, and the instructions given here assume this +environment. However, Pintos and its supporting tools are portable +enough that it should build ``out of the box'' in other environments. Once you've logged into one of these machines, either locally or -remotely, start out by adding our binaries directory to your -@env{PATH} environment. Under @command{csh}, the Stanford default -shell, you can do so with this command: +remotely, start out by adding our binaries directory to your @env{PATH} +environment. Under @command{csh}, Stanford's login shell, you can do so +with this command:@footnote{The term @samp{`uname -m`} expands to either +@file{sun4u} or @file{i686} according to the type of computer you're +logged into.} @example -set path = ( $path /usr/class/cs140/i386/bin ) +set path = ( $path /usr/class/cs140/`uname -m`/bin ) @end example @noindent -It might be a good idea to add this line into the @file{.cshrc} file +(Notice that both @samp{`} are left single quotes or ``backticks.'') +It is a good idea to add this line to the @file{.cshrc} file in your home directory. Otherwise, you'll have to type it every time you log in. @@ -70,26 +72,27 @@ you log in. * Source Tree Overview:: * Building Pintos:: * Running Pintos:: +* Debugging versus Testing:: @end menu @node Source Tree Overview @subsection Source Tree Overview Now you can extract the source for Pintos into a directory named -@file{pintos/src} by executing +@file{pintos/src}, by executing @example tar xzf /usr/class/cs140/pintos/pintos.tar.gz @end example -Alternatively, retrieve -@uref{http://www.stanford.edu/class/cs140/pintos/pintos.tar.gz} and -extract it in a similar way. +Alternatively, fetch +@uref{http://@/www.stanford.edu/@/class/@/cs140/@/pintos/@/pintos.@/tar.gz} +and extract it in a similar way. Let's take a look at what's inside. Here's the directory structure that you should see in @file{pintos/src}: @table @file @item threads/ -Source code for the base kernel, which you will modify starting with +Source code for the base kernel, which you will modify starting in project 1. @item userprog/ @@ -97,62 +100,71 @@ Source code for the user program loader, which you will modify starting with project 2. @item vm/ -An almost empty directory, where you will implement virtual memory in +An almost empty directory. You will implement virtual memory here in project 3. @item filesys/ -Source code for a basic file system, which you will use starting with -project 2 but which you should not modify until project 4. +Source code for a basic file system. You will use this file system +starting with project 2, but you will not modify it until project 4. @item devices/ Source code for I/O device interfacing: keyboard, timer, disk, etc. -You will improve the timer implementation in project 1, but otherwise +You will modify the timer implementation in project 1. Otherwise you should have no need to change this code. @item lib/ An implementation of a subset of the standard C library. The code in this directory is compiled into both the Pintos kernel and, starting -from project 2, user programs that run under it. Headers in this -directory can be included using the @code{#include <@dots{}>} -notation. You should have little need to modify this code. +from project 2, user programs that run under it. In both kernel code +and user programs, headers in this directory can be included using the +@code{#include <@dots{}>} notation. You should have little need to +modify this code. @item 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. +hash tables. In the kernel, headers in this +directory can be included using the @code{#include <@dots{}>} +notation. @item 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 +@code{#include <@dots{}>} notation. @item tests/ -Code for testing each project. +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. + +@item examples/ +Example user programs for use starting with project 2. @item misc/ @itemx utils/ These files may come in handy if you decide to try working with Pintos -away from ITSS's Sun Solaris machines. Otherwise, you can ignore -them. +away from the ITSS machines. Otherwise, you can ignore them. @end table @node Building Pintos @subsection Building Pintos -The next thing to do is to try building the source code supplied for +As the next step, build the source code supplied for the first project. First, @command{cd} into the @file{threads} directory. Then, issue the @samp{make} command. This will create a @file{build} directory under @file{threads}, populate it with a @file{Makefile} and a few subdirectories, and then build the kernel inside. The entire build should take less than 30 seconds. -Watch the commands executed during the build. You should notice that -the build tools' names begin with @samp{i386-elf-}, e.g.@: +Watch the commands executed during the build. On the Linux machines, +the ordinary system tools are used. On a SPARC machine, special build +tools are used, whose names begin with @samp{i386-elf-}, e.g.@: @code{i386-elf-gcc}, @code{i386-elf-ld}. These are ``cross-compiler'' -tools. That is, the build is running on a Sparc machine (called the -@dfn{host}), but the result will run on an 80@var{x}86 machine (called -the @dfn{target}). The @samp{i386-elf-@var{program}} tools, which -reside in @file{/usr/class/cs140/i386/bin}, are specially built for -this configuration. +tools. That is, the build is running on a SPARC machine (called the +@dfn{host}), but the result will run on a simulated 80@var{x}86 machine +(called the @dfn{target}). The @samp{i386-elf-@var{program}} tools are +specially built for this configuration. Following the build, the following are the interesting files in the @file{build} directory: @@ -160,7 +172,7 @@ Following the build, the following are the interesting files in the @table @file @item Makefile A copy of @file{pintos/src/Makefile.build}. It describes how to build -the kernel. @xref{Adding c or h Files}, for more information. +the kernel. @xref{Adding Source Files}, for more information. @item kernel.o Object file for the entire kernel. This is the result of linking @@ -171,17 +183,17 @@ single object file. It contains debug information, so you can run @item kernel.bin Memory image of the kernel. These are the exact bytes loaded into memory to run the Pintos kernel. To simplify loading, it is always -padded out with zero bytes so that it is an exact multiple of 4 kB in +padded out with zero bytes up to an exact multiple of 4 kB in size. @item 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. Its size is fixed by the +starts it up. It is exactly 512 bytes long, a size fixed by the PC BIOS. @item os.dsk -Disk image for the kernel, simply @file{loader.bin} followed by +Disk image for the kernel, which is just @file{loader.bin} followed by @file{kernel.bin}. This file is used as a ``virtual disk'' by the simulator. @end table @@ -194,84 +206,282 @@ recompiled when other source or header files are changed. @node Running Pintos @subsection Running Pintos -To start the kernel that you just built in the Bochs simulator, first -@command{cd} into the newly created @file{build} directory. Then -issue the command @code{pintos run}. This command will create a -@file{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 a simple test program that 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. +We've supplied a program for conveniently running Pintos in a simulator, +called @command{pintos}. In the simplest case, you can invoke +@command{pintos} as @code{pintos @var{argument}@dots{}}. Each +@var{argument} is passed to the Pintos kernel for it to act on. + +Try it out. First @command{cd} into the newly created @file{build} +directory. Then issue the command @code{pintos run alarm-multiple}, +which passes the arguments @code{run alarm-multiple} to the Pintos +kernel. In these arguments, @command{run} instructs the kernel to run a +test and @code{alarm-multiple} is the test to run. + +This command creates a @file{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 @code{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, and you just got a terminal full of corrupt-looking text, 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 @option{-v} option.) - -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 @command{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 @code{stdout}. You can log this output to a file by -redirecting at the command line, e.g.@: @code{pintos run > logfile}. - -The @command{pintos} program offers multiple options for running -Pintos. Specify these options on the command line @emph{before} the -@option{run} command. Use @code{pintos help} to see a list of the -options. You can select a simulator other than Bochs, although the -Leland systems only have Bochs installed. You can start the simulator -running a debugger (@pxref{i386-elf-gdb}). You can set the amount of -memory to give the VM. Finally, you can set up how you want VM output -to be displayed: use @option{-v} to turn off the VGA display, -@option{-t} to use your terminal window as the VGA display instead of -opening a new window, or @option{-s} to suppress the serial output to +setup, or you can use the @option{-v} option to disable X output: +@code{pintos -v -- 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 @command{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 +@code{stdout}. You can log this output to a file by redirecting at the +command line, e.g.@: @code{pintos run alarm-multiple > logfile}. + +The @command{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 @option{--}, so that the whole command looks like +@code{pintos @var{option}@dots{} -- @var{argument}@dots{}}. Invoke +@code{pintos} without any arguments to see a list of available options. +Options can select a simulator to use: the default is Bochs, but on the +Linux machines @option{--qemu} selects qemu. You can run the simulator +with a debugger (@pxref{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 @option{-v} to turn off the VGA display, @option{-t} to use your +terminal window as the VGA display instead of opening a new window +(Bochs only), or @option{-s} to suppress the serial output to @code{stdout}. -The @command{pintos} program offers commands other than @samp{run} and -@samp{help}, but we won't have any need for them until project 2. - -The Pintos kernel has its own command line that you can use to pass -options. These options must be specified @emph{after} the -@option{run} command. These options are not very interesting for now, -but you can see a list of them using the @option{-u} option, e.g.@: -@code{pintos run -u}. - -@node Pintos License -@section Pintos License - -Pintos is distributed under a liberal license that allows it to be -freely used, modified, and distributed. Students and others own their -own code and may use it for any purpose. In the context of Stanford's -CS 140 course, please respect the spirit and the letter of the honor -code by refraining from reading any homework solutions available -online or elsewhere. (Source code for other operating system kernels, -such as Linux or FreeBSD, is of course fair game.) - -There is NO WARRANTY for Pintos, not even for MERCHANTABILITY or -FITNESS FOR A PARTICULAR PURPOSE. - -Please refer to the @file{LICENSE} file at the top level of the Pintos -source distribution for details of license and lack of warranty. - -@node Pintos Trivia -@section Pintos Trivia - -The design of Pintos is inspired by Nachos, an instructional operating -system originally from UC Berkeley, and even uses a few pieces of -Nachos code. Pintos is different from Nachos in two important ways. -First, Nachos requires a host operating system such as Solaris, -whereas Pintos runs on real or simulated 80@var{x}86 hardware. -Second, Nachos is written in C++, whereas, like most real-world -operating systems, Pintos is written in C. - -The name ``Pintos'' was chosen for multiple reasons. First, it was -named for a Mexican food to reflect that it was inspired by Nachos. -Second, Pintos is small and a ``pint'' is a small quantity. Third, -like drivers of the eponymous car, students are likely to have trouble -with blow-ups. +The Pintos kernel has commands and options other than @command{run}. +These are not very interesting for now, but you can see a list of them +using @option{-h}, e.g.@: @code{pintos -h}. + +@node Debugging versus Testing +@subsection Debugging versus Testing + +When you're debugging code, it's useful to be able 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.'' The simulator we use by default, Bochs, can be set +up for +reproducibility, and that's the way that @command{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 @command{pintos} with the option @option{-j @var{seed}}, timer +interrupts will come at irregularly spaced intervals. Within a single +@var{seed} value, execution will still be reproducible, but timer +behavior will change as @var{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 @command{pintos} with +a different option, @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 @option{-j} and @option{-r} are mutually exclusive. + +On the Linux machines only, the qemu simulator is available as an +alternative to Bochs (use @option{--qemu} when invoking +@command{pintos}). The qemu simulator is much faster than Bochs, but it +only supports real-time simulation and does not have a reproducible +mode. + +@node Grading +@section Grading + +We will grade your assignments based on test results and design quality, +each of which comprises 50% of your grade. + +@menu +* Testing:: +* Design:: +@end menu + +@node Testing +@subsection Testing + +Your test result grade will be based on our tests. Each project has +several tests, each of which has a name beginning with @file{tests}. +To completely test your submission, invoke @code{make check} from the +project @file{build} directory. This will build and run each test and +print a ``pass'' or ``fail'' message for each one. When a test fails, +@command{make check} also prints some details of the reason for failure. +After running all the tests, @command{make check} also prints a summary +of the test results. + +For project 1, the tests will probably run faster in Bochs. For the +rest of the projects, they will probably run faster in qemu. + +You can also run individual tests one at a time. A given test @var{t} +writes its output to @file{@var{t}.output}, then a script scores the +output as ``pass'' or ``fail'' and writes the verdict to +@file{@var{t}.result}. To run and grade a single test, @command{make} +the @file{.result} file explicitly from the @file{build} directory, e.g.@: +@code{make tests/threads/alarm-multiple.result}. If @command{make} says +that the test result is up-to-date, but you want to re-run it anyway, +either run @code{make clean} or delete the @file{.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 @option{VERBOSE=1} on the @command{make} command line, as in +@code{make check VERBOSE=1}. You can also provide arbitrary options to the +@command{pintos} run by the tests with @option{PINTOSOPTS='@dots{}'}, +e.g.@: @code{make check PINTOSOPTS='--qemu'} to run the tests under +qemu. + +All of the tests and related files are in @file{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. + +@node Design +@subsection 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. + +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, algorithms, synchronization, and rationale. An +incomplete 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. @xref{Project +Documentation}, for a sample design document for a fictitious project. + +Design quality will also be judged based on 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 +@code{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 design quality are those that specifically +relate to the operating system issues at stake in the project. For +example, the organization of an inode is an important part of file +system design, so in the file system project a poorly designed inode +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 the self-consistent at the very least. Use horizontal and vertical +white space to make code readable. Add a comment to every structure, +structure member, global or static variable, and function definition. +Update existing comments as you modify code. Don't comment out or use +the preprocessor to ignore blocks of code. 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. + +@node License +@section License + +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. + +In the context of Stanford's CS 140 course, 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. + +Pintos comes with NO WARRANTY, not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. + +The @file{LICENSE} file at the top level of the Pintos source +distribution has full details of the license and lack of warranty. + +@node Acknowledgements +@section Acknowledgements + +Pintos and this documentation were written by Ben Pfaff +@email{blp@@cs.stanford.edu}. + +The original structure and form of Pintos was inspired by the Nachos +instructional operating system from the University of California, +Berkeley. A few of the source files were originally more-or-less +literal translations of the Nachos C++ code into C. These files bear +the original UCB license notice. + +A few of the Pintos source files are derived from code used in the +Massachusetts Institute of Technology's 6.828 advanced operating systems +course. These files bear the original MIT license notice. + +The Pintos projects and documentation originated with those designed for +Nachos by current and former CS140 teaching assistants at Stanford +University, including at least Yu Ping, Greg Hutchins, Kelly Shaw, Paul +Twohey, Sameer Qureshi, and John Rector. If you're not on this list but +should be, please let me know. + +Example code for condition variables (@pxref{Condition Variables}) is +from classroom slides originally by Dawson Engler and updated by Mendel +Roseblum. + +@node Trivia +@section 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 80@var{x}86 hardware, whereas Nachos runs as a process on a +host operating system. Second, like most real-world operating systems, +Pintos is written in C, whereas 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. diff --git a/doc/mlfqs.texi b/doc/mlfqs.texi deleted file mode 100644 index 2e229dc..0000000 --- a/doc/mlfqs.texi +++ /dev/null @@ -1,401 +0,0 @@ -@node Multilevel Feedback Scheduling, Coding Standards, References, Top -@appendix Multilevel Feedback Scheduling - -This section gives a brief overview of the behavior of the Solaris 2.6 -Time-Sharing (TS) scheduler, an example of a Multilevel Feedback Queue -scheduler. The information in this handout, in conjunction with that -given in lecture, should be used to answer Problem 1-3. The end of -this document specifies in more detail which aspects of the Solaris -scheduler that you should implement. - -The goal of a multilevel feedback queue scheduler is to fairly and -efficiently schedule a mix of processes with a variety of execution -characteristics. By controlling how a process moves between priority -levels, processes with different characteristics can be scheduled as -appropriate. Priority-based schedulers attempt to provide a -compromise between several desirable metrics (e.g.@: response time for -interactive jobs, throughput for compute-intensive jobs, and fair -allocations for all jobs). - -The queues in the system are ranked according to priority. Processes -waiting in higher priority queues are always scheduled over those in -lower priority queues. Processes at the same priority are usually -scheduled in a round-robin fashion. - -Such schedulers tend to be preemptible to support interactive -processes. That is, a higher priority process is immediately -scheduled if a lower priority process is running on the CPU. - -@menu -* Scheduling in Solaris:: -* Class Independent Functionality:: -* Time-Sharing Scheduling Class:: -* Dispatch Table:: -* Implementation:: -* Fairness:: -* Project Requirements:: -@end menu - -@node Scheduling in Solaris -@section Scheduling in Solaris - -The Solaris operating system is based on Unix System V Release 4 -(SVR4). Scheduling in Solaris, as in all SVR4-based schedulers, is -performed at two levels: class-independent routines and -class-dependent routines. Class-independent routines are those that -are responsible for dispatching and preempting processes (the -low-level mechanisms). Class-dependent routines are those that are -responsible for setting the priority of each of its processes (the -high-level policy). - -By default, Solaris supports three scheduling classes: time-sharing -(TS), real-time (RT), and system (SYS). Users with root privileges -can easily implement and add new scheduling classes by adhering to a -predefined interface. Each scheduling class gives each of its -processes a priority, the range of which is shown below: - -@multitable {Scheduling Class} {Priorities} -@item Scheduling Class @tab Priorities -@item Real-Time @tab 100-159 -@item System @tab 60-99 -@item Time-Sharing @tab 0-59 -@end multitable - -As long as a user has the correct privileges, he or she can submit -jobs to any scheduling class. By default, jobs are executed in the same -scheduling class as the parent process that forked the job. Since -your shell is running in the timesharing class, all of your jobs run -by default in the time-sharing class. - -See the man pages for @command{priocntl} on any machine running -Solaris for information on how to submit jobs to different scheduling -classes. However, since you probably don't have root privileges on -your machine, you won't be able to do much. - -To see the scheduling class of each process in the system, run -@samp{ps -edaflc}. (@samp{-c} is the flag that shows the scheduling -class.) The fourth column shows the scheduling class of the running -process. Most jobs will be running in the TS class, with a few (owned -by root) running in the SYS class. - -@example -elaine1:~> ps -edafc - UID PID PPID CLS PRI STIME TTY TIME CMD - root 0 0 SYS 96 Aug 01 ? 0:00 sched - root 1 0 TS 58 Aug 01 ? 1:06 /etc/init - - root 2 0 SYS 98 Aug 01 ? 0:02 pageout - root 3 0 SYS 60 Aug 01 ? 15:22 fsflush - root 245 239 TS 59 Aug 01 ? 0:00 ttymon - root 181 1 TS 48 Aug 01 ? 0:00 sendmail -q15m - root 239 1 TS 59 Aug 01 ? 0:00 sac -t 300 - root 96 1 TS 58 Aug 01 ? 0:00 rpcbind - root 125 1 TS 59 Aug 01 ? 0:32 syslogd -@end example - -In this document, we only discuss the Solaris time-sharing (TS) -class. Note the priorities of each of the processes, as listed in the -fifth column. - -@node Class Independent Functionality -@section Class Independent Functionality - -The class independent routines arbitrate across the scheduling -classes. This involves three basic responsibilities. - -@itemize @bullet -@item -The process with the highest priority must be dispatched, and the -state of the preempted process saved. - -@item -The class independent functions must notifying the class-dependent -routines when the state of its processes changes (for example, at -creation and termination, when a process changes from blocked to -runnable, or runnable to blocked, and when a 10ms timer expires). - -@item -Processes must be moved between priority queues in the class -independent data structures, as directed by its scheduling class, and -must be moved between blocked and ready queues. -@end itemize - -@node Time-Sharing Scheduling Class -@section Time-Sharing Scheduling Class - -The time-sharing scheduler in Solaris is an example of a multi-level -feedback queue scheduler. A job begins at priority 29. Compute-bound -jobs then filter down to the lower priorities, where they are -scheduled less frequently, but for longer time-slices. Interactive -jobs propagate to the higher priorities, where they are scheduled -whenever they have work to perform, on the assumption that they will -soon relinquish the processor again. In the TS scheduler, the -priority of a process is lowered after it consumes its allocated -time-slice. Its priority is raised if it has not consumed its -time-slice before a starvation interval expires. - -@node Dispatch Table -@section Dispatch Table - -The durations of the time-slices, the changes in priorities, and the -starvation interval are specified in a user-tunable dispatch table. -The system administrator (or anyone with root privileges) can change -the values in this table, thus configuring how the time-sharing -scheduler manages its jobs. While this has the noble intention of -allowing different systems to tune the scheduler to better handle -their workloads, in reality no one really knows how to configure these -tables well. Therefore, we will focus on the default dispatch table. - -To see how this table is configured in your system, run -@samp{dispadmin -c TS -g}. You should see something like the table -shown below. Looking at the man pages on @command{dispadmin} and -@command{ts_dptbl} may also be helpful. - -@multitable {@code{ts_quantum}} {@code{ts_tqexp}} {@code{ts_slpret}} {@code{ts_maxwait}} {@code{ts_lwait}} {priority} -@item @code{ts_quantum} @tab @code{ts_tqexp} @tab @code{ts_slpret} @tab @code{ts_maxwait} @tab @code{ts_lwait} @tab priority -@item 200 @tab 0 @tab 50 @tab 0 @tab 50 @tab 0 -@item 200 @tab 0 @tab 50 @tab 0 @tab 50 @tab 1 -@item 200 @tab 0 @tab 50 @tab 0 @tab 50 @tab 2 -@item 200 @tab 0 @tab 50 @tab 0 @tab 50 @tab 3 -@item 200 @tab 0 @tab 50 @tab 0 @tab 50 @tab 4 -@item 200 @tab 0 @tab 50 @tab 0 @tab 50 @tab 5 -@item 200 @tab 0 @tab 50 @tab 0 @tab 50 @tab 6 -@item 200 @tab 0 @tab 50 @tab 0 @tab 50 @tab 7 -@item 200 @tab 0 @tab 50 @tab 0 @tab 50 @tab 8 -@item 200 @tab 0 @tab 50 @tab 0 @tab 50 @tab 9 -@item 160 @tab 0 @tab 51 @tab 0 @tab 51 @tab 10 -@item 160 @tab 1 @tab 51 @tab 0 @tab 51 @tab 11 -@item 160 @tab 2 @tab 51 @tab 0 @tab 51 @tab 12 -@item 160 @tab 3 @tab 51 @tab 0 @tab 51 @tab 13 -@item 160 @tab 4 @tab 51 @tab 0 @tab 51 @tab 14 -@item 160 @tab 5 @tab 51 @tab 0 @tab 51 @tab 15 -@item 160 @tab 6 @tab 51 @tab 0 @tab 51 @tab 16 -@item 160 @tab 7 @tab 51 @tab 0 @tab 51 @tab 17 -@item 160 @tab 8 @tab 51 @tab 0 @tab 51 @tab 18 -@item 160 @tab 9 @tab 51 @tab 0 @tab 51 @tab 19 -@item 120 @tab 10 @tab 52 @tab 0 @tab 52 @tab 20 -@item 120 @tab 11 @tab 52 @tab 0 @tab 52 @tab 21 -@item 120 @tab 12 @tab 52 @tab 0 @tab 52 @tab 22 -@item 120 @tab 13 @tab 52 @tab 0 @tab 52 @tab 23 -@item 120 @tab 14 @tab 52 @tab 0 @tab 52 @tab 24 -@item 120 @tab 15 @tab 52 @tab 0 @tab 52 @tab 25 -@item 120 @tab 16 @tab 52 @tab 0 @tab 52 @tab 26 -@item 120 @tab 17 @tab 52 @tab 0 @tab 52 @tab 27 -@item 120 @tab 18 @tab 52 @tab 0 @tab 52 @tab 28 -@item 120 @tab 19 @tab 52 @tab 0 @tab 52 @tab 29 -@item 80 @tab 20 @tab 53 @tab 0 @tab 53 @tab 30 -@item 80 @tab 21 @tab 53 @tab 0 @tab 53 @tab 31 -@item 80 @tab 22 @tab 53 @tab 0 @tab 53 @tab 32 -@item 80 @tab 23 @tab 53 @tab 0 @tab 53 @tab 33 -@item 80 @tab 24 @tab 53 @tab 0 @tab 53 @tab 34 -@item 80 @tab 25 @tab 54 @tab 0 @tab 54 @tab 35 -@item 80 @tab 26 @tab 54 @tab 0 @tab 54 @tab 36 -@item 80 @tab 27 @tab 54 @tab 0 @tab 54 @tab 37 -@item 80 @tab 28 @tab 54 @tab 0 @tab 54 @tab 38 -@item 80 @tab 29 @tab 54 @tab 0 @tab 54 @tab 39 -@item 40 @tab 30 @tab 55 @tab 0 @tab 55 @tab 40 -@item 40 @tab 31 @tab 55 @tab 0 @tab 55 @tab 41 -@item 40 @tab 32 @tab 55 @tab 0 @tab 55 @tab 42 -@item 40 @tab 33 @tab 55 @tab 0 @tab 55 @tab 43 -@item 40 @tab 34 @tab 55 @tab 0 @tab 55 @tab 44 -@item 40 @tab 35 @tab 56 @tab 0 @tab 56 @tab 45 -@item 40 @tab 36 @tab 57 @tab 0 @tab 57 @tab 46 -@item 40 @tab 37 @tab 58 @tab 0 @tab 58 @tab 47 -@item 40 @tab 38 @tab 58 @tab 0 @tab 58 @tab 48 -@item 40 @tab 39 @tab 58 @tab 0 @tab 59 @tab 49 -@item 40 @tab 40 @tab 58 @tab 0 @tab 59 @tab 50 -@item 40 @tab 41 @tab 58 @tab 0 @tab 59 @tab 51 -@item 40 @tab 42 @tab 58 @tab 0 @tab 59 @tab 52 -@item 40 @tab 43 @tab 58 @tab 0 @tab 59 @tab 53 -@item 40 @tab 44 @tab 58 @tab 0 @tab 59 @tab 54 -@item 40 @tab 45 @tab 58 @tab 0 @tab 59 @tab 55 -@item 40 @tab 46 @tab 58 @tab 0 @tab 59 @tab 56 -@item 40 @tab 47 @tab 58 @tab 0 @tab 59 @tab 57 -@item 40 @tab 48 @tab 58 @tab 0 @tab 59 @tab 58 -@item 20 @tab 49 @tab 59 @tab 32000 @tab 59 @tab 59 -@end multitable - -You will see one row for every priority in the scheduling class, from -0 to 59. For each priority, there are five columns: - -@table @code -@item ts_quantum -Length of the time-slice. In the actual table, this value is specified -in 10@dmn{ms} clock ticks, but in the output from running -@command{dispadmin}, the value is specified in units of 1@dmn{ms}. - -@item ts_tqexp -Priority level of the new queue on which to place a process if it -exceeds its time quantum. Normally this field links to a lower -priority time-sharing level. - -@item ts_slpret -The new, generally increased, priority to adopt when the job returns -from sleeping (i.e.@: from the blocked queue) if @code{ts_dispwait} -exceeds @code{ts_maxwait}. - -@item ts_maxwait -Each time a process is placed back on the dispatcher queue after its -time quantum expires or when it is awakened, but not when it is -preempted by a higher-priority process, a per-process counter named -@code{ts_dispwait} is zeroed. This counter is incremented once per -second. If a process's @code{ts_dispwait} exceeds its priority's -@code{ts_maxwait}, then the process's priority is changed to -@code{ts_lwait}. This prevents starvation. - -@item ts_lwait -The new, generally increased, priority to adopt if the starvation -timer expires before the job consumes its time-slice (i.e.@: if -@code{ts_dispwait} exceeds @code{ts_maxwait}). -@end table - -In this table, the priority of jobs ranges from a high of 59 down to -0. Time-slices begin at 20@dmn{ms} at the highest priority and -gradually increase in duration up to 200@dmn{ms} at the lowest -priorities. Generally, the priority of a process decreases by 10 -levels after it consumes its time-slice. The priority of a process is -increased to 50 or above when the starvation timer expires. - -@node Implementation -@section Implementation - -For each job in the TS class, the following data structure is -maintained (we've removed a few of the fields for simplicity): - -@example -/* - * time-sharing class specific thread structure - */ -typedef struct tsproc @{ - long ts_timeleft; /* time remaining in quantum */ - long ts_dispwait; /* number of seconds since */ - /* start of quantum (not reset */ - /* upon preemption) */ - pri_t ts_pri; /* priority (0-59) */ - kthread_t *ts_tp; /* pointer to thread */ - struct tsproc *ts_next; /* link to next tsproc on list */ - struct tsproc *ts_prev; /* link to previous tsproc */ -@} tsproc_t; -@end example - -The @code{kthread_t} structure tracks the necessary information to -context-switch to and from this process. This structure is kept -separate from the time-sharing class to separate the -mechanisms of the dispatcher from the policies of the scheduler. - -There are seven interesting routines in the TS class: - -@table @code -@item ts_enterclass(thread *@var{t}) -Called when a new thread is added to the TS class. It initializes a -@code{tsproc} structure for this process and adds it to the list of -processes. - -@item ts_exitclass(thread *@var{t}) -Called when the thread terminates or exits the class. The -@code{tsproc} structure is removed from the list of processes. - -@item ts_tick(thread *@var{t}) -Called once every 10@dmn{ms} with a pointer to the currently running -thread. The @code{ts_timeleft} variable of the running thread is -decremented by one. If @code{ts_timeleft} reaches zero, then its new -priority becomes its old priority's @code{ts_tqexp}, its timeslice is -reset, and @code{ts_dispwait} is zeroed. The thread is then added to -the back of the appropriate priority queue and a new job is scheduled. - -@item ts_update() -Called once a second to check the starvation qualities of each job. -The routine increments the @code{ts_dispwait} counter of every process -in the class (even those that are blocked) by one. If the job is on -the ready queue (i.e.@: the job is neither running nor blocked) and -its @code{ts_dispwait} exceeds @code{ts_maxwait}, then its priority -and @code{ts_dispwait} (but not @code{ts_timeleft}) are reset. This -may involve rearranging the priority queues. - -@item ts_sleep(thread *@var{t}) -Called when the thread blocks (e.g.@: due to I/O or synchronization). -The TS routine does not need to do anything in these circumstance, but -the dispatcher, or class-independent routines, must add the thread to -the blocked queue and schedule a new thread. - -@item ts_wakeup(thread *@var{t}) -Called when the blocked thread becomes ready. If @code{ts_dispwait} -for the process is greater than its priority's @code{ts_maxwait}, then -its priority is set to @code{ts_slpret}, its timeslice -(@code{ts_timeleft}) is reset, and @code{ts_dispwait} is zeroed. If -the priority of this job is higher than that of the running job, it -preempts the currently running job. Otherwise the newly awoken job is -added to the back of its priority queue. - -@item ts_preempt(thread *@var{t}) -Called when the thread is preempted by a higher priority thread. The -preempted thread is added to the front of its priority queue. -@end table - -@node Fairness -@section Fairness - -The Solaris time-sharing scheduler approximates fair allocations by -decreasing the priority of a job the more that it is scheduled. -Therefore, a job that is runnable relatively infrequently remains at a -higher priority and is scheduled over lower priority jobs. However, -due to the configuration of the default dispatch table (in which the -starvation interval is set to zero), you the priority of every process -is raised once a second, regardless of whether or not it is actually -starving. Thus, the allocation history of each process is erased -every second and compute-bound processes tend to acquire more than -their fair share of the resources. - -This behavior is illustrated in the graph below for three competing -jobs that relinquish the processor at different rates while waiting -for I/O to complete: acoarse job that rarely relinquishes the CPU, a -medium job that does so more frequently, and afine job that often -relinquishes the CPU. The graph shows a typical snapshot over a five -second execution interval. As described, each second the priority of -all three jobs is raised to level 50 or higher. As a job executes and -consumes its timeslice, its priority is lowered about ten levels Since -the coarse job runs more frequently, it drops in priority at a faster -rate than the other two jobs. - -@ifnottex -@image{mlfqs1} -@end ifnottex -@iftex -@image{mlfqs1, 3in} -@end iftex - -The impact of this policy on the relative execution times of the three -applications is shown in the next graph below. Because the coarse -application acquires more CPU time, it finishes its work earlier than -the other applications, even though all three jobs require the same -amount of time in a dedicated environment. - -@ifnottex -@image{mlfqs2} -@end ifnottex -@iftex -@image{mlfqs2, 3in} -@end iftex - -@node Project Requirements -@section Project Requirements - -For your project, you need to implement code that is similar in -functionality to the Solaris TS scheduler, but your code does not have -to be structured in the same way. Implement your code in whatever -manner you find easiest when interfacing with the already existing -Pintos code. - -Specifically, you are not required to separate the class-independent -and class-dependent scheduling functionality. You do not need to -support multiple scheduling classes. You can implement any routines -that you feel are necessary, not just the seven functions we -specifically listed. You can pass any parameters that you find -helpful. - -However, it is important that your approach be table-driven, with a -user-configurable dispatch table. Your table should be initialized to -the default values in Solaris 2.6, but you may also want to experiment -with different configurations. To demonstrate the functionality of -your scheduler, you may want to print out the change in priorities of -several different competing processes, as in the first graph above. - diff --git a/doc/pintos.css b/doc/pintos.css index 3dee90c..f904001 100644 --- a/doc/pintos.css +++ b/doc/pintos.css @@ -2,7 +2,11 @@ body { background: white; color: black; padding: 0em 1em 0em 3em; - margin: 0 + margin: 0; + margin-left: auto; + margin-right: auto; + max-width: 8in; + text-align: justify } body>p { margin: 0pt 0pt 0pt 0em; @@ -32,10 +36,14 @@ H1, H2, H3, H4, H5, H6 { font-family: sans-serif; color: blue } +H1, H2 { + text-decoration: underline +} html { - margin: 0 + margin: 0; + font-weight: lighter } -code { +tt, code { font-family: sans-serif } diff --git a/doc/pintos.texi b/doc/pintos.texi index b8e96a0..02db016 100644 --- a/doc/pintos.texi +++ b/doc/pintos.texi @@ -39,7 +39,6 @@ @titlepage @title Pintos @author by Ben Pfaff -@author based on past contributions of CS 140 TAs @end titlepage @contents @@ -57,7 +56,7 @@ * Project 3--Virtual Memory:: * Project 4--File Systems:: * References:: -* Multilevel Feedback Scheduling:: +* 4.4BSD Scheduler:: * Coding Standards:: * Project Documentation:: * Debugging Tools:: @@ -71,7 +70,7 @@ @include vm.texi @include filesys.texi @include references.texi -@include mlfqs.texi +@include 44bsd.texi @include standards.texi @include doc.texi @include debug.texi diff --git a/doc/references.texi b/doc/references.texi index 8aa3b5c..78c6b8c 100644 --- a/doc/references.texi +++ b/doc/references.texi @@ -1,4 +1,4 @@ -@node References, Multilevel Feedback Scheduling, Project 4--File Systems, Top +@node References, 4.4BSD Scheduler, Project 4--File Systems, Top @appendix References @macro bibdfn{cite} @@ -9,6 +9,7 @@ @menu * Hardware References:: * Software References:: +* Operating System Design References:: @end menu @node Hardware References @@ -98,3 +99,11 @@ Edition}. 80@var{x}86-specific parts of the Unix interface. @uref{specs/sysv-abi-update.html/contents.html, , System V Application Binary Interface---DRAFT---24 April 2001}. A draft of a revised version of @bibref{SysV-ABI} which was never completed. + +@node Operating System Design References +@section Operating System Design References + +@bibdfn{4.4BSD} +M.@: K.@: McKusick, K.@: Bostic, M.@: J.@: Karels, J.@: S.@: Quarterman, +@cite{The Design and Implementation of the 4.4@acronym{BSD} Operating +System}. Addison-Wesley 1996. diff --git a/doc/sample.tmpl b/doc/sample.tmpl new file mode 100644 index 0000000..db2e70f --- /dev/null +++ b/doc/sample.tmpl @@ -0,0 +1,103 @@ + +-----------------+ + | 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/standards.texi b/doc/standards.texi index 162ec44..fbd673a 100644 --- a/doc/standards.texi +++ b/doc/standards.texi @@ -1,26 +1,24 @@ -@node Coding Standards, Project Documentation, Multilevel Feedback Scheduling, Top +@node Coding Standards, Project Documentation, 4.4BSD Scheduler, Top @appendix Coding Standards -All of you should have taken a class like CS 107, so we expect you to -be familiar with some set of coding standards such as +All of you should have taken a class like CS 107, so we expect you to be +familiar with some set of coding standards such as @uref{http://www.stanford.edu/class/cs140/projects/misc/CodingStandards.pdf, , CS 107 Coding Standards}. Even if you've taken 107, we recommend -reviewing that document. We expect code at the "Peer-Review Quality" -level as described there. - -Our standards for coding are mostly important in grading. More -information on our grading methodology can be found on the Course Info -page and the Grading page. We also 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. +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. @menu * Coding Style:: -* Conditional Compilation:: * C99:: * Unsafe String Functions:: @end menu @@ -52,8 +50,10 @@ this documentation (@pxref{References}). 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 current -project, so you don't need to make sure that the previous projects also +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 @@ -61,17 +61,20 @@ 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 @option{-o @var{name}}, where +special command-line option of the form @option{-@var{name}}, where @var{name} is a name of your choice. You can add such an option by -modifying @func{argv_init} in @file{threads/init.c}. +modifying @func{parse_options} in @file{threads/init.c}. + +The introduction describes additional coding style requirements +(@pxref{Design}). @node C99 @section C99 The Pintos source code uses a few features of the ``C99'' standard -library that were not in the original 1989 standard for C. Because -they are so new, most classes do not cover these features, so this -section will describe them. The new features used in Pintos are +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: @table @file @@ -83,7 +86,7 @@ expands to 0. @item On systems that support them, this header defines types @code{int@var{n}_t} and @code{uint@var{n}_t} for @var{n} = 8, 16, 32, -64, and possibly others. These are 2's complement signed and unsigned +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 @@ -94,17 +97,17 @@ On all systems, this header defines types @code{intmax_t} and @code{uintmax_t}, which are the system's signed and unsigned integer types with the widest ranges. -For every signed integer type @code{@var{type}_t} it defines, as well +For every signed integer type @code{@var{type}_t} defined here, as well as for @code{ptrdiff_t} defined in @file{}, this header also -defines macros @code{@var{type}_MAX} and @code{@var{type}_MIN} that +defines macros @code{@var{TYPE}_MAX} and @code{@var{TYPE}_MIN} that give the type's range. Similarly, for every unsigned integer type @code{@var{type}_t} defined here, as well as for @code{size_t} defined -in @file{}, this header defines a @code{@var{type}_MAX} +in @file{}, this header defines a @code{@var{TYPE}_MAX} macro giving its maximum value. @item -@file{} is useful on its own, but it provides no way to pass -the types it defines to @func{printf} and related functions. This +@file{} provides no straightforward way to format +the types it defines with @func{printf} and related functions. This header provides macros to help with that. For every @code{int@var{n}_t} defined by @file{}, it provides macros @code{PRId@var{n}} and @code{PRIi@var{n}} for formatting values of @@ -123,7 +126,7 @@ printf ("value=%08"PRId32"\n", value); @noindent The @samp{%} is not supplied by the @code{PRI} macros. As shown above, you supply it yourself and follow it by any flags, field -widths, etc. +width, etc. @item The @func{printf} function has some new type modifiers for printing @@ -142,7 +145,7 @@ For @code{ptrdiff_t} (e.g.@: @samp{%td}). @end table Pintos @func{printf} also implements a nonstandard @samp{'} flag that -group large numbers with commas to make them easier to read. +groups large numbers with commas to make them easier to read. @end table @node Unsafe String Functions @@ -161,7 +164,7 @@ comments in its source code in @code{lib/string.c} for documentation. @item strncpy This function can leave its destination buffer without a null string -terminator and it has performance problems besides. Again, use +terminator. It also has performance problems. Again, use @func{strlcpy}. @item strcat @@ -170,7 +173,7 @@ Again, refer to comments in its source code in @code{lib/string.c} for documentation. @item strncat -The meaning of its buffer size argument often leads to problems. +The meaning of its buffer size argument is surprising. Again, use @func{strlcat}. @item strtok @@ -186,6 +189,6 @@ to comments in @code{lib/stdio.h} for documentation. Same issue as @func{strcpy}. Use @func{vsnprintf} instead. @end table -If you try to use any of these functions, you should get a hint from -the error message, which will refer to an identifier like +If you try to use any of these functions, the error message will give +you a hint by referring to an identifier like @code{dont_use_sprintf_use_snprintf}. diff --git a/doc/texi2html b/doc/texi2html index 1c40443..16ecd4a 100755 --- a/doc/texi2html +++ b/doc/texi2html @@ -343,7 +343,7 @@ use vars qw( #--############################################################################## # CVS version: -# $Id: texi2html,v 1.2 2004-10-14 00:10:34 blp Exp $ +# $Id: texi2html,v 1.3 2005-06-19 03:20:26 blp Exp $ # Homepage: $T2H_HOMEPAGE = "http://texi2html.cvshome.org"; @@ -390,7 +390,7 @@ $THISPROG = "texi2html $THISVERSION"; # program name and version # Copy this file and make changes to it, if you like. # Afterwards, either, load it with command-line option -init_file # -# $Id: texi2html,v 1.2 2004-10-14 00:10:34 blp Exp $ +# $Id: texi2html,v 1.3 2005-06-19 03:20:26 blp Exp $ ###################################################################### # stuff which can also be set by command-line options @@ -1863,7 +1863,7 @@ package Getopt::MySimple; # -------------------------------------------------------------------------- # Locally modified by obachman (Display type instead of env, order by cmp) -# $Id: texi2html,v 1.2 2004-10-14 00:10:34 blp Exp $ +# $Id: texi2html,v 1.3 2005-06-19 03:20:26 blp Exp $ # use strict; # no strict 'refs'; @@ -2207,6 +2207,7 @@ $index_properties = "?", "?", ".", ".", "-", "", + "/", "", ); # @@ -2252,7 +2253,7 @@ $index_properties = # texinfo styles (@foo{bar}) to HTML ones # %style_map = ( - 'acronym', '&do_acronym', + 'acronym', 'ACRONYM', 'asis', '', 'b', 'B', 'cite', 'CITE', @@ -2352,6 +2353,7 @@ $complex_format_map->{smallformat} = $complex_format_map->{smalldisplay}; 'deftypefn', 0, 'deftypeop', 0, 'deftypevr', 0, + 'deftypecv', 0, 'defcv', 0, 'defop', 0, 'deftp', 0, @@ -2361,6 +2363,7 @@ $complex_format_map->{smallformat} = $complex_format_map->{smalldisplay}; 'deftypefnx', 0, 'deftypeopx', 0, 'deftypevrx', 0, + 'deftypecvx', 0, 'defcvx', 0, 'defopx', 0, 'deftpx', 0, @@ -3982,6 +3985,11 @@ sub pass1 $type =~ s/^\{(.*)\}$/$1/; print "# def ($tag): {$type} ", join(', ', @args), "\n" if $T2H_DEBUG & $DEBUG_DEF; + if ($tag eq 'deftypecv') { + my $class = shift (@args); + $class =~ s/^\{(.*)\}$/$1/; + $type .= " of $class"; + } $type .= ':' if (!$T2H_DEF_TABLE); # it's nicer like this $name = shift(@args); $name =~ s/^\{(.*)\}$/$1/; @@ -4025,7 +4033,7 @@ sub pass1 } elsif ($tag eq 'deftypefn' || $tag eq 'deftypevr' || $tag eq 'deftypeop' || $tag eq 'defcv' - || $tag eq 'defop') + || $tag eq 'defop' || $tag eq 'deftypecv') { $ftype = $name; $name = shift(@args); @@ -4040,8 +4048,9 @@ sub pass1 } else { - $_ .= "$type $ftype $name"; - $_ .= " @args" if @args; + my $sep = $ftype =~ /\*$/ ? '' : ' '; + $_ .= "$type $ftype$sep$name"; + $_ .= " @args" if @args; } } else @@ -6119,7 +6128,7 @@ sub apply_style else { # no style } - $text = "\`$text\'" if $do_quotes; + $text = "$text" if $do_quotes; } else { # unknown style diff --git a/doc/threads.texi b/doc/threads.texi index b001548..58cf316 100644 --- a/doc/threads.texi +++ b/doc/threads.texi @@ -3,75 +3,82 @@ 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. Additionally, you -will use at least part of this increased functionality in future -assignments. +better understanding of synchronization problems. -You will be working in primarily in the @file{threads} directory for +You will be working primarily in the @file{threads} directory for this assignment, with some work in the @file{devices} directory on the side. Compilation should be done in the @file{threads} directory. -Before you read the description of this project, you should read all -of the following sections: @ref{Introduction}, @ref{Coding Standards}, -@ref{Project Documentation}, @ref{Debugging Tools}, and -@ref{Development Tools}. You should at least skim the material in -@ref{Threads Tour}. To complete this project you will also need to -read @ref{Multilevel Feedback Scheduling}. +Before you read the description of this project, you should read all of +the following sections: @ref{Introduction}, @ref{Coding Standards}, +@ref{Debugging Tools}, and @ref{Development Tools}. You should at least +skim the material in @ref{Threads Tour} and especially +@ref{Synchronization}. To complete this project you will also need to +read @ref{4.4BSD Scheduler}. + +@menu +* Project 1 Background:: +* Project 1 Requirements:: +* Project 1 FAQ:: +@end menu + +@node Project 1 Background +@section Background + @menu * Understanding Threads:: -* Project 1 Code:: -* Debugging versus Testing:: -* Tips:: -* Problem 1-1 Alarm Clock:: -* Problem 1-2 Priority Scheduling:: -* Problem 1-3 Advanced Scheduler:: -* Threads FAQ:: +* Project 1 Source Files:: +* Project 1 Synchronization:: +* Development Suggestions:: @end menu @node Understanding Threads -@section Understanding Threads +@subsection Understanding Threads -The first step is to read and understand the initial thread system. -Pintos, by default, implements thread creation and thread completion, +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, and condition variables). +primitives (semaphores, locks, condition variables, and memory +barriers). -However, there's a lot of magic going on in some of this code, so if +Some of this code might seem slightly mysterious. If you haven't already compiled and run the base system, as described in the introduction (@pxref{Introduction}), you should do so now. You -can read through parts of the source code by hand to see what's going +can read through parts of the source code to see what's going on. If you like, you can add calls to @func{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. @xref{i386-elf-gdb}, for more information. +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 @func{thread_create}. The first time the thread is -scheduled and runs, it will start from the beginning of that function -and execute it in the context. When that function returns, that thread -completes. Each thread, therefore, acts like a mini-program running +scheduled. You provide a function to be run in this context as an +argument to @func{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 @func{thread_create} acting like @func{main}. -At any given time, Pintos is running exactly one thread, with the -others switched out. The scheduler decides which thread to run next -when it needs to switch between them. (If no thread is ready to run -at any given time, then the special ``idle'' thread runs.) The -synchronization primitives are used to force context switches when one +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 +@func{idle}, runs.) +Synchronization primitives can force context switches when one thread needs to wait for another thread to do something. -The exact mechanics of a context switch are pretty gruesome and have -been provided for you in @file{threads/switch.S} (this is 80@var{x}86 -assembly; don't worry about understanding it). It involves saving the -state of the currently running thread and restoring the state of the +The mechanics of a context switch are +in @file{threads/switch.S}, which is 80@var{x}86 +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 @command{gdb} debugger, slowly trace through a context -switch to see what happens (@pxref{i386-elf-gdb}). You can set a -breakpoint on the @func{schedule} function to start out, and then +switch to see what happens (@pxref{gdb}). You can set a +breakpoint on @func{schedule} to start out, and then single-step from there.@footnote{@command{gdb} might tell you that @func{schedule} doesn't exist, which is arguably a @command{gdb} bug. You can work around this by setting the breakpoint by filename and @@ -81,25 +88,21 @@ 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 @func{switch_threads}, another thread starts running, and the first thing the new thread does -is to return from @func{switch_threads}. We realize this comment will -seem cryptic to you at this point, but you will understand threads -once you understand why the @func{switch_threads} that gets called is -different from the @func{switch_threads} that returns. +is to return from @func{switch_threads}. You will understand the thread +system once you understand why and how the @func{switch_threads} that +gets called is different from the @func{switch_threads} that returns. +@xref{Thread Switching}, for more information. @strong{Warning}: In Pintos, each thread is assigned a small, fixed-size execution stack just under @w{4 kB} in size. The kernel -does try to detect stack overflow, but it cannot always succeed. You +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. @samp{int buf[1000];}. Alternatives to stack allocation include -the page allocator in @file{threads/palloc.c} and the block allocator -in @file{threads/malloc.c}. Note that the page allocator doles out -@w{4 kB} chunks and that @func{malloc} has a @w{2 kB} block size -limit. If you need larger chunks, consider using a linked structure -instead. +the page allocator and the block allocator (@pxref{Memory Allocation}). -@node Project 1 Code -@section Code +@node Project 1 Source Files +@subsection Source Files Here is a brief overview of the files in the @file{threads} directory. You will not need to modify most of this code, but the @@ -112,13 +115,14 @@ code to look at. The kernel loader. Assembles to 512 bytes of code and data that the PC BIOS loads into memory and which in turn loads the kernel into memory, does basic processor initialization, and jumps to the -beginning of the kernel. You should not need to look at this code or -modify it. +beginning of the kernel. @xref{Pintos Loader}, for details. You should +not need to look at this code or modify it. @item kernel.lds.S The linker script used to link the kernel. Sets the load address of the kernel and arranges for @file{start.S} to be at the very beginning -of the kernel image. Again, you should not need to look at this code +of the kernel image. @xref{Pintos 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. @item start.S @@ -128,48 +132,47 @@ Jumps to @func{main}. @itemx init.h Kernel initialization, including @func{main}, the kernel's ``main program.'' You should look over @func{main} at least to see what -gets initialized. +gets initialized. You might want to add your own initialization code +here. @xref{Kernel Initialization}, for details. @item thread.c @itemx thread.h Basic thread support. Much of your work will take place in these -files. @file{thread.h} defines @struct{thread}, which you will -modify in the first three projects. +files. @file{thread.h} defines @struct{thread}, which you are likely +to modify in all four projects. See @ref{struct thread} and @ref{Thread +Support} for more information. @item switch.S @itemx switch.h Assembly language routine for switching threads. Already discussed -above. +above. @xref{Thread Functions}, for more information. @item palloc.c @itemx palloc.h Page allocator, which hands out system memory in multiples of 4 kB -pages. +pages. @xref{Page Allocator}, for more information. @item malloc.c @itemx malloc.h -A very simple implementation of @func{malloc} and @func{free} for -the kernel. +A simple implementation of @func{malloc} and @func{free} for +the kernel. @xref{Block Allocator}, for more information. @item interrupt.c @itemx interrupt.h Basic interrupt handling and functions for turning interrupts on and -off. +off. @xref{Interrupt Handling}, for more information. @item intr-stubs.S @itemx intr-stubs.h -Assembly code for low-level interrupt handling. +Assembly code for low-level interrupt handling. @xref{Interrupt +Infrastructure}, for more information. @item synch.c @itemx synch.h -Basic synchronization primitives: semaphores, locks, and condition -variables. You will need to use these for synchronization through all -four projects. - -@item test.c -@itemx test.h -Test code. For project 1, you will replace this file with your test -cases. +Basic synchronization primitives: semaphores, locks, condition +variables, and memory barriers. You will need to use these for +synchronization in all +four projects. @xref{Synchronization}, for more information. @item io.h Functions for I/O port access. This is mostly used by source code in @@ -179,6 +182,11 @@ the @file{devices} directory that you won't have to touch. Functions and macros related to memory management, including page directories and page tables. This will be more important to you in project 3. For now, you can ignore it. + +@item flags.h +Macros that define a few bits in the 80@var{x}86 ``flags'' register. +Probably of no interest. See @bibref{IA32-v1}, section 3.4.3, for more +information. @end table @menu @@ -187,7 +195,7 @@ project 3. For now, you can ignore it. @end menu @node devices code -@subsection @file{devices} code +@subsubsection @file{devices} code The basic threaded kernel also includes these files in the @file{devices} directory: @@ -196,13 +204,13 @@ The basic threaded kernel also includes these files in the @item timer.c @itemx timer.h System timer that ticks, by default, 100 times per second. You will -modify this code in Problem 1-1. +modify this code in this project. @item vga.c @itemx vga.h VGA display driver. Responsible for writing text to the screen. -You should have no need to look at this code. @func{printf} will -call into the VGA display driver for you, so there's little reason to +You should have no need to look at this code. @func{printf} +calls into the VGA display driver for you, so there's little reason to call this code yourself. @item serial.c @@ -224,7 +232,7 @@ and serial drivers. @end table @node lib files -@subsection @file{lib} files +@subsubsection @file{lib} files Finally, @file{lib} and @file{lib/kernel} contain useful library routines. (@file{lib/user} will be used by user programs, starting in @@ -245,7 +253,8 @@ details: @itemx stdlib.h @itemx string.c @itemx string.h -Implementation of the standard C library. @xref{C99}, for information +A subset of the standard C library. @xref{C99}, for +information on a few recently introduced pieces of the C library that you might not have encountered before. @xref{Unsafe String Functions}, for information on what's been intentionally left out for safety. @@ -273,7 +282,7 @@ you'll probably want to use it a few places yourself in project 1. @item kernel/bitmap.c @itemx kernel/bitmap.h Bitmap implementation. You can use this in your code if you like, but -you probably won't have any need for project 1. +you probably won't have any need for it in project 1. @item kernel/hash.c @itemx kernel/hash.h @@ -281,105 +290,62 @@ Hash table implementation. Likely to come in handy for project 3. @item kernel/console.c @itemx kernel/console.h +@item kernel/stdio.h Implements @func{printf} and a few other functions. @end table -@node Debugging versus Testing -@section Debugging versus Testing - -When you're debugging code, it's useful to be able 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.'' The simulator we use, Bochs, can be set up for -reproducibility, and that's the way that @command{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 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 this project. 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 @command{pintos} with the option @option{-j @var{seed}}, timer -interrupts will come at irregularly spaced intervals. Within a single -@var{seed} value, execution will still be reproducible, but timer -behavior will change as @var{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 @command{pintos} with -a different option, @option{-r}, to make it 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 @option{-j} and @option{-r} are mutually exclusive. - -@node Tips -@section Tips - -@itemize @bullet -@item -There should be no busy waiting in any of your solutions to this -assignment. We consider a tight loop that calls @func{thread_yield} -to be one form of busy waiting. - -@item +@node Project 1 Synchronization +@subsection Synchronization + Proper synchronization is an important part of the solutions to these -problems. It is tempting to synchronize all your code by turning off -interrupts with @func{intr_disable} or @func{intr_set_level}, because -this eliminates concurrency and thus the possibility for race -conditions, but @strong{don't}. Instead, use semaphores, locks, and -condition variables to solve the bulk of your synchronization -problems. Read the tour section on synchronization -(@pxref{Synchronization}) or the comments in @file{threads/synch.c} if -you're unsure what synchronization primitives may be used in what -situations. - -You might run into a few situations where interrupt disabling is the -best way to handle synchronization. If so, you need to explain your -rationale in your design documents. If you're unsure whether a given -situation justifies disabling interrupts, talk to the TAs, who can -help you decide on the right thing to do. +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 @strong{don't}. +Instead, use semaphores, locks, and condition variables to solve the +bulk of your synchronization problems. Read the tour section on +synchronization (@pxref{Synchronization}) or the comments in +@file{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. 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. -@item -All parts of this assignment are required if you intend to earn full -credit on this project. Problem 1-1 (Alarm Clock) could be handy for -later projects, but it is not strictly required. Problems 1-2 -(Priority Scheduling) and 1-3 (Advanced Scheduler) won't be needed for -later projects. +There should be no busy waiting in your submission. A tight loop that +calls @func{thread_yield} is one form of busy waiting. -@item -Problem 1-3 (MLFQS) builds on the features you implement in Problem -1-2. You should have Problem 1-2 fully working before you begin to -tackle Problem 1-3. +@node Development Suggestions +@subsection Development Suggestions -@item 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. @strong{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 successfully -boot. +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 a source code control system such as CVS (@pxref{CVS}) or a @@ -389,40 +355,59 @@ 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. -@item You should expect to run into bugs that you simply don't understand -while working on this and subsequent projects. When you do, go back -and reread the appendix on debugging tools, which is filled with +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 (@pxref{Debugging Tools}). Be sure to read the section on backtraces (@pxref{Backtraces}), which will help you to get the most out of every kernel panic or assertion failure. -@end itemize - -@node Problem 1-1 Alarm Clock -@section Problem 1-1: Alarm Clock - -Improve the implementation of the timer device defined in -@file{devices/timer.c} by reimplementing @func{timer_sleep}. -Threads call @code{timer_sleep(@var{x})} to suspend execution until -time has advanced by at least @w{@var{x} timer ticks}. This is -useful for threads that operate in real-time, for example, for -blinking the cursor once per second. There is no requirement that -threads start running immediately after waking up; just put them on -the ready queue after they have waited for approximately the right -amount of time. - -A working implementation of this function is provided. However, the -version provided is poor, because it ``busy waits,'' that is, it spins -in a tight loop checking the current time until the current time has -advanced far enough. This is undesirable because it wastes time that -could potentially be used more profitably by another thread. Your -solution should not busy wait. + +@node Project 1 Requirements +@section Requirements + +@menu +* Project 1 Design Document:: +* Alarm Clock:: +* Priority Scheduling:: +* Advanced Scheduler:: +@end menu + +@node Project 1 Design Document +@subsection Design Document + +Before you turn in your project, you must copy @uref{threads.tmpl, , the +project 1 design document template} into your source tree under the name +@file{pintos/src/threads/DESIGNDOC} and fill it in. We recommend that +you read the design document template before you start working on the +project. @xref{Project Documentation}, for a sample design document +that goes along with a fictitious project. + +@node Alarm Clock +@subsection Alarm Clock + +Reimplement @func{timer_sleep}, defined in @file{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 +@func{thread_yield} until enough time has gone by. Reimplement it to +avoid busy waiting. + +@deftypefun void timer_sleep (int64_t @var{ticks}) +Suspends execution of the calling thread until time has advanced by at +least @w{@var{x} timer ticks}. Unless the system is otherwise idle, the +thread need not wake up after exactly @var{x} ticks. Just put it on +the ready queue after they have waited for the right amount of time. + +@func{timer_sleep} is useful for threads that operate in real-time, +e.g.@: for blinking the cursor once per second. The argument to @func{timer_sleep} is expressed in timer ticks, not in milliseconds or any another unit. There are @code{TIMER_FREQ} timer ticks per second, where @code{TIMER_FREQ} is a macro defined in -@code{devices/timer.h}. +@code{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. +@end deftypefun Separate functions @func{timer_msleep}, @func{timer_usleep}, and @func{timer_nsleep} do exist for sleeping a specific number of @@ -434,153 +419,140 @@ If your delays seem too short or too long, reread the explanation of the @option{-r} option to @command{pintos} (@pxref{Debugging versus Testing}). -@node Problem 1-2 Priority Scheduling -@section Problem 1-2: Priority Scheduling +The alarm clock implementation is not needed for later projects, +although it could be useful for project 4. -Implement priority scheduling in Pintos. Priority scheduling is a key -building block for real-time systems. Implement functions -@func{thread_set_priority} to set the priority of the running thread -and @func{thread_get_priority} to get the running thread's priority. -(This API only allows a thread to examine and modify its own -priority.) There are already prototypes for these functions in -@file{threads/thread.h}, which you should not change. - -Thread priority ranges from @code{PRI_MIN} (0) to @code{PRI_MAX} (59). -The initial thread priority is passed as an argument to -@func{thread_create}. If there's no reason to choose another -priority, use @code{PRI_DEFAULT} (29). The @code{PRI_} macros are -defined in @file{threads/thread.h}, and you should not change their -values. +@node Priority Scheduling +@subsection 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 +threads are waiting for a lock, semaphore, or condition variable, the highest priority waiting thread should be woken up 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. -One issue with priority scheduling is ``priority inversion'': if a -high priority thread needs to wait for a low priority thread (for -instance, for a lock held by a low priority thread), and a middle priority -thread is on the ready list, then the high priority thread will never -get the CPU because the low priority thread will not get any CPU time. -A partial fix for this problem is to have the waiting thread -``donate'' its priority to the low priority thread while it is holding -the lock, then recall the donation once it has acquired the lock. -Implement this fix. - -You will need to account for all different orders in which priority -donation and inversion can occur. Be sure to handle multiple -donations, in which multiple priorities are donated to a thread. You -must also handle nested donation: given high, medium, and low priority -threads @var{H}, @var{M}, and @var{L}, respectively, if @var{H} is -waiting on a lock that @var{M} holds and @var{M} is waiting on a lock -that @var{L} holds, then both @var{M} and @var{L} should be boosted to -@var{H}'s priority. - -You only need to implement priority donation when a thread is waiting -for a lock held by a lower-priority thread. You do not need to -implement this fix for semaphores or condition variables -although you are welcome to do so. However, you do need to implement +Thread priorities range from @code{PRI_MIN} (0) to @code{PRI_MAX} (63). +Lower numbers correspond to @emph{higher} priorities, so that priority 0 +is the highest priority and priority 63 is the lowest. +The initial thread priority is passed as an argument to +@func{thread_create}. If there's no reason to choose another +priority, use @code{PRI_DEFAULT} (31). The @code{PRI_} macros are +defined in @file{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 @var{H}, @var{M}, and @var{L}, +respectively. If @var{H} needs to wait for @var{L} (for instance, for a +lock held by @var{L}), and @var{M} is on the ready list, then @var{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 @var{H} to ``donate'' +its priority to @var{L} while @var{L} is holding the lock, then recall +the donation once @var{L} releases (and thus @var{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 @var{H} is waiting on +a lock that @var{M} holds and @var{M} is waiting on a lock that @var{L} +holds, then both @var{M} and @var{L} should be boosted to @var{H}'s +priority. + +You need not implement priority donation when a thread is waiting +for a lock held by a lower-priority thread. You need not +implement priority donation for semaphores or condition variables, +but you are welcome to do so. You do need to implement priority scheduling in all cases. -@node Problem 1-3 Advanced Scheduler -@section Problem 1-3: Advanced Scheduler +Finally, implement the following functions that allow a thread to +examine and modify its own priority. Skeletons for these functions are +provided in @file{threads/thread.c}. -Implement Solaris's multilevel feedback queue scheduler (MLFQS) to +@deftypefun void thread_set_priority (int @var{new_priority}) +Sets the current thread's priority to @var{new_priority}. If the +current thread no longer has the highest priority, yields. +@end deftypefun + +@deftypefun int thread_get_priority (void) +Returns the current thread's priority. In the presence of priority +donation, returns the higher (donated) priority. +@end deftypefun + +The priority scheduler is not used in any later project. + +@node Advanced Scheduler +@subsection Advanced Scheduler + +Implement a multilevel feedback queue scheduler similar to the +4.4@acronym{BSD} scheduler to reduce the average response time for running jobs on your system. -@xref{Multilevel Feedback Scheduling}, for a detailed description of +@xref{4.4BSD Scheduler}, for a detailed description of the MLFQS requirements. -Demonstrate that your scheduling algorithm reduces response time -relative to the original Pintos scheduling algorithm (round robin) for -at least one workload of your own design (i.e.@: in addition to the -provided test). +The advanced scheduler builds on the priority scheduler. You should +have the priority scheduler working, except possibly for priority +donation, before you start work on the advanced scheduler. You must write your code so that we can choose a scheduling algorithm policy at Pintos startup time. By default, the round-robin scheduler -must be active, but we must be able to choose the MLFQS by invoking -@command{pintos} with the @option{-o mlfqs} option. Passing this +must be active, but we must be able to choose the 4.4@acronym{BSD} +scheduler +with the @option{-mlfqs} kernel option. Passing this option sets @code{enable_mlfqs}, declared in @file{threads/init.h}, to true. -@node Threads FAQ +When the 4.4@acronym{BSD} scheduler is enabled, threads no longer +directly control their own priorities. The @var{priority} argument to +@func{thread_create} should be ignored, as well as any calls to +@func{thread_set_priority}, and @func{thread_get_priority} should return +the thread's current priority as set by the scheduler. + +The advanced scheduler is not used in any later project. + +@node Project 1 FAQ @section FAQ -@enumerate 1 -@item -@b{I am adding a new @file{.h} or @file{.c} file. How do I fix the -@file{Makefile}s?}@anchor{Adding c or h Files} +@table @b +@item How much code will I need to write? + +Here's a summary of our reference solution, produced by the +@command{diffstat} program. The final row gives total lines inserted +and deleted; a changed line counts as both an insertion and a deletion. + +@verbatim + devices/timer.c | 42 +++++- + threads/fixed-point.h | 120 ++++++++++++++++++ + threads/synch.c | 88 ++++++++++++- + threads/thread.c | 196 ++++++++++++++++++++++++++---- + threads/thread.h | 23 +++ + 5 files changed, 440 insertions(+), 29 deletions(-) +@end verbatim + +@item How do I update the @file{Makefile}s when I add a new source file? +@anchor{Adding Source Files} To add a @file{.c} file, edit the top-level @file{Makefile.build}. -You'll want to add your file to variable @samp{@var{dir}_SRC}, where +Add the new file to variable @samp{@var{dir}_SRC}, where @var{dir} is the directory where you added the file. For this -project, that means you should add it to @code{threads_SRC}, or -possibly @code{devices_SRC} if you put in the @file{devices} -directory. Then run @code{make}. If your new file doesn't get +project, that means you should add it to @code{threads_SRC} or +@code{devices_SRC}. Then run @code{make}. If your new file +doesn't get compiled, run @code{make clean} and then try again. -When you modify the top-level @file{Makefile.build}, the modified +When you modify the top-level @file{Makefile.build} and re-run +@command{make}, the modified version should be automatically copied to -@file{threads/build/Makefile} when you re-run make. The opposite is +@file{threads/build/Makefile}. The converse is not true, so any changes will be lost the next time you run @code{make -clean} from the @file{threads} directory. Therefore, you should -prefer to edit @file{Makefile.build} (unless your changes are meant to -be truly temporary). - -There is no need to edit the @file{Makefile}s to add a @file{.h} file. - -@item -@b{How do I write my test cases?} - -Test cases should be replacements for the existing @file{test.c} -file. Put them in a @file{threads/testcases} directory. -@xref{TESTCASE}, for more information. - -@item -@b{Why can't I disable interrupts?} - -Turning off interrupts should only be done for short amounts of time, -or else you end up losing important things such as disk or input -events. Turning off interrupts also increases the interrupt handling -latency, which can make a machine feel sluggish if taken too far. -Therefore, in general, setting the interrupt level should be used -sparingly. Also, any synchronization problem can be easily solved by -turning interrupts off, since while interrupts are off, there is no -concurrency, so there's no possibility for race conditions. - -To make sure you understand concurrency well, we are discouraging you -from taking this shortcut at all in your solution. If you are unable -to solve a particular synchronization problem with semaphores, locks, -or conditions, or think that they are inadequate for a particular -reason, you may turn to disabling interrupts. If you want to do this, -we require in your design document a complete justification and -scenario (i.e.@: exact sequence of events) to show why interrupt -manipulation is the best solution. If you are unsure, the TAs can -help you determine if you are using interrupts too haphazardly. We -want to emphasize that there are only limited cases where this is -appropriate. - -You might find @file{devices/intq.h} and its users to be an -inspiration or source of rationale. - -@item -@b{Where might interrupt-level manipulation be appropriate?} - -You might find it necessary in some solutions to the Alarm problem. - -You might want it at one small point for the priority scheduling -problem. Note that it is not required to use interrupts for these -problems. There are other, equally correct solutions that do not -require interrupt manipulation. However, if you do manipulate -interrupts and @strong{correctly and fully document it} in your design -document, we will allow limited use of interrupt disabling. - -@item -@b{What does ``warning: no previous prototype for `@var{function}'' -mean?} +clean} from the @file{threads} directory. Unless your changes are +truly temporary, you should prefer to edit @file{Makefile.build}. + +A new @file{.h} file does not require editing the @file{Makefile}s. + +@item What does @code{warning: no previous prototype for `@var{func}'} mean? It means that you defined a non-@code{static} function without preceding it by a prototype. Because non-@code{static} functions are @@ -589,264 +561,113 @@ 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 @file{.c} files, make it @code{static}. -@end enumerate + +@item What is the interval between timer interrupts? + +Timer interrupts occur @code{TIMER_FREQ} times per second. You can +adjust this value by editing @file{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. + +@item How long is a time slice? + +There are @code{TIME_SLICE} ticks per time slice. This macro is +declared in @file{threads/thread.c}. The default is 4 ticks. + +We don't recommend changing this values, because any changes are likely +to cause many of the tests to fail. +@end table @menu -* Problem 1-1 Alarm Clock FAQ:: -* Problem 1-2 Priority Scheduling FAQ:: -* Problem 1-3 Advanced Scheduler FAQ:: +* Alarm Clock FAQ:: +* Priority Scheduling FAQ:: +* Advanced Scheduler FAQ:: @end menu -@node Problem 1-1 Alarm Clock FAQ -@subsection Problem 1-1: Alarm Clock FAQ - -@enumerate 1 -@item -@b{Why can't I use most synchronization primitives in an interrupt -handler?} - -As you've discovered, you cannot sleep in an external interrupt -handler. Since many lock, semaphore, and condition variable functions -attempt to sleep, you won't be able to call those in -@func{timer_interrupt}. You may still use those that never sleep. - -Having said that, you need to make sure that global data does not get -updated by multiple threads simultaneously executing -@func{timer_sleep}. Here are some pieces of information to think -about: - -@enumerate a -@item -Interrupts are turned off while @func{timer_interrupt} runs. This -means that @func{timer_interrupt} will not be interrupted by a -thread running in @func{timer_sleep}. - -@item -A thread in @func{timer_sleep}, however, can be interrupted by a -call to @func{timer_interrupt}, except when that thread has turned -off interrupts. - -@item -Examples of synchronization mechanisms have been presented in lecture. -Going over these examples should help you understand when each type is -useful or needed. @xref{Synchronization}, for specific information -about synchronization in Pintos. -@end enumerate - -@item -@b{What about timer overflow due to the fact that times are defined as -integers? Do I need to check for that?} +@node Alarm Clock FAQ +@subsection Alarm Clock FAQ + +@table @b +@item 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 63-bit numbers, which at 100 ticks per -second should be good for almost 2,924,712,087 years. - -@item -@b{The test program mostly works but reports a few out-of-order -wake ups. I think it's a problem in the test program. What gives?} -@anchor{Out of Order 1-1} - -This test is inherently full of race conditions. On a real system it -wouldn't work perfectly all the time either. There are a few ways you -can help it work more reliably: - -@itemize @bullet -@item -Make time slices longer by increasing @code{TIME_SLICE} in -@file{timer.c} to a large value, such as 100. - -@item -Make the timer tick more slowly by decreasing @code{TIMER_FREQ} in -@file{timer.h} to its minimum value of 19. -@end itemize - -The former two changes are only desirable for testing problem 1-1 and -possibly 1-2. You should revert them before working on other parts -of the project or turn in the project. We will test problem 1-1 with -@code{TIME_SLICE} set to 100 and @code{TIMER_FREQ} set to 19, but we -will leave them at their defaults for all the other problems. - -@item -@b{Should @file{p1-1.c} be expected to work with the MLFQS turned on?} - -No. The MLFQS will adjust priorities, changing thread ordering. -@end enumerate - -@node Problem 1-2 Priority Scheduling FAQ -@subsection Problem 1-2: Priority Scheduling FAQ - -@enumerate 1 -@item -@b{Doesn't the priority scheduling lead to starvation? Or do I have to -implement some sort of aging?} - -It is true that strict priority scheduling can lead to starvation -because thread may not run if a higher-priority thread is runnable. -In this problem, don't worry about starvation or any sort of aging -technique. Problem 4 will introduce a mechanism for dynamically +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 CS 140 curriculum. +@end table + +@node Priority Scheduling FAQ +@subsection Priority Scheduling FAQ + +@table @b +@item 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. -This sort of scheduling is valuable in real-time systems because it +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. -@item -@b{After a lock has been released, does the program need to switch to -the highest priority thread that needs the lock (assuming that its -priority is higher than that of the current thread)?} +@item 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 ready to run list. The +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. -@item -@b{If a thread calls @func{thread_yield} and then it turns out that -it has higher priority than any other threads, does the high-priority -thread continue running?} +@item If the highest-priority thread yields, does it continue running? -Yes. If there is a single highest-priority thread, it continues +Yes. As long as there is a single highest-priority thread, it continues running until it blocks or finishes, even if it calls @func{thread_yield}. +If there are multiple threads have the same highest priority, +@func{thread_yield} should switch among them in ``round robin'' order. + +@item What happens to the priority of a donating thread? -@item -@b{If the highest priority thread is added to the ready to run list it -should start execution immediately. Is it immediate enough if I -wait until next timer interrupt occurs?} +Priority donation only changes the priority of the donee +thread. The donor thread's priority is unchanged. +Priority donation is not additive: if thread @var{A} (with priority 5) donates +to thread @var{B} (with priority 3), then @var{B}'s new priority is 5, not 8. +@item Can a thread's priority change while it is on the ready queue? + +Yes. Consider this case: low-priority thread @var{L} holds a +lock that high-priority thread @var{H} wants, so @var{H} donates its +priority to @var{L}. @var{L} releases the lock and +thus loses the CPU and is moved to the ready queue. Now @var{L}'s +old priority is restored while it is in the ready queue. + +@item 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. -@item -@b{What happens to the priority of the donating thread? Do the priorities -get swapped?} - -No. Priority donation only changes the priority of the low-priority -thread. The donating thread's priority stays unchanged. Also note -that priorities aren't additive: if thread A (with priority 5) donates -to thread B (with priority 3), then B's new priority is 5, not 8. - -@item -@b{Can a thread's priority be changed while it is sitting on the ready -queue?} - -Yes. Consider this case: low-priority thread L currently has a lock -that high-priority thread H wants. H donates its priority to L (the -lock holder). L finishes with the lock, and then loses the CPU and is -moved to the ready queue. Now L's old priority is restored while it -is in the ready queue. - -@item -@b{Can a thread's priority change while it is sitting on the queue of a -semaphore?} - -Yes. Same scenario as above except L gets blocked waiting on a new -lock when H restores its priority. - -@item -@b{Why is @file{p1-2.c}'s FIFO test skipping some threads? I know my -scheduler is round-robin'ing them like it's supposed to. Our output -starts out okay, but toward the end it starts getting out of order.} - -The usual problem is that the serial output buffer fills up. This is -causing serial_putc() to block in thread @var{A}, so that thread -@var{B} is scheduled. Thread @var{B} immediately tries to do output -of its own and blocks on the serial lock (which is held by thread -@var{A}). Now that we've wasted some time in scheduling and locking, -typically some characters have been drained out of the serial buffer -by the interrupt handler, so thread @var{A} can continue its output. -After it finishes, though, some other thread (not @var{B}) is -scheduled, because thread @var{B} was already scheduled while we -waited for the buffer to drain. - -There's at least one other possibility. Context switches are being -invoked by the test when it explicitly calls @func{thread_yield}. -However, the time slice timer is still alive and so, every tick (by -default), a thread gets switched out (caused by @func{timer_interrupt} -calling @func{intr_yield_on_return}) before it gets a chance to run -@func{printf}, effectively skipping it. If we use a different jitter -value, the same behavior is seen where a thread gets started and -switched out completely. - -Normally you can fix these problems using the same techniques -suggested on problem 1-1 (@pxref{Out of Order 1-1}). - -@item -@b{What happens when a thread is added to the ready list which has -higher priority than the currently running thread?} - -The correct behavior is to immediately yield the processor. Your -solution must act this way. - -@item -@b{What should @func{thread_get_priority} return in a thread while -its priority has been increased by a donation?} - -The higher (donated) priority. - -@item -@b{Should @file{p1-2.c} be expected to work with the MLFQS turned on?} - -No. The MLFQS will adjust priorities, changing thread ordering. - -@item -@b{@func{printf} in @func{sema_up} or @func{sema_down} makes the -system reboot!} +@item Calling @func{printf} in @func{sema_up} or @func{sema_down} reboots! +@anchor{printf Reboots} Yes. These functions are called before @func{printf} is ready to go. You could add a global flag initialized to false and set it to true just before the first @func{printf} in @func{main}. Then modify @func{printf} itself to return immediately if the flag isn't set. -@end enumerate - -@node Problem 1-3 Advanced Scheduler FAQ -@subsection Problem 1-3: Advanced Scheduler FAQ - -@enumerate 1 -@item -@b{What is the interval between timer interrupts?} - -Timer interrupts occur @code{TIMER_FREQ} times per second. You can -adjust this value by editing @file{devices/timer.h}. The default is -100 Hz. - -You can also adjust the number of timer ticks per time slice by -modifying @code{TIME_SLICE} in @file{devices/timer.c}. - -@item -@b{Do I have to modify the dispatch table?} - -No, although you are allowed to. It is possible to complete -this problem (i.e.@: demonstrate response time improvement) -without doing so. - -@item -@b{When the scheduler changes the priority of a thread, how does this -affect priority donation?} - -Short (official) answer: Don't worry about it. Your priority donation -code may assume static priority assignment. - -Longer (unofficial) opinion: If you wish to take this into account, -however, your design may end up being ``cleaner.'' You have -considerable freedom in what actually takes place. I believe what -makes the most sense is for scheduler changes to affect the -``original'' (non-donated) priority. This change may actually be -masked by the donated priority. Priority changes should only -propagate with donations, not ``backwards'' from donees to donors. - -@item -@b{What is meant by ``static priority''?} +@end table -Once thread A has donated its priority to thread B, if thread A's -priority changes (due to the scheduler) while the donation still -exists, you do not have to change thread B's donated priority. -However, you are free to do so. +@node Advanced Scheduler FAQ +@subsection Advanced Scheduler FAQ -@item -@b{Do I have to make my dispatch table user-configurable?} +@table @b +@item How does priority donation interact with the advanced scheduler? -No. Hard-coding the dispatch table values is fine. -@end enumerate +It doesn't have to. We won't test priority donation and the advanced +scheduler at the same time. +@end table diff --git a/doc/threads.tmpl b/doc/threads.tmpl new file mode 100644 index 0000000..367baf4 --- /dev/null +++ b/doc/threads.tmpl @@ -0,0 +1,157 @@ + +--------------------+ + | 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, and lecture notes. + + ALARM CLOCK + =========== + +---- 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. + +---- ALGORITHMS ---- + +>> Briefly describe what happens in a call to thread_sleep(), including +>> the effects of the timer interrupt handler. + +>> What steps are taken to minimize the amount of time spent in the timer +>> interrupt handler? + +---- SYNCHRONIZATION ---- + +>> How are race conditions avoided when multiple threads call +>> thread_sleep() simultaneously? + +>> How are race conditions avoided when a timer interrupt occurs during a +>> call to thread_sleep()? + +---- RATIONALE ---- + +>> Why did you choose this design? In what ways is it superior to +>> another design you considered? + + PRIORITY SCHEDULING + =================== + +---- 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. + +>> Explain the data structure used to track priority donation. Use ASCII +>> art to diagram a nested donation. + +---- ALGORITHMS ---- + +>> How do you ensure that the highest priority thread waiting for a lock, +>> semaphore, or condition variable wakes up first? + +>> Describe the sequence of events when a call to lock_acquire() causes a +>> priority donation. How is nested donation handled? + +>> Describe the sequence of events when lock_release() is called on a +>> lock that a higher-priority thread is waiting for. + +---- SYNCHRONIZATION ---- + +>> 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 ---- + +>> Why did you choose this design? In what ways is it superior to +>> another design you considered? + + ADVANCED SCHEDULER + ================== + +---- 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. + +---- ALGORITHMS ---- + +>> Suppose threads A, B, and C have nice values 0, 1, and 2. Each has a +>> recent_cpu value of 0. Fill in the table below showing the scheduling +>> decision and the priority and recent_cpu values for each thread after +>> each given number of timer ticks: + +timer recent_cpu priority thread +ticks A B C A B C to run +----- -- -- -- -- -- -- ------ + 0 + 4 + 8 +12 +16 +20 +24 +28 +32 +36 + +>> Did any ambiguities in the scheduler specification make values in the +>> table uncertain? If so, what rule did you use to resolve them? Does +>> this match the behavior of your scheduler? + +---- RATIONALE ---- + +>> Critique your design, pointing out advantages and disadvantages in +>> your design choices. If you were to have extra time to work on this +>> part of the project, how might you choose to refine or improve your +>> design? + +>> The assignment explains arithmetic for fixed-point math in detail, but +>> it leaves it open to you to implement it. Why did you decide to +>> implement it the way you did? If you created an abstraction layer for +>> fixed-point math, that is, an abstract data type and/or a set of +>> functions or macros to manipulate fixed-point numbers, why did you do +>> so? If not, why not? + + 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/tour.texi b/doc/tour.texi index 81eb5bd..efa1166 100644 --- a/doc/tour.texi +++ b/doc/tour.texi @@ -67,22 +67,21 @@ chicken-and-egg problem if we don't include the former: our current virtual address is roughly @t{0x7c00}, the location where the BIOS loaded us, and we can't jump to @t{0xc0007c00} 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. At any rate, -it's necessary. +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 then we set up the segment registers. We aren't equipped to handle interrupts in protected mode yet, so we disable interrupts. -Finally it's time to load the kernel from disk. We choose a simple, -although inflexible, method to do this: we program the IDE disk +Finally it's time to load the kernel from disk. We use a simple but +inflexible method to do this: we program the IDE disk controller directly. We assume that the kernel is stored starting -from the second sector of the first IDE disk (the first sector -contains the boot loader itself). We also assume that the BIOS has +from the second sector of the first IDE disk (the first sector normally +contains the boot loader). We also assume that the BIOS has already set up the IDE controller for us. We read -@code{KERNEL_LOAD_PAGES} 4 kB pages of data from the disk directly -into virtual memory starting @code{LOADER_KERN_BASE} bytes past +@code{KERNEL_LOAD_PAGES} pages of data (4 kB per page) from the disk directly +into virtual memory, starting @code{LOADER_KERN_BASE} bytes past @code{LOADER_PHYS_BASE}, which by default means that we load the kernel starting 1 MB into physical memory. @@ -92,7 +91,7 @@ arranged that it begins with the assembly module @file{threads/start.S}. This assembly module just calls @func{main}, which never returns. -There's one more trick to the loader: the Pintos kernel command line +There's one more trick: the Pintos kernel command line is stored in the boot loader. The @command{pintos} program actually modifies the boot loader on disk each time it runs the kernel, putting in whatever command line arguments the user supplies to the kernel, @@ -110,19 +109,18 @@ encounter in Pintos from here on out. When @func{main} starts, the system is in a pretty raw state. We're in protected mode with paging enabled, but hardly anything else is ready. Thus, the @func{main} function consists primarily of calls -into other Pintos modules' initialization functions. You should -notice that these are usually named @func{@var{module}_init}, where +into other Pintos modules' initialization functions. +These are usually named @func{@var{module}_init}, where @var{module} is the module's name, @file{@var{module}.c} is the module's source code, and @file{@var{module}.h} is the module's header. First we initialize kernel RAM in @func{ram_init}. The first step is to clear out the kernel's so-called ``BSS'' segment. The BSS is a -segment that should be initialized to all zeros. In C, whenever you +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.@footnote{This isn't -actually required by the ANSI C standard, but it is the case with most -implementations of C on most machines.} Because it's all zeros, the +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 @func{memset} to zero it out. The other task of @func{ram_init} is to read out the machine's memory size from where @@ -132,7 +130,7 @@ later use. Next, @func{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 the console, initialized -just below, tries to use locks and locks in turn require there to be a +just afterward, tries to use locks, and locks in turn require there to be a running thread. Then we initialize the console so that we can use @func{printf}. @@ -144,9 +142,10 @@ character to be output. (We use polling mode until we're ready to set up interrupts later.) Finally we initialize the console device and print a startup message to the console. -@func{main} calls @func{argv_init} to parse the kernel command line. -This entails reading the command line out of the loader and updating -global variables appropriately. +@func{main} calls @func{read_command_line} to break the kernel command +line into arguments, then @func{parse_options} to read any options at +the beginning of the command line. (Executing actions specified on the +command line happens later.) The next block of functions we call initialize the kernel's memory system. @func{palloc_init} sets up the kernel page allocator, which @@ -158,34 +157,34 @@ In projects 2 and later, @func{main} also calls @func{tss_init} and @func{gdt_init}, but we'll talk about those later. @func{main} calls @func{random_init} to initialize the kernel random -number generator. @func{argv_init} might already have done this (if -the user specified @option{-rs} on the @command{pintos} command line) -but calling it a second time is harmless and has no effect. +number generator. If the user specified @option{-rs} on the +@command{pintos} command line, @func{parse_options} has already done +this, but calling it a second time is harmless and has no effect. We initialize the interrupt system in the next set of calls. @func{intr_init} sets up the CPU's @dfn{interrupt descriptor table} -(IDT) to ready it for interrupt handling, then @func{timer_init} and -@func{kbd_init} prepare us to handle timer interrupts and keyboard -interrupts, respectively. In projects 2 and later, we also prepare to -handle interrupts caused by user programs using @func{exception_init} -and @func{syscall_init}. +(IDT) to ready it for interrupt handling (@pxref{Interrupt +Infrastructure}), then @func{timer_init} and @func{kbd_init} prepare for +handling timer interrupts and keyboard interrupts, respectively. In +projects 2 and later, we also prepare to handle interrupts caused by +user programs using @func{exception_init} and @func{syscall_init}. Now that interrupts are set up, we can start preemptively scheduling threads with @func{thread_start}, which also enables interrupts. -Interrupt-driven serial port I/O is also possible now, so we use +With interrupts enabled, interrupt-driven serial port I/O becomes +possible, so we use @func{serial_init_queue} to switch to that mode. Finally, @func{timer_calibrate} calibrates the timer for accurate short delays. -If the filesystem is compiled in, as it will be in project 2 and -later, we now initialize the disks with @func{disk_init}, then the -filesystem with @func{filesys_init}, and run any operations that were -requested on the kernel command line with @func{fsutil_run}. +If the file system is compiled in, as it will starting in project 2, we +now initialize the disks with @func{disk_init}, then the +file system with @func{filesys_init}. Boot is complete, so we print a message. -For project 1, now we execute @func{test}, which runs whatever test is -currently compiled into the kernel. For later projects, we will -instead run a user program and wait for it to complete. +Function @func{run_actions} now parses and executes actions specified on +the kernel command line, such as @command{run} to run a test (in project +1) or a user program (in later projects). Finally, if @option{-q} was specified on the kernel command line, we call @func{power_off} to terminate the machine simulator. Otherwise, @@ -193,63 +192,120 @@ call @func{power_off} to terminate the machine simulator. Otherwise, threads to continue running. @node Threads Tour -@section Threads +@section Threads Project @menu -* struct thread:: -* Thread Functions:: -* Thread Switching:: +* Thread Support:: * Synchronization:: * Interrupt Handling:: * Memory Allocation:: @end menu +@node Thread Support +@subsection Thread Support + +@menu +* struct thread:: +* Thread Functions:: +* Thread Switching:: +@end menu + @node struct thread -@subsection @code{struct thread} +@subsubsection @code{struct thread} The main Pintos data structure for threads is @struct{thread}, -declared in @file{threads/thread.h}. @struct{thread} has these -members with the given type: +declared in @file{threads/thread.h}. + +@deftp {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: -@table @code -@item tid_t tid; +@example +@group + 4 kB +---------------------------------+ + | kernel stack | + | | | + | | | + | V | + | grows downward | + | | + | | + | | + | | + | | + | | + | | + | | + +---------------------------------+ + | magic | + | : | + | : | + | status | + | tid | + 0 kB +---------------------------------+ +@end group +@end example + +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 @func{malloc} or +@func{palloc_get_page} instead (@pxref{Memory Allocation}). +@end deftp + +@deftypecv {Member} {@struct{thread}} {tid_t} tid The thread's thread identifier or @dfn{tid}. Every thread must have a tid that is unique over the entire lifetime of the kernel. By default, @code{tid_t} is a @code{typedef} for @code{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. +@end deftypecv -@item enum thread_status status; -The thread's state, one of these: +@deftypecv {Member} {@struct{thread}} {enum thread_status} status +The thread's state, one of the following: -@table @code -@item THREAD_RUNNING +@defvr {Thread State} @code{THREAD_RUNNING} The thread is running. Exactly one thread is running at a given time. @func{thread_current} returns the running thread. +@end defvr -@item THREAD_READY +@defvr {Thread State} @code{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 @code{ready_list}. +@end defvr -@item THREAD_BLOCKED +@defvr {Thread State} @code{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 @code{THREAD_READY} state with a call to @func{thread_unblock}. +@end defvr -@item THREAD_DYING +@defvr {Thread State} @code{THREAD_DYING} The thread will be destroyed by the scheduler after switching to the next thread. -@end table +@end defvr +@end deftypecv -@item char name[16]; +@deftypecv {Member} {@struct{thread}} {char} name[16] The thread's name as a string, or at least the first few characters of it. +@end deftypecv -@item uint8_t *stack; +@deftypecv {Member} {@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 @@ -257,78 +313,43 @@ 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. -@item int priority; -A thread priority, ranging from the lowest possible priority -@code{PRI_MIN} (0) to the highest possible priority @code{PRI_MAX} -(59). Pintos as provided ignores thread priorities, but you will -implement priority scheduling in problem 1-2 (@pxref{Problem 1-2 -Priority Scheduling}). - -@item struct list_elem elem; +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. @xref{Interrupt Handling}, for more information. +@end deftypecv + +@deftypecv {Member} {@struct{thread}} {int} priority +A thread priority, ranging from @code{PRI_MIN} (0) to @code{PRI_MAX} +(63). Lower numbers correspond to @emph{higher} priorities, so that +priority 0 is the highest priority and priority 63 is the lowest. +Pintos as provided ignores thread priorities, but you will implement +priority scheduling in project 1 (@pxref{Priority Scheduling}). +@end deftypecv + +@deftypecv {Member} {@struct{thread}} {@struct{list_elem}} elem A ``list element'' used to put the thread into doubly linked lists, either the list of threads ready to run or a list of threads waiting on a semaphore. Take a look at @file{lib/kernel/list.h} for -information on how to use the Pintos doubly linked list ADT. +information on how to use Pintos doubly linked lists. +@end deftypecv -@item uint32_t *pagedir; +@deftypecv {Member} {@struct{thread}} {uint32_t *} pagedir Only present in project 2 and later. - -@item unsigned magic; -Always set to @code{THREAD_MAGIC}, which is just a random number -defined in @file{threads/thread.c}, and used to detect stack overflow. -See below for more information. -@end table - -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: - -@example -@group - 4 kB +---------------------------------+ - | kernel stack | - | | | - | | | - | V | - | grows downward | - | | - | | - | | - | | - | | - | | - | | - | | - +---------------------------------+ - | magic | - | : | - | : | - | status | - | tid | - 0 kB +---------------------------------+ -@end group -@end example - -The upshot of this is twofold. 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. - -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 @func{malloc} or -@func{palloc_get_page} instead. - -This brings us to the purpose of @struct{thread}'s @code{magic} -member. If a thread's stack does overflow, then @code{magic} will be -the first member of @struct{thread} to be overwritten, because it is -closest to the kernel stack. Thus, some of the thread functions -(notably @func{thread_current}) check that @code{magic} has the proper -value. +@end deftypecv + +@deftypecv {Member} {@struct{thread}} {unsigned} magic +Always set to @code{THREAD_MAGIC}, which is just a random number defined +in @file{threads/thread.c}, and used to detect stack overflow. +@func{thread_current} checks that the @code{magic} member of the running +thread's @struct{thread} is set to @code{THREAD_MAGIC}. Stack overflow +will normally change this value, triggering the assertion. For greatest +benefit, as you add members to @struct{thread}, leave @code{magic} as +the final member. +@end deftypecv @node Thread Functions -@subsection Thread Functions +@subsubsection Thread Functions @file{threads/thread.c} implements several public functions for thread support. Let's take a look at the most useful: @@ -336,8 +357,11 @@ support. Let's take a look at the most useful: @deftypefun void thread_init (void) Called by @func{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 sets up the initial -thread's stack at the end of a page. Before @func{thread_init} runs, +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 @func{thread_init} runs, @func{thread_current} will fail because the running thread's @code{magic} value is incorrect. Lots of functions call @func{thread_current} directly or indirectly, including @@ -348,45 +372,70 @@ called early in Pintos initialization. @deftypefun void thread_start (void) Called by @func{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 enables the scheduler because -processes are rescheduled on return from the timer interrupt, using +ready. Then enables interrupts, which as a side effect enables the +scheduler because the scheduler runs on return from the timer interrupt, using @func{intr_yield_on_return} (@pxref{External Interrupt Handling}). @end deftypefun +@deftypefun 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. +@end deftypefun + +@deftypefun void thread_print_stats (void) +Called during Pintos shutdown to print thread statistics. +@end deftypefun + @deftypefun void thread_create (const char *@var{name}, int @var{priority}, thread_func *@var{func}, void *@var{aux}) Creates and starts a new thread named @var{name} with the given @var{priority}, returning the new thread's tid. The thread executes @var{func}, passing @var{aux} as the function's single argument. -@func{thread_create} works by allocating a page for the thread's -@struct{thread} and stack and initializing its members, then setting -up a set of fake stack frames for it (we'll talk more about this -later). The thread was initialized in the blocked state, so the final -action before returning is to unblock it, allowing the new thread to +@func{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 (more about this +later). The thread is initialized in the blocked state, so the final +action before returning is to unblock it, which allows the new thread to be scheduled. @end deftypefun +@deftp {Type} {void thread_func (void *@var{aux})} +This is the type of a thread function. Its @var{aux} argument is the +value passed to @func{thread_create}. +@end deftp + @deftypefun void thread_block (void) Transitions the running thread from the running state to the blocked -state. This thread will not run again until @func{thread_unblock} is -called on it, so you'd better have some way arranged for that to -happen. +state. The thread will not run again until @func{thread_unblock} is +called on it, so you'd better have some way arranged for that to happen. +Because @func{thread_block} is so low-level, you should prefer to use +one of the synchronization primitives instead (@pxref{Synchronization}). @end deftypefun @deftypefun void thread_unblock (struct thread *@var{thread}) Transitions @var{thread}, which must be in the blocked state, to the -ready state, allowing it to continue to run. This is called when the -event that the thread is waiting for happens, such as the lock that -the thread is waiting on becoming available. +ready state, allowing it to resume running. 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. @end deftypefun -@deftypefun struct thread *thread_current (void) +@deftypefun {struct thread *} thread_current (void) Returns the running thread. @end deftypefun -@deftypefun void thread_exit (void) NO_RETURN -Causes the current thread to exit. Never returns (hence -@code{NO_RETURN}: @pxref{UNUSED NO_RETURN NO_INLINE PRINTF_FORMAT}). +@deftypefun {tid_t} thread_tid (void) +Returns the running thread's thread id. Equivalent to +@code{thread_current ()->tid}. +@end deftypefun + +@deftypefun {const char *} thread_name (void) +Returns the running thread's name. Equivalent to @code{thread_current +()->name}. +@end deftypefun + +@deftypefun void thread_exit (void) @code{NO_RETURN} +Causes the current thread to exit. Never returns, hence +@code{NO_RETURN} (@pxref{Function and Parameter Attributes}). @end deftypefun @deftypefun void thread_yield (void) @@ -396,37 +445,49 @@ function to keep this thread from running for any particular length of time. @end deftypefun +@deftypefun int thread_get_priority (void) +@deftypefunx void thread_set_priority (int @var{new_priority}) +Skeleton to set and get thread priority. @xref{Priority Scheduling}. +@end deftypefun + +@deftypefun int thread_get_nice (void) +@deftypefunx void thread_set_nice (int @var{new_nice}) +@deftypefunx int thread_get_recent_cpu (void) +@deftypefunx int thread_get_load_avg (void) +Skeletons for the advanced scheduler. @xref{4.4BSD Scheduler}. +@end deftypefun + @node Thread Switching -@subsection Thread Switching +@subsubsection Thread Switching @func{schedule} is the function responsible for switching threads. It is internal to @file{threads/thread.c} and called only by the three public thread functions that need to switch threads: @func{thread_block}, @func{thread_exit}, and @func{thread_yield}. -Before these functions call @func{schedule}, they all disable +Before any of these functions call @func{schedule}, they disable interrupts (or ensure that they are already disabled) and then change the running thread's state to something other than running. The actual @func{schedule} implementation is simple. It records the current thread in local variable @var{cur}, determines the next thread to run as local variable @var{next} (by calling -@func{next_thread_to_run}, and then calls @func{switch_threads} to do +@func{next_thread_to_run}), and then calls @func{switch_threads} to do the actual thread switch. The thread we switched to was also running inside @func{switch_threads}, as are all the threads not currently running in Pintos, so the new thread now returns out of @func{switch_threads}, returning the previously running thread. @func{switch_threads} is an assembly language routine in -@file{threads/switch.S}. It saves registers on the stack, stores the -CPU's current stack pointer into the current @struct{thread}'s @code{stack} +@file{threads/switch.S}. It saves registers on the stack, saves the +CPU's current stack pointer in the current @struct{thread}'s @code{stack} member, restores the new thread's @code{stack} into the CPU's stack pointer, restores registers from the stack, and returns. The rest of the scheduler is implemented as @func{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, which couldn't be freed -before the thread switch because the switch needed to use it. +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 @func{thread_create} creates a new thread, it goes through a fair @@ -441,6 +502,7 @@ in the new thread's stack: The topmost fake stack frame is for @func{switch_threads}, represented by @struct{switch_threads_frame}. The important part of this frame is its @code{eip} member, the return address. We point @code{eip} to +@func{switch_entry}, indicating it to be the function that called @func{switch_entry}. @item @@ -465,9 +527,8 @@ interrupts and calls the thread's function (the function passed to @node Synchronization @subsection Synchronization -Situations often arise in threaded programs in which threads want to -share resources. If resource sharing is not handled in a careful, -controlled fashion, then threads can end up making a big mess. +If sharing of resources between threads is not handled in a careful, +controlled fashion, then 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. @@ -477,6 +538,7 @@ synchronization primitives to help out. * Semaphores:: * Locks:: * Condition Variables:: +* Memory Barriers:: @end menu @node Disabling Interrupts @@ -494,13 +556,16 @@ 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 +(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. +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 (@pxref{External Interrupt Handling}). Types and functions for disabling and enabling interrupts are in @file{threads/interrupt.h}. @@ -515,23 +580,27 @@ Returns the current interrupt state. @end deftypefun @deftypefun {enum intr_level} intr_set_level (enum intr_level @var{level}) -Turns interrupts on or off according to @var{level} and returns the +Turns interrupts on or off according to @var{level}. Returns the previous interrupt state. @end deftypefun @deftypefun {enum intr_level} intr_enable (void) -Turns interrupts on and returns the previous interrupt state. +Turns interrupts on. Returns the previous interrupt state. @end deftypefun @deftypefun {enum intr_level} intr_disable (void) -Turns interrupts off and returns the previous interrupt state. +Turns interrupts off. Returns the previous interrupt state. @end deftypefun @node Semaphores @subsubsection Semaphores -A semaphore is a nonnegative integer along with two operators -for atomically manipulating it, which are: +Pintos' semaphore type and operations are declared in +@file{threads/synch.h}. + +@deftp {Type} {struct semaphore} +Represents a @dfn{semaphore}, a nonnegative integer together with two +operators that manipulate it atomically, which are: @itemize @bullet @item @@ -543,13 +612,7 @@ decrement it. if any). @end itemize -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. - -A semaphore initialized to 0 can be useful for waiting for an event +A semaphore initialized to 0 may be used to wait for an event that will happen exactly once. For example, suppose thread @var{A} starts another thread @var{B} and wants to wait for @var{B} to signal that some activity is complete. @var{A} can create a semaphore @@ -558,60 +621,69 @@ initialized to 0, pass it to @var{B} as it starts it, and then ``ups'' the semaphore. This works regardless of whether @var{A} ``downs'' the semaphore or @var{B} ``ups'' it first. -Pintos declared its semaphore type and operations on them in -@file{threads/synch.h}. +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. -@deftp {Type} {struct semaphore} -Represents a semaphore. +Semaphores can also be initialized to values larger than 1. These are +rarely used. @end deftp -@deftypefun void sema_init (struct semaphore *sema, unsigned value, const char *name) +@deftypefun void sema_init (struct semaphore *@var{sema}, unsigned @var{value}) Initializes @var{sema} as a new semaphore with the given initial -@var{value}. Gives @var{sema} the specified @var{name} for use in -debugging. +@var{value}. @end deftypefun @deftypefun void sema_down (struct semaphore *@var{sema}) -Executes the ``down'' or ``P'' operation on the semaphore, waiting for +Executes the ``down'' or ``P'' operation on @var{sema}, waiting for its value to become positive and then decrementing it by one. @end deftypefun +@deftypefun bool sema_try_down (struct semaphore *@var{sema}) +Tries to execute the ``down'' or ``P'' operation on @var{sema}, +without waiting. Returns true if @var{sema} had a positive value +that was successfully decremented, or false if it was already +zero and thus could not be decremented. Calling this function in a +tight loop wastes CPU time (use @func{sema_down} instead, or find a +different approach). +@end deftypefun + @deftypefun void sema_up (struct semaphore *@var{sema}) -Increments @var{sema}'s value. If any threads are waiting on +Executes the ``up'' or ``V'' operation on @var{sema}, +incrementing its value. If any threads are waiting on @var{sema}, wakes one of them up. @end deftypefun -Semaphores are internally built out of interrupt disabling -(@pxref{Disabling Interrupts}), thread blocking and unblocking -(@func{thread_block} and @func{thread_unblock}). Semaphores maintain -a doubly linked list of waiting threads using the linked list +Semaphores are internally built out of disabling interrupt +(@pxref{Disabling Interrupts}) and thread blocking and unblocking +(@func{thread_block} and @func{thread_unblock}). Each semaphore maintains +a list of waiting threads, using the linked list implementation in @file{lib/kernel/list.c}. @node Locks @subsubsection Locks -A lock is a specialization of a semaphore (@pxref{Semaphores}) 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 a single 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. - -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 @file{threads/synch.h}. @deftp {Type} {struct lock} -Represents a lock. +Represents a @dfn{lock}, a specialized semaphore with an initial value +of 1 (@pxref{Semaphores}). The difference between a lock and such a +semaphore is twofold. First, a semaphore does not have an owner, +meaning that one thread can ``down'' the semaphore and then another one +``up'' it, but a single thread must both acquire and release a lock. +Second, a semaphore can have a value greater than 1, but a lock can only +be owned by a single thread at a time. If these restrictions prove +onerous, 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. @end deftp -@deftypefun void lock_init (struct lock *@var{lock}, const char *@var{name}) -Initializes @var{lock} as a new lock and gives it the specified -@var{name} for use in debugging. +@deftypefun void lock_init (struct lock *@var{lock}) +Initializes @var{lock} as a new lock. @end deftypefun @deftypefun void lock_acquire (struct lock *@var{lock}) @@ -619,6 +691,13 @@ Acquires @var{lock} for use by the current thread, first waiting for any current owner to release it if necessary. @end deftypefun +@deftypefun bool lock_try_acquire (struct lock *@var{lock}) +Tries to acquire @var{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 (use @func{lock_acquire} instead). +@end deftypefun + @deftypefun void lock_release (struct lock *@var{lock}) Releases @var{lock}, which the current thread must own. @end deftypefun @@ -631,7 +710,12 @@ false otherwise. @node Condition Variables @subsubsection Condition Variables -A condition variable allows one piece of code to signal a condition +Condition variable types and functions are declared in +@file{threads/synch.h}. + +@deftp {Type} {struct condition} +Represents a condition variable, which allows one piece of code to +signal a condition and cooperating code to receive the signal and act upon it. Each condition variable is associated with a lock. A given condition variable is associated with only a single lock, but one lock may be @@ -639,15 +723,47 @@ associated with any number of condition variables. A set of condition variables taken together with their lock is called a ``monitor.'' A thread that owns the monitor lock is said to be ``in the monitor.'' -The thread in the monitor has control over all the data protected with +The thread in the monitor has control over all the data protected by the lock. It may freely examine or modify this data. If it discovers that it needs to wait for some condition to become true, then it ``waits'' on the associated condition, which releases the lock and -waits for the condition to be signaled. If it, on the other hand, has +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. +Pintos monitors are ``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. +@end deftp + +@deftypefun void cond_init (struct condition *@var{cond}) +Initializes @var{cond} as a new condition variable. +@end deftypefun + +@deftypefun void cond_wait (struct condition *@var{cond}) +Atomically releases @var{lock} (the monitor lock) and waits for +@var{cond} to be signaled by some other piece of code. After +@var{cond} is signaled, reacquires @var{lock} before returning. +@var{lock} must be held before calling this function. +@end deftypefun + +@deftypefun void cond_signal (struct condition *@var{cond}, struct lock *@var{lock}) +If any threads are waiting on @var{cond} (protected by monitor lock +@var{lock}), then this function wakes up one of them. If no threads are +waiting, returns without performing any action. +@var{lock} must be held before calling this function. +@end deftypefun + +@deftypefun void cond_broadcast (struct condition *@var{cond}, struct lock *@var{lock}) +Wakes up all threads, if any, waiting on @var{cond} (protected by +monitor lock @var{lock}). @var{lock} must be held before calling this +function. +@end deftypefun + +@subsubheading Monitor Example + The classical example of a monitor is handling a buffer into which one ``producer'' thread writes characters and out of which a second ``consumer'' thread reads characters. To implement this case we need, @@ -678,7 +794,7 @@ void put (char ch) @{ char get (void) @{ char ch; lock_acquire (&lock); - while (n == 0) /* @r{Can't read from @var{buf} as long as it's empty.} */ + while (n == 0) /* @r{Can't read @var{buf} as long as it's empty.} */ cond_wait (¬_empty, &lock); ch = buf[tail++ % BUF_SIZE]; /* @r{Get @var{ch} from @var{buf}.} */ n--; @@ -687,41 +803,96 @@ char get (void) @{ @} @end example -As the code above illustrates, Pintos monitors are ``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. +@node Memory Barriers +@subsubsection Memory Barriers -Condition variable types and functions are declared in -@file{threads/synch.h}. +Suppose we add a ``feature'' that, whenever a timer interrupt +occurs, the character in global variable @code{timer_put_char} is +printed on the console, but only if global Boolean variable +@code{timer_do_put} is true. -@deftp {Type} {struct condition} -Represents a condition variable. -@end deftp +If interrupts are enabled, this code for setting up @samp{x} to be +printed is clearly incorrect, because the timer interrupt could intervene +between the two assignments: -@deftypefun void cond_init (struct condition *@var{cond}, const char *@var{name}) -Initializes @var{cond} as a new condition variable and gives it the -specified @var{name} for use in debugging. -@end deftypefun +@example +timer_do_put = true; /* INCORRECT CODE */ +timer_put_char = 'x'; +@end example -@deftypefun void cond_wait (struct condition *@var{cond}, struct lock *@var{name}) -Atomically releases @var{lock} (the monitor lock) and waits for -@var{cond} to be signaled by some other piece of code. After -@var{cond} is signaled, @var{lock} is reacquired before returning. -@var{lock} must be held before calling this function. -@end deftypefun +It might not be as obvious that the following code is just as +incorrect: -@deftypefun void cond_signal (struct condition *@var{cond}, struct lock *@var{lock}) -If any threads are waiting on @var{cond} (protected by monitor lock -@var{lock}), then this function signals one of them to wake up from -its wait. @var{lock} must be held before calling this function. -@end deftypefun +@example +timer_put_char = 'x'; /* INCORRECT CODE */ +timer_do_put = true; +@end example -@deftypefun void cond_broadcast (struct condition *@var{cond}, struct lock *@var{lock}) -Wakes up all threads, if any, waiting on @var{cond} (protected by -monitor lock @var{lock}). @var{lock} must be held before calling this -function. -@end deftypefun +The reason this second example might be a problem is that the compiler +is, in general, free to reorder operations when it doesn't have a +visible 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 +optimization pass 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 changing +compiler versions will produce different behavior. + +The following is @emph{not} a solution, because locks neither prevent +interrupts nor prevent the compiler from reordering the code within the +region where the lock is held: + +@example +lock_acquire (&timer_lock); /* INCORRECT CODE */ +timer_put_char = 'x'; +timer_do_put = true; +lock_release (&timer_lock); +@end example + +Fortunately, real solutions do exist. One possibility is to +disable interrupts around the assignments. This does not prevent +reordering, but it makes the assignments atomic as observed by the +interrupt handler. It also has the extra runtime cost of disabling and +re-enabling interrupts: + +@example +enum intr_level old_level = intr_disable (); +timer_put_char = 'x'; +timer_do_put = true; +intr_set_level (old_level); +@end example + +A second possibility is to mark the declarations of +@code{timer_put_char} and @code{timer_do_put} as @samp{volatile}. This +keyword tells the compiler that the variables are externally observable +and allows it less latitude for optimization. However, the semantics of +@samp{volatile} are not well-defined, so it is not a good general +solution. + +Usually, the best solution is to use a compiler feature called a +@dfn{memory barrier}, a special statement that prevents the compiler +from reordering memory operations across the barrier. In Pintos, +@file{threads/synch.h} defines the @code{barrier()} macro as a memory +barrier. Here's how we would use a memory barrier to fix this code: + +@example +timer_put_char = 'x'; +barrier (); +timer_do_put = true; +@end example + +The compiler also treats invocation of any function defined externally, +that is, in another source file, as a limited form of a memory 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, and, indeed, this is why the base Pintos code +does not need any barriers. + +A function defined in the same source file, or in a header included by +the source file, cannot be relied upon as a memory 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. @node Interrupt Handling @subsection Interrupt Handling @@ -735,9 +906,9 @@ For our purposes, we classify interrupts into two broad categories: @dfn{External interrupts}, that is, interrupts originating outside the CPU. These interrupts come from hardware devices such as the system timer, keyboard, serial ports, and disks. External interrupts are -@dfn{asynchronous}, meaning that they don't occur in a fashion -synchronized with anything going on in the CPU. External interrupts -are what @func{intr_disable} and related functions can arrange to +@dfn{asynchronous}, meaning that their delivery is not +synchronized with normal CPU activities. External interrupts +are what @func{intr_disable} and related functions postpone (@pxref{Disabling Interrupts}). @item @@ -753,10 +924,9 @@ disable internal interrupts. Because the CPU treats all interrupts largely the same way, regardless of source, Pintos uses the same infrastructure for both internal and -external interrupts, up to a point. The next section describes this -common infrastructure. But external and internal interrupts are -actually quite different, so we also follow up that section with one -specific to each category. +external interrupts, to a point. The following section describes this +common infrastructure, and sections after that give the specifics of +external and internal interrupts. If you haven't already read chapter 3 in @bibref{IA32-v1}, it is recommended that you do so now. You might also want to skim chapter 5 @@ -786,13 +956,13 @@ 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 @func{intr_entry}, which pushes all the registers that the processor -didn't save for us, and then calls @func{intr_handler}, which +didn't already save for us, and then calls @func{intr_handler}, which brings us back into C in @file{threads/interrupt.c}. The main job of @func{intr_handler} is to call any function that has -been registered for handling the particular interrupt. (If there's no -function registered, it dumps some information to the console and -panics the kernel.) It does some extra processing for external +been registered for handling the particular interrupt. (If no +function is registered, it dumps some information to the console and +panics.) It does some extra processing for external interrupts that we'll discuss later. When @func{intr_handler} returns, the assembly code in @@ -802,68 +972,54 @@ earlier and directs the CPU to return from the interrupt. A few types and functions apply to both internal and external interrupts. -@deftypefun void intr_register (uint8_t @var{vec}, int @var{dpl}, enum intr_level @var{level}, intr_handler_func *@var{func}, const char *@var{name}) -Registers @var{func} to be called when the interrupt numbered -@var{vec} is triggered. Names the interrupt @var{name} for debugging -purposes. - -If @var{level} is @code{INTR_OFF} then handling of further interrupts -will be disabled while the interrupt is being processed. If @var{vec} -denotes an external interrupt, then @var{level} must be -@code{INTR_OFF}. Otherwise, interrupts should normally be turned on -during the handling of an internal interrupt. - -For internal interrupts, @var{dpl} determines how the interrupt can be -invoked. If @var{dpl} is 0, then the interrupt can be invoked only by -kernel threads. Otherwise @var{dpl} should be 3, which allows user -processes to invoke the interrupt as well (this is useful only -starting with project 2). @var{dpl} has no effect on external -interrupts -@end deftypefun - @deftp {Type} {void intr_handler_func (struct intr_frame *@var{frame})} -This is the type of an interrupt handler function. Its @var{frame} +This is how an interrupt handler function must be declared. Its @var{frame} argument (see below) allows it to determine the cause of the interrupt and the state of the thread that was interrupted. @end deftp @deftp {Type} {struct intr_frame} -The stack frame of an interrupt handler. Its most interesting members -are as follows: -@table @code -@item uint32_t edi; -@item uint32_t esi; -@item uint32_t ebp; -@item uint32_t esp_dummy; -@item uint32_t ebx; -@item uint32_t edx; -@item uint32_t ecx; -@item uint32_t eax; -@item uint16_t es; -@item uint16_t ds; +The stack frame of an interrupt handler, as saved by CPU, the interrupt +stubs, and @func{intr_entry}. Its most interesting members are described +below. +@end deftp + +@deftypecv {Member} {@struct{intr_frame}} uint32_t edi +@deftypecvx {Member} {@struct{intr_frame}} uint32_t esi +@deftypecvx {Member} {@struct{intr_frame}} uint32_t ebp +@deftypecvx {Member} {@struct{intr_frame}} uint32_t esp_dummy +@deftypecvx {Member} {@struct{intr_frame}} uint32_t ebx +@deftypecvx {Member} {@struct{intr_frame}} uint32_t edx +@deftypecvx {Member} {@struct{intr_frame}} uint32_t ecx +@deftypecvx {Member} {@struct{intr_frame}} uint32_t eax +@deftypecvx {Member} {@struct{intr_frame}} uint16_t es +@deftypecvx {Member} {@struct{intr_frame}} uint16_t ds Register values in the interrupted thread saved by @func{intr_entry}. The @code{esp_dummy} value isn't actually used (refer to the description of @code{PUSHA} in @bibref{IA32-v2b} for details). +@end deftypecv -@item uint32_t vec_no; +@deftypecv {Member} {@struct{intr_frame}} uint32_t vec_no The interrupt vector number, ranging from 0 to 255. +@end deftypecv -@item uint32_t error_code; +@deftypecv {Member} {@struct{intr_frame}} uint32_t error_code The ``error code'' pushed on the stack by the CPU for some internal interrupts. +@end deftypecv -@item void (*eip) (void); +@deftypecv {Member} {@struct{intr_frame}} void (*eip) (void) The address of the next instruction to be executed by the interrupted thread. +@end deftypecv -@item void *esp; +@deftypecv {Member} {@struct{intr_frame}} {void *} esp The interrupted thread's stack pointer. -@end table -@end deftp +@end deftypecv @deftypefun {const char *} intr_name (uint8_t @var{vec}) -Returns the registered name of the interrupt numbered @var{vec}, or -@code{"unknown"} if the interrupt's name is not known. +Returns the name of the interrupt numbered @var{vec}, or +@code{"unknown"} if the interrupt has no registered name. @end deftypefun @node Internal Interrupt Handling @@ -875,9 +1031,8 @@ caused it. Thus, because it is related to a thread (or process), an internal interrupt is said to happen in a ``process context.'' In an internal interrupt, it can make sense to examine the -@struct{intr_frame} passed to the interrupt handler. In cases where -the interrupted thread intentionally caused the interrupt, it can even -make sense to modify it. When the interrupt returns, modified members +@struct{intr_frame} passed to the interrupt handler, or even to modify +it. When the interrupt returns, modified members in @struct{intr_frame} become changes to the thread's registers. We'll use this in project 2 to return values from system call handlers. @@ -888,6 +1043,23 @@ 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 (@pxref{Synchronization}). +@deftypefun void intr_register_int (uint8_t @var{vec}, int @var{dpl}, enum intr_level @var{level}, intr_handler_func *@var{handler}, const char *@var{name}) +Registers @var{func} to be called when internal interrupt numbered +@var{vec} is triggered. Names the interrupt @var{name} for debugging +purposes. + +If @var{level} is @code{INTR_OFF} then handling of further interrupts +will be disabled while the interrupt is being processed. Interrupts +should normally be turned on during the handling of an internal +interrupt. + +@var{dpl} determines how the interrupt can be +invoked. If @var{dpl} is 0, then the interrupt can be invoked only by +kernel threads. Otherwise @var{dpl} should be 3, which allows user +processes to invoke the interrupt as well (this is useful only +starting with project 2). +@end deftypefun + @node External Interrupt Handling @subsubsection External Interrupt Handling @@ -900,7 +1072,7 @@ 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, though rarely useful, to examine it, but +one that is. It is possible, although rarely useful, to examine it, but modifying it is a recipe for disaster. The activities of an external interrupt handler are severely @@ -911,7 +1083,7 @@ disabled (@pxref{Disabling Interrupts}) and that interrupts may not be enabled at any point during their execution. Second, an interrupt handler must not call any function that can -sleep, which includes @func{thread_yield}, @func{lock_acquire}, and +sleep, which rules out @func{thread_yield}, @func{lock_acquire}, and many others. This is because external interrupts use space on the stack of the kernel thread that was running at the time the interrupt occurred. If the interrupt handler tried to sleep and that thread @@ -926,31 +1098,38 @@ run in a kernel thread, possibly a thread whose activity is triggered by the interrupt using some synchronization primitive. External interrupts are also special because they are controlled by a -device external to the CPU called a @dfn{programmable interrupt -controller} or @dfn{PIC} for short. When @func{intr_init} sets up the -CPU's IDT, it also initializes the PIC for interrupt handling. The -PIC also has to be ``acknowledged'' at the end of processing for each +pair of devices outside the CPU called @dfn{programmable interrupt +controllers}, @dfn{PICs} for short. When @func{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. @func{intr_handler} takes care of that by calling @func{pic_end_of_interrupt}, which sends the proper signals to the -PIC. +right PIC. -The following additional interrupts functions are related to external +The following additional functions are related to external interrupts. +@deftypefun void intr_register_ext (uint8_t @var{vec}, intr_handler_func *@var{handler}, const char *@var{name}) +Registers @var{handler} to be called when external interrupt numbered +@var{vec} is triggered. Names the interrupt @var{name} for debugging +purposes. The handler will run with interrupts disabled. +@end deftypefun + @deftypefun bool intr_context (void) -Returns true if we are running in an interrupt context and false -otherwise. Mainly used at the beginning of functions that might sleep -or that otherwise should not be, in this form: +Returns true if we are running in an interrupt context, otherwise +false. Mainly used at the beginning of functions that might sleep +or that otherwise should not be called from interrupt context, in this +form: @example ASSERT (!intr_context ()); @end example @end deftypefun -@deftypefun intr_yield_on_return (void) +@deftypefun void intr_yield_on_return (void) When called in an interrupt context, causes @func{thread_yield} to be called just before the interrupt returns. This is used, for example, in the timer interrupt handler to cause a new thread to be scheduled -on every timer tick. +when a thread's time slice expires. @end deftypefun @node Memory Allocation @@ -960,12 +1139,12 @@ Pintos contains two memory allocators, one that allocates memory in units of a page, and one that can allocate blocks of any size. @menu -* Page Allocation:: +* Page Allocator:: * Block Allocator:: @end menu -@node Page Allocation -@subsubsection Page Allocation +@node Page Allocator +@subsubsection Page Allocator The page allocator declared in @file{threads/palloc.h} allocates memory in units of a page. It is most often used to allocate memory @@ -982,11 +1161,11 @@ 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 pools is managed with a bitmap of used pages, one bit per page in -the pool. An allocation request for @var{n} pages scans the bitmap, -starting from the beginning, for @var{n} consecutive bits set to +Each pool's usage is tracked with a bitmap, one bit per page in +the pool. A request to allocate @var{n} pages scans the bitmap +for @var{n} consecutive bits set to false, indicating that those pages are free, and then sets those bits -to true to mark them as now used. This is a ``first fit'' allocation +to true to mark them as used. This is a ``first fit'' allocation strategy. The page allocator is subject to fragmentation. That is, it may not @@ -994,36 +1173,42 @@ be possible to allocate @var{n} contiguous pages even though @var{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 @var{n} / 2 pages are free! -However, single-page requests can't fail due to fragmentation. Thus, -it is best to limit the need to allocate more than one contiguous page -as much as possible. +Single-page requests can't fail due to fragmentation, so +it is best to limit, as much as possible, the need for multiple +contiguous pages. -The interesting page allocator types and functions are described -below. +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 @t{0xcc}, as +a debugging aid (@pxref{Debugging Tips}). + +Page allocator types and functions are described below. @deftp {Type} {enum palloc_flags} A set of flags that describe how to allocate pages. These flags may -be combined in any combination: +be combined in any combination. +@end deftp -@table @code -@item PAL_ASSERT +@defvr {Page Allocator Flag} @code{PAL_ASSERT} If the pages cannot be allocated, panic the kernel. This is only -appropriate during kernel initialization, because user processes +appropriate during kernel initialization. User processes should never be permitted to panic the kernel. +@end defvr -@item PAL_ZERO -Clear the pages to all zeros before returning them. If not set, -newly allocated pages' contents are unpredictable. +@defvr {Page Allocator Flag} @code{PAL_ZERO} +Zero all the bytes in the allocated pages before returning them. If not +set, the contents of newly allocated pages are unpredictable. +@end defvr -@item PAL_USER +@defvr {Page Allocator Flag} @code{PAL_USER} Obtain the pages from the user pool. If not set, pages are allocated from the kernel pool. -@end table -@end deftp +@end defvr @deftypefun {void *} palloc_get_page (enum palloc_flags @var{flags}) -Obtains a single free page, allocating it in the manner specified by -@var{flags}, and returns it. Returns a null pointer if no pages are +Obtains and returns a single page, allocating it in the manner specified by +@var{flags}. Returns a null pointer if no pages are free. @end deftypefun @@ -1053,13 +1238,15 @@ 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 of these applies to blocks no larger than 1 kB (one +memory. The first of these applies to ``small'' blocks, those 1 kB or +smaller (one fourth of the 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 power of 2 +grouped into a page used only for allocations of the smae size. -A different strategy applies to allocating blocks larger than 1 kB. +The second strategy applies to allocating ``large'' blocks, those 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. @@ -1080,3 +1267,8 @@ those over approximately 4 kB each. The interface to the block allocator is through the standard C library functions @func{malloc}, @func{calloc}, and @func{free}. + +When a block is freed, all of its bytes are cleared to @t{0xcc}, as +a debugging aid (@pxref{Debugging Tips}). + +The block allocator may not be called from interrupt context. diff --git a/doc/userprog.texi b/doc/userprog.texi index 9fe874d..bab70ec 100644 --- a/doc/userprog.texi +++ b/doc/userprog.texi @@ -1,53 +1,65 @@ @node Project 2--User Programs, Project 3--Virtual Memory, Project 1--Threads, Top @chapter Project 2: User Programs -Now that you've worked with Pintos and are familiar with its +Now that you've worked with Pintos and are becoming familiar with its infrastructure and thread package, it's time to start working on the -parts of the system that will allow users to run programs on top of -your operating system. The base code already supports loading and -running a single user program at a time with little interactivity -possible. You will allow multiple programs to be loaded in at once, -and to interact with the OS via system calls. +parts of the system that allow running user programs. +The base code already supports loading and +running user programs, but no I/O or interactivity +is possible. In this project, you will enable programs to interact with +the OS via system calls. You will be working out of the @file{userprog} directory for this assignment. However, you will also be interacting with almost every other part of the code for this assignment. We will describe the -relevant parts below. If you are confident in your HW1 code, you can -build on top of it. However, if you wish you can start with a fresh -copy of the code. No code from project 1 is required for this -assignment. +relevant parts below. -Up to now, all of the code you have written for Pintos has been part +You can build project 2 on top of your project 1 submission or you can +start with a fresh copy. No code from project 1 is required for this +assignment. The ``alarm clock'' functionality may be useful in later +projects, but it is not strictly required. + +@menu +* Project 2 Background:: +* Project 2 Requirements:: +* Project 2 FAQ:: +* 80x86 Calling Convention:: +@end menu + +@node Project 2 Background +@section Background + +Up to now, all of the code you have run under Pintos has been part of the operating system kernel. This means, for example, that all the test code from the last assignment ran as part of the 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. This project deals with consequences of the change. -We allow more than one user program to run at a time. Because user +We allow more than one user program to run at a time. User programs are written and compiled to work under the illusion that they -have the entire machine, when you load into memory and run more than -one process at a time, you must manage things correctly to maintain -this illusion. - -Before we delve into the details of the new code that you'll be -working with, you should probably undo the test cases from project 1. +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 the previous project, we compiled our test code directly into your +kernel, so we had to require certain specific function interfaces within +the kernel. From now on, 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. @menu -* Project 2 Code:: +* Project 2 Source Files:: * Using the File System:: * How User Programs Work:: * Virtual Memory Layout:: -* Grading Requirements:: -* Problem 2-1 Argument Passing:: -* Problem 2-2 System Calls:: -* User Programs FAQ:: -* 80x86 Calling Convention:: -* System Calls:: +* Accessing User Memory:: @end menu -@node Project 2 Code -@section Code +@node Project 2 Source Files +@subsection Source Files The easiest way to get an overview of the programming you will be doing is to simply go over each part you'll be working with. In @@ -61,15 +73,14 @@ Loads ELF binaries and starts processes. @item pagedir.c @itemx pagedir.h -A simple manager for 80@var{x} page directories and page tables. +A simple manager for 80@var{x}86 page directories and page tables. Although you probably won't want to modify this code for this project, -you may want to call some of its functions. In particular, -@func{pagedir_get_page} may be helpful for accessing user memory. +you may want to call some of its functions. @item syscall.c @itemx syscall.h Whenever a user process wants to access some kernel functionality, it -needs to do so via a system call. This is a skeleton system call +invokes a system call. This is a skeleton system call handler. Currently, it just prints a message and terminates the user process. In part 2 of this project you will add code to do everything else needed by system calls. @@ -79,39 +90,32 @@ else needed by system calls. When a user process performs a privileged or prohibited operation, it traps into the kernel as an ``exception'' or ``fault.''@footnote{We will treat these terms as synonymous. There is no standard -distinction between them, although the Intel processor manuals define +distinction between them, although Intel processor manuals define them slightly differently on 80@var{x}86.} These files handle exceptions. Currently all exceptions simply print a message and terminate the process. Some, but not all, solutions to project 2 require modifying @func{page_fault} in this file. @item gdt.c -@itemx gdt.c +@itemx gdt.h The 80@var{x}86 is a segmented architecture. The Global Descriptor Table (GDT) is a table that describes the segments in use. These files set up the GDT. @strong{You should not need to modify these -files for any of the projects.} However, you can read the code if +files for any of the projects.} You can read the code if you're interested in how the GDT works. @item tss.c -@itemx tss.c +@itemx tss.h The Task-State Segment (TSS) is used for 80@var{x}86 architectural task switching. Pintos uses the TSS only for switching stacks when a user process enters an interrupt handler, as does Linux. @strong{You should not need to modify these files for any of the projects.} -However, you can read the code if you're interested in how the TSS +You can read the code if you're interested in how the TSS works. @end table -Finally, in @file{lib/kernel}, you might want to use -@file{bitmap.[ch]}. A bitmap is basically an array of bits, each of -which can be true or false. Bitmaps are typically used to keep track -of the usage of a large array of (identical) resources: if resource -@var{n} is in use, then bit @var{n} of the bitmap is true. You might -find it useful for tracking memory pages, for example. - @node Using the File System -@section Using the File System +@subsection Using the File System You will need to use some file system code for this project. First, user programs are loaded from the file system. Second, many of the @@ -121,7 +125,7 @@ provided a simple file system in the @file{filesys} directory. You will want to look over the @file{filesys.h} and @file{file.h} interfaces to understand how to use the file system, and especially its many limitations. @strong{You should not modify the file system -code for this project}. Proper use of the file system routines now +code for this project.} Proper use of the file system routines now will make life much easier for project 4, when you improve the file system implementation. Until then, you will have to put up with the following limitations: @@ -129,12 +133,12 @@ following limitations: @itemize @bullet @item No synchronization. Concurrent accesses will interfere with one -another, so external synchronization is needed. @xref{Synchronizing -File Access}, for more details. +another. You should use a global lock to ensure that only one process at a +time is executing file system code. @item -File size is fixed at creation time. Because the root directory is -represented as a file, the number of files that may be created is also +File size is fixed at creation time. The root directory is +represented as a file, so the number of files that may be created is also limited. @item @@ -151,95 +155,117 @@ File names are limited to 14 characters. @item A system crash mid-operation may corrupt the disk in a way -that cannot be repaired automatically. No `fsck' tool is -provided in any case. +that cannot be repaired automatically. There is no file system repair +tool anyway. @end itemize -However one important feature is included: +One important feature is included: @itemize @bullet @item -Unix-like semantics for filesys_remove() are implemented. +Unix-like semantics for @func{filesys_remove} are implemented. That is, if a file is open when it is removed, its blocks -are not deallocated and it may still be accessed by the +are not deallocated and it may still be accessed by any threads that have it open until the last one closes it. @xref{Removing an Open File}, for more information. @end itemize -You need to be able to create and format simulated disks. The -@command{pintos} program provides this functionality with its -@option{make-disk} command. From the @file{userprog/build} directory, -execute @code{pintos make-disk fs.dsk 2}. This command creates a 2 MB -simulated disk named @file{fs.dsk}. (It does not actually start -Pintos.) Then format the disk by passing the @option{-f} option to -Pintos on the kernel's command line: @code{pintos run -f}. - -You'll need a way to get files in and out of the simulated file -system. The @code{pintos} @option{put} and @option{get} commands are -designed for this. To copy @file{@var{file}} into the Pintos file -system, use the command @file{pintos put @var{file}}. To copy it to -the Pintos file system under the name @file{@var{newname}}, add the -new name to the end of the command: @file{pintos put @var{file} -@var{newname}}. The commands for copying files out of a VM are -similar, but substitute @option{get} for @option{get}. - -Incidentally, these commands work by passing special options -@option{-ci} and @option{-co} on the kernel's command line and copying -to and from a special simulated disk named @file{scratch.dsk}. If -you're very curious, you can look at the @command{pintos} program as -well as @file{filesys/fsutil.c} to learn the implementation details, -but it's really not relevant for this project. - -Here's a summary of how you would create and format a disk, copy the -@command{echo} program into the new disk, and then run @command{echo}. -It assumes that you've already built the tests in -@file{tests/userprog} and that the current directory is +You need to be able to create simulated disks. The +@command{pintos-mkdisk} program provides this functionality. From the +@file{userprog/build} directory, execute @code{pintos-mkdisk fs.dsk 2}. +This command creates a 2 MB simulated disk named @file{fs.dsk}. Then +format the disk by passing @option{-f -q} on the kernel's command +line: @code{pintos -f -q}. The @option{-f} option causes the disk to be +formatted, and @option{-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 @code{pintos} @option{-p} (``put'') and @option{-g} (``get'') +options do this. To copy @file{@var{file}} into the +Pintos file system, use the command @file{pintos -p @var{file} -- -q}. +(The @samp{--} is needed because @option{-p} is for the @command{pintos} +script, not for the simulated kernel.) To copy it to the Pintos file +system under the name @file{@var{newname}}, add @option{-a +@var{newname}}: @file{pintos -p @var{file} -a @var{newname} -- -q}. The +commands for copying files out of a VM are similar, but substitute +@option{-g} for @option{-p}. + +Incidentally, these commands work by passing special commands +@command{put} and @command{get} on the kernel's command line and copying +to and from a special simulated ``scratch'' disk. If you're very +curious, you can look at the @command{pintos} program as well as +@file{filesys/fsutil.c} to learn the implementation details. + +Here's a summary of how to create and format a disk, copy the +@command{echo} program into the new disk, and then run @command{echo}, +passing argument @code{x}. (Argument passing won't work until +you've implemented it.) It assumes +that you've already built the +examples in @file{examples} and that the current directory is @file{userprog/build}: @example -pintos make-disk fs.dsk 2 -pintos run -f -pintos put ../../tests/userprog/echo echo -pintos run -ex echo +pintos-mkdisk fs.dsk 2 +pintos -f -q +pintos -p ../../examples/echo -a echo -- -q +pintos -q run 'echo x' @end example -You can delete a file from the Pintos file system using the @option{-r -@var{file}} kernel option, e.g.@: @code{pintos run -r @var{file}}. -Also, @option{-ls} lists the files in the file system and @option{-p +The three final steps can actually be combined into a single command: + +@example +pintos-mkdisk fs.dsk 2 +pintos -p ../../examples/echo -a echo -- -f -q run 'echo x' +@end example + +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 @code{--fs-disk=2} option creates a temporary disk just for the +duration of the @command{pintos} run. The Pintos automatic test suite +makes extensive use of this syntax: + +@example +pintos --fs-disk=2 -p ../../examples/echo -a echo -- -f -q run 'echo x' +@end example + +You can delete a file from the Pintos file system using the @code{rm +@var{file}} kernel action, e.g.@: @code{pintos -q rm @var{file}}. Also, +@command{ls} lists the files in the file system and @code{cat @var{file}} prints a file's contents to the display. @node How User Programs Work -@section How User Programs Work - -Pintos can run normal C programs. In fact, it can run any program you -want, provided it's compiled into the proper file format, and uses -only the system calls you implement. (For example, @func{malloc} -makes use of functionality that isn't provided by any of the syscalls -we require you to support.) The only other limitation is that Pintos -can't run programs using floating point operations, since it doesn't -include the necessary kernel functionality to save and restore the -processor's floating-point unit when switching threads. You can look -in @file{tests/userprog} directory for some examples. - -Pintos loads ELF executables, where ELF is an executable format used -by Linux, Solaris, and many other Unix and Unix-like systems. -Therefore, you can use any compiler and linker that produce -80@var{x}86 ELF executables to produce programs for Pintos. We -recommend using the tools we provide in the @file{tests/userprog} -directory. By default, the @file{Makefile} in this directory will -compile the test programs we provide. You can edit the -@file{Makefile} to compile your own test programs as well. - -One thing you should realize immediately is that, until you copy a -test program to the emulated disk, Pintos will be unable to do very -much useful work. You will also find that you won't be able to do +@subsection How User Programs Work + +Pintos can run normal C programs. In fact, Pintos can run any program +you want, as long as it's compiled into the proper file format and uses +only the system calls you implement. Notably, @func{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 @file{src/examples} directory contains a few sample user +programs. The @file{Makefile} in this directory +compiles the provided examples, and you can edit it +compile your own programs as well. + +Pintos loads @dfn{ELF} executables. 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 80@var{x}86 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 emulated disk, 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 disk. -A useful technique is to create a clean reference disk and copy that +You might want to create a clean reference disk and copy that over whenever you trash your @file{fs.dsk} beyond a useful state, which may happen occasionally while debugging. @node Virtual Memory Layout -@section Virtual Memory Layout +@subsection Virtual Memory Layout Virtual memory in Pintos is divided into two regions: user virtual memory and kernel virtual memory. User virtual memory ranges from @@ -248,24 +274,24 @@ virtual address 0 up to @code{PHYS_BASE}, which is defined in virtual memory occupies the rest of the virtual address space, from @code{PHYS_BASE} up to 4 GB. -User virtual memory is per-process. Conceptually, each process is -free to use the entire space of user virtual memory however it -chooses. When the kernel switches from one process to another, it -also switches user virtual address spaces by switching the processor's -page directory base register (see @func{pagedir_activate in -@file{userprog/pagedir.c}}. @struct{thread} contains a pointer to a +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 @func{pagedir_activate} in +@file{userprog/pagedir.c}). @struct{thread} contains a pointer to a process's page directory. 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. That is, virtual address @code{PHYS_ADDR} accesses physical +memory, starting at @code{PHYS_BASE}. That is, virtual address +@code{PHYS_ADDR} accesses physical address 0, virtual address @code{PHYS_ADDR} + @t{0x1234} access physical address @t{0x1234}, and so on up to the size of the machine's physical memory. -User programs can only access user virtual memory. An attempt to -access kernel virtual memory will cause a page fault, handled by +A user program can only access its own user virtual memory. An attempt to +access kernel virtual memory causes a page fault, handled by @func{page_fault} in @file{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 @@ -273,155 +299,247 @@ the running process. However, even in the kernel, an attempt to access memory at a user virtual address that doesn't have a page mapped into it will cause a page fault. -You must handle memory fragmentation gracefully, that is, a process -that needs @var{N} pages of memory must not require that all @var{N} -be contiguous. In fact, it must not require that any of the pages be -contiguous. +You must handle memory fragmentation gracefully, that is, a process that +needs @var{N} pages of user virtual memory must not require those pages +to be contiguous in kernel virtual memory. -@node Grading Requirements -@section Grading Requirements +@menu +* Typical Memory Layout:: +@end menu -For testing and grading purposes, we have some simple overall -requirements: +@node Typical Memory Layout +@subsubsection Typical Memory Layout -@itemize @bullet -@item -The kernel should print out the program's name and exit status whenever -a process terminates, whether termination is caused by the @code{exit} -system call or for another reason. +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: -@itemize @minus -@item -The message must be formatted exactly as if it was printed with -@code{printf ("%s: exit(%d)\n", @dots{});} given appropriate arguments. +@html +
+@end html +@example +@group + PHYS_BASE +----------------------------------+ + | user stack | + | | | + | | | + | V | + | grows downward | + | | + | | + | | + | | + | grows upward | + | ^ | + | | | + | | | + +----------------------------------+ + | uninitialized data segment (BSS) | + +----------------------------------+ + | initialized data segment | + +----------------------------------+ + | code segment | + 0x08048000 +----------------------------------+ + | | + | | + | | + | | + | | + 0 +----------------------------------+ +@end group +@end example +@html +
+@end html -@item -The name printed should be the full name passed to -@func{process_execute}, except that it is acceptable to truncate it to -15 characters to allow for the limited space in @struct{thread}. The -name printed need not include arguments. +In this project, the user stack is fixed in size, but in project 3 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 +@t{0x08084000}, approximately 128 MB from the bottom of the address +space. This value is specified in @bibref{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 @samp{info +ld}. + +To view the layout of a particular executable, run @command{objdump} +(80@var{x}86) or @command{i386-elf-objdump} (SPARC) with the @option{-p} +option. + +@node Accessing User Memory +@subsection 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 @code{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. -@item -Do not print a message when a kernel thread that is not a process -terminates. +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. If you +choose this route, you'll want to look at the functions in +@file{userprog/pagedir.c} and in @file{threads/mmu.h}. This is the +simplest way to handle user memory access. -@item -Do not print messages about process termination for the @code{halt} -system call. +The second method is to check only that a user +pointer points below @code{PHYS_BASE}, then dereference it. +An invalid user pointer will cause a ``page fault'' that you can +handle by modifying the code for @func{page_fault} in +@file{userprog/exception.cc}. 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). -@item -No message need be printed when a process fails to load. -@end itemize +In either case, you need to make sure not to ``leak'' resources. For +example, suppose that your system call has acquired a lock or +allocated a page of memory. 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. +Therefore, for those who want to try the latter technique, we'll +provide a little bit of helpful code: -@item -Aside from this, the kernel should print out no other messages that -Pintos as provided doesn't already print. You -may understand all those debug messages, but we won't, and it just -clutters our ability to see the stuff we care about. +@verbatim +/* Tries to copy a byte from user address USRC to kernel address KDST. + Returns true if successful, false if USRC is invalid. */ +static inline bool get_user (uint8_t *kdst, const uint8_t *usrc) { + int eax; + asm ("mov %%eax, offset 1f; mov %%al, %2; mov %0, %%al; 1:" + : "=m" (*kdst), "=&a" (eax) : "m" (*usrc)); + return eax != 0; +} -@item -Additionally, while it may be useful to hard-code which process will -run at startup while debugging, before you submit your code you must -make sure that it takes the start-up process name and arguments from -the @samp{-ex} argument. For example, running @code{pintos run -ex -"testprogram 1 2 3 4"} will spawn @samp{testprogram 1 2 3 4} as the -first process. +/* Tries to write BYTE to user address UDST. + Returns true if successful, false if UDST is invalid. */ +static inline bool put_user (uint8_t *udst, uint8_t byte) { + int eax; + asm ("mov %%eax, offset 1f; mov %0, %b2; 1:" + : "=m" (*udst), "=&a" (eax) : "r" (byte)); + return eax != 0; +} +@end verbatim -@item -In the previous project, we required that you provided some specific -function interfaces, because we tested your project by compiling our -test code into it. For this project and all later projects, this is -no longer necessary, because we will do all of our testing with user -programs. You must make sure that the user program interface meets -the specifications described in the assignments, but given that -constraint you are free to restructure or rewrite kernel code however -you wish. -@end itemize +Each of these functions assumes that the user address has already been +verified to be below @code{PHYS_BASE}. They also assume that you've +modified @func{page_fault} so that a page fault in the kernel causes +@code{eax} to be set to 0 and its former value copied into @code{eip}. -@node Problem 2-1 Argument Passing -@section Problem 2-1: Argument Passing - -Currently, @func{process_execute} does not support passing arguments -to new processes. UNIX and other operating systems do allow passing -command line arguments to a program, which accesses them via the argc, -argv arguments to main. You must implement this functionality by -extending @func{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, @code{process_execute("grep foo bar")} -should run @command{grep} passing two arguments @code{foo} and -@file{bar}. A few details: - -@itemize -@item -Multiple spaces are considered the same as a single space, so that -@code{process_execute("grep foo bar")} would be equivalent to our -original example. +@node Project 2 Requirements +@section Requirements -@item -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). +@menu +* Project 2 Design Document:: +* Process Termination Messages:: +* Argument Passing:: +* System Calls:: +* Denying Writes to Executables:: +@end menu -@item -You can parse the argument strings any way you like. If you're lost, +@node Project 2 Design Document +@subsection Design Document + +Before you turn in your project, you must copy @uref{userprog.tmpl, , +the project 2 design document template} into your source tree under the +name @file{pintos/src/userprog/DESIGNDOC} and fill it in. We recommend +that you read the design document template before you start working on +the project. @xref{Project Documentation}, for a sample design document +that goes along with a fictitious project. + +@node Process Termination Messages +@subsection Process Termination Messages + +Whenever a user process terminates, because it called @code{exit} +or for any other reason, print the process's name +and exit code, formatted as if printed by @code{printf ("%s: +exit(%d)\n", @dots{});}. The name printed should be the full name +passed to @func{process_execute}, omitting command-line arguments. +Do not print these messages when a kernel thread that is not a user +process terminates, or +when the @code{halt} system call is invoked. The message is optional +when a process fails to load. + +Aside from this, don't print any other +messages that Pintos as provided doesn't already print. You may find +extra messages useful during debugging, but they will confuse the +grading scripts and thus lower your score. + +@node Argument Passing +@subsection Argument Passing + +Currently, @func{process_execute} does not support passing arguments to +new processes. Implement this functionality, by extending +@func{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, @code{process_execute("grep foo bar")} should run +@command{grep} passing two arguments @code{foo} and @code{bar}. + +Within a command line, multiple spaces are equivalent to a single space, +so that @code{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). + +You can parse argument strings any way you like. If you're lost, look at @func{strtok_r}, prototyped in @file{lib/string.h} and implemented with thorough comments in @file{lib/string.c}. You can find more about it by looking at the man page (run @code{man strtok_r} at the prompt). -@item -@xref{80x86 Calling Convention}, for information on exactly how you +@xref{Program Startup Details}, for information on exactly how you need to set up the stack. -@end itemize -@strong{This functionality is extremely important.} Almost all our -test cases rely on being able to pass arguments, so if you don't get -this right, a lot of things will not appear to work correctly with our -tests. If the tests fail, so do you. Fortunately, this part -shouldn't be too hard. - -@node Problem 2-2 System Calls -@section Problem 2-2: System Calls - -Implement the system call handler in @file{userprog/syscall.c} to -properly deal with all the system calls described below. Currently, -it ``handles'' system calls by terminating the process. You will need -to decipher system call arguments and take the appropriate action for -each. - -You are required to support the following system calls, whose syscall -numbers are defined in @file{lib/syscall-nr.h} and whose C functions -called by user programs are prototyped in @file{lib/user/syscall.h}: - -@table @code -@item SYS_halt -@itemx void halt (void) -Stops Pintos by calling @func{power_off} (declared in +@node System Calls +@subsection System Calls + +Implement the system call handler in @file{userprog/syscall.c}. The +skeleton implementation we provide ``handles'' system calls by +terminating the process. It will need to retrieve the system call +number, then any system call arguments, and carry appropriate actions. + +Implement the following system calls. The prototypes listed are those +seen by a user program that includes @file{lib/user/syscall.h}. System +call numbers for each system call are defined in +@file{lib/syscall-nr.h}: + +@deftypefn {System Call} void halt (void) +Terminates Pintos by calling @func{power_off} (declared in @file{threads/init.h}). Note that this should be seldom used, since then you lose some information about possible deadlock situations, etc. +@end deftypefn -@item SYS_exit -@itemx void exit (int @var{status}) +@deftypefn {System Call} void exit (int @var{status}) Terminates the current user program, returning @var{status} to the -kernel. If the process's parent @func{wait}s for it, this is the status +kernel. If the process's parent @code{wait}s for it (see below), this +is the status that will be returned. Conventionally, a @var{status} of 0 indicates -a successful exit. Other values may be used to indicate user-defined -conditions (usually errors). +success and nonzero values indicate errors. +@end deftypefn -@item SYS_exec -@itemx pid_t exec (const char *@var{cmd_line}) +@deftypefn {System Call} pid_t exec (const char *@var{cmd_line}) Runs the executable whose name is given in @var{cmd_line}, passing any given arguments, and returns the new process's program id (pid). Must -return pid -1, which otherwise should not be a valid program id, if -there is an error loading this program. - -@item SYS_wait -@itemx int wait (pid_t @var{pid}) -Waits for process @var{pid} to die and returns its exit status. If it -was terminated by the kernel (i.e.@: killed due to an exception), -returns -1. If @var{pid} is invalid or if it was not a child of the +return pid -1, which otherwise should not be a valid pid, if +the program cannot load or run for any reason. +@end deftypefn + +@deftypefn {System Call} int wait (pid_t @var{pid}) +Waits for process @var{pid} to die and returns the status it passed to +@code{exit}. Returns -1 if @var{pid} +was terminated by the kernel (i.e.@: killed due to an exception). If +@var{pid} is invalid or if it was not a child of the calling thread, or if @code{wait} has already been successfully called for the given @var{pid}, returns -1 immediately, without waiting. @@ -438,29 +556,27 @@ All of a process's resources, including its @struct{thread}, must be freed whether its parent ever waits for it or not, and regardless of whether the child exits before or after its parent. -Children are not inherited, that is, if @var{A} has child @var{B} and -@var{B} has child @var{C}, then @var{A} always returns immediately -should it try to wait for @var{C}, even if @var{B} is dead. +Children are not inherited: if @var{A} has child @var{B} and +@var{B} has child @var{C}, then @code{wait(C)} always returns immediately +when called from @var{A}, even if @var{B} is dead. Consider all the ways a wait can occur: nested waits (@var{A} waits for @var{B}, then @var{B} waits for @var{C}), multiple waits (@var{A} -waits for @var{B}, then @var{A} waits for @var{C}), and so on. Does -your @func{wait} work if it is called on a process that has not yet -been scheduled for the first time? +waits for @var{B}, then @var{A} waits for @var{C}), and so on. +@end deftypefn -@item SYS_create -@itemx bool create (const char *@var{file}, unsigned @var{initial_size}) -Create a new file called @var{file} initially @var{initial_size} bytes +@deftypefn {System Call} bool create (const char *@var{file}, unsigned @var{initial_size}) +Creates a new file called @var{file} initially @var{initial_size} bytes in size. Returns true if successful, false otherwise. +@end deftypefn -@item SYS_remove -@itemx bool remove (const char *@var{file}) -Delete the file called @var{file}. Returns true if successful, false +@deftypefn {System Call} bool remove (const char *@var{file}) +Deletes the file called @var{file}. Returns true if successful, false otherwise. +@end deftypefn -@item SYS_open -@itemx int open (const char *@var{file}) -Open the file called @var{file}. Returns a nonnegative integer handle +@deftypefn {System Call} int open (const char *@var{file}) +Opens the file called @var{file}. Returns a nonnegative integer handle called a ``file descriptor'' (fd), or -1 if the file could not be opened. All open files associated with a process should be closed when the process exits or is terminated. @@ -469,33 +585,33 @@ File descriptors numbered 0 and 1 are reserved for the console: fd 0 is standard input (@code{stdin}), fd 1 is standard output (@code{stdout}). These special file descriptors are valid as system call arguments only as explicitly described below. +@end deftypefn -@item SYS_filesize -@itemx int filesize (int @var{fd}) +@deftypefn {System Call} int filesize (int @var{fd}) Returns the size, in bytes, of the file open as @var{fd}. +@end deftypefn -@item SYS_read -@itemx int read (int @var{fd}, void *@var{buffer}, unsigned @var{size}) -Read @var{size} bytes from the file open as @var{fd} into +@deftypefn {System Call} int read (int @var{fd}, void *@var{buffer}, unsigned @var{size}) +Reads @var{size} bytes from the file open as @var{fd} into @var{buffer}. Returns the number of bytes actually read (0 at end of file), or -1 if the file could not be read (due to a condition other than end of file). Fd 0 reads from the keyboard using @func{kbd_getc}. +@end deftypefn -@item SYS_write -@itemx int write (int @var{fd}, const void *@var{buffer}, unsigned @var{size}) -Write @var{size} bytes from @var{buffer} to the open file @var{fd}. +@deftypefn {System Call} int write (int @var{fd}, const void *@var{buffer}, unsigned @var{size}) +Writes @var{size} bytes from @var{buffer} to the open file @var{fd}. Returns the number of bytes actually written, or -1 if the file could -not be written. +not be written. Fd 1 writes to the console. Your code to write to the console should write all of @var{buffer} in one call to @func{putbuf}, at least as long as @var{size} is not bigger than a few hundred bytes. Otherwise, lines of text output by different processes may end up interleaved on the console, confusing both human readers and our grading scripts. +@end deftypefn -@item SYS_seek -@itemx void seek (int @var{fd}, unsigned @var{position}) +@deftypefn {System Call} void seek (int @var{fd}, unsigned @var{position}) Changes the next byte to be read or written in open file @var{fd} to @var{position}, expressed in bytes from the beginning of the file. (Thus, a @var{position} of 0 is the file's start.) @@ -507,38 +623,38 @@ have a fixed length until project 4 is complete, so writes past end of file will return an error.) These semantics are implemented in the file system and do not require any special effort in system call implementation. +@end deftypefn -@item SYS_tell -@itemx unsigned tell (int @var{fd}) +@deftypefn {System Call} unsigned tell (int @var{fd}) Returns the position of the next byte to be read or written in open file @var{fd}, expressed in bytes from the beginning of the file. +@end deftypefn -@item SYS_close -@itemx void close (int @var{fd}) -Close file descriptor @var{fd}. -@end table +@deftypefn {System Call} void close (int @var{fd}) +Closes file descriptor @var{fd}. +@end deftypefn The file defines other syscalls. Ignore them for now. You will implement some of them in project 3 and the rest in project 4, so be sure to design your system with extensibility in mind. -To implement syscalls, you will need to provide a way of copying data -from the user's virtual address space into the kernel and vice versa. +To implement syscalls, you need to provide ways to copy data +from user virtual address space into the kernel and vice versa. +You need this ability before you can +even obtain the system call number, because the system call number is +on the user's stack in the user's virtual address space. This can be a bit tricky: what if the user provides an invalid -pointer, a pointer into kernel memory, or points to a block that is +pointer, a pointer into kernel memory, or a block partially in one of those regions? You should handle these cases by -terminating the user process. You will need this code before you can -even obtain the system call number, because the system call number is -on the user's stack in the user's virtual address space. We recommend +terminating the user process. We recommend writing and testing this code before implementing any other system call functionality. -@anchor{Synchronizing File Access} -You must make sure that system calls are properly synchronized so that +You must synchronize system calls so that any number of user processes can make them at once. In particular, it -is not safe to call into the filesystem code provided in the +is not safe to call into the file system code provided in the @file{filesys} directory from multiple threads at once. For now, we -recommend adding a single lock that controls access to the filesystem +recommend adding a single lock that controls access to the file system code. You should acquire this lock before calling any functions in the @file{filesys} directory, and release it afterward. Don't forget that @func{process_execute} also accesses files. @strong{For now, we @@ -552,7 +668,7 @@ system call's return value. When you're done with this part, and forevermore, Pintos should be bulletproof. Nothing that a user program can do should ever cause the -OS to crash, halt, assert fail, or otherwise stop running. It is +OS to crash, panic, fail an assertion, or otherwise malfunction. It is important to emphasize this point: our tests will try to break your system calls in many, many ways. You need to think of all the corner cases and handle them. The sole way a user program should be able to @@ -562,77 +678,92 @@ If a system call is passed an invalid argument, acceptable options include returning an error value (for those calls that return a value), returning an undefined value, or terminating the process. -@xref{System Calls}, for more information on how syscalls work. +@xref{System Call Details}, for details on how system calls work. -@node User Programs FAQ +@node Denying Writes to Executables +@subsection Denying Writes to Executables + +Add code to deny writes to files in use as executables. Many OSes do +this because of the unpredictable results if a process tried to run code +that was in the midst of being changed on disk. This is especially +important once virtual memory is implemented in project 3, but it can't +hurt even now. + +You can use @func{file_deny_write} to prevent writes to an open file. +Calling @func{file_allow_write} on the file will re-enable them (unless +the file is denied writes by another opener). Closing a file will also +re-enable writes. + +@node Project 2 FAQ @section FAQ -@enumerate 1 -@item -@b{Do we need a working project 1 to implement project 2?} +@table @asis +@item How much code will I need to write? -No. +Here's a summary of our reference solution, produced by the +@command{diffstat} program. The final row gives total lines inserted +and deleted; a changed line counts as both an insertion and a deletion. -@item -@b{@samp{pintos put} always panics.} +@verbatim + threads/thread.c | 13 + threads/thread.h | 26 + + userprog/exception.c | 8 + userprog/process.c | 247 ++++++++++++++-- + userprog/syscall.c | 468 ++++++++++++++++++++++++++++++- + userprog/syscall.h | 1 + 6 files changed, 725 insertions(+), 38 deletions(-) +@end verbatim -Here are the most common causes: +@item The kernel always panics when I run @code{pintos -p @var{file} -- -q}. -@itemize @bullet -@item -The disk hasn't yet been formatted (with @samp{pintos run -f}). +Did you format the disk (with @samp{pintos -f})? -@item -The file name specified is too long. The file system limits file names -to 14 characters. If you're using a command like @samp{pintos put -../../tests/userprog/echo}, that overflows the limit. Use -@samp{pintos put ../../tests/userprog/echo echo} to put the file under -the name @file{echo} instead. +Is your file name too long? The file system limits file names to 14 +characters. A command like @samp{pintos -p ../../examples/echo -- -q} +will exceed the limit. Use @samp{pintos -p ../../examples/echo -a echo +-- -q} to put the file under the name @file{echo} instead. -@item -The file system is full. +Is the file system full? -@item -The file system already contains 10 files. (There's a 10-file limit for -the base Pintos file system.) +Does the file system already contain 16 files? The base Pintos file +system has a 16-file limit. -@item -The file system is so fragmented that there's not enough contiguous +The file system may be so fragmented that there's not enough contiguous space for your file. -@end itemize -@item -@b{All my user programs die with page faults.} +@item When I run @code{pintos -p ../file --}, @file{file} isn't copied. -This will generally happen if you haven't implemented problem 2-1 -yet. The reason is that the basic C library for user programs tries -to read @var{argc} and @var{argv} off the stack. Because the stack -isn't properly set up yet, this causes a page fault. +Files are written under the name you refer to them, by default, so in +this case the file copied in would be named @file{../file}. You +probably want to run @code{pintos -p ../file -a file --} instead. -@item -@b{I implemented 2-1 and now all my user programs die with -@samp{system call!}.} +@item 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 @var{argc} and @var{argv} off the stack. If the stack +isn't properly set up, this causes a page fault. +@item All my user programs die with @code{system call!} + +You'll have to implement system calls before you see anything else. Every reasonable program tries to make at least one system call (@func{exit}) and most programs make more than that. Notably, -@func{printf} invokes the @code{write} system call. The default -system call handler just prints @samp{system call!} and terminates the -program. You'll have to implement 2-2 before you see anything more -interesting. Until then, you can use @func{hex_dump} to convince -yourself that 2-1 is implemented correctly (@pxref{Argument Passing to -main}). +@func{printf} invokes the @code{write} system call. The default system +call handler just prints @samp{system call!} and terminates the program. +Until then, you can use @func{hex_dump} to convince yourself that +argument passing is implemented correctly (@pxref{Program Startup Details}). -@item -@b{Is there a way I can disassemble user programs?} +@item How can I can disassemble user programs? -The @command{i386-elf-objdump} utility can disassemble entire user -programs or object files. Invoke it as @code{i386-elf-objdump -d -@var{file}}. You can also use @code{i386-elf-gdb}'s -@command{disassemble} command to disassemble individual functions in -object files compiled with debug information. +The @command{objdump} (80@var{x}86) or @command{i386-elf-objdump} +(SPARC) utility can disassemble entire user +programs or object files. Invoke it as @code{objdump -d +@var{file}}. You can use @code{gdb}'s +@command{disassemble} command to disassemble individual functions +(@pxref{gdb}). -@item -@b{Why can't I use many C include files in my Pintos programs?} +@item Why do many C include files not work in 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. @@ -641,21 +772,18 @@ 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.) -@item -@b{Can I use lib@var{foo} in my Pintos programs?} +@item Can I use lib@var{foo} in my Pintos programs? The chances are good that lib@var{foo} 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 -userland C library does not have a @func{malloc} implementation. +user program C library does not have a @func{malloc} implementation. -@item -@b{How do I compile new user programs?} +@item How do I compile new user programs? -You need to modify @file{tests/Makefile}. +Modify @file{src/examples/Makefile}, then run @command{make}. -@item -@b{What's the difference between @code{tid_t} and @code{pid_t}?} +@item What's the difference between @code{tid_t} and @code{pid_t}? A @code{tid_t} identifies a kernel thread, which may have a user process running in it (if created with @func{process_execute}) or not @@ -671,314 +799,199 @@ You can choose whatever suitable types you like for @code{tid_t} and a one-to-one mapping, so that the same values in both identify the same process, or you can use a more complex mapping. It's up to you. -@item -@b{I can't seem to figure out how to read from and write to user -memory. What should I do?} +@item Keyboard input doesn't work with @command{pintos} option @option{-v}. -The kernel must treat user memory delicately. As part of a system -call, the user can pass to the kernel a null pointer, a pointer to -unmapped virtual memory, or a pointer to kernel virtual address space -(above @code{PHYS_BASE}). All of these types of invalid pointers must -be rejected without harm to the kernel or other running processes. At -your option, the kernel may handle invalid pointers by terminating the -process or returning from the system call with an error. - -There are at least two reasonable ways to do this correctly. The -first method is to ``verify then access'':@footnote{These terms are -made up for this document. They are not standard terminology.} verify -the validity of a user-provided pointer, then dereference it. If you -choose this route, you'll want to look at the functions in -@file{userprog/pagedir.c} and in @file{threads/mmu.h}. This is the -simplest way to handle user memory access. - -The second method is to ``assume and react'': directly dereference -user pointers, after checking that they point below @code{PHYS_BASE}. -Invalid user pointers will then cause a ``page fault'' that you can -handle by modifying the code for @func{page_fault} in -@file{userprog/exception.cc}. 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). - -In either case, you need to make sure not to ``leak'' resources. For -example, suppose that your system call has acquired a lock or -allocated a page of memory. 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 then access,'' then this should -be straightforward, but for ``assume and react'' it's more difficult, -because there's no way to return an error code from a memory access. -Therefore, for those who want to try the latter technique, we'll -provide a little bit of helpful code: - -@verbatim -/* Tries to copy a byte from user address USRC to kernel address DST. - Returns true if successful, false if USRC is invalid. */ -static inline bool get_user (uint8_t *dst, const uint8_t *usrc) { - int eax; - asm ("mov %%eax, offset 1f; mov %%al, %2; mov %0, %%al; 1:" - : "=m" (*dst), "=&a" (eax) : "m" (*usrc)); - return eax != 0; -} - -/* Tries write BYTE to user address UDST. - Returns true if successful, false if UDST is invalid. */ -static inline bool put_user (uint8_t *udst, uint8_t byte) { - int eax; - asm ("mov %%eax, offset 1f; mov %0, %b2; 1:" - : "=m" (*udst), "=&a" (eax) : "r" (byte)); - return eax != 0; -} -@end verbatim - -Each of these functions assumes that the user address has already been -verified to be below @code{PHYS_BASE}. They also assume that you've -modified @func{page_fault} so that a page fault in the kernel causes -@code{eax} to be set to 0 and its former value copied into @code{eip}. - -@item -@b{I'm also confused about reading from and writing to the stack. Can -you help?} - -@itemize @bullet -@item -Only non-@samp{char} values will have issues when writing them to -memory. If a digit is in a string, it is considered a character. -However, the value of @code{argc} would be a non-char. - -@item -You will need to write characters and non-characters into main memory. - -@item -When you add items to the stack, you will be decrementing the stack -pointer. You'll need to decrement the stack pointer before writing to -the location. - -@item -Each character is 1 byte. -@end itemize - -@item -@b{Why doesn't keyboard input work with @samp{pintos -v}?} - -Serial input isn't implemented. Don't use @samp{pintos -v} if you -want to use the shell or otherwise provide keyboard input. -@end enumerate +Serial input isn't implemented. Don't use @option{-v} if you +want to use the shell or otherwise need keyboard input. +@end table @menu -* Problem 2-1 Argument Passing FAQ:: -* Problem 2-2 System Calls FAQ:: +* Argument Passing FAQ:: +* System Calls FAQ:: @end menu -@node Problem 2-1 Argument Passing FAQ -@subsection Problem 2-1: Argument Passing FAQ +@node Argument Passing FAQ +@subsection Argument Passing FAQ -@enumerate 1 -@item -@b{Why is the top of the stack at @t{0xc0000000}? Isn't that off the -top of user virtual memory? Shouldn't it be @t{0xbfffffff}?} +@table @asis +@item Isn't the top of stack off the top of user virtual memory? -When the processor pushes data on the stack, it decrements the stack +The top of stack is at @code{PHYS_BASE}, typically @t{0xc0000000}, which +is also where kernel virtual memory starts. +But when the processor pushes data on the stack, it decrements the stack pointer first. Thus, the first (4-byte) value pushed on the stack will be at address @t{0xbffffffc}. -Also, the stack should always be aligned to a 4-byte boundary, but -@t{0xbfffffff} isn't. - -@item -@b{Is @code{PHYS_BASE} fixed?} +@item Is @code{PHYS_BASE} fixed? No. You should be able to support @code{PHYS_BASE} values that are -any multiple of @t{0x10000000} from @t{0x80000000} to @t{0xc0000000}, +any multiple of @t{0x10000000} from @t{0x80000000} to @t{0xf0000000}, simply via recompilation. -@end enumerate +@end table -@node Problem 2-2 System Calls FAQ -@subsection Problem 2-2: System Calls FAQ +@node System Calls FAQ +@subsection System Calls FAQ -@enumerate 1 -@item -@b{Can I just cast a pointer to a @struct{file} object to get a -unique file descriptor? Can I just cast a @code{struct thread *} to a -@code{pid_t}? It's so much simpler that way!} +@table @asis +@item Can I just cast a @code{struct file *} to get a file descriptor? +@itemx Can I just cast a @code{struct thread *} to a @code{pid_t}? -This is a design decision you will have to make for yourself. -However, note that most operating systems do distinguish between file +You will have to make these design decisions yourself. +Most operating systems do distinguish between file descriptors (or pids) and the addresses of their kernel data structures. You might want to give some thought as to why they do so before committing yourself. -@item -@b{Can I set a maximum number of open files per process?} +@item Can I set a maximum number of open files per process? -From a design standpoint, it would be better not to set an arbitrary -maximum. That said, if your design calls for it, you may impose a -limit of 128 open files per process (as the Solaris machines here do). +It is better not to set an arbitrary limit. You may impose a limit of +128 open files per process, if necessary. -@item +@item What happens when an open file is removed? @anchor{Removing an Open File} -@b{What happens when two (or more) processes have a file open and one of -them removes it?} -You should copy the standard Unix semantics for files. That is, when -a file is removed an process which has a file descriptor for that file -may continue to do operations on that descriptor. This means that +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. -@item -@b{I've discovered that some of my user programs need more than one 4 -kB page of stack space. What should I do?} +@item 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. -@end enumerate +stack space for each process. In the next project, you will implement a +better solution. +@end table @node 80x86 Calling Convention @section 80@var{x}86 Calling Convention -What follows is a quick and dirty discussion of the 80@var{x}86 -calling convention. Some of the basics should be familiar from CS -107, and if you've already taken CS 143 or EE 182, then you should -have seen even more of it. I've omitted some of the complexity, since -this isn't a class in how function calls work, so don't expect this to -be exactly correct in full, gory detail. If you do want all the -details, you can refer to @bibref{SysV-i386}. - -Whenever a function call happens, you need to put the arguments on the -call stack for that function, before the code for that function -executes, so that the callee has access to those values. The caller -has to be responsible for this (be sure you understand why). -Therefore, when you compile a program, the assembly code emitted will -have in it, before every function call, a bunch of instructions that -prepares for the call in whatever manner is conventional for the -machine you're working on. This includes saving registers as needed, -putting stuff on the stack, saving the location to return to somewhere -(so that when the callee finishes, it knows where the caller code is), -and some other bookkeeping stuff. Then you do the jump to the -callee's code, and it goes along, assuming that the stack and -registers are prepared in the appropriate manner. When the callee is -done, it looks at the return location as saved earlier, and jumps back -to that location. The caller may then have to do some cleanup: -clearing arguments and the return value off the stack, restoring -registers that were saved before the call, and so on. - -If you think about it, some of these things should remind you of -context switching. - -As an aside, in general, function calls are not cheap. You have to do -a bunch of memory writes to prepare the stack, you need to save and -restore registers before and after a function call, you need to write -the stack pointer, you have a couple of jumps which probably wrecks -some of your caches. This is why inlining code can be much faster. +This section summarizes important points of the convention used for +normal function calls on 32-bit 80@var{x}86 implementations of Unix. +Some details are omitted for brevity. If you do want all the details, +you can refer to @bibref{SysV-i386}. + +The basic calling convention works like this: + +@enumerate 1 +@item +The caller pushes each of the function's arguments on the stack one by +one, normally using the @code{PUSH} assembly language instruction. +Arguments are pushed in right-to-left order. + +@item +The caller pushes the address of its next instruction (the @dfn{return +address}) on the stack and jumps to the first instruction of the callee. +A single 80@var{x}86 instruction, @code{CALL}, does both. + +@item +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. + +@item +If the callee has a return value, it stores it into register @code{EAX}. + +@item +The callee returns by popping the return address from the stack and +jumping to the location it specifies, using the 80@var{x}86 @code{RET} +instruction. + +@item +The caller pops the arguments off the stack. +@end enumerate + +Consider a function @func{f} that takes three @code{int} arguments. +This diagram shows a sample stack frame as seen by the callee at the +beginning of step 3 above, supposing that @func{f} is invoked as +@code{f(1, 2, 3)}. The stack addresses are arbitrary: + +@html +
+@end html +@example + +----------------+ + 0xbffffe7c | 3 | + +----------------+ + 0xbffffe78 | 2 | + +----------------+ + 0xbffffe74 | 1 | + +----------------+ +stack pointer --> 0xbffffe70 | return address | + +----------------+ +@end example +@html +
+@end html @menu -* Argument Passing to main:: +* Program Startup Details:: +* System Call Details:: @end menu -@node Argument Passing to main -@subsection Argument Passing to @code{main()} - -In @func{main}'s case, there is no caller to prepare the stack -before it runs. Therefore, the kernel needs to do it. Fortunately, -since there's no caller, there are no registers to save, no return -address to deal with, etc. The only difficult detail to take care of, -after loading the code, is putting the arguments to @func{main} on -the stack. - -(The above is a small lie: most compilers will emit code where main -isn't strictly speaking the first function. This isn't an important -detail. If you want to look into it more, try disassembling a program -and looking around a bit. However, you can just act as if -@func{main} is the very first function called.) - -Pintos is written for the 80@var{x}86 architecture. Therefore, we -need to adhere to the 80@var{x}86 calling convention. Basically, you -put all the arguments on the stack and move the stack pointer -appropriately. You also need to insert space for the function's -``return address'': even though the initial function doesn't really -have a caller, its stack frame must have the same layout as any other -function's. The program will assume that the stack has been laid out -this way when it begins running. - -So, what are the arguments to @func{main}? Just two: an @samp{int} -(@code{argc}) and a @samp{char **} (@code{argv}). @code{argv} is an -array of strings, and @code{argc} is the number of strings in that -array. However, the hard part isn't these two things. The hard part -is getting all the individual strings in the right place. As we go -through the procedure, let us consider the following example command: -@samp{/bin/ls -l foo bar}. - -The first thing to do is to break the command line into individual -strings: @samp{/bin/ls}, @samp{-l}, @samp{foo}, and @samp{bar}. These -constitute the arguments of the command, including the program name -itself (which belongs in @code{argv[0]}). - -These individual, null-terminated strings should be placed on the user -stack. They may be placed in any order, as you'll see shortly, -without affecting how main works, but for simplicity let's assume they -are in reverse order (keeping in mind that the stack grows downward on -an 80@var{x}86 machine). As we copy the strings onto the stack, we -record their (virtual) stack addresses. These addresses will become -important when we write the argument vector (two paragraphs down). - -After we push all of the strings onto the stack, we adjust the stack -pointer so that it is word-aligned: that is, we move it down to the -next 4-byte boundary. This is required because we will next be -placing several words of data on the stack, and they must be aligned -to be read correctly. In our example, as you'll see below, -the strings start at address @t{0xffed}. One word below that would be -at @t{0xffe9}, so we could in theory put the next word on the stack -there. However, since the stack pointer should always be -word-aligned, we instead leave the stack pointer at @t{0xffe8}. - -Once we align the stack pointer, we then push the elements of the -argument vector, that is, a null pointer, then the addresses of the -strings @samp{/bin/ls}, @samp{-l}, @samp{foo}, and @samp{bar}) onto -the stack. This must be done in reverse order, such that -@code{argv[0]} is at the lowest virtual address, again because the -stack is growing downward. (The null pointer pushed first is because -@code{argv[argc]} must be a null pointer.) This is because we are now -writing the actual array of strings; if we write them in the wrong -order, then the strings will be in the wrong order in the array. This -is also why, strictly speaking, it doesn't matter what order the -strings themselves are placed on the stack: as long as the pointers -are in the right order, the strings themselves can really be anywhere. -After we finish, we note the stack address of the first element of the -argument vector, which is @code{argv} itself. - -Then we push @code{argv} (that is, the address of the first element of -the @code{argv} array) onto the stack, along with the length of the -argument vector (@code{argc}, 4 in this example). This must also be -done in this order, since @code{argc} is the first argument to -@func{main} and therefore is on first (smaller address) on the -stack. Finally, we push a fake ``return address'' and leave the stack -pointer to point to its location. - -All this may sound very confusing, so here's a picture which will -hopefully clarify what's going on. This represents the state of the -stack and the relevant registers right before the beginning of the -user program (assuming for this example that the stack bottom is -@t{0xc0000000}): +@node Program Startup Details +@subsection Program Startup Details + +The Pintos C library for user programs designates @func{_start}, in +@file{lib/user/entry.c}, as the entry point for user programs. This +function is a wrapper around @func{main} that calls @func{exit} if +@func{main} returns: + +@example +void +_start (int argc, char *argv[]) +@{ + exit (main (argc, argv)); +@} +@end example + +The kernel is responsible for setting up the arguments for the initial +function on the stack, in accordance with the calling convention +explained in the preceding section, before it allows the user program to +begin executing. + +Consider the following example command: @samp{/bin/ls -l foo bar}. +First, the kernel must break the command into words, as @samp{/bin/ls}, +@samp{-l}, @samp{foo}, and @samp{bar}, and place them 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 +@code{argv}. The order ensure that @code{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 @code{argv} (the address of @code{argv[0]}) and @code{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 show the state of the stack and the relevant registers +right before the beginning of the user program, assuming +@code{PHYS_BASE} is @t{0xc0000000}: @html
@end html -@multitable {@t{0xbfffffff}} {``return address''} {@t{/bin/ls\0}} -@item Address @tab Name @tab Data -@item @t{0xbffffffc} @tab @code{*argv[3]} @tab @samp{bar\0} -@item @t{0xbffffff8} @tab @code{*argv[2]} @tab @samp{foo\0} -@item @t{0xbffffff5} @tab @code{*argv[1]} @tab @samp{-l\0} -@item @t{0xbfffffed} @tab @code{*argv[0]} @tab @samp{/bin/ls\0} -@item @t{0xbfffffec} @tab word-align @tab @samp{\0} -@item @t{0xbfffffe8} @tab @code{argv[4]} @tab @t{0} -@item @t{0xbfffffe4} @tab @code{argv[3]} @tab @t{0xbffffffc} -@item @t{0xbfffffe0} @tab @code{argv[2]} @tab @t{0xbffffff8} -@item @t{0xbfffffdc} @tab @code{argv[1]} @tab @t{0xbffffff5} -@item @t{0xbfffffd8} @tab @code{argv[0]} @tab @t{0xbfffffed} -@item @t{0xbfffffd4} @tab @code{argv} @tab @t{0xbfffffd8} -@item @t{0xbfffffd0} @tab @code{argc} @tab 4 -@item @t{0xbfffffcc} @tab ``return address'' @tab 0 +@multitable {@t{0xbfffffff}} {return address} {@t{/bin/ls\0}} {@code{void (*) ()}} +@item Address @tab Name @tab Data @tab Type +@item @t{0xbffffffc} @tab @code{argv[3][@dots{}]} @tab @samp{bar\0} @tab @code{char[4]} +@item @t{0xbffffff8} @tab @code{argv[2][@dots{}]} @tab @samp{foo\0} @tab @code{char[4]} +@item @t{0xbffffff5} @tab @code{argv[1][@dots{}]} @tab @samp{-l\0} @tab @code{char[3]} +@item @t{0xbfffffed} @tab @code{argv[0][@dots{}]} @tab @samp{/bin/ls\0} @tab @code{char[8]} +@item @t{0xbfffffec} @tab word-align @tab 0 @tab @code{uint8_t} +@item @t{0xbfffffe8} @tab @code{argv[4]} @tab @t{0} @tab @code{char *} +@item @t{0xbfffffe4} @tab @code{argv[3]} @tab @t{0xbffffffc} @tab @code{char *} +@item @t{0xbfffffe0} @tab @code{argv[2]} @tab @t{0xbffffff8} @tab @code{char *} +@item @t{0xbfffffdc} @tab @code{argv[1]} @tab @t{0xbffffff5} @tab @code{char *} +@item @t{0xbfffffd8} @tab @code{argv[0]} @tab @t{0xbfffffed} @tab @code{char *} +@item @t{0xbfffffd4} @tab @code{argv} @tab @t{0xbfffffd8} @tab @code{char **} +@item @t{0xbfffffd0} @tab @code{argc} @tab 4 @tab @code{int} +@item @t{0xbfffffcc} @tab return address @tab 0 @tab @code{void (*) ()} @end multitable @html
@@ -993,8 +1006,7 @@ the user virtual address space, in the page just below virtual address You may find the non-standard @func{hex_dump} function, declared in @file{}, useful for debugging your argument passing code. -Here's what it would show in the above example, given that -@code{PHYS_BASE} is @t{0xc0000000}: +Here's what it would show in the above example: @verbatim bfffffc0 00 00 00 00 | ....| @@ -1003,19 +1015,19 @@ 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.| @end verbatim -@node System Calls -@section System Calls +@node System Call Details +@subsection System Call Details -We have already been dealing with one way that the operating system +The first project already dealt with 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. +by entities outside the CPU (@pxref{External Interrupt Handling}). -The operating system is also called to deal with software exceptions, -which are events generated in response to the code. These can be -errors such as a page fault or division by zero. However, exceptions -are also the means by which a user program can request services -(``system calls'') from the operating system. +The operating system also deals with software exceptions, which are +events that occur in program code (@pxref{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 80@var{x}86 architecture, the @samp{int} instruction is the most commonly used means for invoking system calls. This instruction @@ -1025,38 +1037,15 @@ system call number and any additional arguments are expected to be pushed on the stack in the normal fashion before invoking the interrupt. -The normal calling convention pushes function arguments on the stack -from right to left and the stack grows downward. Thus, when the -system call handler @func{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 +Thus, when the system call handler @func{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 @func{syscall_handler} as the @samp{esp} member of the @code{struct intr_frame} passed to it. -Here's an example stack frame for calling a system call numbered 10 -with three arguments passed as 1, 2, and 3. The stack addresses are -arbitrary: - -@html -
-@end html -@multitable {@t{0xbffffe7c}} {Value} -@item Address @tab Value -@item @t{0xbffffe7c} @tab 3 -@item @t{0xbffffe78} @tab 2 -@item @t{0xbffffe74} @tab 1 -@item @t{0xbffffe70} @tab 10 -@end multitable -@html -
-@end html - -In this example, the caller's stack pointer would be at -@t{0xbffffe70}. - The 80@var{x}86 convention for function return values is to place them -in the @samp{EAX} register. System calls that return a value can do +in the @code{EAX} register. System calls that return a value can do so by modifying the @samp{eax} member of @struct{intr_frame}. You should try to avoid writing large amounts of repetitive code for diff --git a/doc/userprog.tmpl b/doc/userprog.tmpl new file mode 100644 index 0000000..07183db --- /dev/null +++ b/doc/userprog.tmpl @@ -0,0 +1,134 @@ + +--------------------------+ + | CS 140 | + | PROJECT 2: USER PROGRAMS | + | 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, and lecture notes. + + ARGUMENT PASSING + ================ + +---- 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. + +---- ALGORITHMS ---- + +>> 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 ---- + +>> Why does Pintos implement strtok_r() but not strtok()? + +>> 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. + + SYSTEM CALLS + ============ + +---- 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. + +>> Describe how file descriptors are associated with open files. Are +>> file descriptors unique within the entire OS or just within a single +>> process? + +---- ALGORITHMS ---- + +>> Describe your code for copying data from user programs into the kernel +>> and vice versa. + +>> Suppose a system call causes a full page (4,096 bytes) of data to be +>> copied from user space into the kernel. What is the least and the +>> greatest possible number of inspections of the page table (e.g. calls +>> to pagedir_get_page()) that might result? What about for a system +>> call that only copies 2 bytes of data? Is there room for improvement +>> in these numbers, and how much? + +>> Briefly describe your implementation of the "wait" system call and how +>> it interacts with process termination. + +>> Any access to user program memory at a user-specified address can fail +>> due to a bad pointer value. Such accesses must cause the process to +>> be terminated. System calls are fraught with such accesses, e.g. a +>> "write" system call requires reading the system call number from the +>> user stack, then each of the call's three arguments, then an arbitrary +>> amount of user memory, and any of these can fail at any point. This +>> poses a design and error-handling problem: how do you best avoid +>> obscuring the primary function of code in a morass of error-handling? +>> Furthermore, when an error is detected, how do you ensure that all +>> temporarily allocated resources (locks, buffers, etc.) are freed? In +>> a few paragraphs, describe the strategy or strategies you adopted for +>> managing these issues. Give an example. + +---- SYNCHRONIZATION ---- + +>> The "exec" system call returns -1 if loading the new executable fails, +>> so it cannot return before the new executable has completed loading. +>> How does your code ensure this? How is the load success/failure +>> status passed back to the thread that calls "exec"? + +>> Consider parent process P with child process 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? + +---- RATIONALE ---- + +>> Why did you choose to implement user-to-kernel copying the way you +>> did? + +>> What advantages or disadvantages can you see to your design for file +>> descriptors? + +>> The default tid_t to pid_t mapping is the identity mapping. If you +>> changed it, what advantages are there to your 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/vm.texi b/doc/vm.texi index f922810..e4dfc70 100644 --- a/doc/vm.texi +++ b/doc/vm.texi @@ -1,78 +1,65 @@ @node Project 3--Virtual Memory, Project 4--File Systems, Project 2--User Programs, Top @chapter Project 3: Virtual Memory -By now you should be familiar with the inner workings of Pintos. -You've already come a long way: your OS can properly handle multiple -threads of execution with proper synchronization, and can load -multiple user programs at once. However, when loading user programs, -your OS is limited by how much main memory the simulated machine has. -In this assignment, you will remove that limitation. - -You will be using the @file{vm} directory for this project. The -@file{vm} directory contains only the @file{Makefile}s. The only -change from @file{userprog} is that this new @file{Makefile} turns on -the setting @option{-DVM}. All code you write will either be newly -generated files (e.g.@: if you choose to implement your paging code in -their own source files), or will be modifications to pre-existing code -(e.g.@: you will change the behavior of @file{process.c} -significantly). - -There are only a couple of source files you will probably be -encountering for the first time: - -@table @file -@item devices/disk.h -@itemx devices/disk.c -Provides access to the physical disk, abstracting away the rather -awful IDE interface. -@end table +By now you should be familiar with the inner workings of Pintos. Your +OS can properly handle multiple threads of execution with proper +synchronization, and can load multiple user programs at once. However, +the number and size of programs that can run is limited by the machine's +main memory size. In this assignment, you will remove that limitation. -You will be building this assignment on the last one. It will benefit +You will build this assignment on top of the last one. It will benefit you to get your project 2 in good working order before this assignment -so those bugs don't keep haunting you. - -All the test programs from the previous project should also work with -this project. You should also write programs to test the new features -introduced in this project. +so those bugs don't keep haunting you. Test programs from the previous +project should also work with this project. You will continue to handle Pintos disks and file systems the same way you did in the previous assignment (@pxref{Using the File System}). @menu -* VM Design:: +* Project 3 Background:: +* Project 3 Requirements:: +* Project 3 FAQ:: +@end menu + +@node Project 3 Background +@section Background + +@menu +* Project 3 Source Files:: * Page Faults:: * Disk as Backing Store:: -* Memory Mapped Files:: -* Stack:: -* Problem 3-1 Page Table Management:: -* Problem 3-2 Paging To and From Disk:: -* Problem 3-3 Memory Mapped Files:: -* Virtual Memory FAQ:: +* Memory Mapped Files Background:: @end menu -@node VM Design -@section A Word about Design +@node Project 3 Source Files +@subsection Source Files + +You will work in the @file{vm} directory for this project. The +@file{vm} directory contains only @file{Makefile}s. The only +change from @file{userprog} is that this new @file{Makefile} turns on +the setting @option{-DVM}. All code you write will be in new +files or in files introduced in earlier projects. + +You will probably be encountering just a few files for the first time: -It is important for you to note that in addition to getting virtual -memory working, this assignment is also meant to be an open-ended -design problem. We will expect you to come up with a design that -makes sense. You will have the freedom to choose how to handle page -faults, how to organize the swap disk, how to implement paging, etc. -In each case, we will expect you to provide a defensible justification -in your design documentation as to why your choices are reasonable. -You should evaluate your design on all the available criteria: speed -of handling a page fault, space overhead in memory, minimizing the -number of page faults, simplicity, etc. +@table @file +@item devices/disk.h +@itemx devices/disk.c +Provides access to the physical disk, abstracting away the rather awful +IDE interface. You will use this interface to access the swap disk. +@end table -In keeping with this, you will find that we are going to say as little -as possible about how to do things. Instead we will focus on what end -functionality we require your OS to support. +@menu +* Page Faults:: +* Disk as Backing Store:: +* Memory Mapped Files:: +@end menu @node Page Faults -@section Page Faults +@subsection Page Faults For the last assignment, whenever a context switch occurred, the new -process would install its own page table into the machine. The page +process installed its own page table into the machine. The new page table contained all the virtual-to-physical translations for the process. Whenever the processor needed to look up a translation, it consulted the page table. As long as the process only accessed @@ -86,56 +73,64 @@ that the page must be brought in from a disk file or from swap. You will have to implement a more sophisticated page fault handler to handle these cases. +@menu +* Page Table Structure:: +* Working with Virtual Addresses:: +* Accessed and Dirty Bits:: +@end menu + +@node Page Table Structure +@subsubsection Page Table Structure + On the 80@var{x}86, the page table format is fixed by hardware. We have provided code for managing page tables for you to use in -@file{userprog/pagedir.c}. The functions in there should provide an -abstract interface to all the page table functionality that you need +@file{userprog/pagedir.c}. The functions in there provide an +abstract interface to all the page table functionality that you should need to complete the project. However, you may still find it worthwhile to understand a little about the hardware page table format, so we'll go into a little of detail about that in this section. -The top-level paging data structure is a 4 kB page called the ``page +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 4 kB page called a -``page table'' (PT) arranged in the same fashion as an array of 1,024 +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 into physical memory. +kB virtual page to a physical page. -Thus, translation of a virtual address into a physical address follows +Translation of a virtual address into a physical address follows the three-step process illustrated in the diagram below:@footnote{Actually, virtual to physical translation on the -80@var{x}86 architecture happens via an intermediate ``linear +80@var{x}86 architecture occurs via an intermediate ``linear address,'' but Pintos (and most other 80@var{x}86 OSes) set up the CPU -so that linear and virtual addresses are one and the same, so that you +so that linear and virtual addresses are one and the same. Thus, you can effectively ignore this CPU feature.} @enumerate 1 @item -The top 10 bits of the virtual address (bits 22:32) are used to index -into the page directory. If the PDE is marked ``present,'' the +The most-significant 10 bits of the virtual address (bits 22@dots{}32) +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. @item -The next 10 bits of the virtual address (bits 12:22) are used to index -into the page table. If the PTE is marked ``present,'' the physical +The next 10 bits of the virtual address (bits 12@dots{}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. - @item -The bottom 12 bits of the virtual address (bits 0:12) are added to the -data page's physical base address, producing the final physical -address. +The least-significant 12 bits of the virtual address (bits 0@dots{}11) +are added to the data page's physical base address, yielding the final +physical address. @end enumerate @example @group -32 22 12 0 -+--------------------------------------------------------------------+ + 31 22 21 12 11 0 ++----------------------+----------------------+----------------------+ | Page Directory Index | Page Table Index | Page Offset | -+--------------------------------------------------------------------+ ++----------------------+----------------------+----------------------+ | | | _______/ _______/ _____/ / / / @@ -162,140 +157,151 @@ address. @end group @end example +@node Working with Virtual Addresses +@subsubsection Working with Virtual Addresses + Header @file{threads/mmu.h} has useful functions for various operations on virtual addresses. You should look over the header -yourself, but its most important functions include these: +yourself. The most important functions are described below. -@table @code -@item pd_no(@var{va}) -Returns the page directory index in virtual address @var{va}. +@deftypefun uintptr_t pd_no (const void *@var{va}) +Returns the page directory index for virtual address @var{va}. +@end deftypefun -@item pt_no(@var{va}) -Returns the page table index in virtual address @var{va}. +@deftypefun uintptr_t pt_no (const void *@var{va}) +Returns the page table index for virtual address @var{va}. +@end deftypefun -@item pg_ofs(@var{va}) -Returns the page offset in virtual address @var{va}. +@deftypefun unsigned pg_ofs (const void *@var{va}) +Returns the page offset of virtual address @var{va}. +@end deftypefun -@item pg_round_down(@var{va}) +@deftypefun {void *} pg_round_down (const void *@var{va}) Returns @var{va} rounded down to the nearest page boundary, that is, -@var{va} but with its page offset set to 0. +@var{va} with its page offset set to 0. +@end deftypefun -@item pg_round_up(@var{va}) +@deftypefun {void *} pg_round_up (const void *@var{va}) Returns @var{va} rounded up to the nearest page boundary. -@end table +@end deftypefun + +@node Accessed and Dirty Bits +@subsubsection Accessed and Dirty Bits + +Most of the page table is under the control of the operating system, but +two bits in each page table entry are also manipulated by the CPU. On +any read or write to the page referenced by a PTE, the CPU sets the +PTE's @dfn{accessed bit} to 1; on any write, the CPU sets the @dfn{dirty +bit} to 1. The CPU never resets these bits to 0, but the OS may do so. + +You will need to use the accessed and dirty bits in your submission to +choose which pages to evict from memory and to decide whether evicted +pages need to be written to disk. The page table code in +@file{userprog/pagedir.c} provides functions for checking and setting +these bits. These functions are described at the end of this section. + +You need to watch out for @dfn{aliases}, that is, two (or more) +different virtual pages that refer to the same physical page frame. +When an aliased page is accessed, the accessed and dirty bits are +updated in only one page table entry (the one for the virtual address +used to access the page). The accessed and dirty bits for the other +aliased virtual addresses are not updated. + +In Pintos, every user virtual page is aliased to its kernel virtual +address. You must manage these aliases somehow. For example, your code +could check and update the accessed and dirty bits for both addresses. +Alternatively, the kernel could avoid the problem by only accessing user +data through the user virtual address. + +@deftypefun bool pagedir_is_dirty (uint32_t *@var{pd}, const void *@var{vpage}) +@deftypefunx bool pagedir_is_accessed (uint32_t *@var{pd}, const void *@var{vpage}) +Returns true if page directory @var{pd} contains a page table entry for +virtual page @var{vpage} that is marked dirty (or accessed). Otherwise, +returns false. +@end deftypefun + +@deftypefun void pagedir_set_dirty (uint32_t *@var{pd}, const void *@var{vpage}, bool @var{value}) +@deftypefunx void pagedir_set_accessed (uint32_t *@var{pd}, const void *@var{vpage}, bool @var{value}) +If page directory @var{pd} has a page table entry for @var{vpage}, then +its dirty (or accessed) bit is set to @var{value}. +@end deftypefun @node Disk as Backing Store -@section Disk as Backing Store - -In VM systems, since memory is less plentiful than disk, you will -effectively use memory as a cache for disk. Looking at it from -another angle, you will use disk as a backing store for memory. This -provides the abstraction of an (almost) unlimited virtual memory size. -Part of your task in this project is to do this, with the additional -constraint that your performance should be close to that provided by -physical memory. You will use the page tables' ``dirty'' bits to -denote whether pages need to be written back to disk when they're -evicted from main memory and the ``accessed'' bit for page replacement -algorithms. Whenever the hardware writes memory, it sets the dirty -bit, and if it reads or writes to the page, it sets the accessed bit. +@subsection Disk as Backing Store -As with any caching system, performance depends on the policy used to -decide which things are kept in memory and which are only stored on -disk. On a page fault, the kernel must decide which page to replace. -Ideally, it will throw out a page that will not be referenced for a -long time, keeping in memory those pages that are soon to be -referenced. Another consideration is that if the replaced page has -been modified, the page must be first saved to disk before the needed -page can be brought in. Many virtual memory systems avoid this extra -overhead by writing modified pages to disk in advance, so that later -page faults can be completed more quickly (but you do not have to -implement this optimization). +VM systems effectively use memory as a cache for disk. From another +perspective, disk is a ``backing store'' for memory. This provides the +abstraction of an (almost) unlimited virtual memory size. You must +implement such a system, with the additional constraint that performance +should be close to that provided by physical memory. You can use dirty +bits to tell whether pages need to be written back to disk when they're +evicted from main memory, and the accessed bits for page replacement +algorithms (@pxref{Accessed and Dirty Bits}). -@node Memory Mapped Files -@section Memory Mapped Files - -The traditional way to access the file system is via @code{read} and -@code{write} system calls, but that requires an extra level of copying -between the kernel and the user level. A secondary interface is -simply to ``map'' the file into the virtual address space. The -program can then use load and store instructions directly on the file -data. (An alternative way of viewing the file system is as ``durable -memory.'' Files just store data structures. If you access data -structures in memory using load and store instructions, why not access -data structures in files the same way?) - -Memory mapped files are typically implemented using system calls. One -system call maps the file to a particular part of the address space. -For example, one might conceptually map the file @file{foo}, which is -1000 bytes -long, starting at address 5000. Assuming that nothing else is already -at virtual addresses 5000@dots{}6000, any memory accesses to these -locations will access the corresponding bytes of @file{foo}. +As with any caching system, performance depends on the policy used to +decide what to keep in the cache and what to evict. On a page fault, +the kernel must decide which page to replace. Ideally, it will throw +out a page that will not be referenced for a long time, keeping in +memory those pages that are soon to be referenced. Another +consideration is that if the replaced page has been modified, the page +must be first written to disk before the needed page can be brought in. +Many virtual memory systems avoid this extra overhead by writing +modified pages to disk in advance, so that later page faults can be +completed more quickly (but you do not have to implement this +optimization). + +@node Memory Mapped Files Background +@subsection Memory Mapped Files + +The file system is most commonly accessed with @code{read} and +@code{write} system calls. A secondary interface is to ``map'' +the file into the virtual address space. The program can then use load +and store instructions directly on the file data. An alternative view +is to see the file system is as ``durable memory'': files just store +data structures, so if you access ordinary data structures using normal +program instructions, why not access durable data structures the same +way? + +Suppose file @file{foo} is @t{0x1000} bytes (4 kB, or one page) long. +If @file{foo} is mapped into memory starting at address @t{0x5000}, then +any memory accesses to locations @t{0x5000}@dots{}@t{0x5fff} will access +the corresponding bytes of @file{foo}. A consequence of memory mapped files is that address spaces are sparsely populated with lots of segments, one for each memory mapped -file (plus one each for code, data, and stack). You will implement -memory mapped files in problem 3-3. You should -design your solutions to problems 3-1 and 3-2 to anticipate this. - -@node Stack -@section Stack - -In project 2, the stack was a single page at the top of the user -virtual address space. The stack's location does not change in this -project, but your kernel should allocate additional pages to the stack -on demand. That is, if the stack grows past its current bottom, the -system should allocate additional pages for the stack as necessary -(unless those pages are unavailable because they are in use by another -segment). - -It is impossible to predict how large the stack will grow at compile -time, so we must allocate pages as necessary. You should only allocate -additional pages if they ``appear'' to be stack accesses. You must -devise a heuristic that attempts to distinguish stack accesses from -other accesses. Document and explain the heuristic in your -design documentation. - -The first stack page need not be loaded lazily. You can initialize it -with the command line at load time, with no need to wait for it to be -faulted in. Even if you did wait, the very first instruction in the -user program is likely to be one that faults in the page. - -Stack facts: - -@itemize -@item -The user program's current stack pointer is in the @struct{intr_frame}'s -@code{esp} member. +file (plus one each for code, data, and stack). -@item -Only buggy 80@var{x}86 user programs write to memory within the -stack but below the stack pointer. This is because more advanced OSes -may interrupt a process at any time to deliver a ``signal'' and this -uses the stack.@footnote{This rule is common but not universal. One -modern exception is the -@uref{http://www.x86-64.org/documentation/abi.pdf, @var{x}86-64 System -V ABI}, which designates 128 bytes below the stack pointer as a ``red -zone'' that may not be modified by signal or interrupt handlers.} +@node Project 3 Requirements +@section Requirements -@item -The 80@var{x}86 @code{push} instruction may cause a page fault 4 bytes -below the stack pointer, because it checks access permissions before it -adjusts the stack pointer. (Otherwise, the instruction would not be -restartable in a straightforward fashion.) +This assignment is an open-ended design problem. We are going to say as +little as possible about how to do things. Instead we will focus on +what functionality we require your OS to support. We will expect +you to come up with a design that makes sense. You will have the +freedom to choose how to handle page faults, how to organize the swap +disk, how to implement paging, etc. -@item -Similarly, the 80@var{x}86 @code{pusha} instruction, which pushes all 32 -bytes of the 8 general-purpose registers at once, may cause a page fault -32 bytes below the stack pointer. +@menu +* Project 3 Design Document:: +* Page Table Management:: +* Paging To and From Disk:: +* Lazy Loading:: +* Stack Growth:: +* Memory Mapped Files:: +@end menu -@item -Most OSes impose some sort of limit on the stack size. Sometimes it is -user-adjustable. -@end itemize +@node Project 3 Design Document +@subsection Design Document + +Before you turn in your project, you must copy @uref{vm.tmpl, , the +project 3 design document template} into your source tree under the name +@file{pintos/src/vm/DESIGNDOC} and fill it in. We recommend that you +read the design document template before you start working on the +project. @xref{Project Documentation}, for a sample design document +that goes along with a fictitious project. -@node Problem 3-1 Page Table Management -@section Problem 3-1: Page Table Management +@node Page Table Management +@subsection Page Table Management Implement page directory and page table management to support virtual memory. You will need data structures to accomplish the following @@ -314,9 +320,8 @@ in @bibref{IA32-v3}, and in practice it is probably easier to add a new data structure. @item -Some way of finding a page on disk if it is not in memory. You won't -need this data structure until problem 3-2, but planning ahead is a -good idea. +Some way of finding a page on disk (in a file or in swap) if it is not +in memory. You can generalize the virtual-to-physical page table, so that it allows you to locate a page wherever it is in physical memory or on disk, or @@ -325,7 +330,7 @@ you can make this a separate table. @item Some way of translating from physical page frames back to virtual page frames, so that when you evict a physical page from its frame, you can -invalidate its translation(s). +invalidate its page table entry (or entries). @end itemize The page fault handler, @func{page_fault} in @@ -337,14 +342,13 @@ Locate the page backing the virtual address that faulted. It might be in the file system, in swap, or it might be an invalid virtual address. If you implement sharing, it might even -already be in physical memory and just not set up in the page table, +already be in physical memory, but not in the page table. If the virtual address is invalid, that is, if there's nothing assigned to go there, or if the virtual address is above @code{PHYS_BASE}, meaning that it belongs to the kernel instead of the -user, then the process's memory access must be disallowed. You should -terminate the process at this point, being sure to free all of its -resources. +user, then the process's memory access must be disallowed. +In this case, terminate the process and free all of its resources. @item If the page is not in physical memory, fetch it by appropriate means. @@ -360,8 +364,8 @@ physical page. You can use the functions in @file{userprog/pagedir.c}. You'll need to modify the ELF loader in @file{userprog/process.c} to do page table management according to your new design. As supplied, it reads all the process's pages from disk and initializes the page -tables for them at the same time. For testing purposes, you'll -probably want to leave the code that reads the pages from disk, but +tables for them at the same time. As a first step, you might +want to leave the code that reads the pages from disk, but use your new page table management code to construct the page tables only as page faults occur for them. @@ -369,81 +373,89 @@ You should use the @func{palloc_get_page} function to get the page frames that you use for storing user virtual pages. Be sure to pass the @code{PAL_USER} flag to this function when you do so, because that allocates pages from a ``user pool'' separate from the ``kernel pool'' -that other calls to @func{palloc_get_page} make. +that other calls to @func{palloc_get_page} make (@pxref{Why PAL_USER?}). + +You might find the Pintos bitmap code, in @file{lib/kernel/bitmap.c} and +@file{lib/kernel/bitmap.h}, useful for tracking pages. A bitmap is an +array of bits, each of which can be true or false. Bitmaps are +typically used to track usage in a set of (identical) resources: if +resource @var{n} is in use, then bit @var{n} of the bitmap is true. There are many possible ways to implement virtual memory. The above is simply an outline of our suggested implementation. -@node Problem 3-2 Paging To and From Disk -@section Problem 3-2: Paging To and From Disk +@node Paging To and From Disk +@subsection Paging To and From Disk Implement paging to and from files and the swap disk. You may use the disk on interface @code{hd1:1} as the swap disk, using the disk interface prototyped in @code{devices/disk.h}. From the @file{vm/build} -directory, use the command @code{pintos make-disk swap.dsk @var{n}} to +directory, use the command @code{pintos-mkdisk swap.dsk @var{n}} to create an @var{n} MB swap disk named @file{swap.dsk}. Afterward, -@file{swap.dsk} will automatically be attached when you run -@command{pintos}. +@file{swap.dsk} will automatically be attached as @code{hd1:1} when you run +@command{pintos}. Alternatively, you can tell @command{pintos} to +use a temporary @var{n}-MB swap disk for a single run with +@option{--swap-disk=@var{n}}. You will need routines to move a page from memory to disk and from disk to memory, where ``disk'' is either a file or the swap disk. If -you do everything correctly, your VM should still work when you +you do a good job, your VM should still work when you implement your own file system for the next assignment. -You will need a way to track pages which are used by a process but -which are not in physical memory, to fully handle page faults. Pages -that you write to swap should not be constrained to be in sequential -order. You will also need a way to track all of the physical memory -pages, to find an unused one when needed, or to evict a page -when memory is needed but no empty pages are available. The data -structures that you designed for problem 3-1 should do most of the work for -you. - -You will need a page replacement algorithm. The hardware sets the -accessed and dirty bits when it accesses memory. You can gain access -to this information using the functions prototyped in -@file{userprog/pagedir.h}. You should be able to take advantage of -this information to implement some algorithm which attempts to achieve -LRU-type behavior. We expect that your algorithm perform at least as -well as a reasonable implementation of the second-chance (clock) -algorithm. You will need to show in your test cases the value of your -page replacement algorithm by demonstrating for some workload that it -pages less frequently using your algorithm than using some inferior -page replacement policy. The canonical example of a poor page -replacement policy is random replacement. - -You must write your code so that we can choose a page replacement -policy at Pintos startup time. By default, the LRU-like algorithm -must be in effect, but we must be able to choose random replacement by -invoking @command{pintos} with the @option{-o random-paging} option. -Passing this option sets @code{enable_random_paging}, declared in -@file{threads/init.h}, to true. +To fully handle page faults, you will need a way to track pages that +are used by a process but which are not in physical memory. Pages in +swap should not be constrained to any particular ordering. You will +also need a way to track physical page frames, to find an unused one +when needed, or to evict a page when memory is needed but no empty pages +are available. The page table data structure that you designed should +do most of the work for you. + +Implement a global page replacement algorithm. You should be able to +use the ``accessed'' and ``dirty'' bits (@pxref{Accessed and Dirty +Bits}) to implement an approximation to LRU. Your algorithm should +perform at least as well as the ``second chance'' or ``clock'' +algorithm. + +Your design should allow for parallelism. Multiple processes should +be able to process page faults at once. If one page fault require +I/O, in the meantime processes that do not fault should continue +executing and other page faults that do not require I/O should be able to +complete. These criteria require some synchronization effort. + +Write your code so that we can choose a page replacement policy at +Pintos startup time. By default, the LRU-like algorithm must be in +effect, but we must be able to choose random replacement by invoking +@command{pintos} with the @option{-rndpg} option. Passing this option +sets @code{enable_random_paging}, declared in @file{threads/init.h}, to +true. + +@node Lazy Loading +@subsection Lazy Loading Since you will already be paging from disk, you should implement a ``lazy'' loading scheme for new processes. When a process is created, -it will not run immediately. Therefore, it doesn't make sense to load -all its code, data, and stack into memory when the process is created, -since it might incur additional disk accesses to do so (if it gets -paged out before it runs). When loading a new process, you should -leave most pages on disk, and bring them in as demanded when the -program begins running. Your VM system should also use the executable -file itself as backing store for read-only segments, since these -segments won't change. - -There are a few special cases. Look at the loop in -@func{load_segment} in @file{userprog/process.c}. Each time -around the loop, @code{read_bytes} represents the number of bytes to -read from the executable file and @code{zero_bytes} represents the number -of bytes to initialize to zero following the bytes read. The two -always sum to @code{PGSIZE}. The page handling depends on these -variables' values: +it will not need all of its resources immediately, so it doesn't make +sense to load all its code, data, and stack into memory when the process +is created. Instead, bring pages in from +the executable only as needed. Use the +executable file itself as backing store for read-only segments, since +these segments won't change. This means that read-only pages should not +be written to swap. + +The core of the program loader is the loop in @func{load_segment} in +@file{userprog/process.c}. +Each time around the loop, @code{read_bytes} receives the number of +bytes to read from the executable file and @code{zero_bytes} receives +the number of bytes to initialize to zero following the bytes read. The +two always sum to @code{PGSIZE} (4,096). The handling of a page depends +on these variables' values: @itemize @bullet @item If @code{read_bytes} equals @code{PGSIZE}, the page should be demand paged from disk on its first access. -@item +@item If @code{zero_bytes} equals @code{PGSIZE}, the page does not need to be read from disk at all because it is all zeroes. You should handle such pages by creating a new page consisting of all zeroes at the @@ -465,24 +477,49 @@ special ``linker script.'' Read @file{Makefile.userprog} for details. We will not test your submission with this special linker script, so the code you turn in must properly handle all cases. -For extra credit, you may implement sharing: when multiple processes -are created that use the same executable file, share read-only pages -among those processes instead of creating separate copies of read-only -segments for each process. If you carefully designed your data -structures in problem 3-1, sharing of read-only pages should not make this -part significantly harder. - -@node Problem 3-3 Memory Mapped Files -@section Problem 3-3: Memory Mapped Files - -Implement memory mapped files. +@node Stack Growth +@subsection Stack Growth + +Implement stack growth. In project 2, the stack was a single page at +the top of the user virtual address space, and programs were limited to +that much stack. Now, if the stack grows past its current size, +allocate additional page as necessary. + +Allocate additional pages only if they ``appear'' to be stack accesses. +Devise a heuristic that attempts to distinguish stack accesses from +other accesses. You can retrieve the user program's current stack +pointer from the @struct{intr_frame}'s @code{esp} member. + +User programs are buggy if they write to the stack below the stack +pointer, because typical real OSes may interrupt a process at any time +to deliver a ``signal,'' which pushes data on the stack.@footnote{This rule is +common but not universal. One modern exception is the +@uref{http://www.x86-64.org/documentation/abi.pdf, @var{x}86-64 System V +ABI}, which designates 128 bytes below the stack pointer as a ``red +zone'' that may not be modified by signal or interrupt handlers.} +However, the 80@var{x}86 @code{PUSH} instruction checks access +permissions before it adjusts the stack pointer, so it may cause a page +fault 4 bytes below the stack pointer. (Otherwise, @code{PUSH} would +not be restartable in a straightforward fashion.) Similarly, the +@code{PUSHA} instruction pushes 32 bytes at once, so it can fault 32 +bytes below the stack pointer. + +You may impose some absolute limit on stack size, as do most OSes. +(Some OSes make the limit user-adjustable, e.g.@: with the +@command{ulimit} command on many Unix systems.) + +The first stack page need not be allocated lazily. You can initialize +it with the command line arguments at load time, with no need to wait +for it to be faulted in. (Even if you did wait, the very first +instruction in the user program is likely to be one that faults in the +page.) -You will need to implement the following system calls: +@node Memory Mapped Files +@subsection Memory Mapped Files -@table @code -@item SYS_mmap -@itemx mapid_t mmap (int @var{fd}, void *@var{addr}) +Implement memory mapped files, including the following system calls. +@deftypefn {System Call} mapid_t mmap (int @var{fd}, void *@var{addr}) Maps the file open as @var{fd} into the process's virtual address space. The entire file is mapped into consecutive virtual pages starting at @var{addr}. @@ -492,49 +529,81 @@ bytes in the final mapped page ``stick out'' beyond the end of the file. Set these bytes to zero when the page is faulted in from disk, and discard them when the page is written back to disk. -If successful, this function returns a ``mapping ID'' that must -uniquely identify the mapping within the given process. On failure, -it must return -1, which otherwise should not be a valid mapping id. +If successful, this function returns a ``mapping ID'' that +uniquely identifies the mapping within the process. On failure, +it must return -1, which otherwise should not be a valid mapping id, +and the process's mappings must be unchanged. A call to @code{mmap} may fail if the file open as @var{fd} has a length of zero bytes. It must fail if @var{addr} is not page-aligned or if the range of pages mapped overlaps any existing set of mapped pages, including the stack or pages mapped at executable load time. +It must also fail if @var{addr} is 0, because some Pintos code assumes +virtual page 0 is not mapped. Finally, file descriptors 0 and 1, +representing console input and output, are not mappable. Your VM system should use the @code{mmap}'d file itself as backing store for the mapping. That is, to evict a page mapped by @code{mmap}, write it to the file it was mapped from. (In fact, you -may choose to implement executable mappings as a special case of file -mappings.) - -@item SYS_munmap -@itemx bool munmap (mapid_t @var{mapping}) +may choose to implement executable mappings as special, copy-on-write +file mappings.) +@end deftypefn +@deftypefn {System Call} void munmap (mapid_t @var{mapping}) Unmaps the mapping designated by @var{mapping}, which must be a mapping ID returned by a previous call to @code{mmap} by the same process that has not yet been unmapped. -@end table +@end deftypefn All mappings are implicitly unmapped when a process exits, whether via @code{exit} or by any other means. When a mapping is unmapped, whether -implicitly or explicitly, all outstanding changes are written to the -file, and the pages are removed from the process's list of used -virtual pages. +implicitly or explicitly, all pages written to by the process are +written back to the file, and pages not written must not be. The pages +are then removed from the process's list of virtual pages. -@node Virtual Memory FAQ +@node Project 3 FAQ @section FAQ -@enumerate 1 -@item -@b{Do we need a working HW 2 to implement HW 3?} +@table @b +@item How much code will I need to write? + +Here's a summary of our reference solution, produced by the +@command{diffstat} program. The final row gives total lines inserted +and deleted; a changed line counts as both an insertion and a deletion. + +This summary is relative to the Pintos base code, but we started from +the reference solution to project 2. @xref{Project 2 FAQ}, for the +summary of project 2. + +@verbatim + Makefile.build | 4 + devices/timer.c | 42 ++ + threads/init.c | 5 + threads/interrupt.c | 2 + threads/thread.c | 31 + + threads/thread.h | 37 +- + userprog/exception.c | 12 + userprog/pagedir.c | 10 + userprog/process.c | 319 +++++++++++++----- + userprog/syscall.c | 545 ++++++++++++++++++++++++++++++- + userprog/syscall.h | 1 + vm/frame.c | 162 +++++++++ + vm/frame.h | 23 + + vm/page.c | 297 ++++++++++++++++ + vm/page.h | 50 ++ + vm/swap.c | 85 ++++ + vm/swap.h | 11 + 17 files changed, 1532 insertions(+), 104 deletions(-) +@end verbatim + +@item Do we need a working HW 2 to implement HW 3? Yes. -@item +@item How do I use the hash table provided in @file{lib/kernel/hash.c}? @anchor{Hash Table} -@b{How do I use the hash table provided in @file{lib/kernel/hash.c}?} -First, you need to embed a @struct{hash_elem} as a member of the +First, you need to add a @struct{hash_elem} as a member of the object that the hash table will contain. Each @struct{hash_elem} allows the object to a member of at most one hash table at a given time. All the hash table functions that deal with hash table items actually use @@ -547,8 +616,9 @@ that is unique for each object, because a given hash table may not contain two objects with equal keys. Then you need to write two functions. The first is a @dfn{hash function} that converts a key into an integer. Some sample hash functions that you can use or just -examine are given in @file{lib/kernel/hash.c}. The second function -needed is a @dfn{comparison function} that compares a pair and returns +examine are given in @file{lib/kernel/hash.c}. The second needed +function is a @dfn{comparison function} that compares a pair of objects +and returns true if the first is less than the second. These two functions have to be compatible with the prototypes for @code{hash_hash_func} and @code{hash_less_func} in @file{lib/kernel/hash.h}. @@ -558,7 +628,7 @@ in a hash table. First, add a @struct{hash_elem} to the thread structure by adding a line to its definition: @example -struct hash_elem h_elem; /* Hash table element. */ +struct hash_elem h_elem; /* Hash table element. */ @end example We'll choose the @code{tid} member in @struct{thread} as the key, @@ -575,7 +645,8 @@ thread_hash (const struct hash_elem *e, void *aux UNUSED) /* Returns true if A's tid is less than B's tid. */ bool -thread_less (const struct hash_elem *a_, const struct hash_elem *b_, +thread_less (const struct hash_elem *a_, + const struct hash_elem *b_, void *aux UNUSED) @{ struct thread *a = hash_entry (a_, struct thread, h_elem); @@ -599,13 +670,9 @@ then we can insert it into the hash table with: hash_insert (&threads, &@var{t}->h_elem); @end example -If you have any other questions about hash tables, the CS109 -and CS161 textbooks have good chapters on them, or you can come -to any of the TA's office hours for further clarification. +The CS109 and CS161 textbooks have chapters on hash tables. -@item -@b{What are the @var{aux} parameters to the hash table functions good -for?} +@item Why do the hash table functions have @var{aux} parameters? In simple cases you won't have any need for the @var{aux} parameters. In these cases you can just pass a null pointer to @func{hash_init} @@ -621,64 +688,61 @@ the items in a hash table contain fixed-length strings, but the items themselves don't indicate what that fixed length is, you could pass the length as an @var{aux} parameter. -@item -@b{The current implementation of the hash table does not do something -that we need it to do. What gives?} +@item Can we change the hash table implementation? You are welcome to modify it. It is not used by any of the code we provided, so modifying it won't affect any code but yours. Do whatever it takes to make it work the way you want. -@item -@b{What controls the layout of user programs?} +@item What extra credit is available? -The linker is responsible for the layout of a user program in -memory. The linker is directed by a ``linker script'' which 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 @samp{info ld}. -@end enumerate +You may implement sharing: when multiple processes are created that use +the same executable file, share read-only pages among those processes +instead of creating separate copies of read-only segments for each +process. If you carefully designed your page table data structures, +sharing of read-only pages should not make this part significantly +harder. + +@end table @menu -* Problem 3-1 and 3-2 FAQ:: -* Problem 3-3 Memory Mapped File FAQ:: +* Page Table and Paging FAQ:: +* Memory Mapped File FAQ:: @end menu -@node Problem 3-1 and 3-2 FAQ -@subsection Problem 3-1 and 3-2 FAQ +@node Page Table and Paging FAQ +@subsection Page Table and Paging FAQ -@enumerate 1 -@item -@b{Does the virtual memory system need to support growth of the data -segment?} +@table @b +@item Does the virtual memory system need to support data segment growth? -No. The size of the data segment is determined by the linker. We -still have no dynamic allocation in Pintos (although it is possible to -``fake'' it at the user level by using memory-mapped files). However, -implementing it would add little additional complexity to a +No. The size of the data segment is determined by the linker. We still +have no dynamic allocation in Pintos (although it is possible to +``fake'' it at the user level by using memory-mapped files). Supporting +data segment growth should add little additional complexity to a well-designed system. -@item -@b{Why do I need to pass @code{PAL_USER} to @func{palloc_get_page} -when I allocate physical page frames?}@anchor{Why PAL_USER?} - -You can layer some other allocator on top of @func{palloc_get_page} -if you like, but it should be the underlying mechanism, directly or -indirectly, for two reasons. First, running out of pages in the user -pool just causes user programs to page, but running out of pages in -the kernel pool will cause all kinds of problems, because many kernel -functions depend on being able to allocate memory. Second, you can -use the @option{-ul} option to @command{pintos} to limit the size of -the user pool, which makes it easy to test your VM implementation with -various user memory sizes. -@end enumerate +@item Why should I use @code{PAL_USER} for allocating page frames? +@anchor{Why PAL_USER?} -@node Problem 3-3 Memory Mapped File FAQ -@subsection Problem 3-3: Memory Mapped File FAQ +Passing @code{PAL_USER} to @func{palloc_get_page} causes it to allocate +memory from the user pool, instead of the main kernel pool. Running out +of pages in the user pool just causes user programs to page, but running +out of pages in the kernel pool will cause many failures because so many +kernel functions need to obtain memory. +You can layer some other allocator on top of @func{palloc_get_page} if +you like, but it should be the underlying mechanism. -@enumerate 1 -@item -@b{How do we interact with memory-mapped files?} +Also, you can use the @option{-u} option to @command{pintos} to limit +the size of the user pool, which makes it easy to test your VM +implementation with various user memory sizes. +@end table + +@node Memory Mapped File FAQ +@subsection Memory Mapped File FAQ + +@table @b +@item How do we interact with memory-mapped files? Let's say you want to map a file called @file{foo} into your address space at address @t{0x10000000}. You open the file then use @code{mmap}: @@ -723,8 +787,7 @@ it: munmap (map); @end example -@item -@b{What if two processes map the same file into memory?} +@item What if two processes map the same file into memory? There is no requirement in Pintos that the two processes see consistent data. Unix handles this by making the two mappings share the @@ -732,34 +795,13 @@ same physical page, but the @code{mmap} system call also has an argument allowing the client to specify whether the page is shared or private (i.e.@: copy-on-write). -@item -@b{What happens if a user removes a @code{mmap}'d file?} +@item What happens if a user removes a @code{mmap}'d file? The mapping should remain valid, following the Unix convention. @xref{Removing an Open File}, for more information. -@item -@b{What if a process writes to a page that is memory-mapped, but the -location written to in the memory-mapped page is past the end -of the memory-mapped file?} - -Can't happen. @code{mmap} maps an entire file and Pintos provides no -way to shorten a file. (Until project 4, there's no way to extend a -file either.) You can remove a file, but the mapping remains valid -(see the previous question). - -@item -@b{Do we have to handle memory mapping @code{stdin} or @code{stdout}?} - -No. Memory mapping implies that a file has a length and that a user -can seek to any location in the file. Since the console device has -neither of these properties, @code{mmap} should return false when the -user attempts to memory map a file descriptor for the console device. - -@item -@b{If a user closes a mapped file, should it be automatically -unmapped?} +@item If a user closes a mapped file, should it be automatically unmapped? No. Once created the mapping is valid until @code{munmap} is called or the process exits. -@end enumerate +@end table diff --git a/doc/vm.tmpl b/doc/vm.tmpl new file mode 100644 index 0000000..ea7349a --- /dev/null +++ b/doc/vm.tmpl @@ -0,0 +1,148 @@ + +---------------------------+ + | 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, and lecture notes. + + PAGE TABLE MANAGEMENT + ===================== + +---- 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. + +---- ALGORITHMS ---- + +>> Describe your code for locating the physical page, if any, that +>> contains the data of a given virtual page. + +>> How does your code coordinate accessed and dirty bits between kernel +>> and user virtual addresses that alias a single physical page, or +>> alternatively how do you avoid the issue? + +---- SYNCHRONIZATION ---- + +>> When two user processes both need a new page frame at the same time, +>> how are races avoided? + +---- RATIONALE ---- + +>> Why did you choose the data structure(s) that you did for representing +>> virtual-to-physical mappings? + + PAGING TO AND FROM DISK + ======================= + +---- 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. + +---- ALGORITHMS ---- + +>> When a physical page frame is required but none is free, some page +>> frame must be evicted. Describe your code for choosing a frame to +>> evict. + +>> Explain your heuristic for deciding whether a page fault for an +>> invalid virtual address should cause the stack to be extended into the +>> page that faulted. + +---- SYNCHRONIZATION ---- + +>> Explain the basics of your VM synchronization design. In particular, +>> explain how it prevents deadlock. (Refer to the textbook for an +>> explanation of the necessary conditions for deadlock.) + +>> A page fault in process P can cause another process Q's page frame to +>> be evicted. How do you ensure that Q cannot access or modify the page +>> during the eviction process? How do you avoid a race between P +>> evicting Q's page frame and Q faulting the page back in? + +>> Suppose a page fault in process P causes a page to be read from disk. +>> How do you ensure that a second process Q cannot interfere by e.g. +>> attempting to evict the page while it is still being read in? + +>> Explain how you handle access to paged-out pages that occur during +>> system calls. Do you use page faults to bring in pages (as in user +>> programs), do you have a mechanism for "locking" pages into physical +>> memory, etc.? How do you gracefully handle attempted accesses to +>> invalid virtual addresses? + +---- RATIONALE ---- + +>> A single lock for the whole VM system would make synchronization easy, +>> but limit parallelism. On the other hand, using many locks +>> complicates synchronization and raises the possibility for deadlock +>> but allows for high parallelism. Explain where your design falls +>> along this continuum and why you chose to design it this way. + + MEMORY MAPPED FILES + =================== + +---- 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. + +---- ALGORITHMS ---- + +>> Describe how memory mapped files integrate into your virtual memory +>> subsystem. Explain how the page fault and eviction processes differ +>> for swap pages and other pages. + +>> Explain how you determine whether a new file mapping overlaps any +>> existing segment. + +---- RATIONALE ---- + +>> 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/grading/Makefile b/grading/Makefile deleted file mode 100644 index 4b6ea0b..0000000 --- a/grading/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -SUBDIRS = userprog vm filesys - -all:: - @echo "Run 'make' in subdirectories $(SUBDIRS)." - @echo "This makefile has only 'clean' targets." - -clean:: - for d in $(SUBDIRS); do $(MAKE) -C $$d $@; done - -distclean:: clean - find . -name '*~' -exec rm '{}' \; diff --git a/grading/filesys/.cvsignore b/grading/filesys/.cvsignore deleted file mode 100644 index 8d6648a..0000000 --- a/grading/filesys/.cvsignore +++ /dev/null @@ -1,48 +0,0 @@ -*.d -*.dsk -*.o -bochsout.txt -bochsrc.txt -child-syn-read -child-syn-rw -child-syn-wrt -dir-empty-name -dir-lsdir -dir-mk-tree -dir-mk-vine -dir-mkdir -dir-open -dir-over-file -dir-rm-cwd -dir-rm-cwd-cd -dir-rm-parent -dir-rm-root -dir-rm-tree -dir-rm-vine -dir-rmdir -dir-under-file -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-too-big -grow-two-files -sm-create -sm-full -sm-random -sm-seq-block -sm-seq-random -lg-create -lg-full -lg-random -lg-seq-block -lg-seq-random -syn-read -syn-remove -syn-rw -syn-write diff --git a/grading/filesys/Make.progs b/grading/filesys/Make.progs deleted file mode 100644 index 329b78a..0000000 --- a/grading/filesys/Make.progs +++ /dev/null @@ -1,59 +0,0 @@ -# -*- makefile -*- - -PROGS = sm-create sm-full sm-seq-block sm-seq-random sm-random \ -lg-create lg-full lg-seq-block lg-seq-random lg-random \ -grow-create grow-seq-sm grow-seq-lg grow-file-size grow-tell \ -grow-sparse grow-too-big grow-root-sm grow-root-lg grow-dir-lg \ -grow-two-files dir-mkdir dir-rmdir dir-mk-vine dir-rm-vine dir-mk-tree \ -dir-rm-tree dir-lsdir dir-rm-cwd dir-rm-cwd-cd dir-rm-parent \ -dir-rm-root dir-over-file dir-under-file dir-empty-name dir-open \ -syn-remove syn-read child-syn-read syn-write child-syn-wrt syn-rw \ -child-syn-rw - -sm_create_SRC = sm-create.c fslib.c fsmain.c -sm_full_SRC = sm-full.c fslib.c fsmain.c -sm_seq_block_SRC = sm-seq-block.c fslib.c fsmain.c -sm_seq_random_SRC = sm-seq-random.c fslib.c fsmain.c -sm_random_SRC = sm-random.c fslib.c fsmain.c - -lg_create_SRC = lg-create.c fslib.c fsmain.c -lg_full_SRC = lg-full.c fslib.c fsmain.c -lg_seq_block_SRC = lg-seq-block.c fslib.c fsmain.c -lg_seq_random_SRC = lg-seq-random.c fslib.c fsmain.c -lg_random_SRC = lg-random.c fslib.c fsmain.c - -grow_create_SRC = grow-create.c fslib.c fsmain.c -grow_seq_sm_SRC = grow-seq-sm.c fslib.c fsmain.c -grow_seq_lg_SRC = grow-seq-lg.c fslib.c fsmain.c -grow_file_size_SRC = grow-file-size.c fslib.c fsmain.c -grow_tell_SRC = grow-tell.c fslib.c fsmain.c -grow_sparse_SRC = grow-sparse.c fslib.c fsmain.c -grow_too_big_SRC = grow-too-big.c fslib.c fsmain.c -grow_root_sm_SRC = grow-root-sm.c fslib.c fsmain.c -grow_root_lg_SRC = grow-root-lg.c fslib.c fsmain.c -grow_dir_lg_SRC = grow-dir-lg.c fslib.c fsmain.c -grow_two_files_SRC = grow-two-files.c fslib.c fsmain.c - -dir_mkdir_SRC = dir-mkdir.c fslib.c fsmain.c -dir_rmdir_SRC = dir-rmdir.c fslib.c fsmain.c -dir_mk_vine_SRC = dir-mk-vine.c fslib.c fsmain.c -dir_rm_vine_SRC = dir-rm-vine.c fslib.c fsmain.c -dir_mk_tree_SRC = dir-mk-tree.c mk-tree.c fslib.c fsmain.c -dir_rm_tree_SRC = dir-rm-tree.c mk-tree.c fslib.c fsmain.c -dir_lsdir_SRC = dir-lsdir.c fslib.c fsmain.c -dir_rm_cwd_SRC = dir-rm-cwd.c fslib.c fsmain.c -dir_rm_cwd_cd_SRC = dir-rm-cwd-cd.c fslib.c fsmain.c -dir_rm_parent_SRC = dir-rm-parent.c fslib.c fsmain.c -dir_rm_root_SRC = dir-rm-root.c fslib.c fsmain.c -dir_over_file_SRC = dir-over-file.c fslib.c fsmain.c -dir_under_file_SRC = dir-under-file.c fslib.c fsmain.c -dir_empty_name_SRC = dir-empty-name.c fslib.c fsmain.c -dir_open_SRC = dir-open.c fslib.c fsmain.c - -syn_remove_SRC = syn-remove.c fslib.c fsmain.c -syn_read_SRC = syn-read.c fslib.c fsmain.c -child_syn_read_SRC = child-syn-read.c fslib.c -syn_write_SRC = syn-write.c fslib.c fsmain.c -child_syn_wrt_SRC = child-syn-wrt.c fslib.c -syn_rw_SRC = syn-rw.c fslib.c fsmain.c -child_syn_rw_SRC = child-syn-rw.c fslib.c diff --git a/grading/filesys/Makefile b/grading/filesys/Makefile deleted file mode 100644 index 05e4d90..0000000 --- a/grading/filesys/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -include Make.progs - -SRCDIR = ../../src - -all: $(PROGS) - -include $(SRCDIR)/Makefile.userprog - -CFLAGS += -Werror diff --git a/grading/filesys/dir-empty-name.exp b/grading/filesys/dir-empty-name.exp deleted file mode 100644 index c63b116..0000000 --- a/grading/filesys/dir-empty-name.exp +++ /dev/null @@ -1,3 +0,0 @@ -(dir-empty-name) begin -(dir-empty-name) create "" (must return false) -(dir-empty-name) end diff --git a/grading/filesys/dir-mk-tree.c b/grading/filesys/dir-mk-tree.c deleted file mode 100644 index cd4f65a..0000000 --- a/grading/filesys/dir-mk-tree.c +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include -#include -#include "fslib.h" -#include "mk-tree.h" - -const char test_name[] = "dir-mk-tree"; - -void -test_main (void) -{ - make_tree (4, 3, 3, 4); -} - diff --git a/grading/filesys/dir-rm-root.exp b/grading/filesys/dir-rm-root.exp deleted file mode 100644 index b6c87cb..0000000 --- a/grading/filesys/dir-rm-root.exp +++ /dev/null @@ -1,4 +0,0 @@ -(dir-rm-root) begin -(dir-rm-root) remove "/" (must return false) -(dir-rm-root) create "/a" -(dir-rm-root) end diff --git a/grading/filesys/fsmain.c b/grading/filesys/fsmain.c deleted file mode 100644 index 76113a6..0000000 --- a/grading/filesys/fsmain.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include "fslib.h" - -int -main (void) -{ - msg ("begin"); - random_init (0); - test_main (); - msg ("end"); - return 0; -} diff --git a/grading/filesys/grow-create.c b/grading/filesys/grow-create.c deleted file mode 100644 index ba6a40d..0000000 --- a/grading/filesys/grow-create.c +++ /dev/null @@ -1,3 +0,0 @@ -const char test_name[] = "grow-create"; -#define TEST_SIZE 0 -#include "create.inc" diff --git a/grading/filesys/grow-create.exp b/grading/filesys/grow-create.exp deleted file mode 100644 index 8439e84..0000000 --- a/grading/filesys/grow-create.exp +++ /dev/null @@ -1,5 +0,0 @@ -(grow-create) begin -(grow-create) create "blargle" -(grow-create) open "blargle" for verification -(grow-create) close "blargle" -(grow-create) end diff --git a/grading/filesys/grow-dir-lg.c b/grading/filesys/grow-dir-lg.c deleted file mode 100644 index b68ccee..0000000 --- a/grading/filesys/grow-dir-lg.c +++ /dev/null @@ -1,4 +0,0 @@ -const char test_name[] = "grow-dir-lg"; -#define FILE_CNT 50 -#define DIRECTORY "/x" -#include "grow-dir.inc" diff --git a/grading/filesys/grow-root-lg.c b/grading/filesys/grow-root-lg.c deleted file mode 100644 index 537a7e3..0000000 --- a/grading/filesys/grow-root-lg.c +++ /dev/null @@ -1,3 +0,0 @@ -const char test_name[] = "grow-root-lg"; -#define FILE_CNT 50 -#include "grow-dir.inc" diff --git a/grading/filesys/grow-root-sm.c b/grading/filesys/grow-root-sm.c deleted file mode 100644 index 92eead5..0000000 --- a/grading/filesys/grow-root-sm.c +++ /dev/null @@ -1,3 +0,0 @@ -const char test_name[] = "grow-root-sm"; -#define FILE_CNT 10 -#include "grow-dir.inc" diff --git a/grading/filesys/grow-seq-lg.c b/grading/filesys/grow-seq-lg.c deleted file mode 100644 index 9cd7c74..0000000 --- a/grading/filesys/grow-seq-lg.c +++ /dev/null @@ -1,3 +0,0 @@ -const char test_name[] = "grow-seq-lg"; -#define TEST_SIZE 72943 -#include "grow-seq.inc" diff --git a/grading/filesys/grow-seq-sm.c b/grading/filesys/grow-seq-sm.c deleted file mode 100644 index a99fa0c..0000000 --- a/grading/filesys/grow-seq-sm.c +++ /dev/null @@ -1,3 +0,0 @@ -const char test_name[] = "grow-seq-sm"; -#define TEST_SIZE 5678 -#include "grow-seq.inc" diff --git a/grading/filesys/grow-too-big.exp b/grading/filesys/grow-too-big.exp deleted file mode 100644 index 683630f..0000000 --- a/grading/filesys/grow-too-big.exp +++ /dev/null @@ -1,7 +0,0 @@ -(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) end diff --git a/grading/filesys/lg-create.c b/grading/filesys/lg-create.c deleted file mode 100644 index c419e14..0000000 --- a/grading/filesys/lg-create.c +++ /dev/null @@ -1,3 +0,0 @@ -const char test_name[] = "sm-create"; -#define TEST_SIZE 75678 -#include "create.inc" diff --git a/grading/filesys/lg-create.exp b/grading/filesys/lg-create.exp deleted file mode 100644 index 4bc3b9a..0000000 --- a/grading/filesys/lg-create.exp +++ /dev/null @@ -1,5 +0,0 @@ -(sm-create) begin -(sm-create) create "blargle" -(sm-create) open "blargle" for verification -(sm-create) close "blargle" -(sm-create) end diff --git a/grading/filesys/lg-full.c b/grading/filesys/lg-full.c deleted file mode 100644 index cd21ee6..0000000 --- a/grading/filesys/lg-full.c +++ /dev/null @@ -1,3 +0,0 @@ -const char test_name[] = "sm-full"; -#define TEST_SIZE 75678 -#include "full.inc" diff --git a/grading/filesys/lg-random.c b/grading/filesys/lg-random.c deleted file mode 100644 index 689246a..0000000 --- a/grading/filesys/lg-random.c +++ /dev/null @@ -1,4 +0,0 @@ -const char test_name[] = "sm-random"; -#define BLOCK_SIZE 512 -#define TEST_SIZE (512 * 150) -#include "random.inc" diff --git a/grading/filesys/lg-seq-block.c b/grading/filesys/lg-seq-block.c deleted file mode 100644 index 0a2df57..0000000 --- a/grading/filesys/lg-seq-block.c +++ /dev/null @@ -1,4 +0,0 @@ -const char test_name[] = "sm-seq-block"; -#define TEST_SIZE 75678 -#define BLOCK_SIZE 513 -#include "seq-block.inc" diff --git a/grading/filesys/lg-seq-random.c b/grading/filesys/lg-seq-random.c deleted file mode 100644 index 9e19c74..0000000 --- a/grading/filesys/lg-seq-random.c +++ /dev/null @@ -1,3 +0,0 @@ -const char test_name[] = "sm-seq-random"; -#define TEST_SIZE 75678 -#include "seq-random.inc" diff --git a/grading/filesys/lib/.cvsignore b/grading/filesys/lib/.cvsignore deleted file mode 100644 index a438335..0000000 --- a/grading/filesys/lib/.cvsignore +++ /dev/null @@ -1 +0,0 @@ -*.d diff --git a/grading/filesys/lib/user/.cvsignore b/grading/filesys/lib/user/.cvsignore deleted file mode 100644 index a438335..0000000 --- a/grading/filesys/lib/user/.cvsignore +++ /dev/null @@ -1 +0,0 @@ -*.d diff --git a/grading/filesys/main.c b/grading/filesys/main.c deleted file mode 100644 index 52561bc..0000000 --- a/grading/filesys/main.c +++ /dev/null @@ -1,10 +0,0 @@ -#include "fslib.h" - -int -main (void) -{ - msg ("begin"); - test_main (); - msg ("end"); - return 0; -} diff --git a/grading/filesys/mk-tree.h b/grading/filesys/mk-tree.h deleted file mode 100644 index e10334c..0000000 --- a/grading/filesys/mk-tree.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef MK_TREE_H -#define MK_TREE_H 1 - -void make_tree (int at, int bt, int ct, int dt); - -#endif /* mk-tree.h */ diff --git a/grading/filesys/review.txt b/grading/filesys/review.txt deleted file mode 100644 index c53a801..0000000 --- a/grading/filesys/review.txt +++ /dev/null @@ -1,61 +0,0 @@ -TESTCASES [[/10]] ------------------ - -3 Didn't test/explain large files - -3 Didn't test/explain file growth - -2 Didn't test/explain directories - -2 Didn't test/explain cache performance - +1...+3 Bonus for demonstrating VM running on file system - - -DESIGN [[/40]] --------------- - -DESIGNDOC - -5 Doesn't explain synchronization - -5 Doesn't explain inode design (e.g. direct, indirect, etc. structure) - -5 Doesn't explain block cache structure - -5 Doesn't explain read-ahead/write-behind design - -Overall: - -1 Gratuitous use of malloc() (e.g. for allocating a list or a lock) - -1 Inappropriate use of ASSERT (e.g. to verify that malloc() succeeded) - -Synchronization and consistency - -5 One big lock for entire file system - -3 Doesn't mark inode deleted in bitmap when file removed and closed - -2 Doesn't mark indirect blocks deleted in bitmap when file removed, closed - -5 Keeps copy of inode_disk in inode but doesn't account for it in cache - -Large Files - -5 No direct blocks - -10 No indirect or doubly indirect blocks of any sort - -Subdirectories - -2 Directories cannot grow - +1 Supports Unix-like . and .. - +2 Supports recursive directory removal - -Buffer Cache - -3 Uses linear search instead of hash table, etc. - -2 Poor cache replacement algorithm (not LRU, etc.) - -1 Does not prioritize metadata in cache - -2 Locks entire cache during I/O - -Read-Ahead/Write-Behind (max -10) - -7 No read-ahead - -7 No write-behind - -5 Busy-waiting in write-behind thread - -2 Spawns a new thread on every block read - +2 Prioritizes real reads over read-ahead - - -STYLE [[/10]] -------------- - -5...-10 Fixing code after submission - -5 Doesn't compile as submitted - +1...+5 Cool test programs etc. - - -COMMENTS --------- - diff --git a/grading/filesys/run-tests b/grading/filesys/run-tests deleted file mode 100755 index 1b768ab..0000000 --- a/grading/filesys/run-tests +++ /dev/null @@ -1,129 +0,0 @@ -#! /usr/bin/perl - -# Find the directory that contains the grading files. -our ($GRADES_DIR); - -# Add our Perl library directory to the include path. -BEGIN { - ($GRADES_DIR = $0) =~ s#/[^/]+$##; - -d $GRADES_DIR or die "$GRADES_DIR: stat: $!\n"; - unshift @INC, "$GRADES_DIR/../lib"; -} - -use warnings; -use strict; -use Pintos::Grading; - -our ($hw) = "filesys"; -our (@TESTS); # Tests to run. -our ($test); -our ($action); - -parse_cmd_line qw (sm-create sm-full sm-seq-block sm-seq-random sm-random - - lg-create lg-full lg-seq-block lg-seq-random lg-random - - grow-create grow-seq-sm grow-seq-lg grow-file-size grow-tell - grow-sparse grow-too-big grow-root-sm grow-root-lg - grow-dir-lg grow-two-files - - dir-mkdir dir-rmdir dir-mk-vine dir-rm-vine dir-mk-tree - dir-rm-tree dir-lsdir dir-rm-cwd dir-rm-cwd-cd - dir-rm-parent dir-rm-root dir-over-file dir-under-file - dir-empty-name dir-open - - syn-remove syn-read syn-write syn-rw); - -clean_dir (), exit if $action eq 'clean'; - -extract_sources (); -exit if $action eq 'extract'; - -build (); -exit success () if $action eq 'build'; - -run_and_grade_tests (); -write_grades (); -write_details (); -exit success () if $action eq 'test'; - -assemble_final_grade (); -exit if $action eq 'assemble'; - -die "Don't know how to '$action'"; - -# Runs $test in directory output/$test. -# Returns 'ok' if it went ok, otherwise an explanation. -sub run_test { - my ($result); - - my ($fs_size) = $test ne 'grow-too-big' ? 2 : .25; - xsystem ("pintos make-disk output/$test/fs.dsk $fs_size >/dev/null 2>&1", - DIE => "failed to create file system disk"); - xsystem ("pintos make-disk output/$test/swap.dsk 2 >/dev/null 2>&1", - DIE => "failed to create swap disk"); - - # Format disk, install test. - my (@base_opts) = ("--os-disk=pintos/src/$hw/build/os.dsk", - "--fs-disk=output/$test/fs.dsk", - "--swap-disk=output/$test/swap.dsk", - "-v"); - $result = run_pintos ([@base_opts, - "put", "-f", "$GRADES_DIR/$test", $test], - LOG => "$test/put", TIMEOUT => 60); - return $result if $result ne 'ok'; - - my (@extra_files); - push (@extra_files, "child-syn-read") if $test eq 'syn-read'; - push (@extra_files, "child-syn-wrt") if $test eq 'syn-write'; - push (@extra_files, "child-syn-rw") if $test eq 'syn-rw'; - for my $fn (@extra_files) { - $result = run_pintos ([@base_opts, "put", "$GRADES_DIR/$fn", $fn], - LOG => "$test/put-$fn", TIMEOUT => 60); - return "Error running `put $fn': $result" if $result ne 'ok'; - } - - # Run. - return run_pintos ([@base_opts, "run", "-q", "-ex", $test], - LOG => "$test/run", TIMEOUT => 120); -} - -sub grade_dir_lsdir { - my (@output) = @_; - verify_common (@output); - @output = get_core_output (@output); - - my ($begin); - for my $i (0...$#output) { - $begin = $i, last if $output[$i] eq '(dir-lsdir) begin'; - } - die "\"(dir-lsdir) begin\" does not appear in output\n" if !defined $begin; - - my ($end); - for my $i (0...$#output) { - $end = $i, last if $output[$i] eq '(dir-lsdir) end'; - } - die "\"(dir-lsdir) end\" does not appear in output\n" if !defined $end; - die "\"begin\" follows \"end\" in output\n" if $begin > $end; - - my (%count); - for my $fn (@output[$begin + 1...$end - 1]) { - $fn =~ s/\s+$//; - die "Unexpected file \"$fn\" in lsdir output\n" - unless grep ($_ eq $fn, qw (. .. dir-lsdir)); - die "File \"$fn\" listed twice in lsdir output\n" - if $count{$fn}; - $count{$fn}++; - } - die "No files in lsdir output\n" if scalar (keys (%count)) == 0; - die "File \"dir-lsdir\" missing from lsdir output\n" - if !$count{"dir-lsdir"}; -} - -# This should be improved, but none of the fall 2004 submissions -# survived the test! -# I suppose it could be a bug in the test but a lot of the submissions -# had kernel panics, etc. -sub grade_syn_rw { - verify_common (@_); -} diff --git a/grading/filesys/sm-create.c b/grading/filesys/sm-create.c deleted file mode 100644 index 47f4f99..0000000 --- a/grading/filesys/sm-create.c +++ /dev/null @@ -1,3 +0,0 @@ -const char test_name[] = "sm-create"; -#define TEST_SIZE 5678 -#include "create.inc" diff --git a/grading/filesys/sm-create.exp b/grading/filesys/sm-create.exp deleted file mode 100644 index 4bc3b9a..0000000 --- a/grading/filesys/sm-create.exp +++ /dev/null @@ -1,5 +0,0 @@ -(sm-create) begin -(sm-create) create "blargle" -(sm-create) open "blargle" for verification -(sm-create) close "blargle" -(sm-create) end diff --git a/grading/filesys/sm-full.c b/grading/filesys/sm-full.c deleted file mode 100644 index 7fabd50..0000000 --- a/grading/filesys/sm-full.c +++ /dev/null @@ -1,3 +0,0 @@ -const char test_name[] = "sm-full"; -#define TEST_SIZE 5678 -#include "full.inc" diff --git a/grading/filesys/sm-full.exp b/grading/filesys/sm-full.exp deleted file mode 100644 index 44c99ee..0000000 --- a/grading/filesys/sm-full.exp +++ /dev/null @@ -1,8 +0,0 @@ -(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) close "quux" -(sm-full) end diff --git a/grading/filesys/sm-random.c b/grading/filesys/sm-random.c deleted file mode 100644 index 56f4a73..0000000 --- a/grading/filesys/sm-random.c +++ /dev/null @@ -1,4 +0,0 @@ -const char test_name[] = "sm-random"; -#define BLOCK_SIZE 13 -#define TEST_SIZE (13 * 123) -#include "random.inc" diff --git a/grading/filesys/sm-random.exp b/grading/filesys/sm-random.exp deleted file mode 100644 index 37adb48..0000000 --- a/grading/filesys/sm-random.exp +++ /dev/null @@ -1,7 +0,0 @@ -(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 diff --git a/grading/filesys/sm-seq-block.c b/grading/filesys/sm-seq-block.c deleted file mode 100644 index a6a1a4b..0000000 --- a/grading/filesys/sm-seq-block.c +++ /dev/null @@ -1,4 +0,0 @@ -const char test_name[] = "sm-seq-block"; -#define TEST_SIZE 5678 -#define BLOCK_SIZE 513 -#include "seq-block.inc" diff --git a/grading/filesys/sm-seq-block.exp b/grading/filesys/sm-seq-block.exp deleted file mode 100644 index f7b4ccc..0000000 --- a/grading/filesys/sm-seq-block.exp +++ /dev/null @@ -1,8 +0,0 @@ -(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) close "noodle" -(sm-seq-block) end diff --git a/grading/filesys/sm-seq-random.c b/grading/filesys/sm-seq-random.c deleted file mode 100644 index 0ec496e..0000000 --- a/grading/filesys/sm-seq-random.c +++ /dev/null @@ -1,3 +0,0 @@ -const char test_name[] = "sm-seq-random"; -#define TEST_SIZE 5678 -#include "seq-random.inc" diff --git a/grading/filesys/sm-seq-random.exp b/grading/filesys/sm-seq-random.exp deleted file mode 100644 index 9432552..0000000 --- a/grading/filesys/sm-seq-random.exp +++ /dev/null @@ -1,8 +0,0 @@ -(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) close "nibble" -(sm-seq-random) end diff --git a/grading/filesys/syn-read.h b/grading/filesys/syn-read.h deleted file mode 100644 index 6b1d8ed..0000000 --- a/grading/filesys/syn-read.h +++ /dev/null @@ -1,2 +0,0 @@ -#define BUF_SIZE 1024 -static const char filename[] = "data"; diff --git a/grading/filesys/tests.txt b/grading/filesys/tests.txt deleted file mode 100644 index 5818cac..0000000 --- a/grading/filesys/tests.txt +++ /dev/null @@ -1,57 +0,0 @@ -CORRECTNESS [[total]] ---------------------- - -Small files (<= 63 kB) - -1 sm-create: create small file, verify initialization to zeros - -1 sm-full: write small file in single system call, reread to verify - -1 sm-seq-block: write small file one block at a time, reread to verify - -1 sm-seq-random: write small file a random amount at a time, reread & verify - -1 sm-random: write small file randomly, reread randomly to verify -Score: /5 - -Large files (> 63 kB) - -1 lg-create: create large file, verify initialization to zeros - -1 lg-full: write large file in single system call, reread to verify - -1 lg-seq-block: write large file one block at a time, reread to verify - -1 lg-seq-random: write large file a random amount at a time, reread & verify - -1 lg-random: write large file randomly, reread randomly to verify -Score: /5 - -File growth - -1 grow-create: create empty file, verify - -1 grow-seq-sm: extend empty file sequentially to small size, verify - -1 grow-seq-lg: extend empty file sequentially to large size, verify - -1 grow-file-size: filesize must return proper value as file grows - -1 grow-tell: tell must return proper value as file grows - -1 grow-sparse: create empty file, seek past 64 kB, write byte, verify zeros - -1 grow-too-big: create empty file, seek past 2 GB, write byte, can't crash - -1 grow-root-sm: create 20 small files in root directory - -1 grow-root-lg: create 50 small files in root directory - -1 grow-dir-lg: create subdirectory, create 50 small files in it - -1 grow-two-files: growing two files alternately must work -Score: /11 - -Subdirectories and file management - -1 dir-mkdir: mkdir a, create a/b, chdir a, open b - -1 dir-rmdir: create directory, remove directory, chdir into it must now fail - -1 dir-mk-vine: create deep chain of directories, create & check files inside - -1 dir-rm-vine: create and remove deep chain of directories - -1 dir-mk-tree: create wide, deep directory tree, create & check files in it - -1 dir-rm-tree: create and remove wide, deep directory tree - -1 dir-lsdir: lsdir must work - -1 dir-rm-cwd: removing current directory must not crash - -1 dir-rm-cwd-cd: if current directory removable, then cd'ing to it must fail - -1 dir-rm-parent: removing current directory and then its parent can't crash - -1 dir-rm-root: must not be able to remove root directory - -1 dir-over-file: creating a directory named after an existing file must fail - -1 dir-under-file: creating file named after existing directory must fail - -1 dir-empty-name: creating file named after the empty string must fail - -1 dir-open: if directories openable as files, writing them must fail -Score: /15 - -Synchronization - -1 syn-remove: read and write deleted file - -1 syn-read: one process writes file then many read it - -1 syn-write: many processes write different parts of file, then verify - -1 syn-rw: one process extends file sequentially as many read it sequentially -Score: /4 diff --git a/grading/lib/.cvsignore b/grading/lib/.cvsignore deleted file mode 100644 index a438335..0000000 --- a/grading/lib/.cvsignore +++ /dev/null @@ -1 +0,0 @@ -*.d diff --git a/grading/lib/Algorithm/Diff.pm b/grading/lib/Algorithm/Diff.pm deleted file mode 100644 index 904c530..0000000 --- a/grading/lib/Algorithm/Diff.pm +++ /dev/null @@ -1,1713 +0,0 @@ -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/grading/lib/Pintos/Grading.pm b/grading/lib/Pintos/Grading.pm deleted file mode 100644 index ad2a825..0000000 --- a/grading/lib/Pintos/Grading.pm +++ /dev/null @@ -1,851 +0,0 @@ -use strict; -use warnings; - -our ($test); - -our ($GRADES_DIR); -our ($verbose); -our %result; -our %details; -our %extra; -our @TESTS; -our $action; -our $hw; - -use POSIX; -use Getopt::Long qw(:config no_ignore_case); -use Algorithm::Diff; - -# We execute lots of subprocesses. -# Without this, our stdout output can get flushed multiple times, -# which is harmless but looks bizarre. -$| = 1; - -sub parse_cmd_line { - my ($do_regex, $no_regex); - GetOptions ("v|verbose+" => \$verbose, - "h|help" => sub { usage (0) }, - "d|do-tests=s" => \$do_regex, - "n|no-tests=s" => \$no_regex, - "c|clean" => sub { set_action ('clean'); }, - "x|extract" => sub { set_action ('extract'); }, - "b|build" => sub { set_action ('build'); }, - "t|test" => sub { set_action ('test'); }, - "a|assemble" => sub { set_action ('assemble'); }) - or die "Malformed command line; use --help for help.\n"; - die "Non-option arguments not supported; use --help for help.\n" - if @ARGV > 0; - @TESTS = split(/,/, join (',', @TESTS)) if defined @TESTS; - - if (!defined $action) { - $action = -e 'review.txt' ? 'assemble' : 'test'; - } - - my (@default_tests) = @_; - @TESTS = @default_tests; - @TESTS = grep (/$do_regex/, @TESTS) if defined $do_regex; - @TESTS = grep (!/$no_regex/, @TESTS) if defined $no_regex; -} - -sub set_action { - my ($new_action) = @_; - die "actions `$action' and `$new_action' conflict\n" - if defined ($action) && $action ne $new_action; - $action = $new_action; -} - -sub usage { - my ($exitcode) = @_; - print < "extraction failed\n"); - - # Run custom script for this submission, if provided. - if (-e "fixme.sh") { - print "Running fixme.sh...\n"; - xsystem ("sh -e fixme.sh", DIE => "fix script failed\n"); - } else { - print "No fixme.sh, assuming no custom changes needed.\n"; - } - - # Apply patches from grading directory. - # Patches are applied in lexicographic order, so they should - # probably be named 00debug.patch, 01bitmap.patch, etc. - # Filenames in patches should be in the format pintos/src/... - print "Patching...\n"; - for my $patch (glob ("$GRADES_DIR/patches/*.patch")) { - my ($stem); - ($stem = $patch) =~ s%^$GRADES_DIR/patches/%% or die; - print "Applying $patch...\n"; - xsystem ("patch -fs -p0 < $patch", - LOG => $stem, DIE => "applying patch $stem failed\n"); - } -} - -# Returns the name of the tarball to extract. -sub choose_tarball { - my (@tarballs) - = grep (/^[a-z0-9]+\.[A-Za-z]+\.\d+\.\d+\.\d+\.\d+.\d+\.tar\.gz$/, - glob ("*.tar.gz")); - die "no pintos dir, no files matching username.MMM.DD.YY.hh.mm.ss.tar.gz\n" - if scalar (@tarballs) == 0; - - if (@tarballs > 1) { - # Sort tarballs in order by time. - @tarballs = sort { ext_mdyHMS ($a) cmp ext_mdyHMS ($b) } @tarballs; - - print "Multiple tarballs:\n"; - print "\t$_ submitted ", ext_mdyHMS ($_), "\n" foreach @tarballs; - print "Choosing $tarballs[$#tarballs]\n"; - } - return $tarballs[$#tarballs]; -} - -# Extract the date within a tarball name into a string that compares -# lexicographically in chronological order. -sub ext_mdyHMS { - my ($s) = @_; - my ($ms, $d, $y, $H, $M, $S) = - $s =~ /.([A-Za-z]+)\.(\d+)\.(\d+)\.(\d+)\.(\d+).(\d+)\.tar\.gz$/ - or die; - my ($m) = index ("janfebmaraprmayjunjulaugsepoctnovdec", lc $ms) / 3 - or die; - return sprintf "%02d-%02d-%02d %02d:%02d:%02d", $y, $m, $d, $H, $M, $S; -} - -# Building. - -sub build { - print "Compiling...\n"; - xsystem ("cd pintos/src/$hw && make", LOG => "make") eq 'ok' - or return "Build error"; -} - -# Run and grade the tests. -sub run_and_grade_tests { - for $test (@TESTS) { - print "$test: "; - my ($result) = get_test_result (); - chomp ($result); - - my ($grade) = grade_test ($test); - chomp ($grade); - - my ($msg) = $result eq 'ok' ? $grade : "$result - $grade"; - $msg .= " - with warnings" - if $grade eq 'ok' && defined $details{$test}; - print "$msg\n"; - - $result{$test} = $grade; - } -} - -# Write test grades to tests.out. -sub write_grades { - my (@summary) = snarf ("$GRADES_DIR/tests.txt"); - - my ($ploss) = 0; - my ($tloss) = 0; - my ($total) = 0; - my ($warnings) = 0; - for (my ($i) = 0; $i <= $#summary; $i++) { - local ($_) = $summary[$i]; - if (my ($loss, $test) = /^ -(\d+) ([-a-zA-Z0-9]+):/) { - my ($result) = $result{$test} || "Not tested."; - - if ($result eq 'ok') { - if (!defined $details{$test}) { - # Test successful and no warnings. - splice (@summary, $i, 1); - $i--; - } else { - # Test successful with warnings. - s/-(\d+) //; - $summary[$i] = $_; - splice (@summary, $i + 1, 0, - " Test passed with warnings. " - . "Details at end of file."); - $warnings++; - } - } else { - $ploss += $loss; - $tloss += $loss; - splice (@summary, $i + 1, 0, - map (" $_", split ("\n", $result))); - } - } elsif (my ($ptotal) = /^Score: \/(\d+)$/) { - $total += $ptotal; - $summary[$i] = "Score: " . ($ptotal - $ploss) . "/$ptotal"; - splice (@summary, $i, 0, " All tests passed.") - if $ploss == 0 && !$warnings; - $ploss = 0; - $warnings = 0; - $i++; - } - } - my ($ts) = "(" . ($total - $tloss) . "/" . $total . ")"; - $summary[0] =~ s/\[\[total\]\]/$ts/; - - open (SUMMARY, ">tests.out"); - print SUMMARY map ("$_\n", @summary); - close (SUMMARY); -} - -# Write failure and warning details to details.out. -sub write_details { - open (DETAILS, ">details.out"); - my ($n) = 0; - for $test (@TESTS) { - next if $result{$test} eq 'ok' && !defined $details{$test}; - - my ($details) = $details{$test}; - next if !defined ($details) && ! -e "output/$test/run.out"; - - my ($banner); - if ($result{$test} ne 'ok') { - $banner = "$test failure details"; - } else { - $banner = "$test warnings"; - } - - print DETAILS "\n" if $n++; - print DETAILS "--- $banner ", '-' x (50 - length ($banner)); - print DETAILS "\n\n"; - - if (!defined $details) { - my (@output) = snarf ("output/$test/run.out"); - - # Print only the first in a series of recursing panics. - my ($panic) = 0; - for my $i (0...$#output) { - local ($_) = $output[$i]; - if (/PANIC/ && $panic++ > 0) { - @output = @output[0...$i]; - push (@output, - "[...details of recursive panic(s) omitted...]"); - last; - } - } - $details = "Output:\n\n" . join ('', map ("$_\n", @output)); - } - print DETAILS $details; - - print DETAILS "\n", "-" x 10, "\n\n$extra{$test}" - if defined $extra{$test}; - } - close (DETAILS); -} - -sub xsystem { - my ($command, %options) = @_; - print "$command\n" if $verbose || $options{VERBOSE}; - - my ($log) = $options{LOG}; - - my ($pid, $status); - eval { - local $SIG{ALRM} = sub { die "alarm\n" }; - alarm $options{TIMEOUT} if defined $options{TIMEOUT}; - $pid = fork; - die "fork: $!\n" if !defined $pid; - if (!$pid) { - if (defined $log) { - open (STDOUT, ">output/$log.out"); - open (STDERR, ">output/$log.err"); - } - chdir $options{CHDIR} or die "$options{CHDIR}: chdir: $!\n" - if defined ($options{CHDIR}); - if (!defined ($options{EXEC})) { - exec ($command); - } else { - exec (@{$options{EXEC}}); - } - exit (-1); - } - waitpid ($pid, 0); - $status = $?; - alarm 0; - }; - - my ($result); - if ($@) { - die unless $@ eq "alarm\n"; # propagate unexpected errors - my ($i); - for ($i = 0; $i < 10; $i++) { - kill ('SIGTERM', $pid); - sleep (1); - my ($retval) = waitpid ($pid, WNOHANG); - last if $retval == $pid || $retval == -1; - print "Timed out - Waiting for $pid to die" if $i == 0; - print "."; - } - print "\n" if $i; - $result = 'timeout'; - } else { - if (WIFSIGNALED ($status)) { - my ($signal) = WTERMSIG ($status); - die "Interrupted\n" if $signal == SIGINT; - print "Child terminated with signal $signal\n"; - } - - $result = $status == 0 ? "ok" : "error"; - } - - if ($result eq 'error' && defined $options{DIE}) { - my ($msg) = $options{DIE}; - if (defined ($log)) { - chomp ($msg); - $msg .= "; see output/$log.err and output/$log.out for details\n"; - } - die $msg; - } elsif ($result ne 'error' && defined ($log)) { - unlink ("output/$log.err"); - } - - return $result; -} - -sub get_test_result { - my ($cache_file) = "output/$test/run.result"; - # Reuse older results if any. - if (open (RESULT, "<$cache_file")) { - my ($result); - $result = ; - chomp $result; - close (RESULT); - return $result; - } - - # If there's residue from an earlier test, move it to .old. - # If there's already a .old, delete it. - xsystem ("rm -rf output/$test.old", VERBOSE => 1) if -d "output/$test.old"; - rename "output/$test", "output/$test.old" or die "rename: $!\n" - if -d "output/$test"; - - # Make output directory. - mkdir "output/$test"; - - # Run the test. - my ($result) = run_test ($test); - - # Delete any disks in the output directory because they take up - # lots of space. - unlink (glob ("output/$test/*.dsk")); - - # Save the results for later. - open (DONE, ">$cache_file") or die "$cache_file: create: $!\n"; - print DONE "$result\n"; - close (DONE); - - return $result; -} - -sub run_pintos { - my ($cmd_line, %args) = @_; - unshift (@$cmd_line, 'pintos'); - my ($retval) = xsystem (join (' ', @$cmd_line), %args, EXEC => $cmd_line); - return 'ok' if $retval eq 'ok'; - if ($retval eq 'timeout') { - my ($msg) = "Timed out after $args{TIMEOUT} seconds"; - my ($load_avg) = `uptime` =~ /(load average:.*)$/i; - $msg .= " - $load_avg" if defined $load_avg; - return $msg; - } - return 'Error running Bochs' if $retval eq 'error'; - die; -} - -# Grade the test. -sub grade_test { - # Read test output. - my ($outfile) = "output/$test/run.out"; - if (! -e $outfile) { - if (-s "output/$test/make.err") { - # make failed. - $details{$test} = snarf ("output/$test/make.err"); - return "make failed. Error messages at end of file."; - } - return "preparation for test failed"; - } - my (@output) = snarf ($outfile); - - # If there's a function "grade_$test", use it to evaluate the output. - # If there's a file "$GRADES_DIR/$test.exp", compare its contents - # against the output. - # (If both exist, prefer the function.) - # - # If the test was successful, it returns normally. - # If it failed, it invokes `die' with an error message terminated - # by a new-line. The message will be given as an explanation in - # the output file tests.out. - # (Internal errors will invoke `die' without a terminating - # new-line, in which case we detect it and propagate the `die' - # upward.) - my ($grade_func) = "grade_$test"; - $grade_func =~ s/-/_/g; - if (-e "$GRADES_DIR/$test.exp" && !defined (&$grade_func)) { - eval { - verify_common (@output); - compare_output ("$GRADES_DIR/$test.exp", @output); - } - } else { - eval "$grade_func (\@output)"; - } - if ($@) { - die $@ if $@ =~ /at \S+ line \d+$/; - return $@; - } - return "ok"; -} - -# Do final grade. -# Combines grade.txt, tests.out, review.txt, and details.out, -# producing grade.out. -sub assemble_final_grade { - open (OUT, ">grade.out") or die "grade.out: create: $!\n"; - - open (GRADE, ") { - last if /^\s*$/; - print OUT; - } - close (GRADE); - - my (@tests) = snarf ("tests.out"); - my ($p_got, $p_pos) = $tests[0] =~ m%\((\d+)/(\d+)\)% or die; - - my (@review) = snarf ("review.txt"); - my ($part_lost) = (0, 0); - for (my ($i) = $#review; $i >= 0; $i--) { - local ($_) = $review[$i]; - if (my ($loss) = /^\s*([-+]\d+)/) { - $part_lost += $loss; - } elsif (my ($out_of) = m%\[\[/(\d+)\]\]%) { - my ($got) = $out_of + $part_lost; - $got = 0 if $got < 0; - $review[$i] =~ s%\[\[/\d+\]\]%($got/$out_of)% or die; - $part_lost = 0; - - $p_got += $got; - $p_pos += $out_of; - } - } - die "Lost points outside a section\n" if $part_lost; - - for (my ($i) = 1; $i <= $#review; $i++) { - if ($review[$i] =~ /^-{3,}\s*$/ && $review[$i - 1] !~ /^\s*$/) { - $review[$i] = '-' x (length ($review[$i - 1])); - } - } - - print OUT "\nOVERALL SCORE\n"; - print OUT "-------------\n"; - print OUT "$p_got points out of $p_pos total\n\n"; - - print OUT map ("$_\n", @tests), "\n"; - print OUT map ("$_\n", @review), "\n"; - - print OUT "DETAILS\n"; - print OUT "-------\n\n"; - print OUT map ("$_\n", snarf ("details.out")); -} - -# Clean up our generated files. -sub clean_dir { - # Verify that we're roughly in the correct directory - # before we go blasting away files. - choose_tarball (); - - # Blow away everything. - xsystem ("rm -rf output pintos", VERBOSE => 1); - xsystem ("rm -f details.out tests.out", VERBOSE => 1); -} - -# Provided a test's output as an array, verifies that it, in general, -# looks sensible; that is, that there are no PANIC or FAIL messages, -# that Pintos started up and shut down normally, and so on. -# Die if something odd found. -sub verify_common { - my (@output) = @_; - - die "No output at all\n" if @output == 0; - - look_for_panic (@output); - look_for_fail (@output); - look_for_triple_fault (@output); - - die "Didn't start up properly: no \"Pintos booting\" startup message\n" - if !grep (/Pintos booting with.*kB RAM\.\.\./, @output); - die "Didn't start up properly: no \"Boot complete\" startup message\n" - if !grep (/Boot complete/, @output); - die "Didn't shut down properly: no \"Timer: # ticks\" shutdown message\n" - if !grep (/Timer: \d+ ticks/, @output); - die "Didn't shut down properly: no \"Powering off\" shutdown message\n" - if !grep (/Powering off/, @output); -} - -sub look_for_panic { - my (@output) = @_; - - my ($panic) = grep (/PANIC/, @output); - return unless defined $panic; - - my ($details) = "Kernel panic:\n $panic\n"; - - my (@stack_line) = grep (/Call stack:/, @output); - if (@stack_line != 0) { - $details .= " $stack_line[0]\n\n"; - $details .= "Translation of backtrace:\n"; - my (@addrs) = $stack_line[0] =~ /Call stack:((?: 0x[0-9a-f]+)+)/; - - my ($A2L); - if (`uname -m` - =~ /i.86|pentium.*|[pk][56]|nexgen|viac3|6x86|athlon.*/) { - $A2L = "addr2line"; - } else { - $A2L = "i386-elf-addr2line"; - } - my ($kernel_o); - if ($hw eq 'threads') { - $kernel_o = "output/$test/kernel.o"; - } else { - $kernel_o = "pintos/src/$hw/build/kernel.o"; - } - open (A2L, "$A2L -fe $kernel_o @addrs|"); - for (;;) { - my ($function, $line); - last unless defined ($function = ); - $line = ; - chomp $function; - chomp $line; - $details .= " $function ($line)\n"; - } - } - - if ($panic =~ /sec_no < d->capacity/) { - $details .= < 1; - - my ($details) = < 1; - } - $details{$test} = $details; - die "Triple-fault caused spontaneous reboot(s). " - . "Details at end of file.\n"; -} - -# Get @output without header or trailer. -# Die if not possible. -sub get_core_output { - my (@output) = @_; - - my ($first); - for ($first = 0; $first <= $#output; $first++) { - my ($line) = $output[$first]; - $first++, last - if ($hw ne 'threads' && $line =~ /^Executing '$test.*':$/) - || ($hw eq 'threads' - && grep (/^Boot complete.$/, @output[0...$first - 1]) - && $line =~ /^\s*$/); - } - - my ($last); - for ($last = $#output; $last >= 0; $last--) { - $last--, last if $output[$last] =~ /^Timer: \d+ ticks$/; - } - - if ($last < $first) { - my ($no_first) = $first > $#output; - my ($no_last) = $last < $#output; - die "Couldn't locate output.\n"; - } - - return @output[$first ... $last]; -} - -sub canonicalize_exit_codes { - my (@output) = @_; - - # Exit codes are supposed to be printed in the form "process: exit(code)" - # but people get unfortunately creative with it. - for my $i (0...$#output) { - local ($_) = $output[$i]; - - my ($process, $code); - if ((($process, $code) = /^([-a-z0-9 ]+):.*[ \(](-?\d+)\b\)?$/) - || (($process, $code) = /^([-a-z0-9 ]+) exit\((-?\d+)\)$/) - || (($process, $code) - = /^([-a-z0-9 ]+) \(.*\): exit\((-?\d+)\)$/) - || (($process, $code) = /^([-a-z0-9 ]+):\( (-?\d+) \) $/) - || (($code, $process) = /^shell: exit\((-?\d+)\) \| ([-a-z0-9]+)/)) - { - # We additionally truncate to 15 character and strip all - # but the first word. - $process = substr ($process, 0, 15); - $process =~ s/\s.*//; - $output[$i] = "$process: exit($code)\n"; - } - } - - return @output; -} - -sub strip_exit_codes { - return grep (!/^[-a-z0-9]+: exit\(-?\d+\)/, canonicalize_exit_codes (@_)); -} - -sub compare_output { - my ($exp, @actual) = @_; - - # Canonicalize output for comparison. - @actual = get_core_output (map ("$_\n", @actual)); - if ($hw eq 'userprog') { - @actual = canonicalize_exit_codes (@actual); - } elsif ($hw eq 'vm' || $hw eq 'filesys') { - @actual = strip_exit_codes (@actual); - } - - # There *was* some output, right? - die "Program produced no output.\n" if !@actual; - - # Read expected output. - my (@exp) = map ("$_\n", snarf ($exp)); - - # Pessimistically, start preparation of detailed failure message. - my ($details) = ""; - $details .= "$test actual output:\n"; - $details .= join ('', map (" $_", @actual)); - - # Set true when we find expected output that matches our actual - # output except for some extra actual output (that doesn't seem to - # be an error message etc.). - my ($fuzzy_match) = 0; - - # Compare actual output against each allowed output. - while (@exp != 0) { - # Grab one set of allowed output from @exp into @expected. - my (@expected); - while (@exp != 0) { - my ($s) = shift (@exp); - last if $s eq "--OR--\n"; - push (@expected, $s); - } - - $details .= "\n$test acceptable output:\n"; - $details .= join ('', map (" $_", @expected)); - - # Check whether actual and expected match. - # If it's a perfect match, return. - if ($#actual == $#expected) { - my ($eq) = 1; - for (my ($i) = 0; $i <= $#expected; $i++) { - $eq = 0 if $actual[$i] ne $expected[$i]; - } - return if $eq; - } - - # They differ. Output a diff. - my (@diff) = ""; - my ($d) = Algorithm::Diff->new (\@expected, \@actual); - my ($not_fuzzy_match) = 0; - while ($d->Next ()) { - my ($ef, $el, $af, $al) = $d->Get (qw (min1 max1 min2 max2)); - if ($d->Same ()) { - push (@diff, map (" $_", $d->Items (1))); - } else { - push (@diff, map ("- $_", $d->Items (1))) if $d->Items (1); - push (@diff, map ("+ $_", $d->Items (2))) if $d->Items (2); - if ($d->Items (1) - || grep (/\($test\)|exit\(-?\d+\)|dying due to|Page fault/, - $d->Items (2))) { - $not_fuzzy_match = 1; - } - } - } - - # If we didn't find anything that means it's not, - # it's a fuzzy match. - $fuzzy_match = 1 if !$not_fuzzy_match; - - $details .= "Differences in `diff -u' format:\n"; - $details .= join ('', @diff); - $details .= "(This is considered a `fuzzy match'.)\n" - if !$not_fuzzy_match; - } - - # Failed to match. Report failure. - if ($fuzzy_match) { - $details = - "This test passed, but with extra, unexpected output.\n" - . "Please inspect your code to make sure that it does not\n" - . "produce output other than as specified in the project\n" - . "description.\n\n" - . "$details"; - } else { - $details = - "This test failed because its output did not match any\n" - . "of the acceptable form(s).\n\n" - . "$details"; - } - - $details{$test} = $details; - die "Output differs from expected. Details at end of file.\n" - unless $fuzzy_match; -} - -# Reads and returns the contents of the specified file. -# In an array context, returns the file's contents as an array of -# lines, omitting new-lines. -# In a scalar context, returns the file's contents as a single string. -sub snarf { - my ($file) = @_; - open (OUTPUT, $file) or die "$file: open: $!\n"; - my (@lines) = ; - chomp (@lines); - close (OUTPUT); - return wantarray ? @lines : join ('', map ("$_\n", @lines)); -} - -# Returns true if the two specified files are byte-for-byte identical, -# false otherwise. -sub files_equal { - my ($a, $b) = @_; - my ($equal); - open (A, "<$a") or die "$a: open: $!\n"; - open (B, "<$b") or die "$b: open: $!\n"; - if (-s A != -s B) { - $equal = 0; - } else { - my ($sa, $sb); - for (;;) { - sysread (A, $sa, 1024); - sysread (B, $sb, 1024); - $equal = 0, last if $sa ne $sb; - $equal = 1, last if $sa eq ''; - } - } - close (A); - close (B); - return $equal; -} - -# Returns true if the specified file is byte-for-byte identical with -# the specified string. -sub file_contains { - my ($file, $expected) = @_; - open (FILE, "<$file") or die "$file: open: $!\n"; - my ($actual); - sysread (FILE, $actual, -s FILE); - my ($equal) = $actual eq $expected; - close (FILE); - return $equal; -} - -sub success { - for my $test (@TESTS) { - return 1 if !defined ($result{$test}) || $result{$test} ne 'ok'; - } - return 0; -} - -1; diff --git a/grading/lib/cksum.h b/grading/lib/cksum.h deleted file mode 100644 index 79b7c42..0000000 --- a/grading/lib/cksum.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef CKSUM_H -#define CKSUM_H - -#include - -unsigned long cksum(const unsigned char *b, size_t n); - -#endif /* cksum.h */ diff --git a/grading/threads/alarm-multiple.c b/grading/threads/alarm-multiple.c deleted file mode 100644 index f5085c0..0000000 --- a/grading/threads/alarm-multiple.c +++ /dev/null @@ -1,154 +0,0 @@ -/* Problem 1-1: Alarm Clock tests. - - 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 "threads/test.h" -#include -#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 (void) -{ - /* This test does not work with the MLFQS. */ - ASSERT (!enable_mlfqs); - - 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; - - printf ("\n" - "Creating %d threads to sleep %d times each.\n" - "Thread 0 sleeps 10 ticks each time,\n" - "thread 1 sleeps 20 ticks each time, and so on.\n" - "If successful, product of iteration count and\n" - "sleep duration will appear in nondescending order.\n\n" - "Running test... ", - thread_cnt, iterations); - - /* 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, "output"); - 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); - printf ("done\n\n"); - - /* 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; - - printf ("thread %d: duration=%d, iteration=%d, product=%d\n", - t->id, t->duration, t->iterations, new_prod); - - if (new_prod >= product) - product = new_prod; - else - printf ("FAIL: thread %d woke up out of order (%d > %d)!\n", - 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) - printf ("FAIL: thread %d woke up %d times instead of %d\n", - i, threads[i].iterations, iterations); - - printf ("Test complete.\n"); - - 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/grading/threads/mlfqs.c b/grading/threads/mlfqs.c deleted file mode 100644 index cbf6e81..0000000 --- a/grading/threads/mlfqs.c +++ /dev/null @@ -1,130 +0,0 @@ -/* Problem 1-4: Advanced Scheduler tests. - - This depends on a correctly working Alarm Clock (Problem 1-1). - - Run this test with and without the MLFQS enabled. The - threads' reported test should be better with MLFQS on than - with it off. You may have to tune the loop counts to get - reasonable numbers. - - 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 and yph. */ - -/* Uncomment to print progress messages. */ -#define SHOW_PROGRESS - -#include "threads/test.h" -#include -#include -#include "threads/synch.h" -#include "threads/thread.h" -#include "devices/timer.h" - -static thread_func io_thread; -static thread_func cpu_thread; -static thread_func io_cpu_thread; - -void -test (void) -{ - static thread_func *funcs[] = {io_thread, cpu_thread, io_cpu_thread}; - static const char *names[] = {"IO", "CPU", "IO & CPU"}; - struct semaphore done[3]; - int i; - - printf ("\n" - "Testing multilevel feedback queue scheduler.\n"); - - /* Start threads. */ - for (i = 0; i < 3; i++) - { - sema_init (&done[i], 0, names[i]); - thread_create (names[i], PRI_DEFAULT, funcs[i], &done[i]); - } - - /* Wait for threads to finish. */ - for (i = 0; i < 3; i++) - sema_down (&done[i]); - printf ("Multilevel feedback queue scheduler test done.\n"); -} - -static void -cpu_thread (void *sema_) -{ - struct semaphore *sema = sema_; - int64_t start = timer_ticks (); - struct lock lock; - int i; - - lock_init (&lock, "cpu"); - - for (i = 0; i < 5000; i++) - { - lock_acquire (&lock); -#ifdef SHOW_PROGRESS - printf ("CPU intensive: %d\n", thread_get_priority ()); -#endif - lock_release (&lock); - } - - printf ("CPU bound thread finished in %"PRId64" ticks.\n", - timer_elapsed (start)); - - sema_up (sema); -} - -static void -io_thread (void *sema_) -{ - struct semaphore *sema = sema_; - int64_t start = timer_ticks (); - int i; - - for (i = 0; i < 1000; i++) - { - timer_sleep (10); -#ifdef SHOW_PROGRESS - printf ("IO intensive: %d\n", thread_get_priority ()); -#endif - } - - printf ("IO bound thread finished in %"PRId64" ticks.\n", - timer_elapsed (start)); - - sema_up (sema); -} - -static void -io_cpu_thread (void *sema_) -{ - struct semaphore *sema = sema_; - struct lock lock; - int64_t start = timer_ticks (); - int i; - - lock_init (&lock, "io & cpu"); - - for (i = 0; i < 800; i++) - { - int j; - - timer_sleep (10); - - for (j = 0; j < 15; j++) - { - lock_acquire (&lock); -#ifdef SHOW_PROGRESS - printf ("Alternating IO/CPU: %d\n", thread_get_priority ()); -#endif - lock_release (&lock); - } - } - - printf ("Alternating IO/CPU thread finished in %"PRId64" ticks.\n", - timer_elapsed (start)); - - sema_up (sema); -} diff --git a/grading/threads/priority-donate-multiple.c b/grading/threads/priority-donate-multiple.c deleted file mode 100644 index db527de..0000000 --- a/grading/threads/priority-donate-multiple.c +++ /dev/null @@ -1,86 +0,0 @@ -/* Problem 1-3: Priority Scheduling tests. - - 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 "threads/test.h" -#include -#include "threads/synch.h" -#include "threads/thread.h" - -static void test_donate_multiple (void); - -void -test (void) -{ - /* This test does not work with the MLFQS. */ - ASSERT (!enable_mlfqs); - - /* Make sure our priority is the default. */ - ASSERT (thread_get_priority () == PRI_DEFAULT); - - test_donate_multiple (); -} - -static thread_func a_thread_func; -static thread_func b_thread_func; - -static void -test_donate_multiple (void) -{ - struct lock a, b; - - printf ("\n" - "Testing multiple priority donation.\n" - "If the statements printed below are all true, you pass.\n"); - - lock_init (&a, "a"); - lock_init (&b, "b"); - - lock_acquire (&a); - lock_acquire (&b); - - thread_create ("a", PRI_DEFAULT + 1, a_thread_func, &a); - printf ("Main thread should have priority %d. Actual priority: %d.\n", - PRI_DEFAULT + 1, thread_get_priority ()); - - thread_create ("b", PRI_DEFAULT + 2, b_thread_func, &b); - printf ("Main thread should have priority %d. Actual priority: %d.\n", - PRI_DEFAULT + 2, thread_get_priority ()); - - lock_release (&b); - printf ("Thread b should have just finished.\n"); - printf ("Main thread should have priority %d. Actual priority: %d.\n", - PRI_DEFAULT + 1, thread_get_priority ()); - - lock_release (&a); - printf ("Thread a should have just finished.\n"); - printf ("Main thread should have priority %d. Actual priority: %d.\n", - PRI_DEFAULT, thread_get_priority ()); - printf ("Multiple priority priority donation test finished.\n"); -} - -static void -a_thread_func (void *lock_) -{ - struct lock *lock = lock_; - - lock_acquire (lock); - printf ("Thread a acquired lock a.\n"); - lock_release (lock); - printf ("Thread a finished.\n"); -} - -static void -b_thread_func (void *lock_) -{ - struct lock *lock = lock_; - - lock_acquire (lock); - printf ("Thread b acquired lock b.\n"); - lock_release (lock); - printf ("Thread b finished.\n"); -} diff --git a/grading/threads/priority-donate-multiple.exp b/grading/threads/priority-donate-multiple.exp deleted file mode 100644 index aaff85e..0000000 --- a/grading/threads/priority-donate-multiple.exp +++ /dev/null @@ -1,13 +0,0 @@ -Testing multiple priority donation. -If the statements printed below are all true, you pass. -Main thread should have priority 30. Actual priority: 30. -Main thread should have priority 31. Actual priority: 31. -Thread b acquired lock b. -Thread b finished. -Thread b should have just finished. -Main thread should have priority 30. Actual priority: 30. -Thread a acquired lock a. -Thread a finished. -Thread a should have just finished. -Main thread should have priority 29. Actual priority: 29. -Multiple priority priority donation test finished. diff --git a/grading/threads/priority-donate-nest.c b/grading/threads/priority-donate-nest.c deleted file mode 100644 index 8e7316f..0000000 --- a/grading/threads/priority-donate-nest.c +++ /dev/null @@ -1,103 +0,0 @@ -/* Problem 1-3: Priority Scheduling tests. - - 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 "threads/test.h" -#include -#include "threads/synch.h" -#include "threads/thread.h" - -static void test_donate_nest (void); - -void -test (void) -{ - /* This test does not work with the MLFQS. */ - ASSERT (!enable_mlfqs); - - /* Make sure our priority is the default. */ - ASSERT (thread_get_priority () == PRI_DEFAULT); - - test_donate_nest (); -} - -static thread_func medium_thread_func; -static thread_func high_thread_func; - -struct locks - { - struct lock *a; - struct lock *b; - }; - -static void -test_donate_nest (void) -{ - struct lock a, b; - struct locks locks; - - printf ("\n" - "Testing nested priority donation.\n" - "If the statements printed below are all true, you pass.\n"); - - lock_init (&a, "a"); - lock_init (&b, "b"); - - lock_acquire (&a); - - locks.a = &a; - locks.b = &b; - thread_create ("medium", PRI_DEFAULT + 1, medium_thread_func, &locks); - thread_yield (); - printf ("Low thread should have priority %d. Actual priority: %d.\n", - PRI_DEFAULT + 1, thread_get_priority ()); - - thread_create ("high", PRI_DEFAULT + 2, high_thread_func, &b); - thread_yield (); - printf ("Low thread should have priority %d. Actual priority: %d.\n", - PRI_DEFAULT + 2, thread_get_priority ()); - - lock_release (&a); - thread_yield (); - printf ("Medium thread should just have finished.\n"); - printf ("Low thread should have priority %d. Actual priority: %d.\n", - PRI_DEFAULT, thread_get_priority ()); - printf ("Nested priority priority donation test finished.\n"); -} - -static void -medium_thread_func (void *locks_) -{ - struct locks *locks = locks_; - - lock_acquire (locks->b); - lock_acquire (locks->a); - - printf ("Medium thread should have priority %d. Actual priority: %d.\n", - PRI_DEFAULT + 2, thread_get_priority ()); - printf ("Medium thread got the lock.\n"); - - lock_release (locks->a); - thread_yield (); - - lock_release (locks->b); - thread_yield (); - - printf ("High thread should have just finished.\n"); - printf ("Middle thread finished.\n"); -} - -static void -high_thread_func (void *lock_) -{ - struct lock *lock = lock_; - - lock_acquire (lock); - printf ("High thread got the lock.\n"); - lock_release (lock); - printf ("High thread finished.\n"); -} diff --git a/grading/threads/priority-donate-nest.exp b/grading/threads/priority-donate-nest.exp deleted file mode 100644 index d6e496c..0000000 --- a/grading/threads/priority-donate-nest.exp +++ /dev/null @@ -1,13 +0,0 @@ -Testing nested priority donation. -If the statements printed below are all true, you pass. -Low thread should have priority 30. Actual priority: 30. -Low thread should have priority 31. Actual priority: 31. -Medium thread should have priority 31. Actual priority: 31. -Medium thread got the lock. -High thread got the lock. -High thread finished. -High thread should have just finished. -Middle thread finished. -Medium thread should just have finished. -Low thread should have priority 29. Actual priority: 29. -Nested priority priority donation test finished. diff --git a/grading/threads/priority-donate-one.c b/grading/threads/priority-donate-one.c deleted file mode 100644 index afc1e83..0000000 --- a/grading/threads/priority-donate-one.c +++ /dev/null @@ -1,74 +0,0 @@ -/* Problem 1-3: Priority Scheduling tests. - - 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 "threads/test.h" -#include -#include "threads/synch.h" -#include "threads/thread.h" - -static void test_donate_return (void); - -void -test (void) -{ - /* This test does not work with the MLFQS. */ - ASSERT (!enable_mlfqs); - - /* Make sure our priority is the default. */ - ASSERT (thread_get_priority () == PRI_DEFAULT); - - test_donate_return (); -} - -static thread_func acquire1_thread_func; -static thread_func acquire2_thread_func; - -static void -test_donate_return (void) -{ - struct lock lock; - - printf ("\n" - "Testing priority donation.\n" - "If the statements printed below are all true, you pass.\n"); - - lock_init (&lock, "donor"); - lock_acquire (&lock); - thread_create ("acquire1", PRI_DEFAULT + 1, acquire1_thread_func, &lock); - printf ("This thread should have priority %d. Actual priority: %d.\n", - PRI_DEFAULT + 1, thread_get_priority ()); - thread_create ("acquire2", PRI_DEFAULT + 2, acquire2_thread_func, &lock); - printf ("This thread should have priority %d. Actual priority: %d.\n", - PRI_DEFAULT + 2, thread_get_priority ()); - lock_release (&lock); - printf ("acquire2, acquire1 must already have finished, in that order.\n" - "This should be the last line before finishing this test.\n" - "Priority donation test done.\n"); -} - -static void -acquire1_thread_func (void *lock_) -{ - struct lock *lock = lock_; - - lock_acquire (lock); - printf ("acquire1: got the lock\n"); - lock_release (lock); - printf ("acquire1: done\n"); -} - -static void -acquire2_thread_func (void *lock_) -{ - struct lock *lock = lock_; - - lock_acquire (lock); - printf ("acquire2: got the lock\n"); - lock_release (lock); - printf ("acquire2: done\n"); -} diff --git a/grading/threads/priority-donate-one.exp b/grading/threads/priority-donate-one.exp deleted file mode 100644 index a178523..0000000 --- a/grading/threads/priority-donate-one.exp +++ /dev/null @@ -1,11 +0,0 @@ -Testing priority donation. -If the statements printed below are all true, you pass. -This thread should have priority 30. Actual priority: 30. -This thread should have priority 31. Actual priority: 31. -acquire2: got the lock -acquire2: done -acquire1: got the lock -acquire1: done -acquire2, acquire1 must already have finished, in that order. -This should be the last line before finishing this test. -Priority donation test done. diff --git a/grading/threads/priority-preempt.exp b/grading/threads/priority-preempt.exp deleted file mode 100644 index 0004b9c..0000000 --- a/grading/threads/priority-preempt.exp +++ /dev/null @@ -1,9 +0,0 @@ -Testing priority preemption. -Thread high-priority iteration 0 -Thread high-priority iteration 1 -Thread high-priority iteration 2 -Thread high-priority iteration 3 -Thread high-priority iteration 4 -Thread high-priority done! -The high-priority thread should have already completed. -Priority preemption test done. diff --git a/grading/threads/review.txt b/grading/threads/review.txt deleted file mode 100644 index 739fbac..0000000 --- a/grading/threads/review.txt +++ /dev/null @@ -1,57 +0,0 @@ -TESTCASES [[/10]] ------------------ - -2 Problem 1-1: no test cases/no test output/no description in TESTCASES - -1 Problem 1-1: not enough testing/inconclusive test output - -2 Problem 1-2: no test cases/no test output/no description in TESTCASES - -1 Problem 1-2: not enough testing/inconclusive test output - -2 Problem 1-3: no test cases/no test output/no description in TESTCASES - -1 Problem 1-3: not enough testing/inconclusive test output - - -DESIGN [[/40]] --------------- - - -20 Missing or far too brief DESIGNDOC - -2 Changing interfaces, each (max -6) - -DESIGNDOC (per problem): - -1 Minor details missing - -2 Major details missing - -5 Totally missing - -Overall: - -1 Gratuitous use of malloc() (e.g. for allocating a list or a lock) - -1 Inappropriate use of ASSERT (e.g. to verify that malloc() succeeded) - -Problem 1-1: Alarm Clock - -1 Uses lock/interrupt disabling without justifying - -1 Uses a lock within interrupt handler - -3 Busy waiting - -2 Wakes up too often, e.g. by using semaphores with negative values - -1 Traverses entire list of sleeping threads every tick - -1 Doesn't protect data structure in timer_interrupt - -1 Doesn't protect data structure in timer_sleep - -3 Bad design - -Problem 1-2: Priority Scheduler - -3 Doesn't use sorted queue scheduler, and don't justify why they didn't - -2 sema_up() doesn't pick highest-priority waiting thread - -1 Should use sorted queue in semaphores, unless explained in DESIGNDOC - -1 cond_signal() doesn't pick highest-priority waiting thread - -1 Should use sorted queue in conditions, unless explained in DESIGNDOC - -2 Yield should pick the highest-priority thread (including current) - -3 Bad design - +2 Used a heap or other advanced priority queue for ready_list - -Problem 1-3: Advanced Scheduler - -2 Isn't table-driven - -5 Bad design - - -STYLE [[/10]] -------------- - -1 No attempt to conform to existing coding style - - -COMMENTS --------- diff --git a/grading/threads/run-tests b/grading/threads/run-tests deleted file mode 100755 index bd705e4..0000000 --- a/grading/threads/run-tests +++ /dev/null @@ -1,288 +0,0 @@ -#! /usr/bin/perl - -# Find the directory that contains the grading files. -our ($GRADES_DIR); - -# Add our Perl library directory to the include path. -BEGIN { - ($GRADES_DIR = $0) =~ s#/[^/]+$##; - -d $GRADES_DIR or die "$GRADES_DIR: stat: $!\n"; - unshift @INC, "$GRADES_DIR/../lib"; -} - -use warnings; -use strict; -use Pintos::Grading; - -our ($hw) = "threads"; -our (@TESTS); # Tests to run. -our ($test); -our (%details); -our (%result); -our ($action); - -parse_cmd_line qw (alarm-single alarm-multiple alarm-zero alarm-negative - priority-preempt priority-fifo priority-donate-one - priority-donate-multiple priority-donate-nest - mlfqs-on mlfqs-off); - -clean_dir (), exit if $action eq 'clean'; - -extract_sources (); -exit if $action eq 'extract'; - -build (); -exit if $action eq 'build'; - -run_and_grade_tests (); -if (defined ($result{'mlfqs-on'}) && defined ($result{'mlfqs-off'})) { - grade_mlfqs_speedup (); - grade_mlfqs_priority (); -} -write_grades (); -write_details (); -exit success () if $action eq 'test'; - -assemble_final_grade (); -exit success () if $action eq 'assemble'; - -die "Don't know how to '$action'"; - -# Runs $test in directory output/$test. -# Returns 'ok' if it went ok, otherwise an explanation. -sub run_test { - # Changes devices/timer.c if necessary. - my ($new_time_slice) = $test eq 'priority-fifo' ? 100 : 1; - my (@timer_c) = snarf ("pintos/src/devices/timer.c"); - if (!grep (/^\#define TIME_SLICE $new_time_slice$/, @timer_c)) { - @timer_c = grep (!/^\#define TIME_SLICE/, @timer_c); - unshift (@timer_c, "#define TIME_SLICE $new_time_slice"); - open (TIMER_C, ">pintos/src/devices/timer.c"); - print TIMER_C map ("$_\n", @timer_c); - close (TIMER_C); - } - - # Changes devices/timer.h if necessary. - my ($new_timer_freq) = $test eq 'priority-fifo' ? 19 : 100; - my (@timer_h) = snarf ("pintos/src/devices/timer.h"); - if (!grep (/^\#define TIMER_FREQ $new_time_slice$/, @timer_h)) { - @timer_h = grep (!/^\#define TIMER_FREQ/, @timer_h); - unshift (@timer_h, "#define TIMER_FREQ $new_timer_freq"); - open (TIMER_H, ">pintos/src/devices/timer.h"); - print TIMER_H map ("$_\n", @timer_h); - close (TIMER_H); - } - - # Copy in the new test.c and delete enough files to ensure a full rebuild. - my ($src) = "$GRADES_DIR/" . ($test !~ /^mlfqs/ ? "$test.c" : "mlfqs.c"); - -e $src or die "$src: stat: $!\n"; - xsystem ("cp $src pintos/src/threads/test.c", DIE => "cp failed\n"); - unlink ("pintos/src/threads/build/threads/test.o"); - unlink ("pintos/src/threads/build/kernel.o"); - unlink ("pintos/src/threads/build/kernel.bin"); - unlink ("pintos/src/threads/build/os.dsk"); - - # Build. - if (xsystem ("cd pintos/src/threads && make", - LOG => "$test/make") ne 'ok') { - $details{$test} = snarf ("output/$test/make.err"); - return "Compile error"; - } - - # Copy out files for backtraces later. - xsystem ("cp pintos/src/threads/build/kernel.o output/$test"); - xsystem ("cp pintos/src/threads/build/os.dsk output/$test"); - - # Run. - my ($timeout) = $test !~ /^mlfqs/ ? 15 : 600; - my (@command) = ("-v", "run", "-q"); - push (@command, "-o mlfqs") if $test eq 'mlfqs-on'; - return run_pintos (\@command, - CHDIR => "pintos/src/threads/build", - LOG => "$test/run", - TIMEOUT => $timeout); -} - -sub grade_alarm_single { - verify_alarm (1, @_); -} - -sub grade_alarm_multiple { - verify_alarm (7, @_); -} - -sub verify_alarm { - my ($iterations, @output) = @_; - - verify_common (@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) { - die $_ if /out of order/i; - - my ($p) = /product=(\d+)$/; - next if !defined $p; - - my ($q) = shift (@products); - die "Too many wakeups.\n" if !defined $q; - die "Out of order wakeups ($p vs. $q).\n" if $p != $q; # FIXME - } - die scalar (@products) . " fewer wakeups than expected.\n" - if @products != 0; -} - -sub grade_alarm_zero { - my (@output) = @_; - verify_common (@output); - die "Crashed in timer_sleep()\n" if !grep (/^Success\.$/, @output); -} - -sub grade_alarm_negative { - my (@output) = @_; - verify_common (@output); - die "Crashed in timer_sleep()\n" if !grep (/^Success\.$/, @output); -} - -sub grade_priority_fifo { - my (@output) = @_; - - verify_common (@output); - my ($thread_cnt) = 10; - my ($iter_cnt) = 5; - my (@order); - my (@t) = (-1) x $thread_cnt; - local ($_); - foreach (@output) { - my ($idx) = /^Thread (\d+)/ or next; - my ($iter) = /iteration (\d+)$/; - $iter = $iter_cnt if /done!$/; - die "Malformed output\n" if !defined $iter; - if (@order < $thread_cnt) { - push (@order, $idx); - die "Thread $idx repeated within first $thread_cnt iterations: " - . join (' ', @order) . ".\n" - if grep ($_ == $idx, @order) != 1; - } else { - die "Thread $idx ran when $order[0] should have.\n" - if $idx != $order[0]; - push (@order, shift @order); - } - die "Thread $idx out of order output.\n" if $t[$idx] != $iter - 1; - $t[$idx] = $iter; - } - - my ($err) = ""; - for my $idx (0..$#t) { - if ($t[$idx] == -1) { - $err .= "Thread $idx did not run at all.\n"; - } elsif ($t[$idx] != $iter_cnt) { - $err .= "Thread $idx only completed $t[$idx] iterations.\n"; - } - } - die $err if $err ne ''; -} - -sub grade_mlfqs_on { - my (@output) = @_; - verify_common (@output); - our (@mlfqs_on_stats) = mlfqs_stats (@output); -} - -sub grade_mlfqs_off { - my (@output) = @_; - verify_common (@output); - our (@mlfqs_off_stats) = mlfqs_stats (@output); -} - -sub grade_mlfqs_speedup { - our (@mlfqs_off_stats); - our (@mlfqs_on_stats); - eval { - check_mlfqs (); - my ($off_ticks) = $mlfqs_off_stats[1]; - my ($on_ticks) = $mlfqs_on_stats[1]; - die "$off_ticks ticks without MLFQS, $on_ticks with MLFQS\n" - if $on_ticks >= $off_ticks; - die "ok\n"; - }; - chomp $@; - $result{'mlfqs-speedup'} = $@; -} - -sub grade_mlfqs_priority { - our (@mlfqs_off_stats); - our (@mlfqs_on_stats); - eval { - check_mlfqs () if !defined (@mlfqs_on_stats); - for my $cat qw (CPU IO MIX) { - die "Priority changed away from PRI_DEFAULT (29) without MLFQS\n" - if $mlfqs_off_stats[0]{$cat}{MIN} != 29 - || $mlfqs_off_stats[0]{$cat}{MAX} != 29; - die "Minimum priority never changed from PRI_DEFAULT (29) " - . "with MLFQS\n" - if $mlfqs_on_stats[0]{$cat}{MIN} == 29; - die "Maximum priority never changed from PRI_DEFAULT (29) " - . "with MLFQS\n" - if $mlfqs_on_stats[0]{$cat}{MAX} == 29; - } - die "ok\n"; - }; - chomp $@; - $result{'mlfqs-priority'} = $@; -} - -sub check_mlfqs { - our (@mlfqs_off_stats); - our (@mlfqs_on_stats); - die "p1-4 didn't finish with MLFQS on or off\n" - if !defined (@mlfqs_off_stats) && !defined (@mlfqs_on_stats); - die "p1-4 didn't finish with MLFQS on\n" - if !defined (@mlfqs_on_stats); - die "p1-4 didn't finish with MLFQS off\n" - if !defined (@mlfqs_off_stats); -} - -sub mlfqs_stats { - my (@output) = @_; - my (%stats) = (CPU => {}, IO => {}, MIX => {}); - my (%map) = ("CPU intensive" => 'CPU', - "IO intensive" => 'IO', - "Alternating IO/CPU" => 'MIX'); - my (%rmap) = reverse %map; - my ($ticks); - local ($_); - foreach (@output) { - $ticks = $1 if /Timer: (\d+) ticks/; - my ($thread, $pri) = /^([A-Za-z\/ ]+): (\d+)$/ or next; - my ($t) = $map{$thread} or next; - - my ($s) = $stats{$t}; - $$s{N}++; - $$s{SUM} += $pri; - $$s{SUM2} += $pri * $pri; - $$s{MIN} = $pri if !defined ($$s{MIN}) || $pri < $$s{MIN}; - $$s{MAX} = $pri if !defined ($$s{MAX}) || $pri > $$s{MAX}; - } - - my (%expect_n) = (CPU => 5000, IO => 1000, MIX => 12000); - for my $cat (values (%map)) { - my ($s) = $stats{$cat}; - die "$rmap{$cat} printed $$s{N} times, not $expect_n{$cat}\n" - if $$s{N} != $expect_n{$cat}; - die "$rmap{$cat} priority dropped to $$s{MIN}, below PRI_MIN (0)\n" - if $$s{MIN} < 0; - die "$rmap{$cat} priority rose to $$s{MAX}, above PRI_MAX (59)\n" - if $$s{MAX} > 59; - $$s{MEAN} = $$s{SUM} / $$s{N}; - } - - return (\%stats, $ticks); -} diff --git a/grading/threads/tests.txt b/grading/threads/tests.txt deleted file mode 100644 index 2a27bb2..0000000 --- a/grading/threads/tests.txt +++ /dev/null @@ -1,25 +0,0 @@ -CORRECTNESS [[total]] -------------------- - -Points are taken off for tests that failed. Only failing tests are -listed. - -Problem 1-1: Alarm Clock - -3 alarm-single: Multiple threads each sleep once (public) - -3 alarm-multiple: Multiple threads each sleep many times (public) - -1 alarm-zero: Zero wait time must not crash or hang - -1 alarm-negative: Negative wait time must not crash or hang -Score: /8 - -Problem 1-2: Priority Scheduler - -2 priority-preempt: Higher-priority thread preempts others (public) - -2 priority-fifo: Threads of equal priority run round-robin (public) - -2 priority-donate-one: Priority donation with single lock (public) - -2 priority-donate-multiple: Priority donation with multiple locks - -2 priority-donate-nest: Nested priority donation with single lock -Score: /10 - -Problem 1-3: Advanced Scheduler - -4 mlfqs-speedup: Public testcase doesn't run faster with MLFQS - -4 mlfqs-priority: Priorities don't change properly -Score: /8 diff --git a/grading/userprog/.cvsignore b/grading/userprog/.cvsignore deleted file mode 100644 index 72f1786..0000000 --- a/grading/userprog/.cvsignore +++ /dev/null @@ -1,65 +0,0 @@ -# Automatically generated by mkmf - do not modify! -*.d -*.dsk -*.o -bochsrc.txt -bochsout.txt -child-arg -child-bad -child-close -child-simple -null -args-argc -args-argv0 -args-argvn -args-single -args-multiple -args-dbl-space -sc-bad-sp -sc-bad-arg -sc-boundary -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-oom -multi-child-fd diff --git a/grading/userprog/Make.base b/grading/userprog/Make.base deleted file mode 100644 index 1e04ab8..0000000 --- a/grading/userprog/Make.base +++ /dev/null @@ -1,37 +0,0 @@ -SRCDIR = ../../src - -PROGS = $(TESTS) child-simple child-arg child-bad child-close -child_simple_SRC = child-simple.c -child_arg_SRC = child-arg.c -child_bad_SRC = child-bad.c -child_close_SRC = child-close.c - -DISKS = $(patsubst %,%.dsk,$(TESTS)) null.dsk - -disks: $(DISKS) - -null.o: null.S -null: null.o - $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ -null.dsk: null - -exec-once.dsk exec-multiple.dsk wait-simple.dsk wait-twice.dsk: child-simple -exec-arg.dsk: child-arg -wait-killed.dsk: child-bad -multi-child-fd.dsk: child-close - -%.dsk: % - ./prep-disk $< - -clean:: - rm -f $(DISKS) - -include $(SRCDIR)/Makefile.userprog - -# Use -Werror because otherwise there's so much output spew -# that it's very difficult to pick out warnings. -CFLAGS += -Werror - -Makefile: Make.base Make.tests mkmf - ./mkmf - diff --git a/grading/userprog/Make.tests b/grading/userprog/Make.tests deleted file mode 100644 index a1f1ec5..0000000 --- a/grading/userprog/Make.tests +++ /dev/null @@ -1,54 +0,0 @@ -args-argc -args-argv0 -args-argvn -args-single -args-multiple -args-dbl-space -sc-bad-sp -sc-bad-arg -sc-boundary -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-oom -multi-child-fd diff --git a/grading/userprog/Makefile b/grading/userprog/Makefile deleted file mode 100644 index 225180f..0000000 --- a/grading/userprog/Makefile +++ /dev/null @@ -1,107 +0,0 @@ -# This file is automatically generated from Make.base and Make.tests -# by mkmf. Do not modify! - -TESTS = \ - args-argc args-argv0 args-argvn args-single args-multiple \ - args-dbl-space sc-bad-sp sc-bad-arg sc-boundary 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-oom multi-child-fd -args_argc_SRC = args-argc.c -args_argv0_SRC = args-argv0.c -args_argvn_SRC = args-argvn.c -args_single_SRC = args-single.c -args_multiple_SRC = args-multiple.c -args_dbl_space_SRC = args-dbl-space.c -sc_bad_sp_SRC = sc-bad-sp.c -sc_bad_arg_SRC = sc-bad-arg.c -sc_boundary_SRC = sc-boundary.c -halt_SRC = halt.c -exit_SRC = exit.c -create_normal_SRC = create-normal.c -create_empty_SRC = create-empty.c -create_null_SRC = create-null.c -create_bad_ptr_SRC = create-bad-ptr.c -create_long_SRC = create-long.c -create_exists_SRC = create-exists.c -create_bound_SRC = create-bound.c -open_normal_SRC = open-normal.c -open_missing_SRC = open-missing.c -open_boundary_SRC = open-boundary.c -open_empty_SRC = open-empty.c -open_null_SRC = open-null.c -open_bad_ptr_SRC = open-bad-ptr.c -open_twice_SRC = open-twice.c -close_normal_SRC = close-normal.c -close_twice_SRC = close-twice.c -close_stdin_SRC = close-stdin.c -close_stdout_SRC = close-stdout.c -close_bad_fd_SRC = close-bad-fd.c -read_normal_SRC = read-normal.c -read_bad_ptr_SRC = read-bad-ptr.c -read_boundary_SRC = read-boundary.c -read_zero_SRC = read-zero.c -read_stdout_SRC = read-stdout.c -read_bad_fd_SRC = read-bad-fd.c -write_normal_SRC = write-normal.c -write_bad_ptr_SRC = write-bad-ptr.c -write_boundary_SRC = write-boundary.c -write_zero_SRC = write-zero.c -write_stdin_SRC = write-stdin.c -write_bad_fd_SRC = write-bad-fd.c -exec_once_SRC = exec-once.c -exec_arg_SRC = exec-arg.c -exec_multiple_SRC = exec-multiple.c -exec_missing_SRC = exec-missing.c -exec_bad_ptr_SRC = exec-bad-ptr.c -wait_simple_SRC = wait-simple.c -wait_twice_SRC = wait-twice.c -wait_killed_SRC = wait-killed.c -wait_bad_pid_SRC = wait-bad-pid.c -multi_recurse_SRC = multi-recurse.c -multi_oom_SRC = multi-oom.c -multi_child_fd_SRC = multi-child-fd.c - -SRCDIR = ../../src - -PROGS = $(TESTS) child-simple child-arg child-bad child-close -child_simple_SRC = child-simple.c -child_arg_SRC = child-arg.c -child_bad_SRC = child-bad.c -child_close_SRC = child-close.c - -DISKS = $(patsubst %,%.dsk,$(TESTS)) null.dsk - -disks: $(DISKS) - -null.o: null.S -null: null.o - $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ -null.dsk: null - -exec-once.dsk exec-multiple.dsk wait-simple.dsk wait-twice.dsk: child-simple -exec-arg.dsk: child-arg -wait-killed.dsk: child-bad -multi-child-fd.dsk: child-close - -%.dsk: % - ./prep-disk $< - -clean:: - rm -f $(DISKS) - -include $(SRCDIR)/Makefile.userprog - -# Use -Werror because otherwise there's so much output spew -# that it's very difficult to pick out warnings. -CFLAGS += -Werror - -Makefile: Make.base Make.tests mkmf - ./mkmf - diff --git a/grading/userprog/args-argc.c b/grading/userprog/args-argc.c deleted file mode 100644 index 863785d..0000000 --- a/grading/userprog/args-argc.c +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include -#include - -int -main (int argc, char *argv[] UNUSED) -{ - printf ("(args-argc) argc=%d\n", argc); - return 0; -} diff --git a/grading/userprog/args-argc.exp b/grading/userprog/args-argc.exp deleted file mode 100644 index e9d12a4..0000000 --- a/grading/userprog/args-argc.exp +++ /dev/null @@ -1,2 +0,0 @@ -(args-argc) argc=5 -args-argc: exit(0) diff --git a/grading/userprog/args-argv0.c b/grading/userprog/args-argv0.c deleted file mode 100644 index 9220d38..0000000 --- a/grading/userprog/args-argv0.c +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include -#include - -int -main (int argc UNUSED, char *argv[]) -{ - printf ("(args-argv0) argv[0] = '%s'\n", argv[0]); - return 0; -} diff --git a/grading/userprog/args-argv0.exp b/grading/userprog/args-argv0.exp deleted file mode 100644 index c674f62..0000000 --- a/grading/userprog/args-argv0.exp +++ /dev/null @@ -1,2 +0,0 @@ -(args-argv0) argv[0] = 'args-argv0' -args-argv0: exit(0) diff --git a/grading/userprog/args-argvn.c b/grading/userprog/args-argvn.c deleted file mode 100644 index 061b31b..0000000 --- a/grading/userprog/args-argvn.c +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include - -int -main (int argc, char *argv[]) -{ - printf ("(args-argvn) argv[argc] = %p\n", argv[argc]); - return 0; -} diff --git a/grading/userprog/args-argvn.exp b/grading/userprog/args-argvn.exp deleted file mode 100644 index 46cee00..0000000 --- a/grading/userprog/args-argvn.exp +++ /dev/null @@ -1,2 +0,0 @@ -(args-argvn) argv[argc] = 0 -args-argvn: exit(0) diff --git a/grading/userprog/args-dbl-space.c b/grading/userprog/args-dbl-space.c deleted file mode 100644 index ef63912..0000000 --- a/grading/userprog/args-dbl-space.c +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include - -int -main (int argc, char *argv[]) -{ - int i; - - for (i = 0; i < argc; i++) - { - int j; - - for (j = i; j < i + 2; j++) - if (argv[j] == NULL) - goto error; - if (!strcmp (argv[i], "two") - && !strcmp (argv[i + 1], "args")) - { - printf ("(args-dbl-space) success\n"); - return 0; - } - error:; - } - - printf ("(args-dbl-space) failure\n"); - printf ("(args-dbl-space) argc=%d\n", argc); - for (i = 0; i <= argc; i++) - if (argv[i] >= (char *) 0xbffff000 && argv[i] < (char *) 0xc0000000) - printf ("(args-dbl-space) argv[%d]='%s'\n", i, argv[i]); - else - printf ("(args-dbl-space) argv[%d]=%p\n", i, argv[i]); - return 1; -} diff --git a/grading/userprog/args-dbl-space.exp b/grading/userprog/args-dbl-space.exp deleted file mode 100644 index 531bddf..0000000 --- a/grading/userprog/args-dbl-space.exp +++ /dev/null @@ -1,2 +0,0 @@ -(args-dbl-space) success -args-dbl-space: exit(0) diff --git a/grading/userprog/args-multiple.c b/grading/userprog/args-multiple.c deleted file mode 100644 index 715deeb..0000000 --- a/grading/userprog/args-multiple.c +++ /dev/null @@ -1,35 +0,0 @@ -#include -#include - -int -main (int argc, char *argv[]) -{ - int i; - - for (i = 0; i < argc - 2; i++) - { - int j; - - for (j = i; j < i + 4; j++) - if (argv[j] == NULL) - goto error; - if (!strcmp (argv[i], "some") - && !strcmp (argv[i + 1], "arguments") - && !strcmp (argv[i + 2], "for") - && !strcmp (argv[i + 3], "you!")) - { - printf ("(args-multiple) success\n"); - return 0; - } - error:; - } - - printf ("(args-multiple) failure\n"); - printf ("(args-multiple) argc=%d\n", argc); - for (i = 0; i <= argc; i++) - if (argv[i] >= (char *) 0xbffff000 && argv[i] < (char *) 0xc0000000) - printf ("(args-multiple) argv[%d]='%s'\n", i, argv[i]); - else - printf ("(args-multiple) argv[%d]=%p\n", i, argv[i]); - return 1; -} diff --git a/grading/userprog/args-multiple.exp b/grading/userprog/args-multiple.exp deleted file mode 100644 index 538be6f..0000000 --- a/grading/userprog/args-multiple.exp +++ /dev/null @@ -1,2 +0,0 @@ -(args-multiple) success -args-multiple: exit(0) diff --git a/grading/userprog/args-single.c b/grading/userprog/args-single.c deleted file mode 100644 index 23e6ec2..0000000 --- a/grading/userprog/args-single.c +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include - -int -main (int argc, char *argv[]) -{ - if (!strcmp (argv[0], "onearg") || !strcmp (argv[1], "onearg")) - { - printf ("(args-single) success\n"); - return 0; - } - else - { - int i; - - printf ("(args-single) failure\n"); - printf ("(args-single) argc=%d\n", argc); - for (i = 0; i <= argc; i++) - if (argv[i] >= (char *) 0xbffff000 && argv[i] < (char *) 0xc0000000) - printf ("(args-single) argv[%d]='%s'\n", i, argv[i]); - else - printf ("(args-single) argv[%d]=%p\n", i, argv[i]); - return 1; - } -} diff --git a/grading/userprog/args-single.exp b/grading/userprog/args-single.exp deleted file mode 100644 index 3a2d321..0000000 --- a/grading/userprog/args-single.exp +++ /dev/null @@ -1,2 +0,0 @@ -(args-single) success -args-single: exit(0) diff --git a/grading/userprog/child-arg.c b/grading/userprog/child-arg.c deleted file mode 100644 index 748798b..0000000 --- a/grading/userprog/child-arg.c +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include - -int -main (int argc, char *argv[]) -{ - if (!strcmp (argv[0], "childarg") || !strcmp (argv[1], "childarg")) - { - printf ("(child-arg) success\n"); - return 0; - } - else - { - int i; - - printf ("(child-arg) failure\n"); - printf ("(child-arg) argc=%d\n", argc); - for (i = 0; i <= argc; i++) - if (argv[i] >= (char *) 0xbffff000 && argv[i] < (char *) 0xc0000000) - printf ("(child-arg) argv[%d]='%s'\n", i, argv[i]); - else - printf ("(child-arg) argv[%d]=%p\n", i, argv[i]); - return 1; - } -} diff --git a/grading/userprog/child-bad.c b/grading/userprog/child-bad.c deleted file mode 100644 index 97a9a3b..0000000 --- a/grading/userprog/child-bad.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(child-bad) begin\n"); - asm volatile ("mov %esp, 0x20101234; int 0x30"); - printf ("(child-bad) end\n"); - return 0; -} diff --git a/grading/userprog/child-close.c b/grading/userprog/child-close.c deleted file mode 100644 index 1134341..0000000 --- a/grading/userprog/child-close.c +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include -#include -#include - -int -main (int argc UNUSED, char *argv[]) -{ - if (isdigit (*argv[0])) - close (atoi (argv[0])); - else if (isdigit (*argv[1])) - close (atoi (argv[1])); - else - { - printf ("(child-close) fail: bad command-line arguments\n"); - return 1; - } - printf ("(child-close) success\n"); - return 0; -} diff --git a/grading/userprog/child-simple.c b/grading/userprog/child-simple.c deleted file mode 100644 index 8360cc8..0000000 --- a/grading/userprog/child-simple.c +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int -main (void) -{ - printf ("(child-simple) run\n"); - return 81; -} diff --git a/grading/userprog/close-bad-fd.c b/grading/userprog/close-bad-fd.c deleted file mode 100644 index ad929a8..0000000 --- a/grading/userprog/close-bad-fd.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(close-bad-fd) begin\n"); - close (0x20101234); - printf ("(close-bad-fd) end\n"); - return 0; -} diff --git a/grading/userprog/close-normal.c b/grading/userprog/close-normal.c deleted file mode 100644 index 2b5e95d..0000000 --- a/grading/userprog/close-normal.c +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include - -int -main (void) -{ - int handle; - printf ("(close-normal) begin\n"); - handle = open ("sample.txt"); - if (handle < 2) - printf ("(close-normal) fail: open() returned %d\n", handle); - close (handle); - printf ("(close-normal) end\n"); - return 0; -} diff --git a/grading/userprog/close-normal.exp b/grading/userprog/close-normal.exp deleted file mode 100644 index f3be9bc..0000000 --- a/grading/userprog/close-normal.exp +++ /dev/null @@ -1,3 +0,0 @@ -(close-normal) begin -(close-normal) end -close-normal: exit(0) diff --git a/grading/userprog/close-stdin.c b/grading/userprog/close-stdin.c deleted file mode 100644 index 0ead7f0..0000000 --- a/grading/userprog/close-stdin.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(close-stdin) begin\n"); - close (0); - printf ("(close-stdin) end\n"); - return 0; -} diff --git a/grading/userprog/close-stdin.exp b/grading/userprog/close-stdin.exp deleted file mode 100644 index a27f2eb..0000000 --- a/grading/userprog/close-stdin.exp +++ /dev/null @@ -1,6 +0,0 @@ -(close-stdin) begin -(close-stdin) end -close-stdin: exit(0) ---OR-- -(close-stdin) begin -close-stdin: exit(-1) diff --git a/grading/userprog/close-stdout.c b/grading/userprog/close-stdout.c deleted file mode 100644 index 1c74ce3..0000000 --- a/grading/userprog/close-stdout.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(close-stdout) begin\n"); - close (1); - printf ("(close-stdout) end\n"); - return 0; -} diff --git a/grading/userprog/close-twice.c b/grading/userprog/close-twice.c deleted file mode 100644 index 2514286..0000000 --- a/grading/userprog/close-twice.c +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include - -int -main (void) -{ - int handle; - printf ("(close-twice) begin\n"); - handle = open ("sample.txt"); - if (handle < 2) - printf ("(close-twice) fail: open() returned %d\n", handle); - close (handle); - close (handle); - printf ("(close-twice) end\n"); - return 0; -} diff --git a/grading/userprog/close-twice.exp b/grading/userprog/close-twice.exp deleted file mode 100644 index deb3d32..0000000 --- a/grading/userprog/close-twice.exp +++ /dev/null @@ -1,6 +0,0 @@ -(close-twice) begin -(close-twice) end -close-twice: exit(0) ---OR-- -(close-twice) begin -close-twice: exit(-1) diff --git a/grading/userprog/create-bad-ptr.c b/grading/userprog/create-bad-ptr.c deleted file mode 100644 index a6b7f76..0000000 --- a/grading/userprog/create-bad-ptr.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(create-bad-ptr) begin\n"); - create ((char *) 0x20101234, 0); - printf ("(create-bad-ptr) end\n"); - return 0; -} diff --git a/grading/userprog/create-bad-ptr.exp b/grading/userprog/create-bad-ptr.exp deleted file mode 100644 index ec8dad6..0000000 --- a/grading/userprog/create-bad-ptr.exp +++ /dev/null @@ -1,6 +0,0 @@ -(create-bad-ptr) begin -(create-bad-ptr) end -create-bad-ptr: exit(0) ---OR-- -(create-bad-ptr) begin -create-bad-ptr: exit(-1) diff --git a/grading/userprog/create-bound.c b/grading/userprog/create-bound.c deleted file mode 100644 index 4f240df..0000000 --- a/grading/userprog/create-bound.c +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include -#include -#include - -static char * -mk_boundary_string (const char *src) -{ - static char dst[8192]; - char *p = dst + (4096 - (uintptr_t) dst % 4096 - strlen (src) / 2); - strlcpy (p, src, 4096); - return p; -} - -int -main (void) -{ - printf ("(create-bound) begin\n"); - printf ("(create-bound) create(): %d\n", - create (mk_boundary_string ("quux.dat"), 0)); - printf ("(create-bound) end\n"); - return 0; -} diff --git a/grading/userprog/create-bound.exp b/grading/userprog/create-bound.exp deleted file mode 100644 index 0022477..0000000 --- a/grading/userprog/create-bound.exp +++ /dev/null @@ -1,4 +0,0 @@ -(create-bound) begin -(create-bound) create(): 1 -(create-bound) end -create-bound: exit(0) diff --git a/grading/userprog/create-empty.c b/grading/userprog/create-empty.c deleted file mode 100644 index 4c4ffa3..0000000 --- a/grading/userprog/create-empty.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(create-empty) begin\n"); - create ("", 0); - printf ("(create-empty) end\n"); - return 0; -} diff --git a/grading/userprog/create-empty.exp b/grading/userprog/create-empty.exp deleted file mode 100644 index affc1f8..0000000 --- a/grading/userprog/create-empty.exp +++ /dev/null @@ -1,6 +0,0 @@ -(create-empty) begin -(create-empty) end -create-empty: exit(0) ---OR-- -(create-empty) begin -create-empty: exit(-1) diff --git a/grading/userprog/create-exists.c b/grading/userprog/create-exists.c deleted file mode 100644 index 487804d..0000000 --- a/grading/userprog/create-exists.c +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(create-exists) begin\n"); - printf ("(create-exists) create(\"quux.dat\"): %d\n", - create ("quux.dat", 0)); - printf ("(create-exists) create(\"warble.dat\"): %d\n", - create ("warble.dat", 0)); - printf ("(create-exists) create(\"quux.dat\"): %d\n", - create ("quux.dat", 0)); - printf ("(create-exists) end\n"); - return 0; -} diff --git a/grading/userprog/create-exists.exp b/grading/userprog/create-exists.exp deleted file mode 100644 index 861381d..0000000 --- a/grading/userprog/create-exists.exp +++ /dev/null @@ -1,6 +0,0 @@ -(create-exists) begin -(create-exists) create("quux.dat"): 1 -(create-exists) create("warble.dat"): 1 -(create-exists) create("quux.dat"): 0 -(create-exists) end -create-exists: exit(0) diff --git a/grading/userprog/create-long.c b/grading/userprog/create-long.c deleted file mode 100644 index 81ef507..0000000 --- a/grading/userprog/create-long.c +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include -#include - -int -main (void) -{ - static char name[256]; - memset (name, 'x', sizeof name); - name[sizeof name - 1] = '\0'; - - printf ("(create-long) begin\n"); - printf ("(create-long) create: %d\n", create (name, 0)); - printf ("(create-long) end\n"); - - return 0; -} diff --git a/grading/userprog/create-long.exp b/grading/userprog/create-long.exp deleted file mode 100644 index d1aae6d..0000000 --- a/grading/userprog/create-long.exp +++ /dev/null @@ -1,4 +0,0 @@ -(create-long) begin -(create-long) create: 0 -(create-long) end -create-long: exit(0) diff --git a/grading/userprog/create-normal.c b/grading/userprog/create-normal.c deleted file mode 100644 index e2646b2..0000000 --- a/grading/userprog/create-normal.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(create-normal) begin\n"); - printf ("(create-normal) create(): %d\n", create ("quux.dat", 0)); - printf ("(create-normal) end\n"); - return 0; -} diff --git a/grading/userprog/create-normal.exp b/grading/userprog/create-normal.exp deleted file mode 100644 index 93b8b48..0000000 --- a/grading/userprog/create-normal.exp +++ /dev/null @@ -1,4 +0,0 @@ -(create-normal) begin -(create-normal) create(): 1 -(create-normal) end -create-normal: exit(0) diff --git a/grading/userprog/create-null.c b/grading/userprog/create-null.c deleted file mode 100644 index eafcb40..0000000 --- a/grading/userprog/create-null.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(create-null) begin\n"); - create (NULL, 0); - printf ("(create-null) end\n"); - return 0; -} diff --git a/grading/userprog/create-null.exp b/grading/userprog/create-null.exp deleted file mode 100644 index a853b05..0000000 --- a/grading/userprog/create-null.exp +++ /dev/null @@ -1,6 +0,0 @@ -(create-null) begin -(create-null) end -create-null: exit(0) ---OR-- -(create-null) begin -create-null: exit(-1) diff --git a/grading/userprog/exec-arg.c b/grading/userprog/exec-arg.c deleted file mode 100644 index cef5ece..0000000 --- a/grading/userprog/exec-arg.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(exec-arg) begin\n"); - wait (exec ("child-arg childarg")); - printf ("(exec-arg) end\n"); - return 0; -} diff --git a/grading/userprog/exec-arg.exp b/grading/userprog/exec-arg.exp deleted file mode 100644 index 2b8aee7..0000000 --- a/grading/userprog/exec-arg.exp +++ /dev/null @@ -1,5 +0,0 @@ -(exec-arg) begin -(child-arg) success -child-arg: exit(0) -(exec-arg) end -exec-arg: exit(0) diff --git a/grading/userprog/exec-bad-ptr.c b/grading/userprog/exec-bad-ptr.c deleted file mode 100644 index fce7adb..0000000 --- a/grading/userprog/exec-bad-ptr.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(exec-bad-ptr) begin\n"); - exec ((char *) 0x20101234); - printf ("(exec-bad-ptr) end\n"); - return 0; -} diff --git a/grading/userprog/exec-missing.c b/grading/userprog/exec-missing.c deleted file mode 100644 index 5fcc3d8..0000000 --- a/grading/userprog/exec-missing.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(exec-missing) begin\n"); - printf ("(exec-missing) exec(\"no-such-file\"): %d\n", - exec ("no-such-file")); - printf ("(exec-missing) end\n"); - return 0; -} diff --git a/grading/userprog/exec-once.c b/grading/userprog/exec-once.c deleted file mode 100644 index 87bf2be..0000000 --- a/grading/userprog/exec-once.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(exec-once) begin\n"); - wait (exec ("child-simple")); - printf ("(exec-once) end\n"); - return 0; -} diff --git a/grading/userprog/exit.c b/grading/userprog/exit.c deleted file mode 100644 index 6be04df..0000000 --- a/grading/userprog/exit.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(exit) begin\n"); - exit (57); - printf ("(exit) fail\n"); - return 0; -} diff --git a/grading/userprog/exit.exp b/grading/userprog/exit.exp deleted file mode 100644 index 00e83e8..0000000 --- a/grading/userprog/exit.exp +++ /dev/null @@ -1,2 +0,0 @@ -(exit) begin -exit: exit(57) diff --git a/grading/userprog/halt.c b/grading/userprog/halt.c deleted file mode 100644 index e3e4abf..0000000 --- a/grading/userprog/halt.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(halt) begin\n"); - halt (); - printf ("(halt) fail\n"); - return 0; -} diff --git a/grading/userprog/halt.exp b/grading/userprog/halt.exp deleted file mode 100644 index d421b32..0000000 --- a/grading/userprog/halt.exp +++ /dev/null @@ -1 +0,0 @@ -(halt) begin diff --git a/grading/userprog/lib/.cvsignore b/grading/userprog/lib/.cvsignore deleted file mode 100644 index a438335..0000000 --- a/grading/userprog/lib/.cvsignore +++ /dev/null @@ -1 +0,0 @@ -*.d diff --git a/grading/userprog/lib/user/.cvsignore b/grading/userprog/lib/user/.cvsignore deleted file mode 100644 index a438335..0000000 --- a/grading/userprog/lib/user/.cvsignore +++ /dev/null @@ -1 +0,0 @@ -*.d diff --git a/grading/userprog/mkmf b/grading/userprog/mkmf deleted file mode 100755 index 5afad63..0000000 --- a/grading/userprog/mkmf +++ /dev/null @@ -1,64 +0,0 @@ -#! /usr/bin/perl -w - -use strict; - -my (@tests); - -open (TESTS, ") { - chomp; - /^#/ || /^\s+$/ and next; - s/\s//g; - push (@tests, $_); -} -close (TESTS); - -open (MAKEBASE, "Makefile") or die; -print MAKEFILE <) { - print MAKEFILE $_; -} -close (MAKEBASE); - -open (CVSIGNORE, ">.cvsignore"); -print CVSIGNORE < -#include -#include -#include "sample.inc" - -char actual[sizeof sample]; - -int -main (void) -{ - char child_cmd[128]; - int byte_cnt; - int handle; - - printf ("(multi-child-fd) begin\n"); - - handle = open("sample.txt"); - if (handle < 2) - printf ("(multi-child-fd) fail: open() returned %d\n", handle); - - snprintf (child_cmd, sizeof child_cmd, "child-close %d", handle); - - printf ("(multi-child-fd) wait(exec()) = %d\n", wait (exec (child_cmd))); - - byte_cnt = read (handle, actual, sizeof actual - 1); - if (byte_cnt != sizeof actual - 1) - printf ("(multi-child-fd) fail: read() returned %d instead of %zu\n", - byte_cnt, sizeof actual - 1); - else if (strcmp (sample, actual)) - printf ("(multi-child-fd) fail: expected text differs from actual:\n%s", - actual); - - printf ("(multi-child-fd) end\n"); - return 0; -} diff --git a/grading/userprog/multi-child-fd.exp b/grading/userprog/multi-child-fd.exp deleted file mode 100644 index 474ea91..0000000 --- a/grading/userprog/multi-child-fd.exp +++ /dev/null @@ -1,12 +0,0 @@ -(multi-child-fd) begin -(child-close) success -child-close: exit(0) -(multi-child-fd) wait(exec()) = 0 -(multi-child-fd) end -multi-child-fd: exit(0) ---OR-- -(multi-child-fd) begin -child-close: exit(-1) -(multi-child-fd) wait(exec()) = -1 -(multi-child-fd) end -multi-child-fd: exit(0) diff --git a/grading/userprog/multi-parent-fd.c b/grading/userprog/multi-parent-fd.c deleted file mode 100644 index 56e10b1..0000000 --- a/grading/userprog/multi-parent-fd.c +++ /dev/null @@ -1,35 +0,0 @@ -#include -#include -#include -#include "sample.inc" - -char actual[sizeof sample]; - -int -main (void) -{ - char child_cmd[128]; - int byte_cnt; - int handle; - - printf ("(multi-child-fd) begin\n"); - - handle = open("sample.txt"); - if (handle < 2) - printf ("(multi-child-fd) fail: open() returned %d\n", handle); - - snprintf (child_cmd, sizeof child_cmd, "child-close %d", handle); - - printf ("(multi-child-fd) wait(exec()) = %d\n", wait (exec (child_cmd))); - - byte_cnt = read (handle, actual, sizeof actual - 1); - if (byte_cnt != sizeof actual - 1) - printf ("(multi-child-fd) fail: read() returned %d instead of %d\n", - byte_cnt, sizeof actual - 1); - else if (strcmp (sample, actual)) - printf ("(multi-child-fd) fail: expected text differs from actual:\n%s", - actual); - - printf ("(multi-child-fd) end\n"); - return 0; -} diff --git a/grading/userprog/multi-recurse.c b/grading/userprog/multi-recurse.c deleted file mode 100644 index 4a69d84..0000000 --- a/grading/userprog/multi-recurse.c +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include -#include -#include - -int -main (int argc UNUSED, char *argv[]) -{ - int n = atoi (argv[1]); - if (n == 0) - n = atoi (argv[0]); - - printf ("(multi-recurse) begin %d\n", n); - if (n != 0) - { - char child_cmd[128]; - pid_t child_pid; - - snprintf (child_cmd, sizeof child_cmd, "multi-recurse %d", n - 1); - child_pid = exec (child_cmd); - if (child_pid != -1) - { - int code = wait (child_pid); - if (code != n - 1) - printf ("(multi-recurse) fail: wait(exec(\"%s\")) returned %d\n", - child_cmd, code); - } - else - printf ("(multi-recurse) fail: exec(\"%s\") returned -1\n", child_cmd); - } - - printf ("(multi-recurse) end %d\n", n); - return n; -} diff --git a/grading/userprog/null.S b/grading/userprog/null.S deleted file mode 100644 index ded0e59..0000000 --- a/grading/userprog/null.S +++ /dev/null @@ -1,5 +0,0 @@ -.globl _start -.func _start -_start: - int $0x30 -.endfunc diff --git a/grading/userprog/null.exp b/grading/userprog/null.exp deleted file mode 100644 index 001f798..0000000 --- a/grading/userprog/null.exp +++ /dev/null @@ -1 +0,0 @@ -system call! diff --git a/grading/userprog/open-bad-ptr.c b/grading/userprog/open-bad-ptr.c deleted file mode 100644 index f73e6d8..0000000 --- a/grading/userprog/open-bad-ptr.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(open-bad-ptr) begin\n"); - open ((char *) 0x20101234); - printf ("(open-bad-ptr) end\n"); - return 0; -} diff --git a/grading/userprog/open-boundary.c b/grading/userprog/open-boundary.c deleted file mode 100644 index 374d405..0000000 --- a/grading/userprog/open-boundary.c +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include -#include -#include - -static char * -mk_boundary_string (const char *src) -{ - static char dst[8192]; - char *p = dst + (4096 - (uintptr_t) dst % 4096 - strlen (src) / 2); - strlcpy (p, src, 4096); - return p; -} - -int -main (void) -{ - int handle; - - printf ("(open-boundary) begin\n"); - handle = open (mk_boundary_string ("sample.txt")); - if (handle < 2) - printf ("(open-boundary) fail: open() returned %d\n", handle); - printf ("(open-boundary) end\n"); - return 0; -} diff --git a/grading/userprog/open-boundary.exp b/grading/userprog/open-boundary.exp deleted file mode 100644 index bc1d311..0000000 --- a/grading/userprog/open-boundary.exp +++ /dev/null @@ -1,3 +0,0 @@ -(open-boundary) begin -(open-boundary) end -open-boundary: exit(0) diff --git a/grading/userprog/open-empty.c b/grading/userprog/open-empty.c deleted file mode 100644 index 2529bce..0000000 --- a/grading/userprog/open-empty.c +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include - -int -main (void) -{ - int handle; - printf ("(open-empty) begin\n"); - handle = open (""); - if (handle != -1) - printf ("(open-empty) fail: open() returned %d\n", handle); - printf ("(open-empty) end\n"); - return 0; -} diff --git a/grading/userprog/open-empty.exp b/grading/userprog/open-empty.exp deleted file mode 100644 index 9944192..0000000 --- a/grading/userprog/open-empty.exp +++ /dev/null @@ -1,3 +0,0 @@ -(open-empty) begin -(open-empty) end -open-empty: exit(0) diff --git a/grading/userprog/open-missing.c b/grading/userprog/open-missing.c deleted file mode 100644 index e7a2b84..0000000 --- a/grading/userprog/open-missing.c +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include - -int -main (void) -{ - int handle; - printf ("(open-missing) begin\n"); - handle = open ("no-such-file"); - if (handle != -1) - printf ("(open-missing) fail: open() returned %d\n", handle); - printf ("(open-missing) end\n"); - return 0; -} diff --git a/grading/userprog/open-missing.exp b/grading/userprog/open-missing.exp deleted file mode 100644 index 9e52e13..0000000 --- a/grading/userprog/open-missing.exp +++ /dev/null @@ -1,3 +0,0 @@ -(open-missing) begin -(open-missing) end -open-missing: exit(0) diff --git a/grading/userprog/open-normal.c b/grading/userprog/open-normal.c deleted file mode 100644 index 74dbbf4..0000000 --- a/grading/userprog/open-normal.c +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include - -int -main (void) -{ - int handle; - printf ("(open-normal) begin\n"); - handle = open ("sample.txt"); - if (handle < 2) - printf ("(open-normal) fail: open() returned %d\n", handle); - printf ("(open-normal) end\n"); - return 0; -} diff --git a/grading/userprog/open-normal.exp b/grading/userprog/open-normal.exp deleted file mode 100644 index 7087931..0000000 --- a/grading/userprog/open-normal.exp +++ /dev/null @@ -1,3 +0,0 @@ -(open-normal) begin -(open-normal) end -open-normal: exit(0) diff --git a/grading/userprog/open-null.c b/grading/userprog/open-null.c deleted file mode 100644 index 5d6cc5c..0000000 --- a/grading/userprog/open-null.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(open-null) begin\n"); - open (NULL); - printf ("(open-null) end\n"); - return 0; -} diff --git a/grading/userprog/open-null.exp b/grading/userprog/open-null.exp deleted file mode 100644 index fa8dc57..0000000 --- a/grading/userprog/open-null.exp +++ /dev/null @@ -1,6 +0,0 @@ -(open-null) begin -(open-null) end -open-null: exit(0) ---OR-- -(open-null) begin -open-null: exit(-1) diff --git a/grading/userprog/open-twice.c b/grading/userprog/open-twice.c deleted file mode 100644 index f029fe5..0000000 --- a/grading/userprog/open-twice.c +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include - -int -main (void) -{ - int h1, h2; - printf ("(open-twice) begin\n"); - - h1 = open ("sample.txt"); - if (h1 < 2) - printf ("(open-twice) fail: open() returned %d first time\n", h1); - - h2 = open ("sample.txt"); - if (h2 < 2) - printf ("(open-twice) fail: open() returned %d second time\n", h2); - if (h1 == h2) - printf ("(open-twice) fail: open() returned %d both times\n", h1); - - printf ("(open-twice) end\n"); - return 0; -} diff --git a/grading/userprog/open-twice.exp b/grading/userprog/open-twice.exp deleted file mode 100644 index 650c0d3..0000000 --- a/grading/userprog/open-twice.exp +++ /dev/null @@ -1,3 +0,0 @@ -(open-twice) begin -(open-twice) end -open-twice: exit(0) diff --git a/grading/userprog/patches/00random.patch b/grading/userprog/patches/00random.patch deleted file mode 100644 index 3648632..0000000 --- a/grading/userprog/patches/00random.patch +++ /dev/null @@ -1,96 +0,0 @@ -Modifies bitmap_scan() to return a random set of bits instead of the -first set. Helps to stress-test VM implementation. - -Index: pintos/src/lib/kernel/bitmap.c -=================================================================== -RCS file: /afs/ir.stanford.edu/users/b/l/blp/private/cvs/pintos/src/lib/kernel/bitmap.c,v -retrieving revision 1.11 -diff -u -p -r1.11 bitmap.c ---- pintos/src/lib/kernel/bitmap.c 2 Jan 2005 02:09:58 -0000 1.11 -+++ pintos/src/lib/kernel/bitmap.c 9 Feb 2005 21:45:27 -0000 -@@ -3,6 +3,7 @@ - #include - #include - #include -+#include - #include "threads/malloc.h" - #ifdef FILESYS - #include "filesys/file.h" -@@ -30,6 +31,8 @@ struct bitmap - elem_type *bits; /* Elements that represent bits. */ - }; - -+bool randomize_bitmaps; -+ - /* Returns the index of the element that contains the bit - numbered BIT_IDX. */ - static inline size_t -@@ -227,9 +230,28 @@ bitmap_scan (const struct bitmap *b, siz - { - size_t last = b->bit_cnt - cnt; - size_t i; -+ size_t n = 0; -+ -+ /* Count number of matches. */ - for (i = start; i <= last; i++) -- if (!contains (b, i, cnt, !value)) -- return i; -+ if (!contains (b, i, cnt, !value)) -+ { -+ if (randomize_bitmaps) -+ n++; -+ else -+ return i; -+ } -+ -+ /* Pick one match. */ -+ if (n != 0) -+ { -+ random_init (0); -+ n = random_ulong () % n; -+ for (i = start; i <= last; i++) -+ if (!contains (b, i, cnt, !value) && n-- == 0) -+ return i; -+ NOT_REACHED (); -+ } - } - return BITMAP_ERROR; - } -Index: pintos/src/lib/kernel/bitmap.h -=================================================================== -RCS file: /afs/ir.stanford.edu/users/b/l/blp/private/cvs/pintos/src/lib/kernel/bitmap.h,v -retrieving revision 1.5 -diff -u -p -r1.5 bitmap.h ---- pintos/src/lib/kernel/bitmap.h 15 Dec 2004 06:08:55 -0000 1.5 -+++ pintos/src/lib/kernel/bitmap.h 9 Feb 2005 21:45:27 -0000 -@@ -44,4 +44,6 @@ size_t bitmap_needed_bytes (size_t bit_c - struct bitmap *bitmap_create_preallocated (size_t bit_cnt, - void *, size_t byte_cnt); - -+extern bool randomize_bitmaps; -+ - #endif /* lib/kernel/bitmap.h */ -Index: pintos/src/threads/init.c -=================================================================== -RCS file: /afs/ir.stanford.edu/users/b/l/blp/private/cvs/pintos/src/threads/init.c,v -retrieving revision 1.53 -diff -u -p -r1.53 init.c ---- pintos/src/threads/init.c 7 Feb 2005 05:56:07 -0000 1.53 -+++ pintos/src/threads/init.c 9 Feb 2005 21:45:28 -0000 -@@ -8,6 +8,7 @@ - #include - #include - #include -+#include - #include "devices/kbd.h" - #include "devices/serial.h" - #include "devices/timer.h" -@@ -193,6 +194,8 @@ paging_init (void) - new page tables immediately. See [IA32-v2a] "MOV--Move - to/from Control Registers" and [IA32-v3] 3.7.5. */ - asm volatile ("mov %%cr3, %0" :: "r" (vtop (base_page_dir))); -+ -+ randomize_bitmaps = true; - } - - /* Parses the command line. */ diff --git a/grading/userprog/prep-disk b/grading/userprog/prep-disk deleted file mode 100755 index 39d078e..0000000 --- a/grading/userprog/prep-disk +++ /dev/null @@ -1,66 +0,0 @@ -#! /usr/bin/perl -w - -use strict; -use Getopt::Long; -use POSIX; - -my ($pintos) = "pintos"; -my ($os_disk) = "../../src/userprog/build/os.dsk"; -my ($fs_disk); -my ($test); - -GetOptions ("os-disk=s" => \$os_disk, - "fs-disk=s" => \$fs_disk, - "test=s" => \$test, - "help" => sub { usage (0) }) - or die "option parsing failed; use --help for help\n"; - -if (!defined ($test)) { - die "test name expected; use --help for help\n" - if @ARGV != 1; - $test = shift @ARGV; -} elsif (@ARGV != 0) { - die "can't have non-option arg with --test\n"; -} - -$fs_disk = "$test.dsk" if !defined $fs_disk; - -if (! -e $os_disk) { - print STDERR "$os_disk: stat: $!\n"; - print STDERR "perhaps you should `make' in ../../src/userprog?\n"; - exit 1; -} - -our ($formatted) = 0; - -unlink $fs_disk; -xsystem ("$pintos make-disk '$fs_disk' 2"); -put_file ("$test"); -put_file ("sample.txt") - if grep ($_ eq $test, - qw (open-normal open-boundary open-twice - close-normal close-twice - read-normal read-bad-ptr read-boundary read-zero - write-normal write-bad-ptr write-boundary write-zero - multi-child-fd)); -put_file ("child-simple") - if grep ($_ eq $test, - qw (exec-once exec-multiple - wait-simple wait-twice)); -put_file ("child-arg") if $test eq 'exec-arg'; -put_file ("child-close") if $test eq 'multi-child-fd'; -put_file ("child-bad") if $test eq 'wait-killed'; - -sub put_file { - my ($fn) = @_; - my ($cmd) = "$pintos -v --os-disk='$os_disk' --fs-disk='$fs_disk' put"; - $cmd .= " -f", $formatted = 1 if !$formatted; - $cmd .= " '$fn'"; - xsystem ($cmd); -} - -sub xsystem { - my ($cmd) = @_; - print "$cmd\n"; - system ($cmd) == 0 or die "command failed\n"; -} diff --git a/grading/userprog/read-bad-fd.c b/grading/userprog/read-bad-fd.c deleted file mode 100644 index 959687c..0000000 --- a/grading/userprog/read-bad-fd.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -int -main (void) -{ - char buf; - printf ("(read-bad-fd) begin\n"); - read (0x20101234, &buf, 1); - printf ("(read-bad-fd) end\n"); - return 0; -} diff --git a/grading/userprog/read-bad-fd.exp b/grading/userprog/read-bad-fd.exp deleted file mode 100644 index 0c30c9a..0000000 --- a/grading/userprog/read-bad-fd.exp +++ /dev/null @@ -1,6 +0,0 @@ -(read-bad-fd) begin -(read-bad-fd) end -read-bad-fd: exit(0) ---OR-- -(read-bad-fd) begin -read-bad-fd: exit(-1) diff --git a/grading/userprog/read-bad-ptr.c b/grading/userprog/read-bad-ptr.c deleted file mode 100644 index 5ddb2a3..0000000 --- a/grading/userprog/read-bad-ptr.c +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include - -int -main (void) -{ - int handle; - printf ("(read-bad-ptr) begin\n"); - - handle = open ("sample.txt"); - if (handle < 2) - printf ("(read-bad-ptr) fail: open() returned %d\n", handle); - - read (handle, (char *) 0x20101234, 123); - - printf ("(read-bad-ptr) end\n"); - return 0; -} diff --git a/grading/userprog/read-bad-ptr.exp b/grading/userprog/read-bad-ptr.exp deleted file mode 100644 index fe4d3ed..0000000 --- a/grading/userprog/read-bad-ptr.exp +++ /dev/null @@ -1,6 +0,0 @@ -(read-bad-ptr) begin -(read-bad-ptr) end -read-bad-ptr: exit(0) ---OR-- -(read-bad-ptr) begin -read-bad-ptr: exit(-1) diff --git a/grading/userprog/read-boundary.c b/grading/userprog/read-boundary.c deleted file mode 100644 index 1fd187f..0000000 --- a/grading/userprog/read-boundary.c +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include -#include -#include -#include "sample.inc" - -static char * -mk_boundary_string (const char *src) -{ - static char dst[8192]; - char *p = dst + (4096 - (uintptr_t) dst % 4096 - strlen (src) / 2); - strlcpy (p, src, 4096); - return p; -} - -int -main (void) -{ - int handle; - int byte_cnt; - char *actual_p; - - actual_p = mk_boundary_string (sample); - - printf ("(read-boundary) begin\n"); - - handle = open ("sample.txt"); - if (handle < 2) - printf ("(read-boundary) fail: open() returned %d\n", handle); - - byte_cnt = read (handle, actual_p, sizeof sample - 1); - if (byte_cnt != sizeof sample - 1) - printf ("(read-boundary) fail: read() returned %d instead of %zu\n", - byte_cnt, sizeof sample - 1); - else if (strcmp (sample, actual_p)) - printf ("(read-boundary) fail: expected text differs from actual:\n%s", - actual_p); - - printf ("(read-boundary) end\n"); - return 0; -} diff --git a/grading/userprog/read-boundary.exp b/grading/userprog/read-boundary.exp deleted file mode 100644 index cbbcda8..0000000 --- a/grading/userprog/read-boundary.exp +++ /dev/null @@ -1,3 +0,0 @@ -(read-boundary) begin -(read-boundary) end -read-boundary: exit(0) diff --git a/grading/userprog/read-normal.c b/grading/userprog/read-normal.c deleted file mode 100644 index 9a63083..0000000 --- a/grading/userprog/read-normal.c +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include -#include -#include "sample.inc" - -char actual[sizeof sample]; - -int -main (void) -{ - int handle, byte_cnt; - printf ("(read-normal) begin\n"); - - handle = open ("sample.txt"); - if (handle < 2) - printf ("(read-normal) fail: open() returned %d\n", handle); - - byte_cnt = read (handle, actual, sizeof actual - 1); - if (byte_cnt != sizeof actual - 1) - printf ("(read-normal) fail: read() returned %d instead of %zu\n", - byte_cnt, sizeof actual - 1); - else if (strcmp (sample, actual)) - printf ("(read-normal) fail: expected text differs from actual:\n%s", - actual); - - printf ("(read-normal) end\n"); - return 0; -} diff --git a/grading/userprog/read-normal.exp b/grading/userprog/read-normal.exp deleted file mode 100644 index e45dc51..0000000 --- a/grading/userprog/read-normal.exp +++ /dev/null @@ -1,3 +0,0 @@ -(read-normal) begin -(read-normal) end -read-normal: exit(0) diff --git a/grading/userprog/read-stdout.c b/grading/userprog/read-stdout.c deleted file mode 100644 index df0274c..0000000 --- a/grading/userprog/read-stdout.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -int -main (void) -{ - char buf; - printf ("(read-stdout) begin\n"); - read (1, &buf, 1); - printf ("(read-stdout) end\n"); - return 0; -} diff --git a/grading/userprog/read-stdout.exp b/grading/userprog/read-stdout.exp deleted file mode 100644 index 63c1d52..0000000 --- a/grading/userprog/read-stdout.exp +++ /dev/null @@ -1,6 +0,0 @@ -(read-stdout) begin -(read-stdout) end -read-stdout: exit(0) ---OR-- -(read-stdout) begin -read-stdout: exit(-1) diff --git a/grading/userprog/read-zero.c b/grading/userprog/read-zero.c deleted file mode 100644 index 7f9ec82..0000000 --- a/grading/userprog/read-zero.c +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include - -int -main (void) -{ - int handle, byte_cnt; - char buf; - printf ("(read-zero) begin\n"); - - handle = open ("sample.txt"); - if (handle < 2) - printf ("(read-zero) fail: open() returned %d\n", handle); - - buf = 123; - byte_cnt = read (handle, &buf, 0); - if (byte_cnt != 0) - printf ("(read-zero) fail: read() returned %d instead of 0\n", byte_cnt); - else if (buf != 123) - printf ("(read-zero) fail: 0-byte read() modified buffer\n"); - - printf ("(read-zero) end\n"); - return 0; -} diff --git a/grading/userprog/read-zero.exp b/grading/userprog/read-zero.exp deleted file mode 100644 index f1b438a..0000000 --- a/grading/userprog/read-zero.exp +++ /dev/null @@ -1,3 +0,0 @@ -(read-zero) begin -(read-zero) end -read-zero: exit(0) diff --git a/grading/userprog/review.txt b/grading/userprog/review.txt deleted file mode 100644 index 0a40021..0000000 --- a/grading/userprog/review.txt +++ /dev/null @@ -1,64 +0,0 @@ -Test cases [[/25]] ------------------- - -15 Didn't write own test cases - -10 Insufficient testing - -Design [[/100]] ---------------- - -Quality of DESIGNDOC - -10 Arg passing - -20 Copying data around: - User-to-kernel copying. - Kernel-to-user copying. - String copying. - -20 System calls: - Allocation of file descriptors. - Handling exceptions and related cleanup. - pid_t rationale (if they changed tid_t -> pid_t mapping). - Synchronization of system calls and filesystem. - -Overall: - -1 Gratuitous use of malloc() (e.g. for allocating a list or a lock) - -1 Inappropriate use of ASSERT (e.g. to verify that malloc() succeeded) - -Program arguments: - +1 Support multiple pages of arguments. - -User/kernel copying: - -5 Too many copies of user/kernel copying code - -20 Doesn't check for page boundaries - -10 Imperfect checking for page boundaries - -5 Doesn't check whether pointers are at or above PHYS_BASE - -2 Imperfect checking whether pointers are at or above PHYS_BASE - +3 Copies large chunks while properly observing page boundaries - +3 Scans for string null terminators w/o checking individual bytes - while properly observing page boundaries - +3 Uses get_user() and put_user() functions from FAQ for copying - -System call design: - -5 Disables interrupts without reasonable justification - -2 Doesn't close open files at process exit - -2 Doesn't acquire file system lock to close files at process exit - -5 Buffer overflow in read or write system call - -5 System call error exit leaks memory/fails to release global lock - -5 Uses a pointer as a file descriptor or pid without justifying - -Wait system call: - -3 Busy waiting - -3 A static list of all parent-child pairs is extremely wasteful - -3 Obviously wasteful with memory (not deleting processes) - -2 Finished parent deletes children which may still be running - -1 Enable/disable interrupts - -2 Joinable child lets its struct thread be deleted before parent dies - -1 Race condition between wait and thread exit - -Style [[/25]] -------------- - -5 Extraneous output caused warnings - -5 Didn't print process termination messages - -5 One big function for handling system calls - -5 No attempt to conform to existing coding style - -Comments --------- diff --git a/grading/userprog/run-tests b/grading/userprog/run-tests deleted file mode 100755 index e81b533..0000000 --- a/grading/userprog/run-tests +++ /dev/null @@ -1,173 +0,0 @@ -#! /usr/bin/perl - -# Find the directory that contains the grading files. -our ($GRADES_DIR); - -# Add our Perl library directory to the include path. -BEGIN { - ($GRADES_DIR = $0) =~ s#/[^/]+$##; - -d $GRADES_DIR or die "$GRADES_DIR: stat: $!\n"; - unshift @INC, "$GRADES_DIR/../lib"; -} - -use warnings; -use strict; -use Pintos::Grading; - -our ($hw) = "userprog"; -our (@TESTS); # Tests to run. -our ($test); -our (%extra); -our ($action); - -if ($#ARGV == 0 && $ARGV[0] eq 'null') { - @TESTS = ('null'); - extract_sources (); - build (); - run_and_grade_tests (); - exit success (); -} - -parse_cmd_line qw (args-argc args-argv0 args-argvn args-single args-multiple - args-dbl-space - sc-bad-sp sc-bad-arg sc-boundary - 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-oom multi-child-fd); - -clean_dir (), exit if $action eq 'clean'; - -extract_sources (); -exit if $action eq 'extract'; - -build (); -exit if $action eq 'build'; - -run_and_grade_tests (); -write_grades (); -write_details (); -exit success () if $action eq 'test'; - -assemble_final_grade (); -exit success () if $action eq 'assemble'; - -die "Don't know how to '$action'"; - -# Runs $test in directory output/$test. -# Returns 'ok' if it went ok, otherwise an explanation. -sub run_test { - xsystem ("cp $GRADES_DIR/$test.dsk output/$test/fs.dsk", - DIE => "cp failed\n"); - - my ($args) = ""; - $args = 'some arguments for you!' - if grep ($_ eq $test, qw(args-argc args-argv0 - args-argvn args-multiple)); - $args = 'onearg' if $test eq 'args-single'; - $args = 'two args' if $test eq 'args-dbl-space'; - $args = '15' if $test eq 'multi-recurse'; - $args = '0' if $test eq 'multi-oom'; - $args = " $args" if $args ne ''; - - # Run. - my ($timeout) = $test !~ /^multi-/ ? 10 : 600; - my ($result) = run_pintos (["--os-disk=pintos/src/userprog/build/os.dsk", - "--fs-disk=output/$test/fs.dsk", - "-v", "run", "-q", "-ex", "$test$args"], - LOG => "$test/run", - TIMEOUT => $timeout); - rename "output/$test/fs.dsk", "output/$test/fs.dsk.keep" - if $test eq 'write-normal'; - return $result; -} - -sub grade_write_normal { - my (@output) = @_; - verify_common (@output); - compare_output ("$GRADES_DIR/write-normal.exp", @output); - my ($test_txt) = "output/$test/test.txt"; - get_file ("test.txt", $test_txt) if ! -e $test_txt; - - my (@actual) = snarf ($test_txt); - my (@expected) = snarf ("$GRADES_DIR/sample.txt"); - - my ($eq); - if ($#actual == $#expected) { - $eq = 1; - for my $i (0...$#actual) { - $eq = 0 if $actual[$i] ne $expected[$i]; - } - } else { - $eq = 0; - } - if (!$eq) { - my ($details); - $details = "Expected file content:\n"; - $details .= join ('', map (" $_\n", @expected)); - $details .= "Actual file content:\n"; - $details .= join ('', map (" $_\n", @actual)); - $extra{$test} = $details; - - die "File written didn't have expected content.\n"; - } -} - -sub grade_multi_oom { - my (@output) = @_; - verify_common (@output); - - @output = canonicalize_exit_codes (get_core_output (@output)); - my ($n) = 0; - while (my ($m) = $output[0] =~ /^\(multi-oom\) begin (\d+)$/) { - die "Child process $m started out of order.\n" if $m != $n; - $n = $m + 1; - shift @output; - } - die "Only $n child process(es) started.\n" if $n < 15; - - # There could be a death notice for a process that didn't get - # fully loaded, and/or notices from the loader. - while (@output > 0 - && ($output[0] =~ /^multi-oom: exit\(-1\)$/ - || $output[0] =~ /^load: /)) { - shift @output; - } - - while (--$n >= 0) { - die "Output ended unexpectedly before process $n finished.\n" - if @output < 2; - - local ($_); - chomp ($_ = shift @output); - die "Found '$_' expecting 'end' message.\n" if !/^\(multi-oom\) end/; - die "Child process $n ended out of order.\n" - if !/^\(multi-oom\) end $n$/; - - chomp ($_ = shift @output); - die "Kernel didn't print proper exit message for process $n.\n" - if !/^multi-oom: exit\($n\)$/; - } - die "Spurious output at end: '$output[0]'.\n" if @output; -} - -sub get_file { - my ($guest_fn, $host_fn) = @_; - my ($result) = run_pintos (["--os-disk=pintos/src/userprog/build/os.dsk", - "--fs-disk=output/$test/fs.dsk.keep", - "-v", "get", "$guest_fn", "$host_fn"], - LOG => "$test/get-$guest_fn", - TIMEOUT => 10); - die "`pintos get $guest_fn' failed - $result\n" - if $result ne 'ok'; -} diff --git a/grading/userprog/sample.inc b/grading/userprog/sample.inc deleted file mode 100644 index dd3d718..0000000 --- a/grading/userprog/sample.inc +++ /dev/null @@ -1,6 +0,0 @@ -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.\n" -}; diff --git a/grading/userprog/sample.txt b/grading/userprog/sample.txt deleted file mode 100644 index 9e0dd45..0000000 --- a/grading/userprog/sample.txt +++ /dev/null @@ -1,4 +0,0 @@ -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. diff --git a/grading/userprog/sc-bad-arg.exp b/grading/userprog/sc-bad-arg.exp deleted file mode 100644 index 7babb3c..0000000 --- a/grading/userprog/sc-bad-arg.exp +++ /dev/null @@ -1,2 +0,0 @@ -(sc-bad-arg) begin -sc-bad-arg: exit(-1) diff --git a/grading/userprog/sc-bad-sp.c b/grading/userprog/sc-bad-sp.c deleted file mode 100644 index bf92206..0000000 --- a/grading/userprog/sc-bad-sp.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(sc-bad-sp) begin\n"); - asm volatile ("mov %esp, 0x20101234; int 0x30"); - printf ("(sc-bad-sp) end\n"); - return 0; -} diff --git a/grading/userprog/sc-bad-sp.exp b/grading/userprog/sc-bad-sp.exp deleted file mode 100644 index 4d72143..0000000 --- a/grading/userprog/sc-bad-sp.exp +++ /dev/null @@ -1,2 +0,0 @@ -(sc-bad-sp) begin -sc-bad-sp: exit(-1) diff --git a/grading/userprog/sc-boundary.c b/grading/userprog/sc-boundary.c deleted file mode 100644 index 94c81b9..0000000 --- a/grading/userprog/sc-boundary.c +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include -#include -#include - -static void * -mk_boundary_array (void) -{ - static char dst[8192]; - return dst + (4096 - (uintptr_t) dst % 4096); -} - -int -main (void) -{ - int *p; - - printf ("(sc-boundary) begin\n"); - p = mk_boundary_array (); - p--; - p[0] = SYS_exit; - p[1] = 42; - asm volatile ("mov %%esp, %0; int 0x30" :: "g" (p)); - printf ("(sc-boundary) failed\n"); - return 1; -} diff --git a/grading/userprog/sc-boundary.exp b/grading/userprog/sc-boundary.exp deleted file mode 100644 index 1f38d87..0000000 --- a/grading/userprog/sc-boundary.exp +++ /dev/null @@ -1,2 +0,0 @@ -(sc-boundary) begin -sc-boundary: exit(42) diff --git a/grading/userprog/tests.txt b/grading/userprog/tests.txt deleted file mode 100644 index e6d6599..0000000 --- a/grading/userprog/tests.txt +++ /dev/null @@ -1,96 +0,0 @@ -CORRECTNESS [[total]] ---------------------- - -Argument passing - -4 args-argc: argc is not set correctly - -4 args-single: passing single argument fails - -3 args-argv0: executable name not passed as argv[0] - -3 args-argvn: argv[argc] is not a null pointer - -3 args-multiple: passing multiple arguments fails - -3 args-dbl-space: using multiple spaces between arguments fails -Score: /20 - -System calls - -4 sc-boundary: syscall with args across page boundary must work - -3 sc-bad-sp: system call with a bad stack pointer must not crash OS - -3 sc-bad-arg: syscall with argument off top of stack must not crash OS -Score: /10 - -System calls: halt, exec - -1 halt: halt system call fails - -1 exit: exit system call malfunctions -Score: /2 - -System calls: create - -2 create-normal: create a file in the most normal way - -1 create-empty: pass empty string to create system call - -1 create-null: pass null pointer to create system call - -1 create-bad-ptr: pass invalid pointer to create system call - -1 create-long: pass long file name to create system call - -1 create-exists: pass name of an existing file to create system call - -1 create-bound: pass name of file crossing page boundary -Score: /8 - -System calls: open - -2 open-normal: open a file in the most normal way - -2 open-missing: try to open a file that doesn't exist - -2 open-boundary: pass name of file crossing page boundary - -1 open-empty: pass empty string to open system call - -1 open-null: pass null pointer to open system call - -1 open-bad-ptr: pass invalid pointer to open system call - -1 open-twice: open the same file twice -Score: /10 - -System calls: close - -2 close-normal: close an open file in the most normal way - -2 close-twice: try to close an open file twice - -1 close-stdin: try to close stdin - -1 close-stdout: try to close stdout - -1 close-bad-fd: try to close invalid file descriptor -Score: /7 - -System calls: read - -2 read-normal: read from open file in most normal way - -2 read-bad-ptr: pass invalid pointer to read system call - -2 read-boundary: pass buffer crossing page boundary - -1 read-zero: try to read zero bytes - -1 read-stdout: try to read from stdout - -1 read-bad-fd: try to read from invalid file descriptor -Score: /9 - -System calls: write - -2 write-normal: write to open file in most normal way - -2 write-bad-ptr: pass invalid pointer to write system call - -2 write-boundary: pass buffer crossing page boundary - -1 write-zero: try to write zero bytes - -1 write-stdin: try to write to stdin - -1 write-bad-fd: try to write to invalid file descriptor -Score: /9 - -System calls: exec - -2 exec-once: call exec/wait once - -2 exec-arg: check command-line passing on exec - -2 exec-multiple: call exec/wait multiple times - -2 exec-missing: exec of nonexistent file must return -1 - -1 exec-bad-ptr: pass invalid pointer to exec system call -Score: /9 - -System calls: wait - -2 wait-once: A creates B, A waits for B - -2 wait-twice: A creates B, A waits for B, A waits for B again - -2 wait-quick: A creates B, A waits for B, with different details - -2 wait-multiple: A creates B and C, A waits for B, A waits for C - -2 wait-nested: A creates B, B creates C, ..., B waits for C, A waits for B - -2 wait-dummy: A creates B, A waits for B, A waits for B - -2 wait-invalid: Waiting for an invalid pid must return immediately - -2 wait-other: Waiting for a child of another process must return immediately - -2 wait-no: A creates B and never waits for it (must not crash or hang) -Score: /14 - -Score: /7 - -Multiprogramming - -3 multi-recurse: test recursively executing subprocesses - -3 multi-oom: exhausting user memory must not crash OS - -3 multi-child-fd: child must not be able to close parent's fds -Score: /9 diff --git a/grading/userprog/wait-bad-pid.c b/grading/userprog/wait-bad-pid.c deleted file mode 100644 index 6b8cf88..0000000 --- a/grading/userprog/wait-bad-pid.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(wait-bad-pid) begin\n"); - wait ((pid_t) 0x20101234); - printf ("(wait-bad-pid) end\n"); - return 0; -} diff --git a/grading/userprog/wait-killed.c b/grading/userprog/wait-killed.c deleted file mode 100644 index f1ee40c..0000000 --- a/grading/userprog/wait-killed.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(wait-killed) begin\n"); - printf ("(wait-killed) wait(exec()) = %d\n", wait (exec ("child-bad"))); - printf ("(wait-killed) end\n"); - return 0; -} diff --git a/grading/userprog/wait-simple.c b/grading/userprog/wait-simple.c deleted file mode 100644 index f0ab5eb..0000000 --- a/grading/userprog/wait-simple.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -int -main (void) -{ - printf ("(wait-simple) begin\n"); - printf ("(wait-simple) wait(exec()) = %d\n", wait (exec ("child-simple"))); - printf ("(wait-simple) end\n"); - return 0; -} diff --git a/grading/userprog/wait-twice.c b/grading/userprog/wait-twice.c deleted file mode 100644 index c104b61..0000000 --- a/grading/userprog/wait-twice.c +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include - -int -main (void) -{ - pid_t child; - printf ("(wait-twice) begin\n"); - child = exec ("child-simple"); - printf ("(wait-twice) wait(exec()) = %d\n", wait (child)); - wait (child); - printf ("(wait-twice) end\n"); - return 0; -} diff --git a/grading/userprog/write-bad-fd.c b/grading/userprog/write-bad-fd.c deleted file mode 100644 index 5f46d6b..0000000 --- a/grading/userprog/write-bad-fd.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -int -main (void) -{ - char buf = 123; - printf ("(write-bad-fd) begin\n"); - write (0x20101234, &buf, 1); - printf ("(write-bad-fd) end\n"); - return 0; -} diff --git a/grading/userprog/write-bad-ptr.c b/grading/userprog/write-bad-ptr.c deleted file mode 100644 index b36f447..0000000 --- a/grading/userprog/write-bad-ptr.c +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include - -int -main (void) -{ - int handle; - printf ("(write-bad-ptr) begin\n"); - - handle = open ("sample.txt"); - if (handle < 2) - printf ("(write-bad-ptr) fail: open() returned %d\n", handle); - - write (handle, (char *) 0x20101234, 123); - - printf ("(write-bad-ptr) end\n"); - return 0; -} diff --git a/grading/userprog/write-bad-ptr.exp b/grading/userprog/write-bad-ptr.exp deleted file mode 100644 index 1540400..0000000 --- a/grading/userprog/write-bad-ptr.exp +++ /dev/null @@ -1,6 +0,0 @@ -(write-bad-ptr) begin -(write-bad-ptr) end -write-bad-ptr: exit(0) ---OR-- -(write-bad-ptr) begin -write-bad-ptr: exit(-1) diff --git a/grading/userprog/write-boundary.c b/grading/userprog/write-boundary.c deleted file mode 100644 index 860e2a2..0000000 --- a/grading/userprog/write-boundary.c +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include -#include -#include -#include "sample.inc" - -static char * -mk_boundary_string (const char *src) -{ - static char dst[8192]; - char *p = dst + (4096 - (uintptr_t) dst % 4096 - strlen (src) / 2); - strlcpy (p, src, 4096); - return p; -} - -int -main (void) -{ - int handle; - int byte_cnt; - char *sample_p; - - sample_p = mk_boundary_string (sample); - - printf ("(write-boundary) begin\n"); - - handle = open ("sample.txt"); - if (handle < 2) - printf ("(write-boundary) fail: open() returned %d\n", handle); - - byte_cnt = write (handle, sample_p, sizeof sample - 1); - if (byte_cnt != sizeof sample - 1) - printf ("(write-boundary) fail: write() returned %d instead of %zu\n", - byte_cnt, sizeof sample - 1); - - printf ("(write-boundary) end\n"); - return 0; -} diff --git a/grading/userprog/write-boundary.exp b/grading/userprog/write-boundary.exp deleted file mode 100644 index bf64357..0000000 --- a/grading/userprog/write-boundary.exp +++ /dev/null @@ -1,3 +0,0 @@ -(write-boundary) begin -(write-boundary) end -write-boundary: exit(0) diff --git a/grading/userprog/write-normal.c b/grading/userprog/write-normal.c deleted file mode 100644 index 145d0bf..0000000 --- a/grading/userprog/write-normal.c +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include -#include -#include "sample.inc" - -int -main (void) -{ - int handle, byte_cnt; - printf ("(write-normal) begin\n"); - - if (!create ("test.txt", sizeof sample - 1)) - printf ("(write-normal) create() failed\n"); - - handle = open ("test.txt"); - if (handle < 2) - printf ("(write-normal) fail: open() returned %d\n", handle); - - byte_cnt = write (handle, sample, sizeof sample - 1); - if (byte_cnt != sizeof sample - 1) - printf ("(write-normal) fail: write() returned %d instead of %zu\n", - byte_cnt, sizeof sample - 1); - - printf ("(write-normal) end\n"); - return 0; -} diff --git a/grading/userprog/write-normal.exp b/grading/userprog/write-normal.exp deleted file mode 100644 index 87b05e5..0000000 --- a/grading/userprog/write-normal.exp +++ /dev/null @@ -1,3 +0,0 @@ -(write-normal) begin -(write-normal) end -write-normal: exit(0) diff --git a/grading/userprog/write-stdin.c b/grading/userprog/write-stdin.c deleted file mode 100644 index 1c6862b..0000000 --- a/grading/userprog/write-stdin.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -int -main (void) -{ - char buf = 123; - printf ("(write-stdin) begin\n"); - write (0, &buf, 1); - printf ("(write-stdin) end\n"); - return 0; -} diff --git a/grading/userprog/write-stdin.exp b/grading/userprog/write-stdin.exp deleted file mode 100644 index bee46e0..0000000 --- a/grading/userprog/write-stdin.exp +++ /dev/null @@ -1,6 +0,0 @@ -(write-stdin) begin -(write-stdin) end -write-stdin: exit(0) ---OR-- -(write-stdin) begin -write-stdin: exit(-1) diff --git a/grading/userprog/write-zero.c b/grading/userprog/write-zero.c deleted file mode 100644 index e5b5b95..0000000 --- a/grading/userprog/write-zero.c +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include - -int -main (void) -{ - int handle, byte_cnt; - char buf; - printf ("(write-zero) begin\n"); - - handle = open ("sample.txt"); - if (handle < 2) - printf ("(write-zero) fail: open() returned %d\n", handle); - - buf = 123; - byte_cnt = write (handle, &buf, 0); - if (byte_cnt != 0) - printf ("(write-zero) fail: write() returned %d instead of 0\n", byte_cnt); - - printf ("(write-zero) end\n"); - return 0; -} diff --git a/grading/userprog/write-zero.exp b/grading/userprog/write-zero.exp deleted file mode 100644 index ba76b0c..0000000 --- a/grading/userprog/write-zero.exp +++ /dev/null @@ -1,3 +0,0 @@ -(write-zero) begin -(write-zero) end -write-zero: exit(0) diff --git a/grading/vm/.cvsignore b/grading/vm/.cvsignore deleted file mode 100644 index a9fcf6e..0000000 --- a/grading/vm/.cvsignore +++ /dev/null @@ -1,26 +0,0 @@ -*.d -*.dsk -*.o -child-linear -child-mm-wrt -child-sort -mmap-close -mmap-exit -mmap-overlap -mmap-read -mmap-shuffle -mmap-twice -mmap-unmap -mmap-write -page-linear -page-merge-par -page-merge-seq -page-parallel -page-shuffle -pt-bad-addr -pt-big-stk-obj -pt-grow-stack -pt-write-code -zeros -bochsout.txt -bochsrc.txt diff --git a/grading/vm/Make.progs b/grading/vm/Make.progs deleted file mode 100644 index b79216a..0000000 --- a/grading/vm/Make.progs +++ /dev/null @@ -1,29 +0,0 @@ -# -*- makefile -*- - -TESTS = pt-grow-stack pt-big-stk-obj pt-bad-addr pt-write-code \ -page-linear page-parallel page-merge-seq page-merge-par page-shuffle \ -mmap-read mmap-close mmap-unmap mmap-overlap mmap-twice mmap-write \ -mmap-exit mmap-shuffle - -pt_grow_stack_SRC = pt-grow-stack.c ../lib/arc4.c ../lib/cksum.c -pt_big_stk_obj_SRC = pt-big-stk-obj.c ../lib/arc4.c ../lib/cksum.c -pt_bad_addr_SRC = pt-bad-addr.c -pt_write_code_SRC = pt-write-code.c -page_linear_SRC = page-linear.c ../lib/arc4.c -page_parallel_SRC = page-parallel.c -page_merge_seq_SRC = page-merge-seq.c ../lib/arc4.c -page_merge_par_SRC = page-merge-par.c ../lib/arc4.c -page_shuffle_SRC = page-shuffle.c ../lib/arc4.c ../lib/cksum.c -mmap_read_SRC = mmap-read.c -mmap_close_SRC = mmap-close.c -mmap_unmap_SRC = mmap-unmap.c -mmap_overlap_SRC = mmap-overlap.c -mmap_twice_SRC = mmap-twice.c -mmap_write_SRC = mmap-write.c -mmap_exit_SRC = mmap-exit.c -mmap_shuffle_SRC = mmap-shuffle.c ../lib/arc4.c ../lib/cksum.c - -PROGS = $(TESTS) child-linear child-sort child-mm-wrt -child_linear_SRC = child-linear.c ../lib/arc4.c -child_sort_SRC = child-sort.c -child_mm_wrt_SRC = child-mm-wrt.c diff --git a/grading/vm/Makefile b/grading/vm/Makefile deleted file mode 100644 index 7b3e7f3..0000000 --- a/grading/vm/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -include Make.progs - -SRCDIR = ../../src - -DISKS = $(patsubst %,%.dsk,$(TESTS)) - -disks: $(DISKS) - -mmap-close.dsk mmap-read.dsk mmap-unmap.dsk mmap-twice.dsk: sample.txt -page-parallel.dsk: child-linear -page-merge-seq.dsk page-merge-par.dsk: child-sort -mmap-overlap.dsk: zeros -mmap-exit.dsk: child-mm-wrt - -zeros: - dd if=/dev/zero of=$@ bs=1024 count=6 - -%.dsk: % - ./prep-disk --fs-disk=$@ $^ - -clean:: - rm -f $(DISKS) - -include $(SRCDIR)/Makefile.userprog - -CFLAGS += -Werror diff --git a/grading/vm/Makefile.posix b/grading/vm/Makefile.posix deleted file mode 100644 index 10d2c07..0000000 --- a/grading/vm/Makefile.posix +++ /dev/null @@ -1,45 +0,0 @@ -# This makefile allows the VM tests to be compiled on a POSIX host -# system. It's easier to test them on a host than under Pintos. -# Before and after using this Makefile, run `make clean' to ensure -# that Pintos and host objects don't interfere with one another. - -SRCDIR = ../../src - -include $(SRCDIR)/Make.config -include Make.progs - -VPATH = $(SRCDIR) - -DEFINES = -CPPFLAGS = -I. -CFLAGS = -Wall -W -Wstrict-prototypes -Wmissing-prototypes - -PROGS_SRC = $(foreach prog,$(PROGS),$($(subst -,_,$(prog))_SRC)) -PROGS_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(PROGS_SRC))) -PROGS_DEP = $(patsubst %.o,%.d,$(PROGS_OBJ)) - -all: $(PROGS) - -define TEMPLATE -$(2)_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$($(2)_SRC))) -$(1): $$($(2)_OBJ) posix-compat.o - $$(CC) $$(LDFLAGS) $$^ $$(LDLIBS) -o $$@ -endef - -$(foreach prog,$(PROGS),$(eval $(call TEMPLATE,$(prog),$(subst -,_,$(prog))))) - -$(PROGS): $(LIB) - -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/grading/vm/child-mm-wrt.c b/grading/vm/child-mm-wrt.c deleted file mode 100644 index b0794ec..0000000 --- a/grading/vm/child-mm-wrt.c +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#ifdef PINTOS -#include -#else -#include "posix-compat.h" -#endif -#include "sample.inc" - -#define ACTUAL ((void *) 0x10000000) - -int -main (void) -{ - int fd; - - printf ("(child-mm-wrt) begin\n"); - - /* Write file via mmap. */ - if (!create ("sample.txt", strlen (sample))) - { - printf ("(child-mm-wrt) create() failed\n"); - return 1; - } - - fd = open ("sample.txt"); - if (fd < 0) - { - printf ("(child-mm-wrt) open() failed\n"); - return 1; - } - - if (mmap (fd, ACTUAL) == MAP_FAILED) - { - printf ("(child-mm-wrt) mmap() failed\n"); - return 1; - } - memcpy (ACTUAL, sample, strlen (sample)); - - printf ("(child-mm-wrt) end\n"); - - return 234; -} - diff --git a/grading/vm/child-sort.c b/grading/vm/child-sort.c deleted file mode 100644 index 56161b9..0000000 --- a/grading/vm/child-sort.c +++ /dev/null @@ -1,41 +0,0 @@ -#include -#ifdef PINTOS -#include -#else -#include "posix-compat.h" -#endif - -unsigned char buf[65536]; -size_t histogram[256]; - -int -main (int argc __attribute__ ((unused)), char *argv[]) -{ - int fd; - unsigned char *p; - size_t size; - size_t i; - - fd = open (argv[1]); - if (fd < 0) - { - printf ("(child-sort) open() failed\n"); - return 1; - } - - size = read (fd, 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 (fd, 0); - write (fd, buf, size); - close (fd); - - return 123; -} diff --git a/grading/vm/lib/.cvsignore b/grading/vm/lib/.cvsignore deleted file mode 100644 index a438335..0000000 --- a/grading/vm/lib/.cvsignore +++ /dev/null @@ -1 +0,0 @@ -*.d diff --git a/grading/vm/lib/user/.cvsignore b/grading/vm/lib/user/.cvsignore deleted file mode 100644 index a438335..0000000 --- a/grading/vm/lib/user/.cvsignore +++ /dev/null @@ -1 +0,0 @@ -*.d diff --git a/grading/vm/mmap-close.c b/grading/vm/mmap-close.c deleted file mode 100644 index 335f244..0000000 --- a/grading/vm/mmap-close.c +++ /dev/null @@ -1,49 +0,0 @@ -#include -#include -#include "../lib/arc4.h" -#include "sample.inc" -#ifdef PINTOS -#include -#else -#include "posix-compat.h" -#endif - -#define ACTUAL ((void *) 0x10000000) - -int -main (void) -{ - int fd; - mapid_t map; - - printf ("(mmap-close) begin\n"); - - fd = open ("sample.txt"); - if (fd < 0) - { - printf ("(mmap-close) open() failed\n"); - return 1; - } - - map = mmap (fd, ACTUAL); - if (map == MAP_FAILED) - { - printf ("(mmap-close) mmap() failed\n"); - return 1; - } - - close (fd); - - if (memcmp (ACTUAL, sample, strlen (sample))) - { - printf ("(mmap-close) read of mmap'd file reported bad data\n"); - return 1; - } - - munmap (map); - - /* Done. */ - printf ("(mmap-close) end\n"); - - return 0; -} diff --git a/grading/vm/mmap-close.exp b/grading/vm/mmap-close.exp deleted file mode 100644 index b59b149..0000000 --- a/grading/vm/mmap-close.exp +++ /dev/null @@ -1,2 +0,0 @@ -(mmap-close) begin -(mmap-close) end diff --git a/grading/vm/mmap-exit.c b/grading/vm/mmap-exit.c deleted file mode 100644 index 3601df8..0000000 --- a/grading/vm/mmap-exit.c +++ /dev/null @@ -1,58 +0,0 @@ -#include -#include -#ifdef PINTOS -#include -#else -#include "posix-compat.h" -#endif -#include "sample.inc" - -#define ACTUAL ((void *) 0x10000000) - -int -main (void) -{ - pid_t child; - int code; - int fd; - char buf[1024]; - - printf ("(mmap-exit) begin\n"); - - /* Make child write file. */ - printf ("(mmap-exit) run child\n"); - child = exec ("child-mm-wrt"); - if (child == -1) - { - printf ("(mmap-exit) exec() failed\n"); - return 1; - } - code = wait (child); - if (code != 234) - { - printf ("(mmap-exit) wait() returned bad exit code: %d\n", code); - return 1; - } - printf ("(mmap-exit) child finished\n"); - - /* Read back via read(). */ - fd = open ("sample.txt"); - if (fd < 0) - { - printf ("(mmap-exit) open() failed\n"); - return 1; - } - - read (fd, buf, strlen (sample)); - if (memcmp (buf, sample, strlen (sample))) - { - printf ("(mmap-exit) read of mmap-written file reported bad data\n"); - return 1; - } - close (fd); - - /* Done. */ - printf ("(mmap-exit) end\n"); - - return 0; -} diff --git a/grading/vm/mmap-exit.exp b/grading/vm/mmap-exit.exp deleted file mode 100644 index f0ae820..0000000 --- a/grading/vm/mmap-exit.exp +++ /dev/null @@ -1,6 +0,0 @@ -(mmap-exit) begin -(mmap-exit) run child -(child-mm-wrt) begin -(child-mm-wrt) end -(mmap-exit) child finished -(mmap-exit) end diff --git a/grading/vm/mmap-overlap.c b/grading/vm/mmap-overlap.c deleted file mode 100644 index d2104b8..0000000 --- a/grading/vm/mmap-overlap.c +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include -#ifdef PINTOS -#include -#else -#include "posix-compat.h" -#endif -#include "sample.inc" - -int -main (void) -{ - char *start = (char *) 0x10000000; - size_t i; - int fd[2]; - -#ifndef PINTOS - printf ("Sorry, this test won't work on POSIX,\n" - "because POSIX will accept overlapping mmaps.\n"); - abort (); -#endif - - printf ("(mmap-overlap) begin\n"); - - for (i = 0; i < 2; i++) - { - fd[i] = open ("zeros"); - if (fd[i] < 0) - { - printf ("(mmap-overlap) open() failed\n"); - return 1; - } - if (mmap (fd[i], start) == MAP_FAILED) - { - if (i == 1) - return 0; - else - { - printf ("(mmap-overlap) mmap() failed\n"); - return 1; - } - } - start += 4096; - } - - printf ("(mmap-overlap) fail: mmap of overlapped blocks succeeded\n"); - - /* Done. */ - printf ("(mmap-overlap) end\n"); - - return 0; -} diff --git a/grading/vm/mmap-overlap.exp b/grading/vm/mmap-overlap.exp deleted file mode 100644 index 6b0341a..0000000 --- a/grading/vm/mmap-overlap.exp +++ /dev/null @@ -1 +0,0 @@ -(mmap-overlap) begin diff --git a/grading/vm/mmap-read.c b/grading/vm/mmap-read.c deleted file mode 100644 index cb25ba1..0000000 --- a/grading/vm/mmap-read.c +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include -#ifdef PINTOS -#include -#else -#include "posix-compat.h" -#endif -#include "sample.inc" - -#define ACTUAL ((void *) 0x10000000) - -int -main (void) -{ - int fd; - mapid_t map; - - printf ("(mmap-read) begin\n"); - - fd = open ("sample.txt"); - if (fd < 0) - { - printf ("(mmap-read) open() failed\n"); - return 1; - } - - map = mmap (fd, ACTUAL); - if (map == MAP_FAILED) - { - printf ("(mmap-read) mmap() failed\n"); - return 1; - } - - if (memcmp (ACTUAL, sample, strlen (sample))) - { - printf ("(mmap-read) read of mmap'd file reported bad data\n"); - return 1; - } - - if (!munmap (map)) - { - printf ("(mmap-read) munmap() failed\n"); - return 1; - } - - close (fd); - - /* Done. */ - printf ("(mmap-read) end\n"); - - return 0; -} diff --git a/grading/vm/mmap-read.exp b/grading/vm/mmap-read.exp deleted file mode 100644 index e3ae90f..0000000 --- a/grading/vm/mmap-read.exp +++ /dev/null @@ -1,2 +0,0 @@ -(mmap-read) begin -(mmap-read) end diff --git a/grading/vm/mmap-shuffle.c b/grading/vm/mmap-shuffle.c deleted file mode 100644 index 15a37f4..0000000 --- a/grading/vm/mmap-shuffle.c +++ /dev/null @@ -1,98 +0,0 @@ -#include -#include -#ifdef PINTOS -#include -#else -#include "posix-compat.h" -#endif -#include "../lib/arc4.h" -#include "../lib/cksum.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 SIZE (126 * 512) - -static char *buf = (char *) 0x10000000; - -static struct arc4 * -get_key (void) -{ - static struct arc4 arc4; - static bool inited = false; - if (!inited) - { - arc4_init (&arc4, "quux", 4); - inited = true; - } - return &arc4; -} - -static unsigned long -random_ulong (void) -{ - static unsigned long x; - arc4_crypt (get_key (), &x, sizeof x); - return x; -} - -static void -shuffle (void) -{ - size_t i; - - for (i = 0; i < SIZE; i++) - { - size_t j = i + random_ulong () % (SIZE - i); - char t = buf[i]; - buf[i] = buf[j]; - buf[j] = t; - } -} - -int -main (void) -{ - size_t i; - int fd; - - printf ("(mmap-shuffle) begin\n"); - - /* Create file, mmap. */ - if (!create ("buffer", SIZE)) - { - printf ("(mmap-shuffle) create() failed\n"); - return 1; - } - - fd = open ("buffer"); - if (fd < 0) - { - printf ("(mmap-shuffle) open() failed\n"); - return 1; - } - - if (mmap (fd, buf) == MAP_FAILED) - { - printf ("(mmap-shuffle) mmap() failed\n"); - return 1; - } - - /* Initialize. */ - for (i = 0; i < SIZE; i++) - buf[i] = i * 257; - printf ("(mmap-shuffle) init: cksum=%lu\n", cksum (buf, SIZE)); - - /* Shuffle repeatedly. */ - for (i = 0; i < 10; i++) - { - shuffle (); - printf ("(mmap-shuffle) shuffle %zu: cksum=%lu\n", - i, cksum (buf, SIZE)); - } - - /* Done. */ - printf ("(mmap-shuffle) end\n"); - - return 0; -} diff --git a/grading/vm/mmap-shuffle.exp b/grading/vm/mmap-shuffle.exp deleted file mode 100644 index ef76d85..0000000 --- a/grading/vm/mmap-shuffle.exp +++ /dev/null @@ -1,13 +0,0 @@ -(mmap-shuffle) begin -(mmap-shuffle) init: cksum=1573974830 -(mmap-shuffle) shuffle 0: cksum=1944930050 -(mmap-shuffle) shuffle 1: cksum=3381674432 -(mmap-shuffle) shuffle 2: cksum=3536554503 -(mmap-shuffle) shuffle 3: cksum=373089786 -(mmap-shuffle) shuffle 4: cksum=3509490433 -(mmap-shuffle) shuffle 5: cksum=4003286447 -(mmap-shuffle) shuffle 6: cksum=2956275536 -(mmap-shuffle) shuffle 7: cksum=2417689162 -(mmap-shuffle) shuffle 8: cksum=2870274961 -(mmap-shuffle) shuffle 9: cksum=3894560396 -(mmap-shuffle) end diff --git a/grading/vm/mmap-twice.c b/grading/vm/mmap-twice.c deleted file mode 100644 index a2b5a10..0000000 --- a/grading/vm/mmap-twice.c +++ /dev/null @@ -1,45 +0,0 @@ -#include -#include -#ifdef PINTOS -#include -#else -#include "posix-compat.h" -#endif -#include "sample.inc" - -int -main (void) -{ - char *actual[2] = {(char *) 0x10000000, (char *) 0x20000000}; - size_t i; - int fd[2]; - - printf ("(mmap-twice) begin\n"); - - for (i = 0; i < 2; i++) - { - fd[i] = open ("sample.txt"); - if (fd[i] < 0) - { - printf ("(mmap-twice) open() #%zu failed\n", i); - return 1; - } - if (mmap (fd[i], actual[i]) == MAP_FAILED) - { - printf ("(mmap-twice) mmap() #%zu failed\n", i); - return 1; - } - } - - for (i = 0; i < 2; i++) - if (memcmp (actual[i], sample, strlen (sample))) - { - printf ("(mmap-twice) read of mmap'd file %zu reported bad data\n", - i); - return 1; - } - - printf ("(mmap-twice) end\n"); - - return 0; -} diff --git a/grading/vm/mmap-twice.exp b/grading/vm/mmap-twice.exp deleted file mode 100644 index 302eaea..0000000 --- a/grading/vm/mmap-twice.exp +++ /dev/null @@ -1,2 +0,0 @@ -(mmap-twice) begin -(mmap-twice) end diff --git a/grading/vm/mmap-unmap.c b/grading/vm/mmap-unmap.c deleted file mode 100644 index 327158b..0000000 --- a/grading/vm/mmap-unmap.c +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#ifdef PINTOS -#include -#else -#include "posix-compat.h" -#endif -#include "sample.inc" - -#define ACTUAL ((void *) 0x10000000) - -int -main (void) -{ - int fd; - mapid_t map; - - printf ("(mmap-unmap) begin\n"); - - fd = open ("sample.txt"); - if (fd < 0) - { - printf ("(mmap-unmap) open() failed\n"); - return 1; - } - - map = mmap (fd, ACTUAL); - if (map == MAP_FAILED) - { - printf ("(mmap-unmap) mmap() failed\n"); - return 1; - } - - munmap (map); - - printf ("(mmap-unmap) FAIL: unmapped memory is readable (%d)\n", - *(int *) ACTUAL); - - /* Done. */ - printf ("(mmap-unmap) end\n"); - - return 0; -} diff --git a/grading/vm/mmap-write.c b/grading/vm/mmap-write.c deleted file mode 100644 index 2a9cda4..0000000 --- a/grading/vm/mmap-write.c +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include -#ifdef PINTOS -#include -#else -#include "posix-compat.h" -#endif -#include "sample.inc" - -#define ACTUAL ((void *) 0x10000000) - -int -main (void) -{ - int fd; - mapid_t map; - char buf[1024]; - - printf ("(mmap-write) begin\n"); - - /* Write file via mmap. */ - if (!create ("sample.txt", strlen (sample))) - { - printf ("(mmap-write) create() failed\n"); - return 1; - } - - fd = open ("sample.txt"); - if (fd < 0) - { - printf ("(mmap-write) open() failed\n"); - return 1; - } - - map = mmap (fd, ACTUAL); - if (map == MAP_FAILED) - { - printf ("(mmap-write) mmap() failed\n"); - return 1; - } - memcpy (ACTUAL, sample, strlen (sample)); - munmap (map); - - /* Read back via read(). */ - read (fd, buf, strlen (sample)); - if (memcmp (ACTUAL, sample, strlen (sample))) - { - printf ("(mmap-write) read of mmap-written file reported bad data\n"); - return 1; - } - close (fd); - - /* Done. */ - printf ("(mmap-write) end\n"); - - return 0; -} diff --git a/grading/vm/mmap-write.exp b/grading/vm/mmap-write.exp deleted file mode 100644 index b9fa76f..0000000 --- a/grading/vm/mmap-write.exp +++ /dev/null @@ -1,2 +0,0 @@ -(mmap-write) begin -(mmap-write) end diff --git a/grading/vm/page-linear.c b/grading/vm/page-linear.c deleted file mode 100644 index 18ba30c..0000000 --- a/grading/vm/page-linear.c +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include "../lib/arc4.h" - -#define SIZE (128 * 1024) - -static char buf[SIZE]; - -int -main (void) -{ - struct arc4 arc4; - size_t i; - - printf ("(page-linear) begin\n"); - - /* Encrypt zeros. */ - printf ("(page-linear) read/modify/write pass one\n"); - arc4_init (&arc4, "foobar", 6); - arc4_crypt (&arc4, buf, SIZE); - - /* Decrypt back to zeros. */ - printf ("(page-linear) read/modify/write pass two\n"); - arc4_init (&arc4, "foobar", 6); - arc4_crypt (&arc4, buf, SIZE); - - /* Check that it's all zeros. */ - printf ("(page-linear) read pass\n"); - for (i = 0; i < SIZE; i++) - if (buf[i] != '\0') - { - printf ("(page-linear) byte %zu != 0\n", i); - return 1; - } - - /* Done. */ - printf ("(page-linear) end\n"); - - return 0; -} diff --git a/grading/vm/page-linear.exp b/grading/vm/page-linear.exp deleted file mode 100644 index 4125d13..0000000 --- a/grading/vm/page-linear.exp +++ /dev/null @@ -1,5 +0,0 @@ -(page-linear) begin -(page-linear) read/modify/write pass one -(page-linear) read/modify/write pass two -(page-linear) read pass -(page-linear) end diff --git a/grading/vm/page-merge-par.exp b/grading/vm/page-merge-par.exp deleted file mode 100644 index c669849..0000000 --- a/grading/vm/page-merge-par.exp +++ /dev/null @@ -1,14 +0,0 @@ -(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) merge -(page-merge-par) verify -(page-merge-par) success, buf_idx=516096 -(page-merge-par) end diff --git a/grading/vm/page-parallel.c b/grading/vm/page-parallel.c deleted file mode 100644 index 034a162..0000000 --- a/grading/vm/page-parallel.c +++ /dev/null @@ -1,39 +0,0 @@ -#include -#ifdef PINTOS -#include -#else -#include "posix-compat.h" -#endif - -#define CHILD_CNT 3 - -int -main (void) -{ - pid_t children[CHILD_CNT]; - int i; - - printf ("(page-parallel) begin\n"); - for (i = 0; i < CHILD_CNT; i++) - { - printf ("(page-parallel) start child %d\n", i); - children[i] = exec ("child-linear"); - if (children[i] == -1) - { - printf ("(page-parallel) exec() returned pid -1\n"); - return 1; - } - } - - for (i = 0; i < CHILD_CNT; i++) - { - int code; - printf ("(page-parallel) wait for child %d\n", i); - code = wait (children[i]); - if (code != 0x42) - printf ("(page-parallel) child %d returned bad exit code\n", i); - } - printf ("(page-parallel) end\n"); - - return 0; -} diff --git a/grading/vm/page-parallel.exp b/grading/vm/page-parallel.exp deleted file mode 100644 index 6a2a0b8..0000000 --- a/grading/vm/page-parallel.exp +++ /dev/null @@ -1,8 +0,0 @@ -(page-parallel) begin -(page-parallel) start child 0 -(page-parallel) start child 1 -(page-parallel) start child 2 -(page-parallel) wait for child 0 -(page-parallel) wait for child 1 -(page-parallel) wait for child 2 -(page-parallel) end diff --git a/grading/vm/page-shuffle.c b/grading/vm/page-shuffle.c deleted file mode 100644 index ae2559e..0000000 --- a/grading/vm/page-shuffle.c +++ /dev/null @@ -1,69 +0,0 @@ -#include -#include -#include "../lib/arc4.h" -#include "../lib/cksum.h" - -#define SIZE (128 * 1024) - -static char buf[SIZE]; - -static struct arc4 * -get_key (void) -{ - static struct arc4 arc4; - static bool inited = false; - if (!inited) - { - arc4_init (&arc4, "quux", 4); - inited = true; - } - return &arc4; -} - -static unsigned long -random_ulong (void) -{ - static unsigned long x; - arc4_crypt (get_key (), &x, sizeof x); - return x; -} - -static void -shuffle (void) -{ - size_t i; - - for (i = 0; i < sizeof buf; i++) - { - size_t j = i + random_ulong () % (sizeof buf - i); - char t = buf[i]; - buf[i] = buf[j]; - buf[j] = t; - } -} - -int -main (void) -{ - size_t i; - - printf ("(page-shuffle) begin\n"); - - /* Initialize. */ - for (i = 0; i < sizeof buf; i++) - buf[i] = i * 257; - printf ("(page-shuffle) init: cksum=%lu\n", cksum (buf, sizeof buf)); - - /* Shuffle repeatedly. */ - for (i = 0; i < 10; i++) - { - shuffle (); - printf ("(page-shuffle) shuffle %zu: cksum=%lu\n", - i, cksum (buf, sizeof buf)); - } - - /* Done. */ - printf ("(page-shuffle) end\n"); - - return 0; -} diff --git a/grading/vm/page-shuffle.exp b/grading/vm/page-shuffle.exp deleted file mode 100644 index 48b99ef..0000000 --- a/grading/vm/page-shuffle.exp +++ /dev/null @@ -1,13 +0,0 @@ -(page-shuffle) begin -(page-shuffle) init: cksum=3115322833 -(page-shuffle) shuffle 0: cksum=63424829 -(page-shuffle) shuffle 1: cksum=3779635387 -(page-shuffle) shuffle 2: cksum=384449947 -(page-shuffle) shuffle 3: cksum=1883585126 -(page-shuffle) shuffle 4: cksum=2588854009 -(page-shuffle) shuffle 5: cksum=2250425557 -(page-shuffle) shuffle 6: cksum=866944104 -(page-shuffle) shuffle 7: cksum=3710004019 -(page-shuffle) shuffle 8: cksum=3936244654 -(page-shuffle) shuffle 9: cksum=2799827580 -(page-shuffle) end diff --git a/grading/vm/patches/00random.patch b/grading/vm/patches/00random.patch deleted file mode 100644 index 3648632..0000000 --- a/grading/vm/patches/00random.patch +++ /dev/null @@ -1,96 +0,0 @@ -Modifies bitmap_scan() to return a random set of bits instead of the -first set. Helps to stress-test VM implementation. - -Index: pintos/src/lib/kernel/bitmap.c -=================================================================== -RCS file: /afs/ir.stanford.edu/users/b/l/blp/private/cvs/pintos/src/lib/kernel/bitmap.c,v -retrieving revision 1.11 -diff -u -p -r1.11 bitmap.c ---- pintos/src/lib/kernel/bitmap.c 2 Jan 2005 02:09:58 -0000 1.11 -+++ pintos/src/lib/kernel/bitmap.c 9 Feb 2005 21:45:27 -0000 -@@ -3,6 +3,7 @@ - #include - #include - #include -+#include - #include "threads/malloc.h" - #ifdef FILESYS - #include "filesys/file.h" -@@ -30,6 +31,8 @@ struct bitmap - elem_type *bits; /* Elements that represent bits. */ - }; - -+bool randomize_bitmaps; -+ - /* Returns the index of the element that contains the bit - numbered BIT_IDX. */ - static inline size_t -@@ -227,9 +230,28 @@ bitmap_scan (const struct bitmap *b, siz - { - size_t last = b->bit_cnt - cnt; - size_t i; -+ size_t n = 0; -+ -+ /* Count number of matches. */ - for (i = start; i <= last; i++) -- if (!contains (b, i, cnt, !value)) -- return i; -+ if (!contains (b, i, cnt, !value)) -+ { -+ if (randomize_bitmaps) -+ n++; -+ else -+ return i; -+ } -+ -+ /* Pick one match. */ -+ if (n != 0) -+ { -+ random_init (0); -+ n = random_ulong () % n; -+ for (i = start; i <= last; i++) -+ if (!contains (b, i, cnt, !value) && n-- == 0) -+ return i; -+ NOT_REACHED (); -+ } - } - return BITMAP_ERROR; - } -Index: pintos/src/lib/kernel/bitmap.h -=================================================================== -RCS file: /afs/ir.stanford.edu/users/b/l/blp/private/cvs/pintos/src/lib/kernel/bitmap.h,v -retrieving revision 1.5 -diff -u -p -r1.5 bitmap.h ---- pintos/src/lib/kernel/bitmap.h 15 Dec 2004 06:08:55 -0000 1.5 -+++ pintos/src/lib/kernel/bitmap.h 9 Feb 2005 21:45:27 -0000 -@@ -44,4 +44,6 @@ size_t bitmap_needed_bytes (size_t bit_c - struct bitmap *bitmap_create_preallocated (size_t bit_cnt, - void *, size_t byte_cnt); - -+extern bool randomize_bitmaps; -+ - #endif /* lib/kernel/bitmap.h */ -Index: pintos/src/threads/init.c -=================================================================== -RCS file: /afs/ir.stanford.edu/users/b/l/blp/private/cvs/pintos/src/threads/init.c,v -retrieving revision 1.53 -diff -u -p -r1.53 init.c ---- pintos/src/threads/init.c 7 Feb 2005 05:56:07 -0000 1.53 -+++ pintos/src/threads/init.c 9 Feb 2005 21:45:28 -0000 -@@ -8,6 +8,7 @@ - #include - #include - #include -+#include - #include "devices/kbd.h" - #include "devices/serial.h" - #include "devices/timer.h" -@@ -193,6 +194,8 @@ paging_init (void) - new page tables immediately. See [IA32-v2a] "MOV--Move - to/from Control Registers" and [IA32-v3] 3.7.5. */ - asm volatile ("mov %%cr3, %0" :: "r" (vtop (base_page_dir))); -+ -+ randomize_bitmaps = true; - } - - /* Parses the command line. */ diff --git a/grading/vm/posix-compat.c b/grading/vm/posix-compat.c deleted file mode 100644 index 0625461..0000000 --- a/grading/vm/posix-compat.c +++ /dev/null @@ -1,187 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include "posix-compat.h" - -#undef halt -#undef exit -#undef exec -#undef wait -#undef create -#undef remove -#undef open -#undef filesize -#undef read -#undef write -#undef seek -#undef tell -#undef close -#undef mmap -#undef munmap -#undef chdir -#undef mkdir -#undef lsdir - -void -pintos_halt (void) -{ - exit (-1); -} - -void -pintos_exit (int status) -{ - if (status < -127 || status > 255) - printf ("warning: non-POSIX exit code %d\n", status); - exit (status); -} - -pid_t -pintos_exec (const char *cmd_line) -{ - pid_t child = fork (); - if (child == -1) - return child; - else if (child == 0) - { - char copy[1024]; - char *args[128]; - char **argp; - - strcpy (copy, cmd_line); - - argp = args; - *argp = strtok (copy, " "); - while (*argp++ != NULL) - *argp = strtok (NULL, " "); - - execv (args[0], args); - abort (); - } - else - return child; -} - -int -pintos_wait (pid_t child) -{ - int status = 0; - waitpid (child, &status, 0); - return WIFEXITED (status) ? WEXITSTATUS (status) : -1; -} - -bool -pintos_create (const char *file, unsigned initial_size) -{ - int fd = open (file, O_RDWR | O_CREAT | O_EXCL, 0666); - if (fd < 0) - return false; - ftruncate (fd, initial_size); - close (fd); - return true; -} - -bool -pintos_remove (const char *file) -{ - return unlink (file) == 0; -} - -int -pintos_open (const char *file) -{ - return open (file, O_RDWR); -} - -int -pintos_filesize (int fd) -{ - struct stat s; - fstat (fd, &s); - return s.st_size; -} - -int -pintos_read (int fd, void *buffer, unsigned length) -{ - return read (fd, buffer, length); -} - -int -pintos_write (int fd, const void *buffer, unsigned length) -{ - return write (fd, buffer, length); -} - -void -pintos_seek (int fd, unsigned position) -{ - lseek (fd, position, SEEK_SET); -} - - -unsigned -pintos_tell (int fd) -{ - return lseek (fd, 0, SEEK_CUR); -} - -void -pintos_close (int fd) -{ - close (fd); -} - -mapid_t -pintos_mmap (int fd, void *addr) -{ - size_t page_size = getpagesize (); - int length = pintos_filesize (fd); - uintptr_t ptr = (uintptr_t) mmap (addr, length, PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_FIXED, fd, 0); - return ptr | DIV_ROUND_UP (size, page_size); -} - -bool -pintos_munmap (void *addr, unsigned length) -{ - size_t page_size = getpagesize (); - uintptr_t page_mask = page_size - 1; - void *base = (void *) ((uintptr_t) addr & ~page_mask); - size_t length = ((uintptr_t) addr & page_mask) * page_size; - return munmap (base, length); -} - -bool -pintos_chdir (const char *dir) -{ - return chdir (dir) == 0; -} - -bool -pintos_mkdir (const char *dir) -{ - return mkdir (dir, 0777) == 0; -} - -void -pintos_lsdir (void) -{ - DIR *dir = opendir ("."); - if (dir != NULL) - { - struct dirent *de; - while ((de = readdir (dir)) != NULL) - { - if (!strcmp (de->d_name, ".") || !strcmp (de->d_name, "..")) - continue; - printf ("%s\n", de->d_name); - } - } - closedir (dir); -} diff --git a/grading/vm/posix-compat.h b/grading/vm/posix-compat.h deleted file mode 100644 index 70dee12..0000000 --- a/grading/vm/posix-compat.h +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef POSIX_COMPAT_H -#define POSIX_COMPAT_H - -#include -#include -#include -#include - -#define NO_RETURN __attribute__ ((noreturn)) - -#undef halt -#define halt pintos_halt -void pintos_halt (void) NO_RETURN; - -#undef exit -#define exit pintos_exit -void pintos_exit (int status) NO_RETURN; - -#undef exec -#define exec pintos_exec -pid_t pintos_exec (const char *file); - -#undef wait -#define wait pintos_wait -int pintos_wait (pid_t); - -#undef create -#define create pintos_create -bool pintos_create (const char *file, unsigned initial_size); - -#undef remove -#define remove pintos_remove -bool pintos_remove (const char *file); - -#undef open -#define open pintos_open -int pintos_open (const char *file); - -#undef filesize -#define filesize pintos_filesize -int pintos_filesize (int fd); - -#undef read -#define read pintos_read -int pintos_read (int fd, void *buffer, unsigned length); - -#undef write -#define write pintos_write -int pintos_write (int fd, const void *buffer, unsigned length); - -#undef seek -#define seek pintos_seek -void pintos_seek (int fd, unsigned position); - -#undef tell -#define tell pintos_tell -unsigned pintos_tell (int fd); - -#undef close -#define close pintos_close -void pintos_close (int fd); - -#undef mmap -#define mmap pintos_mmap -bool pintos_mmap (int fd, void *addr, unsigned length); - -#undef munmap -#define munmap pintos_munmap -bool pintos_munmap (void *addr, unsigned length); - -#undef chdir -#define chdir pintos_chdir -bool pintos_chdir (const char *dir); - -#undef mkdir -#define mkdir pintos_mkdir -bool pintos_mkdir (const char *dir); - -#undef lsdir -#define lsdir pintos_lsdir -void pintos_lsdir (void); - -#endif /* posix-compat.h */ diff --git a/grading/vm/prep-disk b/grading/vm/prep-disk deleted file mode 100755 index 50a6b79..0000000 --- a/grading/vm/prep-disk +++ /dev/null @@ -1,48 +0,0 @@ -#! /usr/bin/perl -w - -use strict; -use Getopt::Long; -use POSIX; - -my ($pintos) = "pintos"; -my ($os_disk) = "../../src/userprog/build/os.dsk"; -my ($fs_disk); - -GetOptions ("os-disk=s" => \$os_disk, - "fs-disk=s" => \$fs_disk, - "help" => sub { usage (0) }) - or die "option parsing failed; use --help for help\n"; - -if (!defined $fs_disk) { - die "output disk name expected; use --help for help\n" - if @ARGV < 1; - $fs_disk = shift @ARGV; -} - -if (! -e $os_disk) { - print STDERR "$os_disk: stat: $!\n"; - print STDERR "perhaps you should `make' in ../../src/userprog?\n"; - exit 1; -} - -our ($formatted) = 0; - -unlink $fs_disk; -xsystem ("$pintos make-disk '$fs_disk' 2"); -while (@ARGV) { - put_file (shift (@ARGV)); -} - -sub put_file { - my ($fn) = @_; - my ($cmd) = "$pintos -v --os-disk='$os_disk' --fs-disk='$fs_disk' put"; - $cmd .= " -f", $formatted = 1 if !$formatted; - $cmd .= " '$fn'"; - xsystem ($cmd); -} - -sub xsystem { - my ($cmd) = @_; - print "$cmd\n"; - system ($cmd) == 0 or die "command failed\n"; -} diff --git a/grading/vm/pt-bad-addr.c b/grading/vm/pt-bad-addr.c deleted file mode 100644 index 96a54ee..0000000 --- a/grading/vm/pt-bad-addr.c +++ /dev/null @@ -1,10 +0,0 @@ -#include - -int -main (void) -{ - printf ("(pt-bad-addr) begin\n"); - printf ("(pt-bad-addr) FAIL: bad addr read as %d\n", *(int *) 0x04000000); - printf ("(pt-bad-addr) end\n"); - return 0; -} diff --git a/grading/vm/pt-big-stk-obj.c b/grading/vm/pt-big-stk-obj.c deleted file mode 100644 index 3198c7c..0000000 --- a/grading/vm/pt-big-stk-obj.c +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include -#include "../lib/arc4.h" -#include "../lib/cksum.h" - -int -main (void) -{ - char stk_obj[65536]; - struct arc4 arc4; - - printf ("(pt-big-stk-obj) begin\n"); - arc4_init (&arc4, "foobar", 6); - memset (stk_obj, 0, sizeof stk_obj); - arc4_crypt (&arc4, stk_obj, sizeof stk_obj); - printf ("(pt-big-stk-obj) cksum: %lu\n", cksum (stk_obj, sizeof stk_obj)); - printf ("(pt-big-stk-obj) end\n"); - return 0; -} diff --git a/grading/vm/pt-big-stk-obj.exp b/grading/vm/pt-big-stk-obj.exp deleted file mode 100644 index 3b962e1..0000000 --- a/grading/vm/pt-big-stk-obj.exp +++ /dev/null @@ -1,3 +0,0 @@ -(pt-big-stk-obj) begin -(pt-big-stk-obj) cksum: 3256410166 -(pt-big-stk-obj) end diff --git a/grading/vm/pt-grow-stack.c b/grading/vm/pt-grow-stack.c deleted file mode 100644 index 596285a..0000000 --- a/grading/vm/pt-grow-stack.c +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include -#include "../lib/arc4.h" -#include "../lib/cksum.h" - -int -main (void) -{ - char stack_obj[4096]; - struct arc4 arc4; - - printf ("(pt-grow-stack) begin\n"); - arc4_init (&arc4, "foobar", 6); - memset (stack_obj, 0, sizeof stack_obj); - arc4_crypt (&arc4, stack_obj, sizeof stack_obj); - printf ("(pt-grow-stack) cksum: %lu\n", cksum (stack_obj, sizeof stack_obj)); - printf ("(pt-grow-stack) end\n"); - return 0; -} diff --git a/grading/vm/pt-grow-stack.exp b/grading/vm/pt-grow-stack.exp deleted file mode 100644 index 4ea0331..0000000 --- a/grading/vm/pt-grow-stack.exp +++ /dev/null @@ -1,3 +0,0 @@ -(pt-grow-stack) begin -(pt-grow-stack) cksum: 3424492700 -(pt-grow-stack) end diff --git a/grading/vm/pt-write-code.c b/grading/vm/pt-write-code.c deleted file mode 100644 index a661c15..0000000 --- a/grading/vm/pt-write-code.c +++ /dev/null @@ -1,11 +0,0 @@ -#include - -int -main (void) -{ - printf ("(pt-write-code) begin\n"); - *(int *) main = 0; - printf ("(pt-write code) FAIL: writing the code segment succeeded\n"); - printf ("(pt-write-code) end\n"); - return 0; -} diff --git a/grading/vm/review.txt b/grading/vm/review.txt deleted file mode 100644 index a759345..0000000 --- a/grading/vm/review.txt +++ /dev/null @@ -1,50 +0,0 @@ -TESTCASES [[/10]] ------------------- - -3 Not explaining random vs. LRU algorithm - -3 Not testing/explaining heavily paging program - -3 Not testing/explaining mmap - -1 No other test cases - - -DESIGN [[/40]] --------------- - -DESIGNDOC: - -10 Doesn't discuss page table design - -10 Doesn't discuss swap design - -5 Doesn't discuss page replacement algorithm - -5 Doesn't explain how memory mapping works - -Overall: - -1 Gratuitous use of malloc() (e.g. for allocating a list or a lock) - -1 Inappropriate use of ASSERT (e.g. to verify that malloc() succeeded) - -Swap File Design - -10 Doesn't allow out of order pages within swap disk - -2 Does not synchronize data structure (give name of data structure here) - -Page Table / Page Replacement - -10 Doesn't use an inverted, hashed, or other efficient page table design - -5 Uses random/sequential page replacement - +2 Unusually clever page replacement - +5 Sharing code pages - -2 Does not synchronize data structure (give name of data structure here) - -Demand Paging / Memory Mapping - -10 Loading is not at all lazy - -5 Loading is not entirely lazy - -5 Stack does not grow downward from top of address space - -5 Stack does not grow automatically - -2 Does not synchronize data structure (give name of data structure here) - - -STYLE [[/10]] -------------- - -5...-10 Fixing code after submission - -5 Doesn't compile as submitted - +1...+5 Cool test programs etc. - - -COMMENTS --------- - diff --git a/grading/vm/run-tests b/grading/vm/run-tests deleted file mode 100755 index fea0bfb..0000000 --- a/grading/vm/run-tests +++ /dev/null @@ -1,86 +0,0 @@ -#! /usr/bin/perl - -# Find the directory that contains the grading files. -our ($GRADES_DIR); - -# Add our Perl library directory to the include path. -BEGIN { - ($GRADES_DIR = $0) =~ s#/[^/]+$##; - -d $GRADES_DIR or die "$GRADES_DIR: stat: $!\n"; - unshift @INC, "$GRADES_DIR/../lib"; -} - -use warnings; -use strict; -use Pintos::Grading; - -our ($hw) = "vm"; -our (@TESTS); # Tests to run. -our ($test); -our ($action); - -parse_cmd_line qw (pt-grow-stack pt-big-stk-obj pt-bad-addr pt-write-code - page-linear page-parallel page-merge-seq page-merge-par - page-shuffle mmap-read mmap-close mmap-unmap mmap-overlap - mmap-twice mmap-write mmap-exit mmap-shuffle); - -clean_dir (), exit if $action eq 'clean'; - -extract_sources (); -exit if $action eq 'extract'; - -build (); -exit if $action eq 'build'; - -run_and_grade_tests (); -write_grades (); -write_details (); -exit success () if $action eq 'test'; - -assemble_final_grade (); -exit success () if $action eq 'assemble'; - -die "Don't know how to '$action'"; - -# Runs $test in directory output/$test. -# Returns 'ok' if it went ok, otherwise an explanation. -sub run_test { - # Set up output directory. - xsystem ("cp $GRADES_DIR/$test.dsk output/$test/fs.dsk", - DIE => "cp failed\n"); - xsystem ("pintos make-disk output/$test/swap.dsk 2 >/dev/null 2>&1", - DIE => "failed to create swap disk"); - - # Run. - return run_pintos (["--os-disk=pintos/src/vm/build/os.dsk", - "--fs-disk=output/$test/fs.dsk", - "--swap-disk=output/$test/swap.dsk", - "-v", "run", "-q", "-ex", "$test"], - LOG => "$test/run", - TIMEOUT => 600); -} - -sub grade_process_death { - my ($proc_name, @output) = @_; - - verify_common (@output); - @output = get_core_output (@output); - die "First line of output is not `($proc_name) begin' message.\n" - if $output[0] ne "($proc_name) begin"; - die "Output contains `FAIL' message.\n" - if grep (/FAIL/, @output); - die "Output contains spurious ($proc_name) message.\n" - if grep (/\($proc_name\)/, @output) > 1; -} - -sub grade_pt_bad_addr { - grade_process_death ('pt-bad-addr', @_); -} - -sub grade_pt_write_code { - grade_process_death ('pt-write-code', @_); -} - -sub grade_mmap_unmap { - grade_process_death ('mmap-unmap', @_); -} diff --git a/grading/vm/tests.txt b/grading/vm/tests.txt deleted file mode 100644 index 8839084..0000000 --- a/grading/vm/tests.txt +++ /dev/null @@ -1,29 +0,0 @@ -CORRECTNESS [[total]] ---------------------- - -Page table management - -4 pt-grow-stack: stack should be able to grow beyond one page - -1 pt-big-stk-obj: stack should be able to handle large objects - -3 pt-bad-addr: accesses to invalid virtual memory should fault - -1 pt-write-code: should not be able to write to the code segment -Score: /9 - -Paging - -4 page-linear: sequentially read/modify/write memory twice then read it back - -2 page-parallel: write then read lots of memory--3 processes at once - -2 page-merge-seq: sequential merge sort of large data set - -1 page-merge-par: parallel merge sort of large data set - -3 page-shuffle: shuffle content of large static buffer -Score: /12 - -Memory mapping - -4 mmap-read: use mmap to read a file, munmap - -1 mmap-close: use mmap, close file, read from mapping, munmap - -1 mmap-unmap: after munmap virtual memory area should be invalid - -1 mmap-overlap: try to mmap files in overlapping virtual memory areas - -2 mmap-twice: mmap'ing same file twice should work - -4 mmap-write: after mmap-modify-munmap a read should reflect changes - -4 mmap-exit: after mmap-modify-exit a read should reflect changes - -2 mmap-shuffle: shuffle content of mapped file -Score: /19 - diff --git a/solutions/README b/solutions/README index a4dabc0..c56106d 100644 --- a/solutions/README +++ b/solutions/README @@ -1,9 +1,4 @@ Sample solutions. -* The solutions for p1-1, p1-2, and p2 are good enough. They - pass all the appropriate tests in the grading directory. - -* The solution for p3 is terrible. For example, there's no locking at - all. I just wrote it to make sure that the project was possible - given the tools that we gave the students. It also suffers from bit - rot and won't actually apply anymore. +These solutions are well written and pass all the corresponding tests. +You can run them automatically with `make check' in tests/. diff --git a/solutions/p1-1.patch b/solutions/p1-1.patch deleted file mode 100644 index e93b842..0000000 --- a/solutions/p1-1.patch +++ /dev/null @@ -1,126 +0,0 @@ -? solutions/p1-1.patch -Index: src/devices/timer.c -=================================================================== -RCS file: /afs/ir.stanford.edu/users/b/l/blp/private/cvs/pintos/src/devices/timer.c,v -retrieving revision 1.16 -diff -u -p -u -r1.16 timer.c ---- src/devices/timer.c 13 Dec 2004 22:42:01 -0000 1.16 -+++ src/devices/timer.c 31 Dec 2004 07:12:25 -0000 -@@ -27,6 +27,9 @@ static volatile int64_t ticks; - Initialized by timer_calibrate(). */ - static unsigned loops_per_tick; - -+/* Threads waiting in timer_sleep(). */ -+static struct list wait_list; -+ - static intr_handler_func timer_interrupt; - static bool too_many_loops (unsigned loops); - static void busy_wait (int64_t loops); -@@ -47,6 +50,8 @@ timer_init (void) - outb (0x40, count >> 8); - - intr_register (0x20, 0, INTR_OFF, timer_interrupt, "8254 Timer"); -+ -+ list_init (&wait_list); - } - - /* Calibrates loops_per_tick, used to implement brief delays. */ -@@ -91,15 +96,36 @@ timer_elapsed (int64_t then) - return timer_ticks () - then; - } - -+/* Compares two threads based on their wake-up times. */ -+static bool -+compare_threads_by_wakeup_time (const struct list_elem *a_, -+ const struct list_elem *b_, -+ void *aux UNUSED) -+{ -+ const struct thread *a = list_entry (a_, struct thread, timer_elem); -+ const struct thread *b = list_entry (b_, struct thread, timer_elem); -+ -+ return a->wakeup_time < b->wakeup_time; -+} -+ - /* Suspends execution for approximately TICKS timer ticks. */ - void - timer_sleep (int64_t ticks) - { -- int64_t start = timer_ticks (); -+ struct thread *t = thread_current (); -+ -+ /* Schedule our wake-up time. */ -+ t->wakeup_time = timer_ticks () + ticks; - -+ /* Atomically insert the current thread into the wait list. */ - ASSERT (intr_get_level () == INTR_ON); -- while (timer_elapsed (start) < ticks) -- thread_yield (); -+ intr_disable (); -+ list_insert_ordered (&wait_list, &t->timer_elem, -+ compare_threads_by_wakeup_time, NULL); -+ intr_enable (); -+ -+ /* Wait. */ -+ sema_down (&t->timer_sema); - } - - /* Suspends execution for approximately MS milliseconds. */ -@@ -138,6 +163,16 @@ timer_interrupt (struct intr_frame *args - thread_tick (); - if (ticks % TIME_SLICE == 0) - intr_yield_on_return (); -+ -+ while (!list_empty (&wait_list)) -+ { -+ struct thread *t = list_entry (list_front (&wait_list), -+ struct thread, timer_elem); -+ if (ticks < t->wakeup_time) -+ break; -+ sema_up (&t->timer_sema); -+ list_pop_front (&wait_list); -+ } - } - - /* Returns true if LOOPS iterations waits for more than one timer -Index: src/threads/thread.c -=================================================================== -RCS file: /afs/ir.stanford.edu/users/b/l/blp/private/cvs/pintos/src/threads/thread.c,v -retrieving revision 1.48 -diff -u -p -u -r1.48 thread.c ---- src/threads/thread.c 9 Oct 2004 18:01:37 -0000 1.48 -+++ src/threads/thread.c 31 Dec 2004 07:12:26 -0000 -@@ -337,6 +337,7 @@ init_thread (struct thread *t, const cha - t->stack = (uint8_t *) t + PGSIZE; - t->priority = priority; - t->magic = THREAD_MAGIC; -+ sema_init (&t->timer_sema, 0, "timer"); - } - - /* Allocates a SIZE-byte frame at the top of thread T's stack and -Index: src/threads/thread.h -=================================================================== -RCS file: /afs/ir.stanford.edu/users/b/l/blp/private/cvs/pintos/src/threads/thread.h,v -retrieving revision 1.28 -diff -u -p -u -r1.28 thread.h ---- src/threads/thread.h 29 Sep 2004 01:04:20 -0000 1.28 -+++ src/threads/thread.h 31 Dec 2004 07:12:26 -0000 -@@ -4,6 +4,7 @@ - #include - #include - #include -+#include "threads/synch.h" - - /* States in a thread's life cycle. */ - enum thread_status -@@ -91,6 +92,11 @@ struct thread - - /* Shared between thread.c and synch.c. */ - struct list_elem elem; /* List element. */ -+ -+ /* Problem 1-1. */ -+ int64_t wakeup_time; /* Time to wake this thread up. */ -+ struct list_elem timer_elem; /* Element in timer_wait_list. */ -+ struct semaphore timer_sema; /* Semaphore. */ - - #ifdef USERPROG - /* Owned by userprog/process.c. */ diff --git a/solutions/p1-2.patch b/solutions/p1-2.patch deleted file mode 100644 index 70944ed..0000000 --- a/solutions/p1-2.patch +++ /dev/null @@ -1,308 +0,0 @@ -Index: src/threads/synch.c -=================================================================== -RCS file: /afs/ir.stanford.edu/users/b/l/blp/private/cvs/pintos/src/threads/synch.c,v -retrieving revision 1.15 -diff -u -p -u -r1.15 synch.c ---- src/threads/synch.c 31 Dec 2004 21:13:38 -0000 1.15 -+++ src/threads/synch.c 31 Dec 2004 22:31:03 -0000 -@@ -77,6 +77,18 @@ sema_down (struct semaphore *sema) - intr_set_level (old_level); - } - -+/* Returns true if thread A has lower priority than thread B. */ -+static bool -+donated_lower_priority (const struct list_elem *a_, -+ const struct list_elem *b_, -+ void *aux UNUSED) -+{ -+ const struct thread *a = list_entry (a_, struct thread, donor_elem); -+ const struct thread *b = list_entry (b_, struct thread, donor_elem); -+ -+ return a->priority < b->priority; -+} -+ - /* Up or "V" operation on a semaphore. Increments SEMA's value - and wakes up one thread of those waiting for SEMA, if any. - -@@ -89,10 +100,28 @@ sema_up (struct semaphore *sema) - 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++; -+ if (!list_empty (&sema->waiters)) -+ { -+ /* Find highest-priority waiting thread. */ -+ struct thread *max = list_entry (list_max (&sema->waiters, -+ donated_lower_priority, NULL), -+ struct thread, elem); -+ -+ /* Remove `max' from wait list and unblock. */ -+ list_remove (&max->elem); -+ thread_unblock (max); -+ -+ /* Yield to a higher-priority thread, if we're running in a -+ context where it makes sense to do so. -+ -+ Kind of a funny interaction with donation here. -+ We only support donation for locks, and locks turn off -+ interrupts before calling us, so we automatically don't -+ do the yield here, delegating to lock_release(). */ -+ if (!intr_context () && old_level == INTR_ON) -+ thread_yield_to_higher_priority (); -+ } - intr_set_level (old_level); - } - -@@ -184,6 +213,23 @@ lock_acquire (struct lock *lock) - ASSERT (!lock_held_by_current_thread (lock)); - - old_level = intr_disable (); -+ -+ if (lock->holder != NULL) -+ { -+ /* Donate our priority to the thread holding the lock. -+ First, update the data structures. */ -+ struct thread *donor = thread_current (); -+ donor->want_lock = lock; -+ donor->donee = lock->holder; -+ list_push_back (&lock->holder->donors, &donor->donor_elem); -+ -+ /* Now implement the priority donation itself -+ by recomputing the donee's priority -+ and cascading the donation as far as necessary. */ -+ while (donor->donee != NULL && thread_recompute_priority (donor->donee)) -+ donor = donor->donee; -+ } -+ - sema_down (&lock->semaphore); - lock->holder = thread_current (); - intr_set_level (old_level); -@@ -198,13 +244,38 @@ void - lock_release (struct lock *lock) - { - enum intr_level old_level; -+ struct thread *t = thread_current (); -+ struct list_elem *e; - - ASSERT (lock != NULL); - ASSERT (lock_held_by_current_thread (lock)); - - old_level = intr_disable (); -+ -+ /* Remove donations from threads that want the lock we're -+ releasing. */ -+ for (e = list_begin (&t->donors); e != list_end (&t->donors); ) -+ { -+ struct thread *donor = list_entry (e, struct thread, donor_elem); -+ if (donor->want_lock == lock) -+ { -+ donor->donee = NULL; -+ e = list_remove (e); -+ } -+ else -+ e = list_next (e); -+ } -+ -+ /* Release lock. */ - lock->holder = NULL; - sema_up (&lock->semaphore); -+ -+ /* Recompute our priority based on our remaining donations, -+ then yield to a higher-priority ready thread if one now -+ exists. */ -+ thread_recompute_priority (t); -+ thread_yield_to_higher_priority (); -+ - intr_set_level (old_level); - } - -Index: src/threads/thread.c -=================================================================== -RCS file: /afs/ir.stanford.edu/users/b/l/blp/private/cvs/pintos/src/threads/thread.c,v -retrieving revision 1.48 -diff -u -p -u -r1.48 thread.c ---- src/threads/thread.c 9 Oct 2004 18:01:37 -0000 1.48 -+++ src/threads/thread.c 31 Dec 2004 22:31:03 -0000 -@@ -166,6 +166,8 @@ thread_create (const char *name, int pri - - /* Add to run queue. */ - thread_unblock (t); -+ if (priority > thread_get_priority ()) -+ thread_yield (); - - return tid; - } -@@ -186,6 +188,19 @@ thread_block (void) - schedule (); - } - -+/* Returns true if A has higher priority than B, -+ within a list of threads. */ -+static bool -+thread_higher_priority (const struct list_elem *a_, -+ const struct list_elem *b_, -+ void *aux UNUSED) -+{ -+ const struct thread *a = list_entry (a_, struct thread, elem); -+ const struct thread *b = list_entry (b_, struct thread, elem); -+ -+ return a->priority > b->priority; -+} -+ - /* 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.) */ -@@ -198,7 +212,7 @@ thread_unblock (struct thread *t) - - old_level = intr_disable (); - ASSERT (t->status == THREAD_BLOCKED); -- list_push_back (&ready_list, &t->elem); -+ list_insert_ordered (&ready_list, &t->elem, thread_higher_priority, NULL); - t->status = THREAD_READY; - intr_set_level (old_level); - } -@@ -266,8 +280,8 @@ thread_yield (void) - ASSERT (!intr_context ()); - - old_level = intr_disable (); -- list_push_back (&ready_list, &cur->elem); -+ list_insert_ordered (&ready_list, &cur->elem, thread_higher_priority, NULL); - cur->status = THREAD_READY; - schedule (); - intr_set_level (old_level); - } -@@ -274,19 +288,75 @@ - { - } - --/* 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 priority to PRIORITY. */ -+void -+thread_set_priority (int priority) -+{ -+ struct thread *t = thread_current (); -+ enum intr_level old_level; -+ -+ t->normal_priority = priority; -+ -+ old_level = intr_disable (); -+ while (thread_recompute_priority (t) && t->donee != NULL) -+ t = t->donee; -+ thread_yield_to_higher_priority (); -+ intr_set_level (old_level); -+} -+ -+/* Returns the current thread's priority. */ -+int -+thread_get_priority (void) -+{ -+ return thread_current ()->priority; -+} -+ -+/* Returns true if thread A has lower priority than thread B, -+ within a list of donors. */ -+static bool -+donated_lower_priority (const struct list_elem *a_, -+ const struct list_elem *b_, -+ void *aux UNUSED) -+{ -+ const struct thread *a = list_entry (a_, struct thread, donor_elem); -+ const struct thread *b = list_entry (b_, struct thread, donor_elem); -+ -+ return a->priority < b->priority; -+} -+ -+/* Recomputes T's priority in terms of its normal priority and -+ its donors' priorities, if any. -+ Returns true if T's new priority is higher than its old -+ priority, false otherwise. */ -+bool -+thread_recompute_priority (struct thread *t) -+{ -+ int old = t->priority; -+ int donation = 0; -+ if (!list_empty (&t->donors)) -+ donation = list_entry (list_max (&t->donors, donated_lower_priority, NULL), -+ struct thread, donor_elem)->priority; -+ t->priority = donation > t->normal_priority ? donation : t->normal_priority; -+ return t->priority > old; -+} -+ -+/* If the ready list contains a thread with a higher priority, -+ yields to it. */ -+void -+thread_yield_to_higher_priority (void) -+{ -+ enum intr_level old_level = intr_disable (); -+ if (!list_empty (&ready_list)) -+ { -+ struct thread *cur = thread_current (); -+ struct thread *max = list_entry (list_front (&ready_list), -+ struct thread, elem); -+ if (max->priority > cur->priority) -+ thread_yield (); -+ } -+ intr_set_level (old_level); -+} - - /* Idle thread. Executes when no other thread is ready to run. */ - static void -@@ -335,8 +417,9 @@ init_thread (struct thread *t, const cha - t->status = THREAD_BLOCKED; - strlcpy (t->name, name, sizeof t->name); - t->stack = (uint8_t *) t + PGSIZE; -- t->priority = priority; -+ t->priority = t->normal_priority = priority; - t->magic = THREAD_MAGIC; -+ list_init (&t->donors); - } - - /* Allocates a SIZE-byte frame at the top of thread T's stack and -Index: src/threads/thread.h -=================================================================== -RCS file: /afs/ir.stanford.edu/users/b/l/blp/private/cvs/pintos/src/threads/thread.h,v -retrieving revision 1.28 -diff -u -p -u -r1.28 thread.h ---- src/threads/thread.h 29 Sep 2004 01:04:20 -0000 1.28 -+++ src/threads/thread.h 31 Dec 2004 22:31:03 -0000 -@@ -87,7 +87,14 @@ struct thread - enum thread_status status; /* Thread state. */ - char name[16]; /* Name (for debugging purposes). */ - uint8_t *stack; /* Saved stack pointer. */ -- int priority; /* Priority. */ -+ -+ /* Thread priority. */ -+ int priority; /* Priority, including donations. */ -+ int normal_priority; /* Priority, without donations. */ -+ struct list donors; /* Threads donating priority to us. */ -+ struct list_elem donor_elem; /* Element in donors list. */ -+ struct thread *donee; /* Thread we're donating to. */ -+ struct lock *want_lock; /* Lock we're waiting to acquire. */ - - /* Shared between thread.c and synch.c. */ - struct list_elem elem; /* List element. */ -@@ -118,6 +125,8 @@ const char *thread_name (void); - - void thread_exit (void) NO_RETURN; - void thread_yield (void); -+void thread_yield_to_higher_priority (void); -+bool thread_recompute_priority (struct thread *); - - void thread_set_priority (int); - int thread_get_priority (void); diff --git a/solutions/p1.patch b/solutions/p1.patch new file mode 100644 index 0000000..213e05e --- /dev/null +++ b/solutions/p1.patch @@ -0,0 +1,737 @@ +diff -u src/devices/timer.c~ src/devices/timer.c +--- src/devices/timer.c~ 2005-05-24 15:52:43.000000000 -0700 ++++ src/devices/timer.c 2005-05-26 15:19:20.000000000 -0700 +@@ -23,6 +23,9 @@ static volatile int64_t ticks; + Initialized by timer_calibrate(). */ + static unsigned loops_per_tick; + ++/* Threads waiting in timer_sleep(). */ ++static struct list wait_list; ++ + static intr_handler_func timer_interrupt; + static bool too_many_loops (unsigned loops); + static void busy_wait (int64_t loops); +@@ -43,6 +46,8 @@ timer_init (void) + outb (0x40, count >> 8); + + intr_register_ext (0x20, timer_interrupt, "8254 Timer"); ++ ++ list_init (&wait_list); + } + + /* Calibrates loops_per_tick, used to implement brief delays. */ +@@ -87,15 +92,36 @@ timer_elapsed (int64_t then) + return timer_ticks () - then; + } + ++/* Compares two threads based on their wake-up times. */ ++static bool ++compare_threads_by_wakeup_time (const struct list_elem *a_, ++ const struct list_elem *b_, ++ void *aux UNUSED) ++{ ++ const struct thread *a = list_entry (a_, struct thread, timer_elem); ++ const struct thread *b = list_entry (b_, struct thread, timer_elem); ++ ++ return a->wakeup_time < b->wakeup_time; ++} ++ + /* Suspends execution for approximately TICKS timer ticks. */ + void + timer_sleep (int64_t ticks) + { +- int64_t start = timer_ticks (); ++ struct thread *t = thread_current (); ++ ++ /* Schedule our wake-up time. */ ++ t->wakeup_time = timer_ticks () + ticks; + ++ /* Atomically insert the current thread into the wait list. */ + ASSERT (intr_get_level () == INTR_ON); +- while (timer_elapsed (start) < ticks) +- thread_yield (); ++ intr_disable (); ++ list_insert_ordered (&wait_list, &t->timer_elem, ++ compare_threads_by_wakeup_time, NULL); ++ intr_enable (); ++ ++ /* Wait. */ ++ sema_down (&t->timer_sema); + } + + /* Suspends execution for approximately MS milliseconds. */ +@@ -132,6 +158,16 @@ timer_interrupt (struct intr_frame *args + { + ticks++; + thread_tick (); ++ ++ while (!list_empty (&wait_list)) ++ { ++ struct thread *t = list_entry (list_front (&wait_list), ++ struct thread, timer_elem); ++ if (ticks < t->wakeup_time) ++ break; ++ sema_up (&t->timer_sema); ++ list_pop_front (&wait_list); ++ } + } + + /* Returns true if LOOPS iterations waits for more than one timer +diff -u src/threads/fixed-point.h~ src/threads/fixed-point.h +--- src/threads/fixed-point.h~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/threads/fixed-point.h 2005-06-02 14:11:21.000000000 -0700 +@@ -0,0 +1,120 @@ ++#ifndef THREADS_FIXED_POINT_H ++#define THREADS_FIXED_POINT_H ++ ++#include ++ ++/* Parameters. */ ++#define FIX_BITS 32 /* Total bits per fixed-point number. */ ++#define FIX_P 16 /* Number of integer bits. */ ++#define FIX_Q 16 /* Number of fractional bits. */ ++#define FIX_F (1 << FIX_Q) /* pow(2, FIX_Q). */ ++ ++#define FIX_MIN_INT (-FIX_MAX_INT) /* Smallest representable integer. */ ++#define FIX_MAX_INT ((1 << FIX_P) - 1) /* Largest representable integer. */ ++ ++/* A fixed-point number. */ ++typedef struct ++ { ++ int f; ++ } ++fixed_point_t; ++ ++/* Returns a fixed-point number with F as its internal value. */ ++static inline fixed_point_t ++__mk_fix (int f) ++{ ++ fixed_point_t x; ++ x.f = f; ++ return x; ++} ++ ++/* Returns fixed-point number corresponding to integer N. */ ++static inline fixed_point_t ++fix_int (int n) ++{ ++ ASSERT (n >= FIX_MIN_INT && n <= FIX_MAX_INT); ++ return __mk_fix (n * FIX_F); ++} ++ ++/* Returns fixed-point number corresponding to N divided by D. */ ++static inline fixed_point_t ++fix_frac (int n, int d) ++{ ++ ASSERT (d != 0); ++ ASSERT (n / d >= FIX_MIN_INT && n / d <= FIX_MAX_INT); ++ return __mk_fix ((long long) n * FIX_F / d); ++} ++ ++/* Returns X rounded to the nearest integer. */ ++static inline int ++fix_round (fixed_point_t x) ++{ ++ return (x.f + FIX_F / 2) / FIX_F; ++} ++ ++/* Returns X truncated down to the nearest integer. */ ++static inline int ++fix_trunc (fixed_point_t x) ++{ ++ return x.f / FIX_F; ++} ++ ++/* Returns X + Y. */ ++static inline fixed_point_t ++fix_add (fixed_point_t x, fixed_point_t y) ++{ ++ return __mk_fix (x.f + y.f); ++} ++ ++/* Returns X - Y. */ ++static inline fixed_point_t ++fix_sub (fixed_point_t x, fixed_point_t y) ++{ ++ return __mk_fix (x.f - y.f); ++} ++ ++/* Returns X * Y. */ ++static inline fixed_point_t ++fix_mul (fixed_point_t x, fixed_point_t y) ++{ ++ return __mk_fix ((long long) x.f * y.f / FIX_F); ++} ++ ++/* Returns X * N. */ ++static inline fixed_point_t ++fix_scale (fixed_point_t x, int n) ++{ ++ ASSERT (n >= 0); ++ return __mk_fix (x.f * n); ++} ++ ++/* Returns X / Y. */ ++static inline fixed_point_t ++fix_div (fixed_point_t x, fixed_point_t y) ++{ ++ return __mk_fix ((long long) x.f * FIX_F / y.f); ++} ++ ++/* Returns X / N. */ ++static inline fixed_point_t ++fix_unscale (fixed_point_t x, int n) ++{ ++ ASSERT (n > 0); ++ return __mk_fix (x.f / n); ++} ++ ++/* Returns 1 / X. */ ++static inline fixed_point_t ++fix_inv (fixed_point_t x) ++{ ++ return fix_div (fix_int (1), x); ++} ++ ++/* Returns -1 if X < Y, 0 if X == Y, 1 if X > Y. */ ++static inline int ++fix_compare (fixed_point_t x, fixed_point_t y) ++{ ++ return x.f < y.f ? -1 : x.f > y.f; ++} ++ ++#endif /* threads/fixed-point.h */ +diff -u src/threads/synch.c~ src/threads/synch.c +--- src/threads/synch.c~ 2005-05-24 20:47:28.000000000 -0700 ++++ src/threads/synch.c 2005-06-02 14:20:15.000000000 -0700 +@@ -114,10 +114,28 @@ sema_up (struct semaphore *sema) + 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++; ++ if (!list_empty (&sema->waiters)) ++ { ++ /* Find highest-priority waiting thread. */ ++ struct thread *max = list_entry (list_max (&sema->waiters, ++ thread_lower_priority, NULL), ++ struct thread, elem); ++ ++ /* Remove `max' from wait list and unblock. */ ++ list_remove (&max->elem); ++ thread_unblock (max); ++ ++ /* Yield to a higher-priority thread, if we're running in a ++ context where it makes sense to do so. ++ ++ Kind of a funny interaction with donation here. ++ We only support donation for locks, and locks turn off ++ interrupts before calling us, so we automatically don't ++ do the yield here, delegating to lock_release(). */ ++ if (!intr_context () && old_level == INTR_ON) ++ thread_yield_to_higher_priority (); ++ } + intr_set_level (old_level); + } + +@@ -200,6 +218,23 @@ lock_acquire (struct lock *lock) + ASSERT (!lock_held_by_current_thread (lock)); + + old_level = intr_disable (); ++ ++ if (lock->holder != NULL) ++ { ++ /* Donate our priority to the thread holding the lock. ++ First, update the data structures. */ ++ struct thread *donor = thread_current (); ++ donor->want_lock = lock; ++ donor->donee = lock->holder; ++ list_push_back (&lock->holder->donors, &donor->donor_elem); ++ ++ /* Now implement the priority donation itself ++ by recomputing the donee's priority ++ and cascading the donation as far as necessary. */ ++ if (donor->donee != NULL) ++ thread_recompute_priority (donor->donee); ++ } ++ + sema_down (&lock->semaphore); + lock->holder = thread_current (); + intr_set_level (old_level); +@@ -238,13 +273,37 @@ void + lock_release (struct lock *lock) + { + enum intr_level old_level; ++ struct thread *t = thread_current (); ++ struct list_elem *e; + + ASSERT (lock != NULL); + ASSERT (lock_held_by_current_thread (lock)); + + old_level = intr_disable (); ++ ++ /* Return donations to threads that want this lock. */ ++ for (e = list_begin (&t->donors); e != list_end (&t->donors); ) ++ { ++ struct thread *donor = list_entry (e, struct thread, donor_elem); ++ if (donor->want_lock == lock) ++ { ++ donor->donee = NULL; ++ e = list_remove (e); ++ } ++ else ++ e = list_next (e); ++ } ++ ++ /* Release lock. */ + lock->holder = NULL; + sema_up (&lock->semaphore); ++ ++ /* Recompute our priority based on our remaining donations, ++ then yield to a higher-priority ready thread if one now ++ exists. */ ++ thread_recompute_priority (t); ++ thread_yield_to_higher_priority (); ++ + intr_set_level (old_level); + } + +@@ -264,6 +323,7 @@ struct semaphore_elem + { + struct list_elem elem; /* List element. */ + struct semaphore semaphore; /* This semaphore. */ ++ struct thread *thread; /* Thread. */ + }; + + /* Initializes condition variable COND. A condition variable +@@ -308,12 +368,26 @@ cond_wait (struct condition *cond, struc + ASSERT (lock_held_by_current_thread (lock)); + + sema_init (&waiter.semaphore, 0); ++ waiter.thread = thread_current (); + list_push_back (&cond->waiters, &waiter.elem); + lock_release (lock); + sema_down (&waiter.semaphore); + lock_acquire (lock); + } + ++static bool ++semaphore_elem_lower_priority (const struct list_elem *a_, ++ const struct list_elem *b_, ++ void *aux UNUSED) ++{ ++ const struct semaphore_elem *a ++ = list_entry (a_, struct semaphore_elem, elem); ++ const struct semaphore_elem *b ++ = list_entry (b_, struct semaphore_elem, elem); ++ ++ return a->thread->priority > b->thread->priority; ++} ++ + /* 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. +@@ -330,8 +404,12 @@ cond_signal (struct condition *cond, str + 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); ++ { ++ struct list_elem *max ++ = list_max (&cond->waiters, semaphore_elem_lower_priority, NULL); ++ list_remove (max); ++ sema_up (&list_entry (max, struct semaphore_elem, elem)->semaphore); ++ } + } + + /* Wakes up all threads, if any, waiting on COND (protected by +diff -u src/threads/thread.c~ src/threads/thread.c +--- src/threads/thread.c~ 2005-06-02 14:35:12.000000000 -0700 ++++ src/threads/thread.c 2005-06-02 14:55:56.000000000 -0700 +@@ -5,12 +5,14 @@ + #include + #include + #include "threads/flags.h" ++#include "threads/init.h" + #include "threads/interrupt.h" + #include "threads/intr-stubs.h" + #include "threads/mmu.h" + #include "threads/palloc.h" + #include "threads/switch.h" + #include "threads/synch.h" ++#include "devices/timer.h" + #ifdef USERPROG + #include "userprog/process.h" + #endif +@@ -24,6 +26,9 @@ + that are ready to run but not actually running. */ + static struct list ready_list; + ++/* List of all threads. */ ++static struct list all_threads_list; ++ + /* Idle thread. */ + static struct thread *idle_thread; + +@@ -49,6 +54,7 @@ static long long user_ticks; /* # of + /* Scheduling. */ + #define TIME_SLICE 4 /* # of timer ticks to give each thread. */ + static unsigned thread_ticks; /* # of timer ticks since last yield. */ ++static fixed_point_t load_avg; /* Load average. */ + + static void kernel_thread (thread_func *, void *aux); + +@@ -79,12 +85,15 @@ thread_init (void) + + lock_init (&tid_lock); + list_init (&ready_list); ++ list_init (&all_threads_list); ++ load_avg = fix_int (0); + + /* 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 (); ++ list_push_front (&all_threads_list, &initial_thread->all_elem); + } + + /* Starts preemptive thread scheduling by enabling interrupts. +@@ -101,9 +110,48 @@ void + else + kernel_ticks++; + +- /* Enforce preemption. */ +- if (++thread_ticks >= TIME_SLICE) +- intr_yield_on_return (); ++ if (enable_mlfqs) ++ { ++ /* Update load average. */ ++ if (timer_ticks () % TIMER_FREQ == 0) ++ { ++ size_t ready_threads = list_size (&ready_list); ++ if (t != idle_thread) ++ ready_threads++; ++ ++ load_avg = fix_add (fix_mul (fix_frac (59, 60), load_avg), ++ fix_mul (fix_frac (1, 60), fix_int (ready_threads))); ++ } ++ ++ /* Increment running process's recent_cpu. */ ++ if (t != idle_thread) ++ t->recent_cpu = fix_add (t->recent_cpu, fix_int (1)); ++ ++ /* Update recent_cpu and thread priorities once per second. */ ++ if (timer_ticks () % TIMER_FREQ == 0) ++ { ++ struct list_elem *e; ++ fixed_point_t twice_load = fix_scale (load_avg, 2); ++ fixed_point_t twice_load_plus_1 = fix_add (twice_load, fix_int (1)); ++ fixed_point_t load_factor = fix_div (twice_load, twice_load_plus_1); ++ for (e = list_begin (&all_threads_list); ++ e != list_end (&all_threads_list); e = list_next (e)) ++ { ++ struct thread *t = list_entry (e, struct thread, all_elem); ++ t->recent_cpu = fix_add (fix_mul (load_factor, t->recent_cpu), ++ fix_int (t->nice)); ++ thread_recompute_priority (t); ++ } ++ } ++ } ++ ++ /* Switch threads if time slice has expired. */ ++ if (++thread_ticks >= TIME_SLICE) ++ { ++ if (enable_mlfqs) ++ thread_recompute_priority (thread_current ()); ++ intr_yield_on_return (); ++ } + } + + /* Prints thread statistics. */ +@@ -143,10 +191,12 @@ tid_t + thread_create (const char *name, int priority, + thread_func *function, void *aux) + { ++ struct thread *cur = thread_current (); + struct thread *t; + struct kernel_thread_frame *kf; + struct switch_entry_frame *ef; + struct switch_threads_frame *sf; ++ enum intr_level old_level; + tid_t tid; + + ASSERT (function != NULL); +@@ -157,8 +207,10 @@ thread_create (const char *name, int pri + return TID_ERROR; + + /* Initialize thread. */ +- init_thread (t, name, priority); ++ init_thread (t, name, enable_mlfqs ? cur->priority : priority); + tid = t->tid = allocate_tid (); ++ t->nice = cur->nice; ++ t->recent_cpu = cur->recent_cpu; + + /* Stack frame for kernel_thread(). */ + kf = alloc_frame (t, sizeof *kf); +@@ -174,8 +226,15 @@ thread_create (const char *name, int pri + sf = alloc_frame (t, sizeof *sf); + sf->eip = switch_entry; + ++ /* Add to list of all threads. */ ++ old_level = intr_disable (); ++ list_push_front (&all_threads_list, &t->all_elem); ++ intr_set_level (old_level); ++ + /* Add to run queue. */ + thread_unblock (t); ++ if (priority < thread_get_priority ()) ++ thread_yield (); + + return tid; + } +@@ -196,6 +255,19 @@ thread_block (void) + schedule (); + } + ++/* Returns true if A has lower priority than B, ++ within a list of threads. */ ++bool ++thread_lower_priority (const struct list_elem *a_, ++ const struct list_elem *b_, ++ void *aux UNUSED) ++{ ++ const struct thread *a = list_entry (a_, struct thread, elem); ++ const struct thread *b = list_entry (b_, struct thread, elem); ++ ++ return a->priority > b->priority; ++} ++ + /* 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.) */ +@@ -260,6 +332,7 @@ thread_exit (void) + /* Just set our status to dying and schedule another process. + We will be destroyed during the call to schedule_tail(). */ + intr_disable (); ++ list_remove (&thread_current ()->all_elem); + thread_current ()->status = THREAD_DYING; + schedule (); + NOT_REACHED (); +@@ -282,11 +355,26 @@ thread_yield (void) + intr_set_level (old_level); + } + +-/* Sets the current thread's priority to NEW_PRIORITY. */ ++static void ++recompute_priority_chain (void) ++{ ++ enum intr_level old_level = intr_disable (); ++ thread_recompute_priority (thread_current ()); ++ thread_yield_to_higher_priority (); ++ intr_set_level (old_level); ++} ++ ++/* Sets the current thread's priority to PRIORITY. */ + void +-thread_set_priority (int new_priority) ++thread_set_priority (int priority) + { +- thread_current ()->priority = new_priority; ++ if (!enable_mlfqs) ++ { ++ struct thread *t = thread_current (); ++ ++ t->normal_priority = priority; ++ recompute_priority_chain (); ++ } + } + + /* Returns the current thread's priority. */ +@@ -298,33 +386,93 @@ thread_get_priority (void) + + /* Sets the current thread's nice value to NICE. */ + void +-thread_set_nice (int nice UNUSED) ++thread_set_nice (int nice) + { +- /* Not yet implemented. */ ++ thread_current ()->nice = nice; ++ recompute_priority_chain (); + } + + /* Returns the current thread's nice value. */ + int + thread_get_nice (void) + { +- /* Not yet implemented. */ +- return 0; ++ return thread_current ()->nice; + } + +-/* Returns 100 times the system load average. */ + int + thread_get_load_avg (void) + { +- /* Not yet implemented. */ +- return 0; ++ int load_avg_int; ++ enum intr_level level = intr_disable (); ++ load_avg_int = fix_round (fix_scale (load_avg, 100)); ++ intr_set_level (level); ++ return load_avg_int; + } + +-/* Returns 100 times the current thread's recent_cpu value. */ + int + thread_get_recent_cpu (void) + { +- /* Not yet implemented. */ +- return 0; ++ int recent_cpu_int; ++ enum intr_level level = intr_disable (); ++ recent_cpu_int = fix_round (fix_scale (thread_current ()->recent_cpu, 100)); ++ intr_set_level (level); ++ return recent_cpu_int; ++} ++ ++/* Returns true if thread A has lower priority than thread B, ++ within a list of donors. */ ++static bool ++donated_lower_priority (const struct list_elem *a_, ++ const struct list_elem *b_, ++ void *aux UNUSED) ++{ ++ const struct thread *a = list_entry (a_, struct thread, donor_elem); ++ const struct thread *b = list_entry (b_, struct thread, donor_elem); ++ ++ return a->priority > b->priority; ++} ++ ++/* Recomputes T's priority in terms of its normal priority and ++ its donors' priorities, if any, ++ and cascades the donation as necessary. */ ++void ++thread_recompute_priority (struct thread *t) ++{ ++ int old_priority = t->priority; ++ int default_priority = t->normal_priority; ++ int donation = PRI_MAX; ++ if (enable_mlfqs) ++ { ++ default_priority = fix_round (t->recent_cpu) / 4 + t->nice * 2; ++ if (default_priority < PRI_MIN) ++ default_priority = PRI_MIN; ++ else if (default_priority > PRI_MAX) ++ default_priority = PRI_MAX; ++ } ++ if (!list_empty (&t->donors)) ++ donation = list_entry (list_max (&t->donors, donated_lower_priority, NULL), ++ struct thread, donor_elem)->priority; ++ t->priority = donation < default_priority ? donation : default_priority; ++ if (t->priority < old_priority && t->donee != NULL) ++ thread_recompute_priority (t->donee); ++} ++ ++/* If the ready list contains a thread with a higher priority, ++ yields to it. */ ++void ++thread_yield_to_higher_priority (void) ++{ ++ enum intr_level old_level = intr_disable (); ++ if (!list_empty (&ready_list)) ++ { ++ struct thread *cur = thread_current (); ++ struct thread *max = list_entry (list_max (&ready_list, ++ thread_lower_priority, NULL), ++ struct thread, elem); ++ if (max->priority < cur->priority) ++ thread_yield (); ++ } ++ intr_set_level (old_level); + } + + /* Idle thread. Executes when no other thread is ready to run. */ +@@ -399,8 +547,10 @@ init_thread (struct thread *t, const cha + t->status = THREAD_BLOCKED; + strlcpy (t->name, name, sizeof t->name); + t->stack = (uint8_t *) t + PGSIZE; +- t->priority = priority; ++ t->priority = t->normal_priority = priority; + t->magic = THREAD_MAGIC; ++ sema_init (&t->timer_sema, 0); ++ list_init (&t->donors); + } + + /* Allocates a SIZE-byte frame at the top of thread T's stack and +@@ -426,8 +576,14 @@ 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); ++ else ++ { ++ struct thread *max ++ = list_entry (list_max (&ready_list, thread_lower_priority, NULL), ++ struct thread, elem); ++ list_remove (&max->elem); ++ return max; ++ } + } + + /* Completes a thread switch by activating the new thread's page +diff -u src/threads/thread.h~ src/threads/thread.h +--- src/threads/thread.h~ 2005-06-02 14:32:36.000000000 -0700 ++++ src/threads/thread.h 2005-06-02 14:38:46.000000000 -0700 +@@ -4,6 +4,8 @@ + #include + #include + #include ++#include "threads/synch.h" ++#include "threads/fixed-point.h" + + /* States in a thread's life cycle. */ + enum thread_status +@@ -87,11 +89,26 @@ struct thread + enum thread_status status; /* Thread state. */ + char name[16]; /* Name (for debugging purposes). */ + uint8_t *stack; /* Saved stack pointer. */ +- int priority; /* Priority. */ ++ ++ /* Scheduler data. */ ++ int priority; /* Priority, including donations. */ ++ int normal_priority; /* Priority, without donations. */ ++ struct list donors; /* Threads donating priority to us. */ ++ struct list_elem donor_elem; /* Element in donors list. */ ++ struct thread *donee; /* Thread we're donating to. */ ++ struct lock *want_lock; /* Lock we're waiting to acquire. */ ++ int nice; /* Niceness. */ ++ fixed_point_t recent_cpu; /* Recent amount of CPU time. */ ++ struct list_elem all_elem; /* all_threads list element. */ + + /* Shared between thread.c and synch.c. */ + struct list_elem elem; /* List element. */ + ++ /* Alarm clock. */ ++ int64_t wakeup_time; /* Time to wake this thread up. */ ++ struct list_elem timer_elem; /* Element in timer_wait_list. */ ++ struct semaphore timer_sema; /* Semaphore. */ ++ + #ifdef USERPROG + /* Owned by userprog/process.c. */ + uint32_t *pagedir; /* Page directory. */ +@@ -118,6 +135,10 @@ const char *thread_name (void); + + void thread_exit (void) NO_RETURN; + void thread_yield (void); ++void thread_yield_to_higher_priority (void); ++void thread_recompute_priority (struct thread *); ++bool thread_lower_priority (const struct list_elem *, const struct list_elem *, ++ void *aux); + + int thread_get_priority (void); + void thread_set_priority (int); diff --git a/solutions/p2-null.patch b/solutions/p2-null.patch deleted file mode 100644 index 6777104..0000000 --- a/solutions/p2-null.patch +++ /dev/null @@ -1,183 +0,0 @@ -diff -urp -X pat src/threads/init.c~ src/threads/init.c ---- src/threads/init.c~ 2005-03-30 10:26:02.000000000 -0800 -+++ src/threads/init.c 2005-03-30 20:05:20.000000000 -0800 -@@ -120,8 +120,11 @@ main (void) - /* Run a user program. */ - if (initial_program != NULL) - { -+ struct semaphore done; -+ sema_init (&done, 0, "done"); - printf ("\nExecuting '%s':\n", initial_program); -- process_wait (process_execute (initial_program)); -+ process_execute (initial_program, &done); -+ sema_down (&done); - } - #else - /* Run the compiled-in test function. */ -diff -urp -X pat src/threads/synch.c~ src/threads/synch.c ---- src/threads/synch.c~ 2005-03-29 22:23:20.000000000 -0800 -+++ src/threads/synch.c 2005-03-30 20:01:45.000000000 -0800 -@@ -117,7 +117,7 @@ sema_self_test (void) - printf ("Testing semaphores..."); - sema_init (&sema[0], 0, "ping"); - sema_init (&sema[1], 0, "pong"); -- thread_create ("sema-test", PRI_DEFAULT, sema_test_helper, &sema); -+ thread_create ("sema-test", PRI_DEFAULT, sema_test_helper, &sema, NULL); - for (i = 0; i < 10; i++) - { - sema_up (&sema[0]); -diff -urp -X pat src/threads/test.c~ src/threads/test.c ---- src/threads/test.c~ 2005-01-22 11:14:57.000000000 -0800 -+++ src/threads/test.c 2005-03-30 20:03:11.000000000 -0800 -@@ -93,7 +93,7 @@ test_sleep (int thread_cnt, int iteratio - t->iterations = 0; - - snprintf (name, sizeof name, "thread %d", i); -- thread_create (name, PRI_DEFAULT, sleeper, t); -+ thread_create (name, PRI_DEFAULT, sleeper, t, NULL); - } - - /* Wait long enough for all the threads to finish. */ -diff -urp -X pat src/threads/thread.c~ src/threads/thread.c ---- src/threads/thread.c~ 2005-03-30 10:26:13.000000000 -0800 -+++ src/threads/thread.c 2005-03-30 20:08:17.000000000 -0800 -@@ -51,7 +51,8 @@ static void kernel_thread (thread_func * - 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 void init_thread (struct thread *, const char *name, int priority, -+ struct semaphore *); - static bool is_thread (struct thread *); - static void *alloc_frame (struct thread *, size_t size); - static void schedule (void); -@@ -78,7 +79,7 @@ thread_init (void) - - /* Set up a thread structure for the running thread. */ - initial_thread = running_thread (); -- init_thread (initial_thread, "main", PRI_DEFAULT); -+ init_thread (initial_thread, "main", PRI_DEFAULT, NULL); - initial_thread->status = THREAD_RUNNING; - initial_thread->tid = allocate_tid (); - } -@@ -88,7 +89,7 @@ thread_init (void) - void - thread_start (void) - { -- thread_create ("idle", PRI_DEFAULT, idle, NULL); -+ thread_create ("idle", PRI_DEFAULT, idle, NULL, NULL); - intr_enable (); - } - -@@ -133,7 +134,8 @@ thread_print_stats (void) - Priority scheduling is the goal of Problem 1-3. */ - tid_t - thread_create (const char *name, int priority, -- thread_func *function, void *aux) -+ thread_func *function, void *aux, -+ struct semaphore *completion) - { - struct thread *t; - struct kernel_thread_frame *kf; -@@ -149,7 +151,7 @@ thread_create (const char *name, int pri - return TID_ERROR; - - /* Initialize thread. */ -- init_thread (t, name, priority); -+ init_thread (t, name, priority, completion); - tid = t->tid = allocate_tid (); - - /* Stack frame for kernel_thread(). */ -@@ -245,6 +247,9 @@ thread_exit (void) - { - ASSERT (!intr_context ()); - -+ if (thread_current ()->completion) -+ sema_up (thread_current ()->completion); -+ - #ifdef USERPROG - process_exit (); - #endif -@@ -342,7 +347,8 @@ is_thread (struct thread *t) - /* Does basic initialization of T as a blocked thread named - NAME. */ - static void --init_thread (struct thread *t, const char *name, int priority) -+init_thread (struct thread *t, const char *name, int priority, -+ struct semaphore *completion) - { - ASSERT (t != NULL); - ASSERT (PRI_MIN <= priority && priority <= PRI_MAX); -@@ -353,6 +359,7 @@ init_thread (struct thread *t, const cha - strlcpy (t->name, name, sizeof t->name); - t->stack = (uint8_t *) t + PGSIZE; - t->priority = priority; -+ t->completion = completion; - t->magic = THREAD_MAGIC; - } - -diff -urp -X pat src/threads/thread.h~ src/threads/thread.h ---- src/threads/thread.h~ 2005-03-30 10:26:13.000000000 -0800 -+++ src/threads/thread.h 2005-03-30 20:03:11.000000000 -0800 -@@ -92,6 +92,9 @@ struct thread - /* Shared between thread.c and synch.c. */ - struct list_elem elem; /* List element. */ - -+ /* Completion semaphore. */ -+ struct semaphore *completion; -+ - #ifdef USERPROG - /* Owned by userprog/process.c. */ - uint32_t *pagedir; /* Page directory. */ -@@ -107,7 +110,8 @@ 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 *); -+tid_t thread_create (const char *name, int priority, thread_func *, void *, -+ struct semaphore *); - - void thread_block (void); - void thread_unblock (struct thread *); -diff -urp -X pat src/userprog/process.c~ src/userprog/process.c ---- src/userprog/process.c~ 2005-03-30 20:06:20.000000000 -0800 -+++ src/userprog/process.c 2005-03-30 20:06:27.000000000 -0800 -@@ -21,11 +21,10 @@ static thread_func execute_thread NO_RET - static bool load (const char *cmdline, void (**eip) (void), void **esp); - - /* 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. */ -+ FILENAME. The new thread may be scheduled before -+ process_execute() returns.*/ - tid_t --process_execute (const char *filename) -+process_execute (const char *filename, struct semaphore *completion) - { - char *fn_copy; - tid_t tid; -@@ -38,7 +37,8 @@ process_execute (const char *filename) - strlcpy (fn_copy, filename, PGSIZE); - - /* Create a new thread to execute FILENAME. */ -- tid = thread_create (filename, PRI_DEFAULT, execute_thread, fn_copy); -+ tid = thread_create (filename, PRI_DEFAULT, execute_thread, fn_copy, -+ completion); - if (tid == TID_ERROR) - palloc_free_page (fn_copy); - return tid; -diff -urp -X pat src/userprog/process.h~ src/userprog/process.h ---- src/userprog/process.h~ 2005-03-30 15:09:53.000000000 -0800 -+++ src/userprog/process.h 2005-03-30 20:02:51.000000000 -0800 -@@ -2,8 +2,9 @@ - #define USERPROG_PROCESS_H - - #include "threads/thread.h" -+#include "threads/synch.h" - --tid_t process_execute (const char *filename); -+tid_t process_execute (const char *filename, struct semaphore *); - int process_wait (tid_t); - void process_exit (void); - void process_activate (void); diff --git a/solutions/p2.patch b/solutions/p2.patch index fc53891..4c0bbd9 100644 --- a/solutions/p2.patch +++ b/solutions/p2.patch @@ -1,6 +1,6 @@ -diff -urp -X pat src/threads/thread.c~ src/threads/thread.c ---- src/threads/thread.c~ 2005-03-30 10:26:13.000000000 -0800 -+++ src/threads/thread.c 2005-03-30 16:19:26.000000000 -0800 +diff -u src/threads/thread.c~ src/threads/thread.c +--- src/threads/thread.c~ 2005-06-02 14:35:12.000000000 -0700 ++++ src/threads/thread.c 2005-06-08 13:45:28.000000000 -0700 @@ -13,6 +13,7 @@ #include "threads/synch.h" #ifdef USERPROG @@ -9,7 +9,7 @@ diff -urp -X pat src/threads/thread.c~ src/threads/thread.c #endif /* Random value for struct thread's `magic' member. -@@ -243,16 +244,19 @@ thread_tid (void) +@@ -251,16 +252,19 @@ thread_tid (void) void thread_exit (void) { @@ -31,7 +31,7 @@ diff -urp -X pat src/threads/thread.c~ src/threads/thread.c schedule (); NOT_REACHED (); } -@@ -353,6 +357,11 @@ init_thread (struct thread *t, const cha +@@ -400,6 +404,11 @@ init_thread (struct thread *t, const cha strlcpy (t->name, name, sizeof t->name); t->stack = (uint8_t *) t + PGSIZE; t->priority = priority; @@ -43,9 +43,9 @@ diff -urp -X pat src/threads/thread.c~ src/threads/thread.c t->magic = THREAD_MAGIC; } -diff -urp -X pat src/threads/thread.h~ src/threads/thread.h ---- src/threads/thread.h~ 2005-03-30 10:26:13.000000000 -0800 -+++ src/threads/thread.h 2005-03-30 16:17:45.000000000 -0800 +diff -u src/threads/thread.h~ src/threads/thread.h +--- src/threads/thread.h~ 2005-06-02 14:32:36.000000000 -0700 ++++ src/threads/thread.h 2005-06-08 13:47:09.000000000 -0700 @@ -4,6 +4,7 @@ #include #include @@ -66,14 +66,16 @@ diff -urp -X pat src/threads/thread.h~ src/threads/thread.h /* Shared between thread.c and synch.c. */ struct list_elem elem; /* List element. */ -@@ -97,10 +103,29 @@ struct thread +@@ -96,11 +102,31 @@ struct thread + /* Owned by userprog/process.c. */ uint32_t *pagedir; /* Page directory. */ #endif - ++ struct file *bin_file; /* Executable. */ ++ + /* Owned by syscall.c. */ + struct list fds; /* List of file descriptors. */ + int next_handle; /* Next handle value. */ -+ + /* Owned by thread.c. */ unsigned magic; /* Detects stack overflow. */ }; @@ -96,9 +98,9 @@ diff -urp -X pat src/threads/thread.h~ src/threads/thread.h void thread_init (void); void thread_start (void); void thread_tick (void); -diff -urp -X pat src/userprog/exception.c~ src/userprog/exception.c ---- src/userprog/exception.c~ 2005-01-01 18:09:59.000000000 -0800 -+++ src/userprog/exception.c 2005-03-30 13:26:14.000000000 -0800 +diff -u src/userprog/exception.c~ src/userprog/exception.c +--- src/userprog/exception.c~ 2005-01-01 18:09:59.000000000 -0800 ++++ src/userprog/exception.c 2005-06-08 13:45:28.000000000 -0700 @@ -150,6 +150,14 @@ page_fault (struct intr_frame *f) write = (f->error_code & PF_W) != 0; user = (f->error_code & PF_U) != 0; @@ -114,9 +116,9 @@ diff -urp -X pat src/userprog/exception.c~ src/userprog/exception.c /* To implement virtual memory, delete the rest of the function body, and replace it with code that brings in the page to which fault_addr refers. */ -diff -urp -X pat src/userprog/process.c~ src/userprog/process.c ---- src/userprog/process.c~ 2005-03-30 10:40:10.000000000 -0800 -+++ src/userprog/process.c 2005-03-30 16:19:32.000000000 -0800 +diff -u src/userprog/process.c~ src/userprog/process.c +--- src/userprog/process.c~ 2005-05-26 13:19:48.000000000 -0700 ++++ src/userprog/process.c 2005-06-08 13:49:13.000000000 -0700 @@ -14,11 +14,23 @@ #include "threads/init.h" #include "threads/interrupt.h" @@ -141,13 +143,15 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c + }; /* Starts a new thread running a user program loaded from - FILENAME. The new thread may be scheduled before -@@ -26,29 +38,33 @@ static bool load (const char *cmdline, v + FILENAME. The new thread may be scheduled (and may even exit) +@@ -27,29 +39,37 @@ static bool load (const char *cmdline, v tid_t process_execute (const char *filename) { - char *fn_copy; + struct exec_info exec; ++ char thread_name[16]; ++ char *save_ptr; tid_t tid; - /* Make a copy of FILENAME. @@ -158,13 +162,15 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c - strlcpy (fn_copy, filename, PGSIZE); + /* Initialize exec_info. */ + exec.filename = filename; -+ sema_init (&exec.load_done, 0, "load done"); ++ sema_init (&exec.load_done, 0); /* Create a new thread to execute FILENAME. */ - tid = thread_create (filename, PRI_DEFAULT, execute_thread, fn_copy); - if (tid == TID_ERROR) - palloc_free_page (fn_copy); -+ tid = thread_create (filename, PRI_DEFAULT, execute_thread, &exec); ++ strlcpy (thread_name, filename, sizeof thread_name); ++ strtok_r (thread_name, " ", &save_ptr); ++ tid = thread_create (thread_name, PRI_DEFAULT, execute_thread, &exec); + if (tid != TID_ERROR) + { + sema_down (&exec.load_done); @@ -188,7 +194,7 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c struct intr_frame if_; bool success; -@@ -57,10 +73,28 @@ execute_thread (void *filename_) +@@ -58,10 +78,28 @@ execute_thread (void *filename_) if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG; if_.cs = SEL_UCSEG; if_.eflags = FLAG_IF | FLAG_MBS; @@ -208,10 +214,10 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c + /* Initialize wait_status. */ + if (success) + { -+ lock_init (&exec->wait_status->lock, "child process"); ++ lock_init (&exec->wait_status->lock); + exec->wait_status->ref_cnt = 2; + exec->wait_status->tid = thread_current ()->tid; -+ sema_init (&exec->wait_status->dead, 0, "dead child"); ++ sema_init (&exec->wait_status->dead, 0); + } + + /* Notify parent thread and clean up. */ @@ -220,7 +226,7 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c if (!success) thread_exit (); -@@ -74,19 +108,48 @@ execute_thread (void *filename_) +@@ -75,18 +113,47 @@ execute_thread (void *filename_) NOT_REACHED (); } @@ -273,8 +279,7 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c return -1; } - /* Free the current process's resources. */ -@@ -93,8 +157,29 @@ void +@@ -95,8 +162,32 @@ void process_exit (void) { struct thread *cur = thread_current (); @@ -283,6 +288,9 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c + printf ("%s: exit(%d)\n", cur->name, cur->exit_code); + ++ /* Close executable (and allow writes). */ ++ file_close (cur->bin_file); ++ + /* Notify parent that we're dead. */ + if (cur->wait_status != NULL) + { @@ -304,16 +312,16 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c /* Destroy the current process's page directory and switch back to the kernel-only page directory. */ pd = cur->pagedir; -@@ -192,7 +277,7 @@ struct Elf32_Phdr +@@ -193,7 +284,7 @@ struct Elf32_Phdr #define PF_R 4 /* Readable. */ static bool load_segment (struct file *, const struct Elf32_Phdr *); -static bool setup_stack (void **esp); +static bool setup_stack (const char *cmd_line, void **esp); - /* Aborts loading an executable, with an error message. */ - #define LOAD_ERROR(MSG) \ -@@ -208,13 +293,15 @@ static bool setup_stack (void **esp); + /* Loads an ELF executable from FILENAME into the current thread. + Stores the executable's entry point into *EIP +@@ -209,13 +300,15 @@ static bool setup_stack (void **esp); and its initial stack pointer into *ESP. Returns true if successful, false otherwise. */ bool @@ -330,8 +338,8 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c int i; /* Allocate and activate page directory. */ -@@ -223,6 +310,14 @@ load (const char *filename, void (**eip) - LOAD_ERROR (("page directory allocation failed")); +@@ -224,13 +317,22 @@ load (const char *filename, void (**eip) + goto done; process_activate (); + /* Extract filename from command line. */ @@ -343,9 +351,18 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c + *cp = '\0'; + /* Open executable file. */ - file = filesys_open (filename); - if (file == NULL) -@@ -283,7 +378,7 @@ load (const char *filename, void (**eip) +- file = filesys_open (filename); ++ t->bin_file = file = filesys_open (filename); + if (file == NULL) + { + printf ("load: %s: open failed\n", filename); + goto done; + } ++ file_deny_write (file); + + /* Read and verify executable header. */ + if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr +@@ -284,7 +386,7 @@ load (const char *filename, void (**eip) } /* Set up stack. */ @@ -354,7 +371,15 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c goto done; /* Start address. */ -@@ -392,10 +487,92 @@ load_segment (struct file *file, const s +@@ -294,7 +396,6 @@ load (const char *filename, void (**eip) + + done: + /* We arrive here whether the load is successful or not. */ +- file_close (file); + return success; + } + +@@ -393,10 +494,92 @@ load_segment (struct file *file, const s return true; } @@ -450,7 +475,7 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c { uint8_t *kpage; bool success = false; -@@ -403,9 +580,9 @@ setup_stack (void **esp) +@@ -404,9 +587,9 @@ setup_stack (void **esp) kpage = palloc_get_page (PAL_USER | PAL_ZERO); if (kpage != NULL) { @@ -463,10 +488,10 @@ diff -urp -X pat src/userprog/process.c~ src/userprog/process.c else palloc_free_page (kpage); } -diff -urp -X pat src/userprog/syscall.c~ src/userprog/syscall.c ---- src/userprog/syscall.c~ 2004-09-26 14:15:17.000000000 -0700 -+++ src/userprog/syscall.c 2005-03-30 15:11:37.000000000 -0800 -@@ -1,20 +1,478 @@ +diff -u src/userprog/syscall.c~ src/userprog/syscall.c +--- src/userprog/syscall.c~ 2004-09-26 14:15:17.000000000 -0700 ++++ src/userprog/syscall.c 2005-06-08 13:45:28.000000000 -0700 +@@ -1,20 +1,480 @@ #include "userprog/syscall.h" #include +#include @@ -508,8 +533,8 @@ diff -urp -X pat src/userprog/syscall.c~ src/userprog/syscall.c void syscall_init (void) { - intr_register (0x30, 3, INTR_ON, syscall_handler, "syscall"); -+ lock_init (&fs_lock, "fs"); + intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall"); ++ lock_init (&fs_lock); } + +/* System call handler. */ @@ -568,7 +593,8 @@ diff -urp -X pat src/userprog/syscall.c~ src/userprog/syscall.c +static bool +verify_user (const void *uaddr) +{ -+ return pagedir_get_page (thread_current ()->pagedir, uaddr) != NULL; ++ return (uaddr < PHYS_BASE ++ && pagedir_get_page (thread_current ()->pagedir, uaddr) != NULL); +} + +/* Copies a byte from user address USRC to kernel address DST. @@ -900,7 +926,8 @@ diff -urp -X pat src/userprog/syscall.c~ src/userprog/syscall.c + struct file_descriptor *fd = lookup_fd (handle); + + lock_acquire (&fs_lock); -+ file_seek (fd->file, position); ++ if ((off_t) position >= 0) ++ file_seek (fd->file, position); + lock_release (&fs_lock); + + return 0; @@ -949,9 +976,9 @@ diff -urp -X pat src/userprog/syscall.c~ src/userprog/syscall.c + free (fd); + } +} -diff -urp -X pat src/userprog/syscall.h~ src/userprog/syscall.h ---- src/userprog/syscall.h~ 2004-09-05 22:38:45.000000000 -0700 -+++ src/userprog/syscall.h 2005-03-30 13:26:14.000000000 -0800 +diff -u src/userprog/syscall.h~ src/userprog/syscall.h +--- src/userprog/syscall.h~ 2004-09-05 22:38:45.000000000 -0700 ++++ src/userprog/syscall.h 2005-06-08 13:45:28.000000000 -0700 @@ -2,5 +2,6 @@ #define USERPROG_SYSCALL_H diff --git a/solutions/p3.patch b/solutions/p3.patch index e891eaf..bc0b77d 100644 --- a/solutions/p3.patch +++ b/solutions/p3.patch @@ -1,117 +1,133 @@ -diff -urpN pintos.orig/src/Makefile.build pintos/src/Makefile.build ---- pintos.orig/src/Makefile.build 2004-09-20 20:26:41.000000000 -0700 -+++ pintos/src/Makefile.build 2004-09-27 13:29:43.000000000 -0700 -@@ -51,8 +51,9 @@ userprog_SRC += userprog/syscall.c # Sys - userprog_SRC += userprog/gdt.c # GDT initialization. +diff -u src/Makefile.build~ src/Makefile.build +--- src/Makefile.build~ 2005-06-02 17:24:02.000000000 -0700 ++++ src/Makefile.build 2005-06-08 14:10:54.000000000 -0700 +@@ -53,7 +53,9 @@ userprog_SRC += userprog/gdt.c # GDT in userprog_SRC += userprog/tss.c # TSS management. --# No virtual memory code yet. + # No virtual memory code yet. -#vm_SRC = vm/filename.c # Some file. -+# Virtual memory code. -+vm_SRC = vm/pageframe.c # Page frame management. -+vm_SRC += vm/swap.c # Swap file management. ++vm_SRC = vm/page.c ++vm_SRC += vm/frame.c ++vm_SRC += vm/swap.c # Filesystem code. filesys_SRC = filesys/filesys.c # Filesystem core. -diff -urpN pintos.orig/src/threads/init.c pintos/src/threads/init.c ---- pintos.orig/src/threads/init.c 2004-09-26 14:15:17.000000000 -0700 -+++ pintos/src/threads/init.c 2004-09-27 16:08:03.000000000 -0700 -@@ -21,12 +21,15 @@ - #include "threads/test.h" - #include "threads/thread.h" - #ifdef USERPROG -+#include "userprog/pagedir.h" - #include "userprog/process.h" - #include "userprog/exception.h" - #include "userprog/gdt.h" - #include "userprog/syscall.h" - #include "userprog/tss.h" - #endif -+#include "vm/pageframe.h" -+#include "vm/swap.h" - #ifdef FILESYS - #include "devices/disk.h" - #include "filesys/filesys.h" -@@ -78,6 +81,7 @@ main (void) - /* Initialize memory system, segments, paging. */ - palloc_init (); - paging_init (); -+ pageframe_init (); - #ifdef USERPROG - tss_init (); - gdt_init (); -@@ -105,6 +109,7 @@ main (void) - disk_init (); - filesys_init (format_filesys); - fsutil_run (); -+ swap_init (); - #endif +diff -u src/devices/timer.c~ src/devices/timer.c +--- src/devices/timer.c~ 2005-05-24 15:52:43.000000000 -0700 ++++ src/devices/timer.c 2005-06-08 14:10:54.000000000 -0700 +@@ -23,6 +23,9 @@ static volatile int64_t ticks; + Initialized by timer_calibrate(). */ + static unsigned loops_per_tick; - printf ("Boot complete.\n"); -diff -urpN pintos.orig/src/threads/palloc.c pintos/src/threads/palloc.c -diff -urpN pintos.orig/src/threads/palloc.h pintos/src/threads/palloc.h -diff -urpN pintos.orig/src/threads/synch.c pintos/src/threads/synch.c ---- pintos.orig/src/threads/synch.c 2004-09-19 21:29:53.000000000 -0700 -+++ pintos/src/threads/synch.c 2004-09-27 13:29:43.000000000 -0700 -@@ -330,3 +330,35 @@ cond_name (const struct condition *cond) ++/* Threads waiting in timer_sleep(). */ ++static struct list wait_list; ++ + static intr_handler_func timer_interrupt; + static bool too_many_loops (unsigned loops); + static void busy_wait (int64_t loops); +@@ -43,6 +46,8 @@ timer_init (void) + outb (0x40, count >> 8); - return cond->name; + intr_register_ext (0x20, timer_interrupt, "8254 Timer"); ++ ++ list_init (&wait_list); } -+ -+void -+latch_init (struct latch *latch, const char *name) + + /* Calibrates loops_per_tick, used to implement brief delays. */ +@@ -87,15 +92,36 @@ timer_elapsed (int64_t then) + return timer_ticks () - then; + } + ++/* Compares two threads based on their wake-up times. */ ++static bool ++compare_threads_by_wakeup_time (const struct list_elem *a_, ++ const struct list_elem *b_, ++ void *aux UNUSED) +{ -+ latch->released = false; -+ lock_init (&latch->monitor_lock, name); -+ cond_init (&latch->rel_cond, name); -+} ++ const struct thread *a = list_entry (a_, struct thread, timer_elem); ++ const struct thread *b = list_entry (b_, struct thread, timer_elem); + -+void -+latch_acquire (struct latch *latch) -+{ -+ lock_acquire (&latch->monitor_lock); -+ if (!latch->released) -+ { -+ cond_wait (&latch->rel_cond, &latch->monitor_lock); -+ ASSERT (latch->released); -+ } -+ lock_release (&latch->monitor_lock); ++ return a->wakeup_time < b->wakeup_time; +} + -+void -+latch_release (struct latch *latch) -+{ -+ lock_acquire (&latch->monitor_lock); -+ if (!latch->released) + /* Suspends execution for approximately TICKS timer ticks. */ + void + timer_sleep (int64_t ticks) + { +- int64_t start = timer_ticks (); ++ struct thread *t = thread_current (); ++ ++ /* Schedule our wake-up time. */ ++ t->wakeup_time = timer_ticks () + ticks; + ++ /* Atomically insert the current thread into the wait list. */ + ASSERT (intr_get_level () == INTR_ON); +- while (timer_elapsed (start) < ticks) +- thread_yield (); ++ intr_disable (); ++ list_insert_ordered (&wait_list, &t->timer_elem, ++ compare_threads_by_wakeup_time, NULL); ++ intr_enable (); ++ ++ /* Wait. */ ++ sema_down (&t->timer_sema); + } + + /* Suspends execution for approximately MS milliseconds. */ +@@ -132,6 +158,16 @@ timer_interrupt (struct intr_frame *args + { + ticks++; + thread_tick (); ++ ++ while (!list_empty (&wait_list)) + { -+ latch->released = true; -+ cond_signal (&latch->rel_cond, &latch->monitor_lock); ++ struct thread *t = list_entry (list_front (&wait_list), ++ struct thread, timer_elem); ++ if (ticks < t->wakeup_time) ++ break; ++ sema_up (&t->timer_sema); ++ list_pop_front (&wait_list); + } -+ lock_release (&latch->monitor_lock); -+} -diff -urpN pintos.orig/src/threads/synch.h pintos/src/threads/synch.h ---- pintos.orig/src/threads/synch.h 2004-09-19 21:29:53.000000000 -0700 -+++ pintos/src/threads/synch.h 2004-09-27 13:29:43.000000000 -0700 -@@ -44,4 +44,16 @@ void cond_signal (struct condition *, st - void cond_broadcast (struct condition *, struct lock *); - const char *cond_name (const struct condition *); + } -+/* Latch. */ -+struct latch -+ { -+ bool released; /* Released yet? */ -+ struct lock monitor_lock; /* Monitor lock. */ -+ struct condition rel_cond; /* Signaled when released. */ -+ }; -+ -+void latch_init (struct latch *, const char *); -+void latch_acquire (struct latch *); -+void latch_release (struct latch *); + /* Returns true if LOOPS iterations waits for more than one timer +diff -u src/threads/init.c~ src/threads/init.c +--- src/threads/init.c~ 2005-06-02 15:43:44.000000000 -0700 ++++ src/threads/init.c 2005-06-08 14:10:54.000000000 -0700 +@@ -33,6 +33,8 @@ + #include "filesys/filesys.h" + #include "filesys/fsutil.h" + #endif ++#include "vm/frame.h" ++#include "vm/swap.h" + + /* Amount of physical memory, in 4 kB pages. */ + size_t ram_pages; +@@ -131,6 +133,9 @@ main (void) + filesys_init (format_filesys); + #endif + ++ frame_init (); ++ swap_init (); + - #endif /* threads/synch.h */ -diff -urpN pintos.orig/src/threads/thread.c pintos/src/threads/thread.c ---- pintos.orig/src/threads/thread.c 2004-09-26 14:15:17.000000000 -0700 -+++ pintos/src/threads/thread.c 2004-09-27 13:29:43.000000000 -0700 + printf ("Boot complete.\n"); + + /* Run actions specified on kernel command line. */ +diff -u src/threads/interrupt.c~ src/threads/interrupt.c +--- src/threads/interrupt.c~ 2005-01-21 13:43:16.000000000 -0800 ++++ src/threads/interrupt.c 2005-06-08 14:10:54.000000000 -0700 +@@ -331,6 +331,8 @@ intr_handler (struct intr_frame *frame) + in_external_intr = true; + yield_on_return = false; + } ++ else ++ thread_current ()->user_esp = frame->esp; + + /* Invoke the interrupt's handler. + If there is no handler, invoke the unexpected interrupt +diff -u src/threads/thread.c~ src/threads/thread.c +--- src/threads/thread.c~ 2005-06-02 14:35:12.000000000 -0700 ++++ src/threads/thread.c 2005-06-08 14:10:54.000000000 -0700 @@ -13,6 +13,7 @@ #include "threads/synch.h" #ifdef USERPROG @@ -120,52 +136,52 @@ diff -urpN pintos.orig/src/threads/thread.c pintos/src/threads/thread.c #endif /* Random value for struct thread's `magic' member. -@@ -80,6 +81,7 @@ thread_init (void) - init_thread (initial_thread, "main", PRI_DEFAULT); +@@ -55,7 +56,8 @@ static void kernel_thread (thread_func * + 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 void init_thread (struct thread *, const char *name, int priority, ++ tid_t); + static bool is_thread (struct thread *) UNUSED; + static void *alloc_frame (struct thread *, size_t size); + static void schedule (void); +@@ -82,9 +84,8 @@ thread_init (void) + + /* Set up a thread structure for the running thread. */ + initial_thread = running_thread (); +- init_thread (initial_thread, "main", PRI_DEFAULT); ++ init_thread (initial_thread, "main", PRI_DEFAULT, 0); initial_thread->status = THREAD_RUNNING; - initial_thread->tid = allocate_tid (); -+ sema_up (&initial_thread->can_die); +- initial_thread->tid = allocate_tid (); } /* Starts preemptive thread scheduling by enabling interrupts. -@@ -148,6 +150,7 @@ thread_create (const char *name, int pri +@@ -157,8 +158,8 @@ thread_create (const char *name, int pri + return TID_ERROR; + /* Initialize thread. */ - init_thread (t, name, priority); - tid = t->tid = allocate_tid (); -+ list_push_back (&thread_current ()->children, &t->children_elem); +- init_thread (t, name, priority); +- tid = t->tid = allocate_tid (); ++ init_thread (t, name, priority, allocate_tid ()); ++ tid = t->tid; /* Stack frame for kernel_thread(). */ kf = alloc_frame (t, sizeof *kf); -@@ -224,16 +227,36 @@ thread_tid (void) +@@ -251,16 +252,19 @@ thread_tid (void) void thread_exit (void) { + struct thread *t = thread_current (); -+ struct list_elem *e, *next; + ASSERT (!intr_context ()); ++ syscall_exit (); #ifdef USERPROG process_exit (); #endif -+ syscall_exit (); +- + -+ /* Notify our parent that we're dying. */ -+ latch_release (&t->ready_to_die); -+ -+ /* Notify our children that they can die. */ -+ for (e = list_begin (&t->children); e != list_end (&t->children); -+ e = next) -+ { -+ struct thread *child = list_entry (e, struct thread, children_elem); -+ next = list_next (e); -+ list_remove (e); -+ sema_up (&child->can_die); -+ } -+ -+ /* Wait until our parent is ready for us to die. */ -+ sema_down (&t->can_die); - /* Just set our status to dying and schedule another process. We will be destroyed during the call to schedule_tail(). */ intr_disable (); @@ -174,48 +190,39 @@ diff -urpN pintos.orig/src/threads/thread.c pintos/src/threads/thread.c schedule (); NOT_REACHED (); } -@@ -283,8 +290,22 @@ thread_block (void) - This function will be implemented in problem 1-2. For now, it - does nothing. */ --void --thread_join (tid_t child_tid UNUSED) --{ -+int -+thread_join (tid_t child_tid) -+{ -+ struct thread *cur = thread_current (); -+ struct list_elem *e; -+ -+ for (e = list_begin (&cur->children); e != list_end (&cur->children); ) -+ { -+ struct thread *child = list_entry (e, struct thread, children_elem); -+ e = list_next (e); -+ if (child->tid == child_tid) -+ { -+ latch_acquire (&child->ready_to_die); -+ return child->ret_code; -+ } -+ } -+ return -1; - } +@@ -389,17 +393,28 @@ is_thread (struct thread *t) + /* Does basic initialization of T as a blocked thread named + NAME. */ + static void +-init_thread (struct thread *t, const char *name, int priority) ++init_thread (struct thread *t, const char *name, int priority, tid_t tid) + { + ASSERT (t != NULL); + ASSERT (PRI_MIN <= priority && priority <= PRI_MAX); + ASSERT (name != NULL); - /* Sets the current thread's priority to NEW_PRIORITY. */ -@@ -335,6 +378,12 @@ init_thread (struct thread *t, const cha + memset (t, 0, sizeof *t); ++ t->tid = tid; + t->status = THREAD_BLOCKED; strlcpy (t->name, name, sizeof t->name); t->stack = (uint8_t *) t + PGSIZE; t->priority = priority; -+ latch_init (&t->ready_to_die, "ready-to-die"); -+ sema_init (&t->can_die, 0, "can-die"); ++ t->exit_code = -1; ++ t->wait_status = NULL; + list_init (&t->children); -+ t->ret_code = -1; ++ sema_init (&t->timer_sema, 0); ++ t->pagedir = NULL; ++ t->pages = NULL; ++ t->bin_file = NULL; + list_init (&t->fds); ++ list_init (&t->mappings); + t->next_handle = 2; t->magic = THREAD_MAGIC; } -diff -urpN pintos.orig/src/threads/thread.h pintos/src/threads/thread.h ---- pintos.orig/src/threads/thread.h 2004-09-26 14:15:17.000000000 -0700 -+++ pintos/src/threads/thread.h 2004-09-27 13:29:43.000000000 -0700 +diff -u src/threads/thread.h~ src/threads/thread.h +--- src/threads/thread.h~ 2005-06-02 14:32:36.000000000 -0700 ++++ src/threads/thread.h 2005-06-08 14:10:54.000000000 -0700 @@ -2,8 +2,10 @@ #define THREADS_THREAD_H @@ -227,198 +234,323 @@ diff -urpN pintos.orig/src/threads/thread.h pintos/src/threads/thread.h /* States in a thread's life cycle. */ enum thread_status -@@ -89,12 +91,24 @@ struct thread +@@ -89,18 +91,49 @@ struct thread uint8_t *stack; /* Saved stack pointer. */ int priority; /* Priority. */ -+ /* 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. */ -+ struct list_elem children_elem; /* Element of `children' list. */ -+ int ret_code; /* Return status. */ ++ /* Owned by process.c. */ ++ int exit_code; /* Exit code. */ ++ struct wait_status *wait_status; /* This process's completion status. */ ++ struct list children; /* Completion status of children. */ + /* Shared between thread.c and synch.c. */ struct list_elem elem; /* List element. */ - #ifdef USERPROG +-#ifdef USERPROG ++ /* Alarm clock. */ ++ int64_t wakeup_time; /* Time to wake this thread up. */ ++ struct list_elem timer_elem; /* Element in timer_wait_list. */ ++ struct semaphore timer_sema; /* Semaphore. */ ++ /* Owned by userprog/process.c. */ uint32_t *pagedir; /* Page directory. */ -+ struct hash pages; /* Hash of `struct user_page's. */ +-#endif ++ struct hash *pages; /* Page table. */ ++ struct file *bin_file; /* The binary executable. */ + + /* Owned by syscall.c. */ + struct list fds; /* List of file descriptors. */ ++ struct list mappings; /* Memory-mapped files. */ + int next_handle; /* Next handle value. */ - #endif ++ void *user_esp; /* User's stack pointer. */ - /* Owned by thread.c */ -@@ -120,7 +132,7 @@ void thread_exit (void) NO_RETURN; - void thread_exit (void) NO_RETURN; - void thread_yield (void); + /* Owned by thread.c. */ + unsigned magic; /* Detects stack overflow. */ + }; --void thread_join (tid_t); -+int thread_join (tid_t); - - void thread_set_priority (int); - int thread_get_priority (void); -diff -urpN pintos.orig/src/userprog/exception.c pintos/src/userprog/exception.c ---- pintos.orig/src/userprog/exception.c 2004-09-26 14:15:17.000000000 -0700 -+++ pintos/src/userprog/exception.c 2004-09-27 13:29:44.000000000 -0700 -@@ -1,9 +1,16 @@ - #include "userprog/exception.h" - #include - #include -+#include ++/* Tracks the completion of a process. ++ Reference held by both the parent, in its `children' list, ++ and by the child, in its `wait_status' pointer. */ ++struct wait_status ++ { ++ struct list_elem elem; /* `children' list element. */ ++ struct lock lock; /* Protects ref_cnt. */ ++ int ref_cnt; /* 2=child and parent both alive, ++ 1=either child or parent alive, ++ 0=child and parent both dead. */ ++ tid_t tid; /* Child thread id. */ ++ int exit_code; /* Child exit code, if dead. */ ++ struct semaphore dead; /* 1=child alive, 0=child dead. */ ++ }; ++ + void thread_init (void); + void thread_start (void); + void thread_tick (void); +diff -u src/userprog/exception.c~ src/userprog/exception.c +--- src/userprog/exception.c~ 2005-01-01 18:09:59.000000000 -0800 ++++ src/userprog/exception.c 2005-06-08 14:10:54.000000000 -0700 +@@ -4,6 +4,7 @@ #include "userprog/gdt.h" -+#include "userprog/pagedir.h" -+#include "userprog/process.h" -+#include "filesys/file.h" #include "threads/interrupt.h" -+#include "threads/mmu.h" #include "threads/thread.h" -+#include "vm/pageframe.h" -+#include "vm/swap.h" ++#include "vm/page.h" /* Number of page faults processed. */ static long long page_fault_cnt; -@@ -124,10 +131,13 @@ kill (struct intr_frame *f) - static void - page_fault (struct intr_frame *f) - { -+ struct thread *t; - 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. */ -+ struct user_page tmp_up, *up; -+ struct hash_elem *e; - - /* Obtain faulting address, the virtual address that was - accessed to cause the fault. It may point to code or to -@@ -147,14 +157,62 @@ page_fault (struct intr_frame *f) +@@ -150,9 +151,14 @@ page_fault (struct intr_frame *f) write = (f->error_code & PF_W) != 0; user = (f->error_code & PF_U) != 0; - /* To implement virtual memory, delete the rest of the function - body, and replace it with code that brings in the page to - which fault_addr refers. */ -- 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); -+ if (!not_present) -+ goto bad_access; -+ -+ t = thread_current (); -+ if (t->pagedir == NULL) -+ goto bad_access; -+ -+ //printf ("fault %p (page=%p)\n", fault_addr, pg_round_down (fault_addr)); -+ tmp_up.upage = pg_round_down (fault_addr); -+ e = hash_find (&t->pages, &tmp_up.elem); -+ if (e == NULL) ++ /* Allow the pager to try to handle it. */ ++ if (user && not_present) + { -+ printf ("no user_page for %p\n", fault_addr); -+ goto bad_access; ++ if (!page_in (fault_addr)) ++ thread_exit (); ++ return; + } -+ up = hash_entry (e, struct user_page, elem); + -+ if (up->frame == NULL) -+ { -+ if (!pageframe_allocate (up)) -+ { -+ printf ("virtual memory exhausted, killing process\n"); -+ if (!user) -+ goto bad_access; -+ thread_exit (); -+ } -+ if (up->file != NULL) -+ { -+ off_t amt = file_read_at (up->file, -+ up->frame->kpage, up->file_size, -+ up->file_ofs); -+ ASSERT (amt == (off_t) up->file_size); -+ memset (up->frame->kpage + up->file_size, 0, PGSIZE - up->file_size); -+ } -+ else if (up->swap_page != SIZE_MAX) -+ swap_read (up); -+ else -+ memset (up->frame->kpage, 0, PGSIZE); -+ } -+ pagedir_set_page (t->pagedir, up->upage, up->frame->kpage, true); -+ return; -+ -+ bad_access: -+ if (user || fault_addr > PHYS_BASE) -+ { -+ 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); -+ } -+ else -+ { -+ f->eip = (void (*) (void)) f->eax; -+ f->eax = 0; -+ } + printf ("Page fault at %p: %s error %s page in %s context.\n", + fault_addr, + not_present ? "not present" : "rights violation", +diff -u src/userprog/pagedir.c~ src/userprog/pagedir.c +--- src/userprog/pagedir.c~ 2005-05-20 15:44:13.000000000 -0700 ++++ src/userprog/pagedir.c 2005-06-08 14:10:54.000000000 -0700 +@@ -34,15 +34,7 @@ pagedir_destroy (uint32_t *pd) + ASSERT (pd != base_page_dir); + for (pde = pd; pde < pd + pd_no (PHYS_BASE); pde++) + if (*pde & PG_P) +- { +- uint32_t *pt = pde_get_pt (*pde); +- uint32_t *pte; +- +- for (pte = pt; pte < pt + PGSIZE / sizeof *pte; pte++) +- if (*pte & PG_P) +- palloc_free_page (pte_get_page (*pte)); +- palloc_free_page (pt); +- } ++ palloc_free_page (pde_get_pt (*pde)); + palloc_free_page (pd); } -diff -urpN pintos.orig/src/userprog/process.c pintos/src/userprog/process.c ---- pintos.orig/src/userprog/process.c 2004-09-22 17:58:29.000000000 -0700 -+++ pintos/src/userprog/process.c 2004-09-27 14:43:09.000000000 -0700 -@@ -7,15 +7,18 @@ - #include "userprog/gdt.h" - #include "userprog/pagedir.h" - #include "userprog/tss.h" -+#include "vm/pageframe.h" - #include "filesys/directory.h" - #include "filesys/file.h" - #include "filesys/filesys.h" - #include "threads/flags.h" +diff -u src/userprog/process.c~ src/userprog/process.c +--- src/userprog/process.c~ 2005-05-26 13:19:48.000000000 -0700 ++++ src/userprog/process.c 2005-06-08 14:13:25.000000000 -0700 +@@ -14,11 +14,25 @@ #include "threads/init.h" #include "threads/interrupt.h" -+#include "threads/malloc.h" #include "threads/mmu.h" ++#include "threads/malloc.h" #include "threads/palloc.h" #include "threads/thread.h" -+#include "vm/swap.h" ++#include "vm/page.h" ++#include "vm/frame.h" static thread_func execute_thread NO_RETURN; - static bool load (const char *cmdline, void (**eip) (void), void **esp); -@@ -100,6 +103,10 @@ process_exit (void) - cur->pagedir = NULL; - pagedir_activate (NULL); - pagedir_destroy (pd); -+ -+ /* We maintain the invariant that `hash' is initialized iff -+ `pd != NULL'. */ -+ hash_destroy (&cur->pages); - } +-static bool load (const char *cmdline, void (**eip) (void), void **esp); ++static bool load (const char *cmd_line, void (**eip) (void), void **esp); ++ ++/* Data structure shared between process_execute() in the ++ invoking thread and execute_thread() in the newly invoked ++ thread. */ ++struct exec_info ++ { ++ const char *filename; /* Program to load. */ ++ struct semaphore load_done; /* "Up"ed when loading complete. */ ++ struct wait_status *wait_status; /* Child process. */ ++ bool success; /* Program successfully loaded? */ ++ }; + + /* Starts a new thread running a user program loaded from + FILENAME. The new thread may be scheduled (and may even exit) +@@ -27,29 +41,37 @@ static bool load (const char *cmdline, v + tid_t + process_execute (const char *filename) + { +- char *fn_copy; ++ struct exec_info exec; ++ char thread_name[16]; ++ char *save_ptr; + tid_t tid; + +- /* Make a copy of FILENAME. +- Otherwise there's a race between the caller and load(). */ +- fn_copy = palloc_get_page (0); +- if (fn_copy == NULL) +- return TID_ERROR; +- strlcpy (fn_copy, filename, PGSIZE); ++ /* Initialize exec_info. */ ++ exec.filename = filename; ++ sema_init (&exec.load_done, 0); + + /* Create a new thread to execute FILENAME. */ +- tid = thread_create (filename, PRI_DEFAULT, execute_thread, fn_copy); +- if (tid == TID_ERROR) +- palloc_free_page (fn_copy); ++ strlcpy (thread_name, filename, sizeof thread_name); ++ strtok_r (thread_name, " ", &save_ptr); ++ tid = thread_create (thread_name, PRI_DEFAULT, execute_thread, &exec); ++ if (tid != TID_ERROR) ++ { ++ sema_down (&exec.load_done); ++ if (exec.success) ++ list_push_back (&thread_current ()->children, &exec.wait_status->elem); ++ else ++ tid = TID_ERROR; ++ } ++ + return tid; + } + + /* A thread function that loads a user process and starts it + running. */ + static void +-execute_thread (void *filename_) ++execute_thread (void *exec_) + { +- char *filename = filename_; ++ struct exec_info *exec = exec_; + struct intr_frame if_; + bool success; + +@@ -58,10 +80,28 @@ execute_thread (void *filename_) + if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG; + if_.cs = SEL_UCSEG; + if_.eflags = FLAG_IF | FLAG_MBS; +- success = load (filename, &if_.eip, &if_.esp); ++ success = load (exec->filename, &if_.eip, &if_.esp); + +- /* If load failed, quit. */ +- palloc_free_page (filename); ++ /* Allocate wait_status. */ ++ if (success) ++ { ++ exec->wait_status = thread_current ()->wait_status ++ = malloc (sizeof *exec->wait_status); ++ success = exec->wait_status != NULL; ++ } ++ ++ /* Initialize wait_status. */ ++ if (success) ++ { ++ lock_init (&exec->wait_status->lock); ++ exec->wait_status->ref_cnt = 2; ++ exec->wait_status->tid = thread_current ()->tid; ++ sema_init (&exec->wait_status->dead, 0); ++ } ++ ++ /* Notify parent thread and clean up. */ ++ exec->success = success; ++ sema_up (&exec->load_done); + if (!success) + thread_exit (); + +@@ -75,18 +115,47 @@ execute_thread (void *filename_) + NOT_REACHED (); + } + ++/* Releases one reference to CS and, if it is now unreferenced, ++ frees it. */ ++static void ++release_child (struct wait_status *cs) ++{ ++ int new_ref_cnt; ++ ++ lock_acquire (&cs->lock); ++ new_ref_cnt = --cs->ref_cnt; ++ lock_release (&cs->lock); ++ ++ if (new_ref_cnt == 0) ++ free (cs); ++} ++ + /* 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. +- +- This function will be implemented in problem 2-2. For now, it +- does nothing. */ ++ immediately, without waiting. */ + int +-process_wait (tid_t child_tid UNUSED) ++process_wait (tid_t child_tid) + { ++ struct thread *cur = thread_current (); ++ struct list_elem *e; ++ ++ for (e = list_begin (&cur->children); e != list_end (&cur->children); ++ e = list_next (e)) ++ { ++ struct wait_status *cs = list_entry (e, struct wait_status, elem); ++ if (cs->tid == child_tid) ++ { ++ int exit_code; ++ list_remove (e); ++ sema_down (&cs->dead); ++ exit_code = cs->exit_code; ++ release_child (cs); ++ return exit_code; ++ } ++ } + return -1; } -@@ -182,7 +189,11 @@ struct Elf32_Phdr +@@ -95,8 +164,35 @@ void + process_exit (void) + { + struct thread *cur = thread_current (); ++ struct list_elem *e, *next; + uint32_t *pd; + ++ printf ("%s: exit(%d)\n", cur->name, cur->exit_code); ++ ++ /* Notify parent that we're dead. */ ++ if (cur->wait_status != NULL) ++ { ++ struct wait_status *cs = cur->wait_status; ++ cs->exit_code = cur->exit_code; ++ sema_up (&cs->dead); ++ release_child (cs); ++ } ++ ++ /* Free entries of children list. */ ++ for (e = list_begin (&cur->children); e != list_end (&cur->children); ++ e = next) ++ { ++ struct wait_status *cs = list_entry (e, struct wait_status, elem); ++ next = list_remove (e); ++ release_child (cs); ++ } ++ ++ /* Destroy the page hash table. */ ++ page_exit (); ++ ++ /* Close executable (and allow writes). */ ++ file_close (cur->bin_file); ++ + /* Destroy the current process's page directory and switch back + to the kernel-only page directory. */ + pd = cur->pagedir; +@@ -193,7 +289,7 @@ struct Elf32_Phdr #define PF_R 4 /* Readable. */ static bool load_segment (struct file *, const struct Elf32_Phdr *); -static bool setup_stack (void **esp); -+static bool setup_stack (const char *cmdline, void **esp); -+static unsigned user_page_hash (const struct hash_elem *, void *); -+static bool user_page_less (const struct hash_elem *, const struct hash_elem *, -+ void *); -+static struct user_page *make_user_page (void *upage); ++static bool setup_stack (const char *cmd_line, void **esp); - /* Aborts loading an executable, with an error message. */ - #define LOAD_ERROR(MSG) \ -@@ -198,19 +208,35 @@ static bool setup_stack (void **esp); + /* Loads an ELF executable from FILENAME into the current thread. + Stores the executable's entry point into *EIP +@@ -209,13 +305,15 @@ static bool setup_stack (void **esp); and its initial stack pointer into *ESP. Returns true if successful, false otherwise. */ bool -load (const char *filename, void (**eip) (void), void **esp) -+load (const char *cmdline, void (**eip) (void), void **esp) ++load (const char *cmd_line, void (**eip) (void), void **esp) { struct thread *t = thread_current (); + char filename[NAME_MAX + 2]; @@ -429,60 +561,51 @@ diff -urpN pintos.orig/src/userprog/process.c pintos/src/userprog/process.c + char *cp; int i; -+ /* Create hash of user pages. */ -+ hash_init (&t->pages, user_page_hash, user_page_less, NULL); -+ - /* Allocate page directory. */ - t->pagedir = pagedir_create (); -- if (t->pagedir == NULL) -- LOAD_ERROR (("page directory allocation failed")); -+ if (t->pagedir == NULL) -+ { -+ hash_destroy (&t->pages); -+ LOAD_ERROR (("page directory allocation failed")); -+ } + /* Allocate and activate page directory. */ +@@ -224,13 +322,28 @@ load (const char *filename, void (**eip) + goto done; + process_activate (); + ++ /* Create page hash table. */ ++ t->pages = malloc (sizeof *t->pages); ++ if (t->pages == NULL) ++ goto done; ++ hash_init (t->pages, page_hash, page_less, NULL); + + /* Extract filename from command line. */ -+ while (*cmdline == ' ') -+ cmdline++; -+ strlcpy (filename, cmdline, sizeof filename); ++ while (*cmd_line == ' ') ++ cmd_line++; ++ strlcpy (filename, cmd_line, sizeof filename); + cp = strchr (filename, ' '); + if (cp != NULL) + *cp = '\0'; - ++ /* Open executable file. */ - file = filesys_open (filename); -@@ -269,8 +295,23 @@ load (const char *filename, void (**eip) +- file = filesys_open (filename); ++ t->bin_file = file = filesys_open (filename); + if (file == NULL) + { + printf ("load: %s: open failed\n", filename); + goto done; + } ++ file_deny_write (t->bin_file); + + /* Read and verify executable header. */ + if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr +@@ -284,7 +397,7 @@ load (const char *filename, void (**eip) } /* Set up stack. */ - if (!setup_stack (esp)) -+ if (!setup_stack (cmdline, esp)) ++ if (!setup_stack (cmd_line, esp)) goto done; -+ -+#if 0 -+ { -+ struct hash_iterator i; -+ -+ hash_first (&i, &thread_current ()->pages); -+ while (hash_next (&i)) -+ { -+ struct user_page *up = hash_entry (hash_cur (&i), -+ struct user_page, elem); -+ printf ("%08x ", up->upage); -+ } -+ printf ("\n"); -+ } -+#endif /* Start address. */ - *eip = (void (*) (void)) ehdr.e_entry; -@@ -279,14 +320,12 @@ load (const char *filename, void (**eip) +@@ -294,14 +407,11 @@ load (const char *filename, void (**eip) done: /* We arrive here whether the load is successful or not. */ - file_close (file); -+ //file_close (file); // FIXME return success; } @@ -493,71 +616,105 @@ diff -urpN pintos.orig/src/userprog/process.c pintos/src/userprog/process.c /* Loads the segment described by PHDR from FILE into user address space. Return true if successful, false otherwise. */ static bool -@@ -296,6 +335,7 @@ load_segment (struct file *file, const s +@@ -309,6 +419,7 @@ load_segment (struct file *file, const s + { + void *start, *end; /* Page-rounded segment start and end. */ uint8_t *upage; /* Iterator from start to end. */ ++ off_t file_offset; /* Offset into file. */ off_t filesz_left; /* Bytes left of file data (as opposed to zero-initialized bytes). */ -+ off_t file_ofs; - /* Is this a read-only segment? Not currently used, so it's +@@ -316,7 +427,7 @@ load_segment (struct file *file, const s commented out. You'll want to use it when implementing VM -@@ -340,70 +380,207 @@ load_segment (struct file *file, const s + to decide whether to page the segment from its executable or + from swap. */ +- //bool read_only = (phdr->p_flags & PF_W) == 0; ++ bool read_only = (phdr->p_flags & PF_W) == 0; + + ASSERT (file != NULL); + ASSERT (phdr != NULL); +@@ -360,69 +471,129 @@ load_segment (struct file *file, const s + return false; + } - /* Load the segment page-by-page into memory. */ +- /* Load the segment page-by-page into memory. */ ++ /* Add the segment page-by-page to the hash table. */ filesz_left = phdr->p_filesz + (phdr->p_vaddr & PGMASK); - file_seek (file, ROUND_DOWN (phdr->p_offset, PGSIZE)); -+ file_ofs = ROUND_DOWN (phdr->p_offset, PGSIZE); ++ file_offset = ROUND_DOWN (phdr->p_offset, PGSIZE); for (upage = start; upage < (uint8_t *) end; upage += PGSIZE) { - /* We want to read min(PGSIZE, filesz_left) bytes from the - file into the page and zero the rest. */ - size_t read_bytes = filesz_left >= PGSIZE ? PGSIZE : filesz_left; +- /* We want to read min(PGSIZE, filesz_left) bytes from the +- file into the page and zero the rest. */ +- size_t read_bytes = filesz_left >= PGSIZE ? PGSIZE : filesz_left; - size_t zero_bytes = PGSIZE - read_bytes; - uint8_t *kpage = palloc_get_page (PAL_USER); - if (kpage == NULL) -+ struct user_page *up = make_user_page (upage); -+ if (up == NULL) ++ struct page *p = page_allocate (upage, read_only); ++ if (p == NULL) return false; - /* Do the reading and zeroing. */ - if (file_read (file, kpage, read_bytes) != (int) read_bytes) -+ if (read_bytes > 0) - { +- { - palloc_free_page (kpage); - return false; -+ /* Map page. */ -+ up->file = file; -+ up->file_ofs = file_ofs; -+ up->file_size = read_bytes; -+ file_ofs += read_bytes; - } +- } - memset (kpage + read_bytes, 0, zero_bytes); - filesz_left -= read_bytes; - - /* Add the page to the process's address space. */ - if (!install_page (upage, kpage)) -+ else ++ if (filesz_left > 0) { - palloc_free_page (kpage); - return false; -+ /* Page is all zeros. Nothing to do. */ ++ size_t file_bytes = filesz_left >= PGSIZE ? PGSIZE : filesz_left; ++ p->file = file; ++ p->file_offset = file_offset; ++ p->file_bytes = file_bytes; ++ filesz_left -= file_bytes; ++ file_offset += file_bytes; } -+ filesz_left -= read_bytes; } -+ -+ return true; -+} -+ + + return true; + } + +-/* Create a minimal stack by mapping a zeroed page at the top of +- user virtual memory. */ +-static bool +-setup_stack (void **esp) ++/* Reverse the order of the ARGC pointers to char in ARGV. */ +static void +reverse (int argc, char **argv) -+{ + { +- uint8_t *kpage; +- bool success = false; +- +- kpage = palloc_get_page (PAL_USER | PAL_ZERO); +- if (kpage != NULL) + for (; argc > 1; argc -= 2, argv++) -+ { + { +- success = install_page (((uint8_t *) PHYS_BASE) - PGSIZE, kpage); +- if (success) +- *esp = PHYS_BASE; +- else +- palloc_free_page (kpage); + char *tmp = argv[0]; + argv[0] = argv[argc - 1]; + argv[argc - 1] = tmp; -+ } + } +} ++ +- return success; ++/* Pushes the SIZE bytes in BUF onto the stack in KPAGE, whose ++ page-relative stack pointer is *OFS, and then adjusts *OFS ++ appropriately. The bytes pushed are rounded to a 32-bit ++ boundary. ++ ++ If successful, returns a pointer to the newly pushed object. ++ On failure, returns a null pointer. */ +static void * +push (uint8_t *kpage, size_t *ofs, const void *buf, size_t size) +{ @@ -568,22 +725,31 @@ diff -urpN pintos.orig/src/userprog/process.c pintos/src/userprog/process.c + *ofs -= padsize; + memcpy (kpage + *ofs + (padsize - size), buf, size); + return kpage + *ofs + (padsize - size); -+} -+ -+static bool -+init_cmdline (uint8_t *kpage, uint8_t *upage, const char *cmdline, -+ void **esp) -+{ + } + +-/* Adds a mapping from user virtual address UPAGE to kernel +- virtual address KPAGE to the page table. Fails if UPAGE is +- already mapped or if memory allocation fails. */ ++/* Sets up command line arguments in KPAGE, which will be mapped ++ to UPAGE in user space. The command line arguments are taken ++ from CMD_LINE, separated by spaces. Sets *ESP to the initial ++ stack pointer for the process. */ + static bool +-install_page (void *upage, void *kpage) ++init_cmd_line (uint8_t *kpage, uint8_t *upage, const char *cmd_line, ++ void **esp) + { +- struct thread *t = thread_current (); + size_t ofs = PGSIZE; + char *const null = NULL; -+ char *cmdline_copy; ++ char *cmd_line_copy; + char *karg, *saveptr; + int argc; + char **argv; + + /* Push command line string. */ -+ cmdline_copy = push (kpage, &ofs, cmdline, strlen (cmdline) + 1); -+ if (cmdline_copy == NULL) ++ cmd_line_copy = push (kpage, &ofs, cmd_line, strlen (cmd_line) + 1); ++ if (cmd_line_copy == NULL) + return false; + + if (push (kpage, &ofs, &null, sizeof null) == NULL) @@ -592,7 +758,7 @@ diff -urpN pintos.orig/src/userprog/process.c pintos/src/userprog/process.c + /* Parse command line into arguments + and push them in reverse order. */ + argc = 0; -+ for (karg = strtok_r (cmdline_copy, " ", &saveptr); karg != NULL; ++ for (karg = strtok_r (cmd_line_copy, " ", &saveptr); karg != NULL; + karg = strtok_r (NULL, " ", &saveptr)) + { + char *uarg = upage + (karg - (char *) kpage); @@ -613,203 +779,63 @@ diff -urpN pintos.orig/src/userprog/process.c pintos/src/userprog/process.c + + /* Set initial stack pointer. */ + *esp = upage + ofs; ++ return true; ++} - return true; - } - --/* Create a minimal stack by mapping a zeroed page at the top of -- user virtual memory. */ +- /* 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, true)); +/* Create a minimal stack for T by mapping a page at the -+ top of user virtual memory. Fills in the page using CMDLINE ++ top of user virtual memory. Fills in the page using CMD_LINE + and sets *ESP to the stack pointer. */ - static bool --setup_stack (void **esp) -+setup_stack (const char *cmdline, void **esp) - { -- uint8_t *kpage; -- bool success = false; -+ struct user_page *up = make_user_page ((uint8_t *) PHYS_BASE - PGSIZE); -+ return (up != NULL -+ && pageframe_allocate (up) -+ && init_cmdline (up->frame->kpage, up->upage, cmdline, esp)); -+} -+ -+static unsigned -+user_page_hash (const struct hash_elem *e, void *aux UNUSED) -+{ -+ struct user_page *up = hash_entry (e, struct user_page, elem); -+ return hash_bytes (&up->upage, sizeof up->upage); -+} - -- kpage = palloc_get_page (PAL_USER | PAL_ZERO); -- if (kpage != NULL) +static bool -+user_page_less (const struct hash_elem *a_, -+ const struct hash_elem *b_, void *aux UNUSED) -+{ -+ struct user_page *a = hash_entry (a_, struct user_page, elem); -+ struct user_page *b = hash_entry (b_, struct user_page, elem); -+ -+ return a->upage < b->upage; -+} -+ -+static struct user_page * -+make_user_page (void *upage) ++setup_stack (const char *cmd_line, void **esp) +{ -+ struct user_page *up; -+ -+ up = malloc (sizeof *up); -+ if (up != NULL) - { -- success = install_page (((uint8_t *) PHYS_BASE) - PGSIZE, kpage); -- if (success) -- *esp = PHYS_BASE; -+ memset (up, 0, sizeof *up); -+ up->swap_page = SIZE_MAX; -+ -+ up->upage = upage; -+ if (hash_insert (&thread_current ()->pages, &up->elem) != NULL) -+ { -+ free (up); -+ up = NULL; -+ } -+#if 0 - else -- palloc_free_page (kpage); -+ printf ("make_user_page(%p) okay\n", upage); -+#endif - } -- else -- printf ("failed to allocate process stack\n"); - -- return success; -+ return up; - } - --/* Adds a mapping from user virtual address UPAGE to kernel -- virtual address KPAGE to the page table. Fails if UPAGE is -- already mapped or if memory allocation fails. */ --static bool --install_page (void *upage, void *kpage) -+static void -+dump_page (struct user_page *up) - { -- struct thread *t = thread_current (); -+ off_t amt; - -- /* 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, true)); -+ ASSERT (up->file != NULL); -+ up->file_size = PGSIZE; -+ amt = file_write_at (up->file, up->frame->kpage, -+ up->file_size, up->file_ofs); -+ ASSERT (amt == (off_t) up->file_size); - } -+ -+bool -+process_evict_page (struct thread *t, struct user_page *up) -+{ -+ ASSERT (up->frame != NULL); -+ -+ if (pagedir_test_accessed (t->pagedir, up->upage)) ++ struct page *page = page_allocate (((uint8_t *) PHYS_BASE) - PGSIZE, false); ++ if (page != NULL) + { -+ pagedir_clear_accessed (t->pagedir, up->upage); -+ return false; -+ } -+ -+ if (up->file == NULL) -+ { -+ if (!swap_write (up)) -+ return false; -+ } -+ else if (pagedir_test_dirty (t->pagedir, up->upage)) -+ { -+ /* Need to write out. */ -+ if (up->private) ++ page->frame = frame_alloc_and_lock (page); ++ if (page->frame != NULL) + { -+ up->file = NULL; // FIXME -+ up->private = false; -+ if (!swap_write (up)) -+ return false; ++ bool ok; ++ page->read_only = false; ++ page->private = false; ++ ok = init_cmd_line (page->frame->base, page->addr, cmd_line, esp); ++ frame_unlock (page->frame); ++ return ok; + } -+ -+ dump_page (up); + } -+ else -+ { -+ /* Already on disk, not dirty. -+ Nothing to do. */ -+ } -+ -+ pagedir_clear_page (t->pagedir, up->upage); -+ pageframe_free (up->frame); -+ return true; -+} -+ -diff -urpN pintos.orig/src/userprog/process.h pintos/src/userprog/process.h ---- pintos.orig/src/userprog/process.h 2004-09-21 22:42:17.000000000 -0700 -+++ pintos/src/userprog/process.h 2004-09-27 14:43:13.000000000 -0700 -@@ -2,9 +2,32 @@ - #define USERPROG_PROCESS_H - - #include "threads/thread.h" -+#include "filesys/off_t.h" -+ -+struct user_page -+ { -+ struct hash_elem elem; -+ void *upage; /* Virtual address of mapping. */ -+ -+ /* If FRAME is nonnull, the page is in memory. -+ If FILE is nonnull, the page is on disk. -+ If both are null, the page is all zeroes. -+ If both are nonnull, the page is in memory and backed by a -+ file mapping (not the swap file). */ -+ struct page_frame *frame; -+ size_t swap_page; -+ struct file *file; -+ off_t file_ofs; -+ size_t file_size; /* Number of bytes on disk, <= PGSIZE. */ -+ -+ bool dirty : 1; -+ bool accessed : 1; -+ bool private : 1; /* Write dirty pages to swap or to FILE? */ -+ }; - - tid_t process_execute (const char *filename); - void process_exit (void); - void process_activate (void); -+bool process_evict_page (struct thread *, struct user_page *); - - #endif /* userprog/process.h */ -diff -urpN pintos.orig/src/userprog/syscall.c pintos/src/userprog/syscall.c ---- pintos.orig/src/userprog/syscall.c 2004-09-26 14:15:17.000000000 -0700 -+++ pintos/src/userprog/syscall.c 2004-09-27 14:42:01.000000000 -0700 -@@ -1,20 +1,429 @@ ++ return false; + } +diff -u src/userprog/syscall.c~ src/userprog/syscall.c +--- src/userprog/syscall.c~ 2004-09-26 14:15:17.000000000 -0700 ++++ src/userprog/syscall.c 2005-06-08 14:10:54.000000000 -0700 +@@ -1,20 +1,557 @@ #include "userprog/syscall.h" #include +#include #include ++#include "userprog/process.h" ++#include "userprog/pagedir.h" ++#include "devices/kbd.h" ++#include "filesys/directory.h" ++#include "filesys/filesys.h" ++#include "filesys/file.h" +#include "threads/init.h" #include "threads/interrupt.h" +#include "threads/malloc.h" +#include "threads/mmu.h" +#include "threads/palloc.h" #include "threads/thread.h" -+#include "userprog/pagedir.h" -+#include "userprog/process.h" -+#include "filesys/filesys.h" -+#include "filesys/file.h" -+#include "devices/kbd.h" -+ -+typedef int syscall_function (int, int, int); -+ +- ++#include "vm/page.h" ++ ++ +static int sys_halt (void); +static int sys_exit (int status); +static int sys_exec (const char *ufile); -+static int sys_join (tid_t); ++static int sys_wait (tid_t); +static int sys_create (const char *ufile, unsigned initial_size); +static int sys_remove (const char *ufile); +static int sys_open (const char *ufile); @@ -819,329 +845,354 @@ diff -urpN pintos.orig/src/userprog/syscall.c pintos/src/userprog/syscall.c +static int sys_seek (int handle, unsigned position); +static int sys_tell (int handle); +static int sys_close (int handle); -+ -+struct syscall -+ { -+ size_t arg_cnt; -+ syscall_function *func; -+ }; -+ -+struct syscall syscall_table[] = -+ { -+ {0, (syscall_function *) sys_halt}, -+ {1, (syscall_function *) sys_exit}, -+ {1, (syscall_function *) sys_exec}, -+ {1, (syscall_function *) sys_join}, -+ {2, (syscall_function *) sys_create}, -+ {1, (syscall_function *) sys_remove}, -+ {1, (syscall_function *) sys_open}, -+ {1, (syscall_function *) sys_filesize}, -+ {3, (syscall_function *) sys_read}, -+ {3, (syscall_function *) sys_write}, -+ {2, (syscall_function *) sys_seek}, -+ {1, (syscall_function *) sys_tell}, -+ {1, (syscall_function *) sys_close}, -+ }; -+static const int syscall_cnt = sizeof syscall_table / sizeof *syscall_table; - ++static int sys_mmap (int handle, void *addr); ++static int sys_munmap (int mapping); ++ static void syscall_handler (struct intr_frame *); +- +static void copy_in (void *, const void *, size_t); -+ -+static struct lock fs_lock; - ++ void syscall_init (void) { - intr_register (0x30, 3, INTR_ON, syscall_handler, "syscall"); -+ lock_init (&fs_lock, "fs"); + intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall"); } - - static void --syscall_handler (struct intr_frame *f UNUSED) ++ ++/* System call handler. */ ++static void +syscall_handler (struct intr_frame *f) +{ -+ struct syscall *s; -+ int call_nr; -+ int args[3]; ++ typedef int syscall_function (int, int, int); + -+ copy_in (&call_nr, f->esp, sizeof call_nr); -+ if (call_nr < 0 || call_nr >= syscall_cnt) ++ /* A system call. */ ++ struct syscall + { -+ printf ("bad syscall number %d\n", call_nr); -+ thread_exit (); -+ } ++ size_t arg_cnt; /* Number of arguments. */ ++ syscall_function *func; /* Implementation. */ ++ }; + -+ s = syscall_table + call_nr; -+ ASSERT (s->arg_cnt <= sizeof args / sizeof *args); -+ memset (args, 0, sizeof args); -+ copy_in (args, (uint32_t *) f->esp + 1, sizeof *args * s->arg_cnt); -+ f->eax = s->func (args[0], args[1], args[2]); -+} ++ /* Table of system calls. */ ++ static const struct syscall syscall_table[] = ++ { ++ {0, (syscall_function *) sys_halt}, ++ {1, (syscall_function *) sys_exit}, ++ {1, (syscall_function *) sys_exec}, ++ {1, (syscall_function *) sys_wait}, ++ {2, (syscall_function *) sys_create}, ++ {1, (syscall_function *) sys_remove}, ++ {1, (syscall_function *) sys_open}, ++ {1, (syscall_function *) sys_filesize}, ++ {3, (syscall_function *) sys_read}, ++ {3, (syscall_function *) sys_write}, ++ {2, (syscall_function *) sys_seek}, ++ {1, (syscall_function *) sys_tell}, ++ {1, (syscall_function *) sys_close}, ++ {2, (syscall_function *) sys_mmap}, ++ {1, (syscall_function *) sys_munmap}, ++ }; + ++ const struct syscall *sc; ++ unsigned call_nr; ++ int args[3]; + -+static bool -+verify_user (const void *uaddr) -+{ -+ return pagedir_get_page (thread_current ()->pagedir, uaddr) != NULL; -+} ++ /* Get the system call. */ ++ copy_in (&call_nr, f->esp, sizeof call_nr); ++ if (call_nr >= sizeof syscall_table / sizeof *syscall_table) ++ thread_exit (); ++ sc = syscall_table + call_nr; + -+static inline bool get_user (uint8_t *dst, const uint8_t *usrc) { -+ int eax; -+ asm ("movl $1f, %%eax; movb %2, %%al; movb %%al, %0; 1:" -+ : "=m" (*dst), "=&a" (eax) : "m" (*usrc)); -+ return eax != 0; -+} ++ /* Get the system call arguments. */ ++ ASSERT (sc->arg_cnt <= sizeof args / sizeof *args); ++ memset (args, 0, sizeof args); ++ copy_in (args, (uint32_t *) f->esp + 1, sizeof *args * sc->arg_cnt); + -+static inline bool put_user (uint8_t *udst, uint8_t byte) { -+ int eax; -+ asm ("movl $1f, %%eax; movb %b2, %0; 1:" -+ : "=m" (*udst), "=&a" (eax) : "r" (byte)); -+ return eax != 0; ++ /* Execute the system call, ++ and set the return value. */ ++ f->eax = sc->func (args[0], args[1], args[2]); +} -+ -+static void ++ ++/* Copies SIZE bytes from user address USRC to kernel address ++ DST. ++ Call thread_exit() if any of the user accesses are invalid. */ + static void +-syscall_handler (struct intr_frame *f UNUSED) +copy_in (void *dst_, const void *usrc_, size_t size) +{ + uint8_t *dst = dst_; + const uint8_t *usrc = usrc_; + -+ for (; size > 0; size--, dst++, usrc++) -+ if (usrc >= (uint8_t *) PHYS_BASE || !get_user (dst, usrc)) -+ thread_exit (); ++ while (size > 0) ++ { ++ size_t chunk_size = PGSIZE - pg_ofs (usrc); ++ if (chunk_size > size) ++ chunk_size = size; ++ ++ if (!page_lock (usrc, false)) ++ thread_exit (); ++ memcpy (dst, usrc, chunk_size); ++ page_unlock (usrc); ++ ++ dst += chunk_size; ++ usrc += chunk_size; ++ size -= chunk_size; ++ } +} -+ ++ ++/* Creates a copy of user string US in kernel memory ++ and returns it as a page that must be freed with ++ palloc_free_page(). ++ Truncates the string at PGSIZE bytes in size. ++ Call thread_exit() if any of the user accesses are invalid. */ +static char * +copy_in_string (const char *us) +{ + char *ks; ++ char *upage; + size_t length; -+ ++ + ks = palloc_get_page (0); + if (ks == NULL) -+ { -+ printf ("copy_in_string: out of memory\n"); -+ thread_exit (); -+ } ++ thread_exit (); + -+ for (length = 0; length < PGSIZE; length++) ++ length = 0; ++ for (;;) + { -+ if (us >= (char *) PHYS_BASE || !get_user (ks + length, us++)) ++ upage = pg_round_down (us); ++ if (!page_lock (upage, false)) ++ goto lock_error; ++ ++ for (; us < upage + PGSIZE; us++) + { -+ printf ("bad user reference (%p)\n", us + length); -+ thread_exit (); ++ ks[length++] = *us; ++ if (*us == '\0') ++ { ++ page_unlock (upage); ++ return ks; ++ } ++ else if (length >= PGSIZE) ++ goto too_long_error; + } -+ -+ if (ks[length] == '\0') -+ return ks; ++ ++ page_unlock (upage); + } + -+ printf ("copy_in_string: string too long\n"); ++ too_long_error: ++ page_unlock (upage); ++ lock_error: + palloc_free_page (ks); + thread_exit (); +} -+ ++ ++/* Halt system call. */ +static int +sys_halt (void) -+{ + { +- printf ("system call!\n"); + power_off (); +} -+ ++ ++/* Exit system call. */ +static int -+sys_exit (int ret_code) - { -- printf ("system call!\n"); -+ thread_current ()->ret_code = ret_code; ++sys_exit (int exit_code) ++{ ++ thread_current ()->exit_code = exit_code; thread_exit (); + NOT_REACHED (); +} -+ ++ ++/* Exec system call. */ +static int +sys_exec (const char *ufile) +{ + tid_t tid; + char *kfile = copy_in_string (ufile); -+ -+ lock_acquire (&fs_lock); ++ + tid = process_execute (kfile); -+ lock_release (&fs_lock); -+ ++ + palloc_free_page (kfile); -+ ++ + return tid; +} -+ ++ ++/* Wait system call. */ +static int -+sys_join (tid_t child) ++sys_wait (tid_t child) +{ -+ return thread_join (child); ++ return process_wait (child); +} -+ ++ ++/* Create system call. */ +static int +sys_create (const char *ufile, unsigned initial_size) +{ + char *kfile = copy_in_string (ufile); -+ bool ok; -+ -+ lock_acquire (&fs_lock); -+ ok = filesys_create (kfile, initial_size); -+ lock_release (&fs_lock); -+ ++ bool ok = filesys_create (kfile, initial_size); + palloc_free_page (kfile); -+ ++ + return ok; +} -+ ++ ++/* Remove system call. */ +static int +sys_remove (const char *ufile) +{ + char *kfile = copy_in_string (ufile); -+ bool ok; -+ -+ lock_acquire (&fs_lock); -+ ok = filesys_remove (kfile); -+ lock_release (&fs_lock); -+ ++ bool ok = filesys_remove (kfile); + palloc_free_page (kfile); -+ ++ + return ok; +} -+ -+struct fildes ++ ++/* A file descriptor, for binding a file handle to a file. */ ++struct file_descriptor + { -+ struct list_elem elem; -+ struct file *file; -+ int handle; ++ struct list_elem elem; /* List element. */ ++ struct file *file; /* File. */ ++ int handle; /* File handle. */ + }; -+ ++ ++/* Open system call. */ +static int +sys_open (const char *ufile) +{ + char *kfile = copy_in_string (ufile); -+ struct fildes *fd; ++ struct file_descriptor *fd; + int handle = -1; -+ ++ + fd = malloc (sizeof *fd); -+ if (fd == NULL) -+ goto exit; -+ -+ lock_acquire (&fs_lock); -+ fd->file = filesys_open (kfile); -+ if (fd->file != NULL) ++ if (fd != NULL) + { -+ struct thread *cur = thread_current (); -+ handle = fd->handle = cur->next_handle++; -+ list_push_front (&cur->fds, &fd->elem); ++ fd->file = filesys_open (kfile); ++ if (fd->file != NULL) ++ { ++ struct thread *cur = thread_current (); ++ handle = fd->handle = cur->next_handle++; ++ list_push_front (&cur->fds, &fd->elem); ++ } ++ else ++ free (fd); + } -+ else -+ free (fd); -+ lock_release (&fs_lock); -+ -+ exit: ++ + palloc_free_page (kfile); + return handle; +} -+ -+static struct fildes * ++ ++/* Returns the file descriptor associated with the given handle. ++ Terminates the process if HANDLE is not associated with an ++ open file. */ ++static struct file_descriptor * +lookup_fd (int handle) +{ + struct thread *cur = thread_current (); + struct list_elem *e; -+ ++ + for (e = list_begin (&cur->fds); e != list_end (&cur->fds); + e = list_next (e)) + { -+ struct fildes *fd = list_entry (e, struct fildes, elem); ++ struct file_descriptor *fd; ++ fd = list_entry (e, struct file_descriptor, elem); + if (fd->handle == handle) + return fd; + } -+ -+ printf ("no handle %d\n", handle); -+thread_exit (); ++ ++ thread_exit (); +} -+ ++ ++/* Filesize system call. */ +static int +sys_filesize (int handle) +{ -+ struct fildes *fd = lookup_fd (handle); ++ struct file_descriptor *fd = lookup_fd (handle); + int size; -+ -+ lock_acquire (&fs_lock); ++ + size = file_length (fd->file); -+ lock_release (&fs_lock); -+ ++ + return size; +} -+ ++ ++/* Read system call. */ +static int +sys_read (int handle, void *udst_, unsigned size) +{ + uint8_t *udst = udst_; -+ struct fildes *fd; ++ struct file_descriptor *fd; + int bytes_read = 0; + -+ if (handle == STDIN_FILENO) -+ { -+ for (bytes_read = 0; (size_t) bytes_read < size; bytes_read++) -+ if (udst >= (uint8_t *) PHYS_BASE || !put_user (udst++, kbd_getc ())) -+ thread_exit (); -+ return bytes_read; -+ } ++ /* Look up file descriptor. */ ++ if (handle != STDIN_FILENO) ++ fd = lookup_fd (handle); + -+ lock_acquire (&fs_lock); -+ fd = lookup_fd (handle); + while (size > 0) + { ++ /* How much to read into this page? */ + size_t page_left = PGSIZE - pg_ofs (udst); + size_t read_amt = size < page_left ? size : page_left; + off_t retval; + -+ if (!verify_user (udst)) ++ /* Check that touching this page is okay. */ ++ if (!page_lock (udst, true)) ++ thread_exit (); ++ ++ /* Read from file into page. */ ++ if (handle != STDIN_FILENO) + { -+ lock_release (&fs_lock); -+ thread_exit (); ++ retval = file_read (fd->file, udst, read_amt); ++ if (retval < 0) ++ { ++ if (bytes_read == 0) ++ bytes_read = -1; ++ break; ++ } ++ bytes_read += retval; + } -+ -+ retval = file_read (fd->file, udst, read_amt); -+ if (retval < 0) ++ else + { -+ if (bytes_read == 0) -+ bytes_read = -1; -+ break; ++ size_t i; ++ ++ for (i = 0; i < read_amt; i++) ++ udst[i] = kbd_getc (); ++ bytes_read = read_amt; + } + -+ bytes_read += retval; ++ /* Release page. */ ++ page_unlock (udst); ++ ++ /* If it was a short read we're done. */ + if (retval != (off_t) read_amt) + break; + ++ /* Advance. */ + udst += retval; + size -= retval; + } -+ lock_release (&fs_lock); -+ ++ + return bytes_read; +} -+ ++ ++/* Write system call. */ +static int +sys_write (int handle, void *usrc_, unsigned size) +{ + uint8_t *usrc = usrc_; -+ struct fildes *fd = NULL; ++ struct file_descriptor *fd = NULL; + int bytes_written = 0; + -+ lock_acquire (&fs_lock); ++ /* Lookup up file descriptor. */ + if (handle != STDOUT_FILENO) + fd = lookup_fd (handle); ++ + while (size > 0) + { ++ /* How much bytes to write to this page? */ + size_t page_left = PGSIZE - pg_ofs (usrc); + size_t write_amt = size < page_left ? size : page_left; + off_t retval; + -+ if (!verify_user (usrc)) -+ { -+ lock_release (&fs_lock); -+ thread_exit (); -+ } ++ /* Check that we can touch this user page. */ ++ if (!page_lock (usrc, false)) ++ thread_exit (); + ++ /* Do the write. */ + if (handle == STDOUT_FILENO) + { + putbuf (usrc, write_amt); @@ -1149,79 +1200,182 @@ diff -urpN pintos.orig/src/userprog/syscall.c pintos/src/userprog/syscall.c + } + else + retval = file_write (fd->file, usrc, write_amt); ++ ++ /* Release user page. */ ++ page_unlock (usrc); ++ ++ /* Handle return value. */ + if (retval < 0) + { + if (bytes_written == 0) + bytes_written = -1; + break; + } -+ + bytes_written += retval; ++ ++ /* If it was a short write we're done. */ + if (retval != (off_t) write_amt) + break; + ++ /* Advance. */ + usrc += retval; + size -= retval; + } -+ lock_release (&fs_lock); -+ ++ + return bytes_written; +} -+ ++ ++/* Seek system call. */ +static int +sys_seek (int handle, unsigned position) +{ -+ struct fildes *fd = lookup_fd (handle); -+ -+ lock_acquire (&fs_lock); -+ file_seek (fd->file, position); -+ lock_release (&fs_lock); -+ ++ if ((off_t) position >= 0) ++ file_seek (lookup_fd (handle)->file, position); + return 0; +} -+ ++ ++/* Tell system call. */ +static int +sys_tell (int handle) +{ -+ struct fildes *fd = lookup_fd (handle); -+ unsigned position; -+ -+ lock_acquire (&fs_lock); -+ position = file_tell (fd->file); -+ lock_release (&fs_lock); -+ -+ return position; ++ return file_tell (lookup_fd (handle)->file); +} -+ ++ ++/* Close system call. */ +static int +sys_close (int handle) +{ -+ struct fildes *fd = lookup_fd (handle); -+ lock_acquire (&fs_lock); ++ struct file_descriptor *fd = lookup_fd (handle); + file_close (fd->file); -+ lock_release (&fs_lock); + list_remove (&fd->elem); + free (fd); + return 0; +} ++ ++/* Binds a mapping id to a region of memory and a file. */ ++struct mapping ++ { ++ struct list_elem elem; /* List element. */ ++ int handle; /* Mapping id. */ ++ struct file *file; /* File. */ ++ uint8_t *base; /* Start of memory mapping. */ ++ size_t page_cnt; /* Number of pages mapped. */ ++ }; + ++/* Returns the file descriptor associated with the given handle. ++ Terminates the process if HANDLE is not associated with a ++ memory mapping. */ ++static struct mapping * ++lookup_mapping (int handle) ++{ ++ struct thread *cur = thread_current (); ++ struct list_elem *e; ++ ++ for (e = list_begin (&cur->mappings); e != list_end (&cur->mappings); ++ e = list_next (e)) ++ { ++ struct mapping *m = list_entry (e, struct mapping, elem); ++ if (m->handle == handle) ++ return m; ++ } ++ ++ thread_exit (); ++} ++ ++/* Remove mapping M from the virtual address space, ++ writing back any pages that have changed. */ ++static void ++unmap (struct mapping *m) ++{ ++ list_remove (&m->elem); ++ while (m->page_cnt-- > 0) ++ { ++ page_deallocate (m->base); ++ m->base += PGSIZE; ++ } ++ file_close (m->file); ++ free (m); ++} ++ ++/* Mmap system call. */ ++static int ++sys_mmap (int handle, void *addr) ++{ ++ struct file_descriptor *fd = lookup_fd (handle); ++ struct mapping *m = malloc (sizeof *m); ++ size_t offset; ++ off_t length; ++ ++ if (m == NULL || addr == NULL || pg_ofs (addr) != 0) ++ return -1; ++ ++ m->handle = thread_current ()->next_handle++; ++ m->file = file_reopen (fd->file); ++ if (m->file == NULL) ++ { ++ free (m); ++ return -1; ++ } ++ m->base = addr; ++ m->page_cnt = 0; ++ list_push_front (&thread_current ()->mappings, &m->elem); ++ ++ offset = 0; ++ length = file_length (m->file); ++ while (length > 0) ++ { ++ struct page *p = page_allocate ((uint8_t *) addr + offset, false); ++ if (p == NULL) ++ { ++ unmap (m); ++ return -1; ++ } ++ p->private = false; ++ p->file = m->file; ++ p->file_offset = offset; ++ p->file_bytes = length >= PGSIZE ? PGSIZE : length; ++ offset += p->file_bytes; ++ length -= p->file_bytes; ++ m->page_cnt++; ++ } ++ ++ return m->handle; ++} ++ ++/* Munmap system call. */ ++static int ++sys_munmap (int mapping) ++{ ++ unmap (lookup_mapping (mapping)); ++ return 0; ++} ++ ++/* On thread exit, close all open files and unmap all mappings. */ +void +syscall_exit (void) +{ + struct thread *cur = thread_current (); + struct list_elem *e, *next; -+ ++ + for (e = list_begin (&cur->fds); e != list_end (&cur->fds); e = next) + { -+ struct fildes *fd = list_entry (e, struct fildes, elem); ++ struct file_descriptor *fd = list_entry (e, struct file_descriptor, elem); + next = list_next (e); + file_close (fd->file); + free (fd); ++ } ++ ++ for (e = list_begin (&cur->mappings); e != list_end (&cur->mappings); ++ e = next) ++ { ++ struct mapping *m = list_entry (e, struct mapping, elem); ++ next = list_next (e); ++ unmap (m); + } } -diff -urpN pintos.orig/src/userprog/syscall.h pintos/src/userprog/syscall.h ---- pintos.orig/src/userprog/syscall.h 2004-09-05 22:38:45.000000000 -0700 -+++ pintos/src/userprog/syscall.h 2004-09-27 13:29:44.000000000 -0700 +diff -u src/userprog/syscall.h~ src/userprog/syscall.h +--- src/userprog/syscall.h~ 2004-09-05 22:38:45.000000000 -0700 ++++ src/userprog/syscall.h 2005-06-08 14:10:54.000000000 -0700 @@ -2,5 +2,6 @@ #define USERPROG_SYSCALL_H @@ -1229,188 +1383,655 @@ diff -urpN pintos.orig/src/userprog/syscall.h pintos/src/userprog/syscall.h +void syscall_exit (void); #endif /* userprog/syscall.h */ -diff -urpN pintos.orig/src/vm/pageframe.c pintos/src/vm/pageframe.c ---- pintos.orig/src/vm/pageframe.c 1969-12-31 16:00:00.000000000 -0800 -+++ pintos/src/vm/pageframe.c 2004-09-27 13:29:44.000000000 -0700 -@@ -0,0 +1,75 @@ -+#include "vm/pageframe.h" -+#include +diff -u src/vm/frame.c~ src/vm/frame.c +--- src/vm/frame.c~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/vm/frame.c 2005-06-08 14:10:54.000000000 -0700 +@@ -0,0 +1,162 @@ ++#include "vm/frame.h" ++#include ++#include "vm/page.h" ++#include "devices/timer.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/mmu.h" +#include "threads/palloc.h" -+#include "userprog/process.h" ++#include "threads/synch.h" + -+static struct page_frame *frames; ++static struct frame *frames; +static size_t frame_cnt; + -+static struct page_frame *next_frame; ++static struct lock scan_lock; ++static size_t hand; + -+static inline bool -+in_use (const struct page_frame *pf) ++/* Initialize the frame manager. */ ++void ++frame_init (void) +{ -+ ASSERT ((pf->owner != NULL) == (pf->user_page != NULL)); -+ return pf->owner != NULL; ++ void *base; ++ ++ lock_init (&scan_lock); ++ ++ frames = malloc (sizeof *frames * ram_pages); ++ if (frames == NULL) ++ PANIC ("out of memory allocating page frames"); ++ ++ while ((base = palloc_get_page (PAL_USER)) != NULL) ++ { ++ struct frame *f = &frames[frame_cnt++]; ++ lock_init (&f->lock); ++ f->base = base; ++ f->page = NULL; ++ } +} + -+void -+pageframe_init (void) ++/* Tries to allocate and lock a frame for PAGE. ++ Returns the frame if successful, false on failure. */ ++static struct frame * ++try_frame_alloc_and_lock (struct page *page) +{ -+ uint8_t *kpage; ++ size_t i; + -+ frame_cnt = ram_pages; -+ frames = calloc (sizeof *frames, frame_cnt); -+ if (frames == NULL) -+ PANIC ("can't allocate page frames"); ++ lock_acquire (&scan_lock); + -+ while ((kpage = palloc_get_page (PAL_USER)) != NULL) ++ /* Find a free frame. */ ++ for (i = 0; i < frame_cnt; i++) + { -+ struct page_frame *pf = frames + (vtop (kpage) >> PGBITS); -+ pf->kpage = kpage; ++ struct frame *f = &frames[i]; ++ if (!lock_try_acquire (&f->lock)) ++ continue; ++ if (f->page == NULL) ++ { ++ f->page = page; ++ lock_release (&scan_lock); ++ return f; ++ } ++ lock_release (&f->lock); + } + -+ next_frame = frames; ++ /* No free frame. Find a frame to evict. */ ++ for (i = 0; i < frame_cnt * 2; i++) ++ { ++ /* Get a frame. */ ++ struct frame *f = &frames[hand]; ++ if (++hand >= frame_cnt) ++ hand = 0; ++ ++ if (!lock_try_acquire (&f->lock)) ++ continue; ++ ++ if (f->page == NULL) ++ { ++ f->page = page; ++ lock_release (&scan_lock); ++ return f; ++ } ++ ++ if (page_accessed_recently (f->page)) ++ { ++ lock_release (&f->lock); ++ continue; ++ } ++ ++ lock_release (&scan_lock); ++ ++ /* Evict this frame. */ ++ if (!page_out (f->page)) ++ { ++ lock_release (&f->lock); ++ return NULL; ++ } ++ ++ f->page = page; ++ return f; ++ } ++ ++ lock_release (&scan_lock); ++ return NULL; +} + -+bool -+pageframe_allocate (struct user_page *up) ++ ++/* Tries really hard to allocate and lock a frame for PAGE. ++ Returns the frame if successful, false on failure. */ ++struct frame * ++frame_alloc_and_lock (struct page *page) +{ -+ struct page_frame *pf; -+ size_t loops; ++ size_t try; ++ ++ for (try = 0; try < 3; try++) ++ { ++ struct frame *f = try_frame_alloc_and_lock (page); ++ if (f != NULL) ++ { ++ ASSERT (lock_held_by_current_thread (&f->lock)); ++ return f; ++ } ++ timer_msleep (1000); ++ } + -+ ASSERT (up->frame == NULL); ++ return NULL; ++} + -+ loops = 0; -+ do ++/* Locks P's frame into memory, if it has one. ++ Upon return, p->frame will not change until P is unlocked. */ ++void ++frame_lock (struct page *p) ++{ ++ /* A frame can be asynchronously removed, but never inserted. */ ++ struct frame *f = p->frame; ++ if (f != NULL) + { -+ pf = next_frame++; -+ if (next_frame >= frames + frame_cnt) -+ next_frame = frames; -+ if (loops++ > 2 * frame_cnt) -+ return false; ++ lock_acquire (&f->lock); ++ if (f != p->frame) ++ { ++ lock_release (&f->lock); ++ ASSERT (p->frame == NULL); ++ } + } -+ while (pf->kpage == NULL -+ || (in_use (pf) && !process_evict_page (pf->owner, pf->user_page))); -+ -+ ASSERT (!in_use (pf)); -+ pf->owner = thread_current (); -+ pf->user_page = up; -+ up->frame = pf; -+ return true; +} + ++/* Releases frame F for use by another page. ++ F must be locked for use by the current process. ++ Any data in F is lost. */ +void -+pageframe_free (struct page_frame *pf) ++frame_free (struct frame *f) +{ -+ ASSERT (in_use (pf)); -+ -+ pf->owner = NULL; -+ pf->user_page->frame = NULL; -+ pf->user_page = NULL; ++ ASSERT (lock_held_by_current_thread (&f->lock)); ++ ++ f->page = NULL; ++ lock_release (&f->lock); +} -diff -urpN pintos.orig/src/vm/pageframe.h pintos/src/vm/pageframe.h ---- pintos.orig/src/vm/pageframe.h 1969-12-31 16:00:00.000000000 -0800 -+++ pintos/src/vm/pageframe.h 2004-09-27 13:29:44.000000000 -0700 -@@ -0,0 +1,17 @@ -+#ifndef VM_PAGEFRAME_H -+#define VM_PAGEFRAME_H 1 ++ ++/* Unlocks frame F, allowing it to be evicted. ++ F must be locked for use by the current process. */ ++void ++frame_unlock (struct frame *f) ++{ ++ ASSERT (lock_held_by_current_thread (&f->lock)); ++ lock_release (&f->lock); ++} +diff -u src/vm/frame.h~ src/vm/frame.h +--- src/vm/frame.h~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/vm/frame.h 2005-06-08 14:10:54.000000000 -0700 +@@ -0,0 +1,23 @@ ++#ifndef VM_FRAME_H ++#define VM_FRAME_H + +#include ++#include "threads/synch.h" + -+struct page_frame ++/* A physical frame. */ ++struct frame + { -+ void *kpage; -+ struct thread *owner; -+ struct user_page *user_page; ++ struct lock lock; /* Prevent simultaneous access. */ ++ void *base; /* Kernel virtual base address. */ ++ struct page *page; /* Mapped process page, if any. */ + }; + -+void pageframe_init (void); -+bool pageframe_allocate (struct user_page *); -+void pageframe_free (struct page_frame *); ++void frame_init (void); + -+#endif /* vm/pageframe.h */ -diff -urpN pintos.orig/src/vm/swap.c pintos/src/vm/swap.c ---- pintos.orig/src/vm/swap.c 1969-12-31 16:00:00.000000000 -0800 -+++ pintos/src/vm/swap.c 2004-09-27 13:29:44.000000000 -0700 -@@ -0,0 +1,66 @@ -+#include "vm/swap.h" -+#include ++struct frame *frame_alloc_and_lock (struct page *); ++void frame_lock (struct page *); ++ ++void frame_free (struct frame *); ++void frame_unlock (struct frame *); ++ ++#endif /* vm/frame.h */ +diff -u src/vm/page.c~ src/vm/page.c +--- src/vm/page.c~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/vm/page.c 2005-06-08 14:10:54.000000000 -0700 +@@ -0,0 +1,297 @@ ++#include "vm/page.h" +#include -+#include "vm/pageframe.h" -+#include "threads/mmu.h" ++#include ++#include "vm/frame.h" ++#include "vm/swap.h" +#include "filesys/file.h" -+#include "filesys/filesys.h" -+#include "userprog/process.h" ++#include "threads/malloc.h" ++#include "threads/mmu.h" ++#include "threads/thread.h" ++#include "userprog/pagedir.h" + -+static size_t swap_pages; -+static struct disk *swap_disk; -+static struct bitmap *used_pages; ++/* Maximum size of process stack, in bytes. */ ++#define STACK_MAX (1024 * 1024) + ++/* Destroys the current process's page table. */ +void -+swap_init (void) ++page_exit (void) +{ -+ swap_disk = disk_get (1, 1); -+ if (swap_disk == NULL) -+ PANIC ("no swap disk"); -+ swap_pages = disk_size (swap_disk) / (PGSIZE / DISK_SECTOR_SIZE); -+ printf ("swap disk has room for %zu pages\n", swap_pages); ++ struct hash *h; ++ struct hash_iterator i; + -+ used_pages = bitmap_create (swap_pages); -+ if (used_pages == NULL) -+ PANIC ("couldn't create swap bitmap"); ++ h = thread_current ()->pages; ++ if (h == NULL) ++ return; ++ ++ hash_first (&i, h); ++ hash_next (&i); ++ while (hash_cur (&i)) ++ { ++ struct page *p = hash_entry (hash_cur (&i), struct page, hash_elem); ++ hash_next (&i); ++ frame_lock (p); ++ if (p->frame) ++ frame_free (p->frame); ++ free (p); ++ } ++ hash_destroy (h); +} + -+bool -+swap_write (struct user_page *up) ++/* Returns the page containing the given virtual ADDRESS, ++ or a null pointer if no such page exists. ++ Allocates stack pages as necessary. */ ++static struct page * ++page_for_addr (const void *address) +{ -+ size_t page; -+ disk_sector_t sector; -+ int i; -+ -+ ASSERT (up->frame != NULL); -+ ASSERT (up->file == NULL); ++ if (address < PHYS_BASE) ++ { ++ struct page p; ++ struct hash_elem *e; ++ ++ /* Find existing page. */ ++ p.addr = (void *) pg_round_down (address); ++ e = hash_find (thread_current ()->pages, &p.hash_elem); ++ if (e != NULL) ++ return hash_entry (e, struct page, hash_elem); ++ ++ /* No page. Expand stack? */ ++ if (address >= PHYS_BASE - STACK_MAX ++ && address >= thread_current ()->user_esp - 32) ++ return page_allocate ((void *) address, false); ++ } ++ return NULL; ++} + -+ page = bitmap_scan_and_flip (used_pages, 0, 1, false); -+ if (page == BITMAP_ERROR) ++/* Locks a frame for page P and pages it in. ++ Returns true if successful, false on failure. */ ++static bool ++do_page_in (struct page *p) ++{ ++ /* Get a frame for the page. */ ++ p->frame = frame_alloc_and_lock (p); ++ if (p->frame == NULL) + return false; + -+ up->swap_page = page; -+ sector = (disk_sector_t) page * (PGSIZE / DISK_SECTOR_SIZE); -+ for (i = 0; i < PGSIZE / DISK_SECTOR_SIZE; i++) -+ disk_write (swap_disk, sector++, up->frame->kpage + i * DISK_SECTOR_SIZE); ++ /* Copy data into the frame. */ ++ if (p->sector != (disk_sector_t) -1) ++ { ++ /* Get data from swap. */ ++ swap_in (p); ++ } ++ else if (p->file != NULL) ++ { ++ /* Get data from file. */ ++ off_t read_bytes = file_read_at (p->file, p->frame->base, ++ p->file_bytes, p->file_offset); ++ off_t zero_bytes = PGSIZE - read_bytes; ++ memset (p->frame->base + read_bytes, 0, zero_bytes); ++ if (read_bytes != p->file_bytes) ++ printf ("bytes read (%"PROTd") != bytes requested (%"PROTd")\n", ++ read_bytes, p->file_bytes); ++ } ++ else ++ { ++ /* Provide all-zero page. */ ++ memset (p->frame->base, 0, PGSIZE); ++ } + + return true; +} + ++/* Faults in the page containing FAULT_ADDR. ++ Returns true if successful, false on failure. */ ++bool ++page_in (void *fault_addr) ++{ ++ struct page *p; ++ bool success; ++ ++ /* Can't handle page faults without a hash table. */ ++ if (thread_current ()->pages == NULL) ++ return false; ++ ++ p = page_for_addr (fault_addr); ++ if (p == NULL) ++ return false; ++ ++ frame_lock (p); ++ if (p->frame == NULL) ++ { ++ if (!do_page_in (p)) ++ return false; ++ } ++ ASSERT (lock_held_by_current_thread (&p->frame->lock)); ++ ++ /* Install frame into page table. */ ++ success = pagedir_set_page (thread_current ()->pagedir, p->addr, ++ p->frame->base, !p->read_only); ++ ++ /* Release frame. */ ++ frame_unlock (p->frame); ++ ++ return success; ++} ++ ++/* Evicts page P. ++ P must have a locked frame. ++ Return true if successful, false on failure. */ ++bool ++page_out (struct page *p) ++{ ++ bool dirty; ++ bool ok; ++ ++ ASSERT (p->frame != NULL); ++ ASSERT (lock_held_by_current_thread (&p->frame->lock)); ++ ++ /* Mark page not present in page table, forcing accesses by the ++ process to fault. This must happen before checking the ++ dirty bit, to prevent a race with the process dirtying the ++ page. */ ++ pagedir_clear_page (p->thread->pagedir, p->addr); ++ ++ /* Has the frame been modified? */ ++ dirty = pagedir_is_dirty (p->thread->pagedir, p->addr); ++ ++ /* Write frame contents to disk if necessary. */ ++ if (p->file != NULL) ++ { ++ if (dirty) ++ { ++ if (p->private) ++ ok = swap_out (p); ++ else ++ ok = file_write_at (p->file, p->frame->base, p->file_bytes, ++ p->file_offset) == p->file_bytes; ++ } ++ else ++ ok = true; ++ } ++ else ++ ok = swap_out (p); ++ if (ok) ++ { ++ //memset (p->frame->base, 0xcc, PGSIZE); ++ p->frame = NULL; ++ } ++ return ok; ++} ++ ++/* Returns true if page P's data has been accessed recently, ++ false otherwise. ++ P must have a frame locked into memory. */ ++bool ++page_accessed_recently (struct page *p) ++{ ++ bool was_accessed; ++ ++ ASSERT (p->frame != NULL); ++ ASSERT (lock_held_by_current_thread (&p->frame->lock)); ++ ++ was_accessed = pagedir_is_accessed (p->thread->pagedir, p->addr); ++ if (was_accessed) ++ pagedir_set_accessed (p->thread->pagedir, p->addr, false); ++ return was_accessed; ++} ++ ++/* Adds a mapping for user virtual address VADDR to the page hash ++ table. Fails if VADDR is already mapped or if memory ++ allocation fails. */ ++struct page * ++page_allocate (void *vaddr, bool read_only) ++{ ++ struct thread *t = thread_current (); ++ struct page *p = malloc (sizeof *p); ++ if (p != NULL) ++ { ++ p->addr = pg_round_down (vaddr); ++ ++ p->read_only = read_only; ++ p->private = !read_only; ++ ++ p->frame = NULL; ++ ++ p->sector = (disk_sector_t) -1; ++ ++ p->file = NULL; ++ p->file_offset = 0; ++ p->file_bytes = 0; ++ ++ p->thread = thread_current (); ++ ++ if (hash_insert (t->pages, &p->hash_elem) != NULL) ++ { ++ /* Already mapped. */ ++ free (p); ++ p = NULL; ++ } ++ } ++ return p; ++} ++ ++/* Evicts the page containing address VADDR ++ and removes it from the page table. */ +void -+swap_read (struct user_page *up) ++page_deallocate (void *vaddr) +{ -+ disk_sector_t sector; -+ int i; ++ struct page *p = page_for_addr (vaddr); ++ ASSERT (p != NULL); ++ frame_lock (p); ++ if (p->frame) ++ { ++ struct frame *f = p->frame; ++ if (p->file && !p->private) ++ page_out (p); ++ frame_free (f); ++ } ++ hash_delete (thread_current ()->pages, &p->hash_elem); ++ free (p); ++} + -+ ASSERT (up->frame != NULL); ++/* Returns a hash value for the page that E refers to. */ ++unsigned ++page_hash (const struct hash_elem *e, void *aux UNUSED) ++{ ++ const struct page *p = hash_entry (e, struct page, hash_elem); ++ return ((uintptr_t) p->addr) >> PGBITS; ++} + -+ ASSERT (bitmap_test (used_pages, up->swap_page)); -+ bitmap_reset (used_pages, up->swap_page); ++/* 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; ++} + -+ sector = (disk_sector_t) up->swap_page * (PGSIZE / DISK_SECTOR_SIZE); -+ for (i = 0; i < PGSIZE / DISK_SECTOR_SIZE; i++) -+ disk_read (swap_disk, sector++, up->frame->kpage + i * DISK_SECTOR_SIZE); ++/* Tries to lock the page containing ADDR into physical memory. ++ If WILL_WRITE is true, the page must be writeable; ++ otherwise it may be read-only. ++ Returns true if successful, false on failure. */ ++bool ++page_lock (const void *addr, bool will_write) ++{ ++ struct page *p = page_for_addr (addr); ++ if (p == NULL || (p->read_only && will_write)) ++ return false; ++ ++ frame_lock (p); ++ if (p->frame == NULL) ++ return (do_page_in (p) ++ && pagedir_set_page (thread_current ()->pagedir, p->addr, ++ p->frame->base, !p->read_only)); ++ else ++ return true; ++} ++ ++/* Unlocks a page locked with page_lock(). */ ++void ++page_unlock (const void *addr) ++{ ++ struct page *p = page_for_addr (addr); ++ ASSERT (p != NULL); ++ frame_unlock (p->frame); ++} +diff -u src/vm/page.h~ src/vm/page.h +--- src/vm/page.h~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/vm/page.h 2005-06-08 14:10:54.000000000 -0700 +@@ -0,0 +1,50 @@ ++#ifndef VM_PAGE_H ++#define VM_PAGE_H ++ ++#include ++#include "devices/disk.h" ++#include "filesys/off_t.h" ++#include "threads/synch.h" ++ ++/* Virtual page. */ ++struct page ++ { ++ /* Immutable members. */ ++ void *addr; /* User virtual address. */ ++ bool read_only; /* Read-only page? */ ++ struct thread *thread; /* Owning thread. */ ++ ++ /* Accessed only in owning process context. */ ++ struct hash_elem hash_elem; /* struct thread `pages' hash element. */ ++ ++ /* Set only in owning process context with frame->frame_lock held. ++ Cleared only with scan_lock and frame->frame_lock held. */ ++ struct frame *frame; /* Page frame. */ ++ ++ /* Swap information, protected by frame->frame_lock. */ ++ disk_sector_t sector; /* Starting sector of swap area, or -1. */ ++ ++ /* Memory-mapped file information, protected by frame->frame_lock. */ ++ bool private; /* False to write back to file, ++ true to write back to swap. */ ++ struct file *file; /* File. */ ++ off_t file_offset; /* Offset in file. */ ++ off_t file_bytes; /* Bytes to read/write, 1...PGSIZE. */ ++ }; ++ ++void page_exit (void); ++ ++struct page *page_allocate (void *, bool read_only); ++void page_deallocate (void *vaddr); ++ ++bool page_in (void *fault_addr); ++bool page_out (struct page *); ++bool page_accessed_recently (struct page *); ++ ++bool page_lock (const void *, bool will_write); ++void page_unlock (const void *); ++ ++hash_hash_func page_hash; ++hash_less_func page_less; ++ ++#endif /* vm/page.h */ +diff -u src/vm/swap.c~ src/vm/swap.c +--- src/vm/swap.c~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/vm/swap.c 2005-06-08 14:10:54.000000000 -0700 +@@ -0,0 +1,85 @@ ++#include "vm/swap.h" ++#include ++#include ++#include ++#include "vm/frame.h" ++#include "vm/page.h" ++#include "devices/disk.h" ++#include "threads/mmu.h" ++#include "threads/synch.h" + -+ up->swap_page = SIZE_MAX; ++/* The swap disk. */ ++static struct disk *swap_disk; ++ ++/* Used swap pages. */ ++static struct bitmap *swap_bitmap; ++ ++/* Protects swap_bitmap. */ ++static struct lock swap_lock; ++ ++/* Number of sectors per page. */ ++#define PAGE_SECTORS (PGSIZE / DISK_SECTOR_SIZE) ++ ++/* Sets up swap. */ ++void ++swap_init (void) ++{ ++ swap_disk = disk_get (1, 1); ++ if (swap_disk == NULL) ++ { ++ printf ("no swap disk--swap disabled\n"); ++ swap_bitmap = bitmap_create (0); ++ } ++ else ++ swap_bitmap = bitmap_create (disk_size (swap_disk) / PAGE_SECTORS); ++ if (swap_bitmap == NULL) ++ PANIC ("couldn't create swap bitmap"); ++ lock_init (&swap_lock); ++} ++ ++/* Swaps in page P, which must have a locked frame ++ (and be swapped out). */ ++void ++swap_in (struct page *p) ++{ ++ size_t i; ++ ++ ASSERT (p->frame != NULL); ++ ASSERT (lock_held_by_current_thread (&p->frame->lock)); ++ ASSERT (p->sector != (disk_sector_t) -1); ++ ++ for (i = 0; i < PAGE_SECTORS; i++) ++ disk_read (swap_disk, p->sector + i, ++ p->frame->base + i * DISK_SECTOR_SIZE); ++ bitmap_reset (swap_bitmap, p->sector / PAGE_SECTORS); ++ p->sector = (disk_sector_t) -1; ++} ++ ++/* Swaps out page P, which must have a locked frame. */ ++bool ++swap_out (struct page *p) ++{ ++ size_t slot; ++ size_t i; ++ ++ ASSERT (p->frame != NULL); ++ ASSERT (lock_held_by_current_thread (&p->frame->lock)); ++ ++ lock_acquire (&swap_lock); ++ slot = bitmap_scan_and_flip (swap_bitmap, 0, 1, false); ++ lock_release (&swap_lock); ++ if (slot == BITMAP_ERROR) ++ return false; ++ ++ p->sector = slot * PAGE_SECTORS; ++ for (i = 0; i < PAGE_SECTORS; i++) ++ disk_write (swap_disk, p->sector + i, ++ p->frame->base + i * DISK_SECTOR_SIZE); ++ ++ p->private = false; ++ p->file = NULL; ++ p->file_offset = 0; ++ p->file_bytes = 0; ++ ++ return true; +} -diff -urpN pintos.orig/src/vm/swap.h pintos/src/vm/swap.h ---- pintos.orig/src/vm/swap.h 1969-12-31 16:00:00.000000000 -0800 -+++ pintos/src/vm/swap.h 2004-09-27 13:29:44.000000000 -0700 +diff -u src/vm/swap.h~ src/vm/swap.h +--- src/vm/swap.h~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/vm/swap.h 2005-06-08 14:10:54.000000000 -0700 @@ -0,0 +1,11 @@ +#ifndef VM_SWAP_H +#define VM_SWAP_H 1 + +#include + -+struct user_page; ++struct page; +void swap_init (void); -+bool swap_write (struct user_page *); -+void swap_read (struct user_page *); ++void swap_in (struct page *); ++bool swap_out (struct page *); + +#endif /* vm/swap.h */ diff --git a/solutions/p4.patch b/solutions/p4.patch new file mode 100644 index 0000000..614c379 --- /dev/null +++ b/solutions/p4.patch @@ -0,0 +1,4000 @@ +diff -u src/Makefile.build~ src/Makefile.build +--- src/Makefile.build~ 2005-06-16 21:50:20.000000000 -0700 ++++ src/Makefile.build 2005-06-16 15:09:31.000000000 -0700 +@@ -53,7 +53,9 @@ userprog_SRC += userprog/gdt.c # GDT in + userprog_SRC += userprog/tss.c # TSS management. + + # No virtual memory code yet. +-#vm_SRC = vm/filename.c # Some file. ++vm_SRC = vm/page.c ++vm_SRC += vm/frame.c ++vm_SRC += vm/swap.c + + # Filesystem code. + filesys_SRC = filesys/filesys.c # Filesystem core. +@@ -62,6 +64,7 @@ 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. ++filesys_SRC += filesys/cache.c # Cache. + + SOURCES = $(foreach dir,$(KERNEL_SUBDIRS),$($(dir)_SRC)) + OBJECTS = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SOURCES))) +diff -u src/devices/timer.c~ src/devices/timer.c +--- src/devices/timer.c~ 2005-06-15 15:21:01.000000000 -0700 ++++ src/devices/timer.c 2005-06-16 15:09:31.000000000 -0700 +@@ -23,6 +23,9 @@ static volatile int64_t ticks; + Initialized by timer_calibrate(). */ + static unsigned loops_per_tick; + ++/* Threads waiting in timer_sleep(). */ ++static struct list wait_list; ++ + static intr_handler_func timer_interrupt; + static bool too_many_loops (unsigned loops); + static void busy_wait (int64_t loops); +@@ -43,6 +46,8 @@ timer_init (void) + outb (0x40, count >> 8); + + intr_register_ext (0x20, timer_interrupt, "8254 Timer"); ++ ++ list_init (&wait_list); + } + + /* Calibrates loops_per_tick, used to implement brief delays. */ +@@ -87,15 +92,36 @@ timer_elapsed (int64_t then) + return timer_ticks () - then; + } + ++/* Compares two threads based on their wake-up times. */ ++static bool ++compare_threads_by_wakeup_time (const struct list_elem *a_, ++ const struct list_elem *b_, ++ void *aux UNUSED) ++{ ++ const struct thread *a = list_entry (a_, struct thread, timer_elem); ++ const struct thread *b = list_entry (b_, struct thread, timer_elem); ++ ++ return a->wakeup_time < b->wakeup_time; ++} ++ + /* Suspends execution for approximately TICKS timer ticks. */ + void + timer_sleep (int64_t ticks) + { +- int64_t start = timer_ticks (); ++ struct thread *t = thread_current (); ++ ++ /* Schedule our wake-up time. */ ++ t->wakeup_time = timer_ticks () + ticks; + ++ /* Atomically insert the current thread into the wait list. */ + ASSERT (intr_get_level () == INTR_ON); +- while (timer_elapsed (start) < ticks) +- thread_yield (); ++ intr_disable (); ++ list_insert_ordered (&wait_list, &t->timer_elem, ++ compare_threads_by_wakeup_time, NULL); ++ intr_enable (); ++ ++ /* Wait. */ ++ sema_down (&t->timer_sema); + } + + /* Suspends execution for approximately MS milliseconds. */ +@@ -132,6 +158,16 @@ timer_interrupt (struct intr_frame *args + { + ticks++; + thread_tick (); ++ ++ while (!list_empty (&wait_list)) ++ { ++ struct thread *t = list_entry (list_front (&wait_list), ++ struct thread, timer_elem); ++ if (ticks < t->wakeup_time) ++ break; ++ sema_up (&t->timer_sema); ++ list_pop_front (&wait_list); ++ } + } + + /* Returns true if LOOPS iterations waits for more than one timer +diff -u src/filesys/Make.vars~ src/filesys/Make.vars +--- src/filesys/Make.vars~ 2005-05-24 14:46:45.000000000 -0700 ++++ src/filesys/Make.vars 2005-06-16 15:09:31.000000000 -0700 +@@ -6,6 +6,6 @@ KERNEL_SUBDIRS = threads devices lib lib + TEST_SUBDIRS = tests/userprog tests/filesys/base tests/filesys/extended + + # Uncomment the lines below to enable VM. +-#os.dsk: DEFINES += -DVM +-#KERNEL_SUBDIRS += vm +-#TEST_SUBDIRS += tests/vm ++os.dsk: DEFINES += -DVM ++KERNEL_SUBDIRS += vm ++TEST_SUBDIRS += tests/vm +diff -u src/filesys/cache.c~ src/filesys/cache.c +--- src/filesys/cache.c~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/filesys/cache.c 2005-06-16 15:09:31.000000000 -0700 +@@ -0,0 +1,473 @@ ++#include "filesys/cache.h" ++#include ++#include ++#include "filesys/filesys.h" ++#include "devices/disk.h" ++#include "devices/timer.h" ++#include "threads/malloc.h" ++#include "threads/synch.h" ++#include "threads/thread.h" ++ ++#define INVALID_SECTOR ((disk_sector_t) -1) ++ ++/* A cached block. */ ++struct cache_block ++ { ++ /* Locking to prevent eviction. */ ++ struct lock block_lock; /* Protects fields in group. */ ++ struct condition no_readers_or_writers; /* readers == 0 && writers == 0 */ ++ struct condition no_writers; /* writers == 0 */ ++ int readers, read_waiters; /* # of readers, # waiting to read. */ ++ int writers, write_waiters; /* # of writers (<= 1), # waiting to write. */ ++ ++ /* Sector number. INVALID_SECTOR indicates a free cache block. ++ ++ Changing from free to allocated requires cache_sync. ++ ++ Changing from allocated to free requires block_lock, block ++ must be up-to-date and not dirty, and no one may be ++ waiting on it. */ ++ disk_sector_t sector; ++ ++ /* Is data[] correct? ++ Requires write lock or data_lock. */ ++ bool up_to_date; ++ ++ /* Does data[] need to be written back to disk? ++ Valid only when up-to-date. ++ Requires read lock or write lock or data_lock. */ ++ bool dirty; ++ ++ /* Sector data. ++ Access to data[] requires up-to-date and read or write lock. ++ Bringing up-to-date requires write lock or data_lock. */ ++ struct lock data_lock; /* Protects fields in group. */ ++ uint8_t data[DISK_SECTOR_SIZE]; /* Disk data. */ ++ }; ++ ++/* Cache. */ ++#define CACHE_CNT 64 ++struct cache_block cache[CACHE_CNT]; ++ ++/* Cache lock. ++ ++ Required to allocate a cache block to a sector, to prevent a ++ single sector being allocated two different cache blocks. ++ ++ Required to search the cache for a sector, to prevent the ++ sector from being added while the search is ongoing. ++ ++ Protects hand. */ ++struct lock cache_sync; ++ ++/* Cache eviction hand. ++ Protected by cache_sync. */ ++static int hand = 0; ++ ++static void flushd_init (void); ++static void readaheadd_init (void); ++static void readaheadd_submit (disk_sector_t sector); ++ ++/* Initializes cache. */ ++void ++cache_init (void) ++{ ++ int i; ++ ++ lock_init (&cache_sync); ++ for (i = 0; i < CACHE_CNT; i++) ++ { ++ struct cache_block *b = &cache[i]; ++ lock_init (&b->block_lock); ++ cond_init (&b->no_readers_or_writers); ++ cond_init (&b->no_writers); ++ b->readers = b->read_waiters = 0; ++ b->writers = b->write_waiters = 0; ++ b->sector = INVALID_SECTOR; ++ lock_init (&b->data_lock); ++ } ++ ++ flushd_init (); ++ readaheadd_init (); ++} ++ ++/* Flushes cache to disk. */ ++void ++cache_flush (void) ++{ ++ int i; ++ ++ for (i = 0; i < CACHE_CNT; i++) ++ { ++ struct cache_block *b = &cache[i]; ++ disk_sector_t sector; ++ ++ lock_acquire (&b->block_lock); ++ sector = b->sector; ++ lock_release (&b->block_lock); ++ ++ if (sector == INVALID_SECTOR) ++ continue; ++ ++ b = cache_lock (sector, EXCLUSIVE); ++ if (b->up_to_date && b->dirty) ++ { ++ disk_write (filesys_disk, b->sector, b->data); ++ b->dirty = false; ++ } ++ cache_unlock (b); ++ } ++} ++ ++/* Locks the given SECTOR into the cache and returns the cache ++ block. ++ If TYPE is EXCLUSIVE, then the block returned will be locked ++ only by the caller. The calling thread must not already ++ have any lock on the block. ++ If TYPE is NON_EXCLUSIVE, then block returned may be locked by ++ any number of other callers. The calling thread may already ++ have any number of non-exclusive locks on the block. */ ++struct cache_block * ++cache_lock (disk_sector_t sector, enum lock_type type) ++{ ++ int i; ++ ++ try_again: ++ lock_acquire (&cache_sync); ++ ++ /* Is the block already in-cache? */ ++ for (i = 0; i < CACHE_CNT; i++) ++ { ++ /* Skip any blocks that don't hold SECTOR. */ ++ struct cache_block *b = &cache[i]; ++ lock_acquire (&b->block_lock); ++ if (b->sector != sector) ++ { ++ lock_release (&b->block_lock); ++ continue; ++ } ++ lock_release (&cache_sync); ++ ++ /* Get read or write lock. */ ++ if (type == NON_EXCLUSIVE) ++ { ++ /* Lock for read. */ ++ b->read_waiters++; ++ if (b->writers || b->write_waiters) ++ do { ++ cond_wait (&b->no_writers, &b->block_lock); ++ } while (b->writers); ++ b->readers++; ++ b->read_waiters--; ++ } ++ else ++ { ++ /* Lock for write. */ ++ b->write_waiters++; ++ if (b->readers || b->read_waiters || b->writers) ++ do { ++ cond_wait (&b->no_readers_or_writers, &b->block_lock); ++ } while (b->readers || b->writers); ++ b->writers++; ++ b->write_waiters--; ++ } ++ lock_release (&b->block_lock); ++ ++ /* Our sector should have been pinned in the cache while we ++ were waiting. Make sure. */ ++ ASSERT (b->sector == sector); ++ ++ return b; ++ } ++ ++ /* Not in cache. Find empty slot. ++ We hold cache_sync. */ ++ for (i = 0; i < CACHE_CNT; i++) ++ { ++ struct cache_block *b = &cache[i]; ++ lock_acquire (&b->block_lock); ++ if (b->sector == INVALID_SECTOR) ++ { ++ /* Drop block_lock, which is no longer needed because ++ this is the only code that allocates free blocks, ++ and we still have cache_sync. ++ ++ We can't drop cache_sync yet because someone else ++ might try to allocate this same block (or read from ++ it) while we're still initializing the block. */ ++ lock_release (&b->block_lock); ++ ++ b->sector = sector; ++ b->up_to_date = false; ++ ASSERT (b->readers == 0); ++ ASSERT (b->writers == 0); ++ if (type == NON_EXCLUSIVE) ++ b->readers = 1; ++ else ++ b->writers = 1; ++ lock_release (&cache_sync); ++ return b; ++ } ++ lock_release (&b->block_lock); ++ } ++ ++ /* No empty slots. Evict something. ++ We hold cache_sync. */ ++ for (i = 0; i < CACHE_CNT; i++) ++ { ++ struct cache_block *b = &cache[hand]; ++ if (++hand >= CACHE_CNT) ++ hand = 0; ++ ++ /* Try to grab exclusive write access to block. */ ++ lock_acquire (&b->block_lock); ++ if (b->readers || b->writers || b->read_waiters || b->write_waiters) ++ { ++ lock_release (&b->block_lock); ++ continue; ++ } ++ b->writers = 1; ++ lock_release (&b->block_lock); ++ ++ lock_release (&cache_sync); ++ ++ /* Write block to disk if dirty. */ ++ if (b->up_to_date && b->dirty) ++ { ++ disk_write (filesys_disk, b->sector, b->data); ++ b->dirty = false; ++ } ++ ++ /* Remove block from cache, if possible: someone might have ++ started waiting on it while the lock was released. */ ++ lock_acquire (&b->block_lock); ++ b->writers = 0; ++ if (!b->read_waiters && !b->write_waiters) ++ { ++ /* No one is waiting for it, so we can free it. */ ++ b->sector = INVALID_SECTOR; ++ } ++ else ++ { ++ /* There is a waiter. Give it the block. */ ++ if (b->read_waiters) ++ cond_broadcast (&b->no_writers, &b->block_lock); ++ else ++ cond_signal (&b->no_readers_or_writers, &b->block_lock); ++ } ++ lock_release (&b->block_lock); ++ ++ /* Try again. */ ++ goto try_again; ++ } ++ ++ /* Wait for cache contention to die down. */ ++ lock_release (&cache_sync); ++ timer_sleep (1000); ++ goto try_again; ++} ++ ++/* Bring block B up-to-date, by reading it from disk if ++ necessary, and return a pointer to its data. ++ The caller must have an exclusive or non-exclusive lock on ++ B. */ ++void * ++cache_read (struct cache_block *b) ++{ ++ lock_acquire (&b->data_lock); ++ if (!b->up_to_date) ++ { ++ disk_read (filesys_disk, b->sector, b->data); ++ b->up_to_date = true; ++ b->dirty = false; ++ } ++ lock_release (&b->data_lock); ++ ++ return b->data; ++} ++ ++/* Zero out block B, without reading it from disk, and return a ++ pointer to the zeroed data. ++ The caller must have an exclusive lock on B. */ ++void * ++cache_zero (struct cache_block *b) ++{ ++ ASSERT (b->writers); ++ memset (b->data, 0, DISK_SECTOR_SIZE); ++ b->up_to_date = true; ++ b->dirty = true; ++ ++ return b->data; ++} ++ ++/* Marks block B as dirty, so that it will be written back to ++ disk before eviction. ++ The caller must have a read or write lock on B, ++ and B must be up-to-date. */ ++void ++cache_dirty (struct cache_block *b) ++{ ++ ASSERT (b->up_to_date); ++ b->dirty = true; ++} ++ ++/* Unlocks block B. ++ If B is no longer locked by any thread, then it becomes a ++ candidate for immediate eviction. */ ++void ++cache_unlock (struct cache_block *b) ++{ ++ lock_acquire (&b->block_lock); ++ if (b->readers) ++ { ++ ASSERT (b->writers == 0); ++ if (--b->readers == 0) ++ cond_signal (&b->no_readers_or_writers, &b->block_lock); ++ } ++ else if (b->writers) ++ { ++ ASSERT (b->readers == 0); ++ ASSERT (b->writers == 1); ++ b->writers--; ++ if (b->read_waiters) ++ cond_broadcast (&b->no_writers, &b->block_lock); ++ else ++ cond_signal (&b->no_readers_or_writers, &b->block_lock); ++ } ++ else ++ NOT_REACHED (); ++ lock_release (&b->block_lock); ++} ++ ++/* If SECTOR is in the cache, evicts it immediately without ++ writing it back to disk (even if dirty). ++ The block must be entirely unused. */ ++void ++cache_free (disk_sector_t sector) ++{ ++ int i; ++ ++ lock_acquire (&cache_sync); ++ for (i = 0; i < CACHE_CNT; i++) ++ { ++ struct cache_block *b = &cache[i]; ++ ++ lock_acquire (&b->block_lock); ++ if (b->sector == sector) ++ { ++ lock_release (&cache_sync); ++ ++ /* Only invalidate the block if it's unused. That ++ should be the normal case, but it could be part of a ++ read-ahead (in readaheadd()) or write-behind (in ++ cache_flush()). */ ++ if (b->readers == 0 && b->read_waiters == 0 ++ && b->writers == 0 && b->write_waiters == 0) ++ b->sector = INVALID_SECTOR; ++ ++ lock_release (&b->block_lock); ++ return; ++ } ++ lock_release (&b->block_lock); ++ } ++ lock_release (&cache_sync); ++} ++ ++void ++cache_readahead (disk_sector_t sector) ++{ ++ readaheadd_submit (sector); ++} ++ ++/* Flush daemon. */ ++ ++static void flushd (void *aux); ++ ++/* Initializes flush daemon. */ ++static void ++flushd_init (void) ++{ ++ thread_create ("flushd", PRI_MIN, flushd, NULL); ++} ++ ++/* Flush daemon thread. */ ++static void ++flushd (void *aux UNUSED) ++{ ++ for (;;) ++ { ++ timer_msleep (30 * 1000); ++ cache_flush (); ++ } ++} ++ ++/* A block to read ahead. */ ++struct readahead_block ++ { ++ struct list_elem list_elem; /* readahead_list element. */ ++ disk_sector_t sector; /* Sector to read. */ ++ }; ++ ++/* Protects readahead_list. ++ Monitor lock for readahead_list_nonempty. */ ++static struct lock readahead_lock; ++ ++/* Signaled when a block is added to readahead_list. */ ++static struct condition readahead_list_nonempty; ++ ++/* List of blocks for read-ahead. */ ++static struct list readahead_list; ++ ++static void readaheadd (void *aux); ++ ++/* Initialize read-ahead daemon. */ ++static void ++readaheadd_init (void) ++{ ++ lock_init (&readahead_lock); ++ cond_init (&readahead_list_nonempty); ++ list_init (&readahead_list); ++ thread_create ("readaheadd", PRI_MIN, readaheadd, NULL); ++} ++ ++/* Adds SECTOR to the read-ahead queue. */ ++static void ++readaheadd_submit (disk_sector_t sector) ++{ ++ /* Allocate readahead block. */ ++ struct readahead_block *block = malloc (sizeof *block); ++ if (block == NULL) ++ return; ++ block->sector = sector; ++ ++ /* Add block to list. */ ++ lock_acquire (&readahead_lock); ++ list_push_back (&readahead_list, &block->list_elem); ++ cond_signal (&readahead_list_nonempty, &readahead_lock); ++ lock_release (&readahead_lock); ++} ++ ++/* Read-ahead daemon. */ ++static void ++readaheadd (void *aux UNUSED) ++{ ++ for (;;) ++ { ++ struct readahead_block *ra_block; ++ struct cache_block *cache_block; ++ ++ /* Get readahead block from list. */ ++ lock_acquire (&readahead_lock); ++ while (list_empty (&readahead_list)) ++ cond_wait (&readahead_list_nonempty, &readahead_lock); ++ ra_block = list_entry (list_pop_front (&readahead_list), ++ struct readahead_block, list_elem); ++ lock_release (&readahead_lock); ++ ++ /* Read block into cache. */ ++ cache_block = cache_lock (ra_block->sector, NON_EXCLUSIVE); ++ cache_read (cache_block); ++ cache_unlock (cache_block); ++ free (ra_block); ++ } ++} +diff -u src/filesys/cache.h~ src/filesys/cache.h +--- src/filesys/cache.h~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/filesys/cache.h 2005-06-16 15:09:31.000000000 -0700 +@@ -0,0 +1,23 @@ ++#ifndef FILESYS_CACHE_H ++#define FILESYS_CACHE_H ++ ++#include "devices/disk.h" ++ ++/* Type of block lock. */ ++enum lock_type ++ { ++ NON_EXCLUSIVE, /* Any number of lockers. */ ++ EXCLUSIVE /* Only one locker. */ ++ }; ++ ++void cache_init (void); ++void cache_flush (void); ++struct cache_block *cache_lock (disk_sector_t, enum lock_type); ++void *cache_read (struct cache_block *); ++void *cache_zero (struct cache_block *); ++void cache_dirty (struct cache_block *); ++void cache_unlock (struct cache_block *); ++void cache_free (disk_sector_t); ++void cache_readahead (disk_sector_t); ++ ++#endif /* filesys/cache.h */ +diff -u src/filesys/directory.c~ src/filesys/directory.c +--- src/filesys/directory.c~ 2005-06-16 21:50:20.000000000 -0700 ++++ src/filesys/directory.c 2005-06-16 21:09:01.000000000 -0700 +@@ -3,12 +3,17 @@ + #include + #include + #include "filesys/filesys.h" ++#include "filesys/fsutil.h" + #include "filesys/inode.h" + #include "threads/malloc.h" ++#include "threads/synch.h" + + /* A directory. */ + struct dir + { ++ struct list_elem list_elem; /* open_dirs list element. */ ++ struct lock dir_lock; /* Protects inode. */ ++ int open_cnt; /* Number of openers. */ + struct inode *inode; /* Backing store. */ + }; + +@@ -20,15 +25,21 @@ struct dir_entry + 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 (disk_sector_t sector, size_t entry_cnt) ++/* List of all open directories. */ ++static struct list open_dirs; ++ ++/* Protects open_dirs additions or removals. */ ++static struct lock open_dirs_lock; ++ ++/* Initializes directory modules. */ ++void ++dir_init (void) + { +- return inode_create (sector, entry_cnt * sizeof (struct dir_entry)); ++ list_init (&open_dirs); ++ lock_init (&open_dirs_lock); + } + +-/* Opens the directory in the given INODE, of which it takes ++/* Opens the directory for the given INODE, of which it takes + ownership, and sets *DIRP to the new directory or a null + pointer on failure. Return true if successful, false on + failure. */ +@@ -36,19 +47,46 @@ bool + dir_open (struct inode *inode, struct dir **dirp) + { + struct dir *dir = NULL; ++ struct list_elem *e; + + ASSERT (dirp != NULL); + +- if (inode != NULL) ++ lock_acquire (&open_dirs_lock); ++ ++ if (inode == NULL) ++ goto done; ++ ++ /* Inode must refer to directory. */ ++ if (inode_get_type (inode) != DIR_INODE) ++ goto done; ++ ++ /* Check whether this directory is already open. */ ++ for (e = list_begin (&open_dirs); e != list_end (&open_dirs); ++ e = list_next (e)) + { +- dir = malloc (sizeof *dir); +- if (dir != NULL) +- dir->inode = inode; ++ dir = list_entry (e, struct dir, list_elem); ++ if (dir->inode == inode) ++ { ++ dir->open_cnt++; ++ goto done; ++ } ++ } ++ ++ /* Create new directory. */ ++ dir = calloc (1, sizeof *dir); ++ if (dir != NULL) ++ { ++ list_push_front (&open_dirs, &dir->list_elem); ++ lock_init (&dir->dir_lock); ++ dir->open_cnt = 1; ++ dir->inode = inode; ++ inode_reopen (dir->inode); + } + ++ done: + *dirp = dir; +- if (dir == NULL) +- inode_close (inode); ++ inode_close (inode); ++ lock_release (&open_dirs_lock); + return dir != NULL; + } + +@@ -61,22 +99,34 @@ dir_open_root (struct dir **dirp) + return dir_open (inode_open (ROOT_DIR_SECTOR), dirp); + } + ++/* Re-opens DIR and returns true. */ ++bool ++dir_reopen (struct dir *dir) ++{ ++ dir->open_cnt++; ++ return true; ++} ++ + /* Destroys DIR and frees associated resources. */ + void + dir_close (struct dir *dir) + { +- if (dir != NULL) ++ if (dir == NULL) ++ return; ++ ++ lock_acquire (&open_dirs_lock); ++ if (--dir->open_cnt == 0) + { ++ list_remove (&dir->list_elem); + inode_close (dir->inode); + free (dir); + } ++ lock_release (&open_dirs_lock); + } + + /* 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. */ ++ If successful, returns the file's entry; ++ otherwise, returns a null pointer. */ + static bool + lookup (const struct dir *dir, const char *name, + struct dir_entry *ep, off_t *ofsp) +@@ -113,10 +163,12 @@ dir_lookup (const struct dir *dir, const + ASSERT (dir != NULL); + ASSERT (name != NULL); + ++ lock_acquire ((struct lock *) &dir->dir_lock); + if (lookup (dir, name, &e, NULL)) + *inode = inode_open (e.inode_sector); + else + *inode = NULL; ++ lock_release ((struct lock *)&dir->dir_lock); + + return *inode != NULL; + } +@@ -138,10 +190,11 @@ dir_add (struct dir *dir, const char *na + ASSERT (name != NULL); + + /* Check NAME for validity. */ +- if (*name == '\0' || strlen (name) > NAME_MAX) ++ if (*name == '\0' || strchr (name, '/') || strlen (name) > NAME_MAX) + return false; + + /* Check that NAME is not in use. */ ++ lock_acquire (&dir->dir_lock); + if (lookup (dir, name, NULL, NULL)) + goto done; + +@@ -164,6 +217,7 @@ dir_add (struct dir *dir, const char *na + success = inode_write_at (dir->inode, &e, sizeof e, ofs) == sizeof e; + + done: ++ lock_release (&dir->dir_lock); + return success; + } + +@@ -182,12 +236,14 @@ dir_remove (struct dir *dir, const char + ASSERT (name != NULL); + + /* Find directory entry. */ ++ lock_acquire (&dir->dir_lock); + if (!lookup (dir, name, &e, &ofs)) + goto done; + +- /* Open inode. */ ++ /* Open inode and verify that it is not an in-use directory. */ + inode = inode_open (e.inode_sector); +- if (inode == NULL) ++ if (inode == NULL ++ || (inode_get_type (inode) == DIR_INODE && inode_open_cnt (inode) != 1)) + goto done; + + /* Erase directory entry. */ +@@ -201,6 +257,7 @@ dir_remove (struct dir *dir, const char + + done: + inode_close (inode); ++ lock_release (&dir->dir_lock); + return success; + } + +@@ -211,8 +268,10 @@ dir_list (const struct dir *dir) + struct dir_entry e; + size_t ofs; + ++ lock_acquire ((struct lock *) &dir->dir_lock); + for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e; + ofs += sizeof e) + if (e.in_use) + printf ("%s\n", e.name); ++ lock_release ((struct lock *) &dir->dir_lock); + } +diff -u src/filesys/directory.h~ src/filesys/directory.h +--- src/filesys/directory.h~ 2005-06-16 21:50:20.000000000 -0700 ++++ src/filesys/directory.h 2005-06-16 15:09:31.000000000 -0700 +@@ -13,9 +13,10 @@ + + struct inode; + struct dir; +-bool dir_create (disk_sector_t sector, size_t entry_cnt); ++void dir_init (void); + bool dir_open (struct inode *, struct dir **); + bool dir_open_root (struct dir **); ++bool dir_reopen (struct dir *); + void dir_close (struct dir *); + bool dir_lookup (const struct dir *, const char *name, struct inode **); + bool dir_add (struct dir *, const char *name, disk_sector_t); +diff -u src/filesys/file.c~ src/filesys/file.c +--- src/filesys/file.c~ 2005-06-16 21:50:20.000000000 -0700 ++++ src/filesys/file.c 2005-06-16 21:02:34.000000000 -0700 +@@ -1,6 +1,8 @@ + #include "filesys/file.h" + #include ++#include "filesys/directory.h" + #include "filesys/inode.h" ++#include "filesys/filesys.h" + #include "threads/malloc.h" + + /* An open file. */ +@@ -18,7 +20,7 @@ struct file * + file_open (struct inode *inode) + { + struct file *file = calloc (1, sizeof *file); +- if (inode != NULL && file != NULL) ++ if (inode != NULL && file != NULL && inode_get_type (inode) == FILE_INODE) + { + file->inode = inode; + file->pos = 0; +diff -u src/filesys/filesys.c~ src/filesys/filesys.c +--- src/filesys/filesys.c~ 2005-06-16 21:50:20.000000000 -0700 ++++ src/filesys/filesys.c 2005-06-16 21:03:07.000000000 -0700 +@@ -2,11 +2,13 @@ + #include + #include + #include ++#include "filesys/cache.h" + #include "filesys/file.h" + #include "filesys/free-map.h" + #include "filesys/inode.h" + #include "filesys/directory.h" + #include "devices/disk.h" ++#include "threads/thread.h" + + /* The disk that contains the filesystem. */ + struct disk *filesys_disk; +@@ -23,6 +24,8 @@ filesys_init (bool format) + PANIC ("hd0:1 (hdb) not present, filesystem initialization failed"); + + inode_init (); ++ dir_init (); ++ cache_init (); + free_map_init (); + + if (format) +@@ -37,6 +40,103 @@ void + filesys_done (void) + { + free_map_close (); ++ cache_flush (); ++} ++ ++/* Extracts a file name part from *SRCP into PART, ++ and updates *SRCP so that the next call will return the next ++ file name part. ++ Returns 1 if successful, 0 at end of string, -1 for a too-long ++ file name part. */ ++static int ++get_next_part (char part[NAME_MAX], const char **srcp) ++{ ++ const char *src = *srcp; ++ char *dst = part; ++ ++ /* Skip leading slashes. ++ If it's all slashes, we're done. */ ++ while (*src == '/') ++ src++; ++ if (*src == '\0') ++ return 0; ++ ++ /* Copy up to NAME_MAX character from SRC to DST. ++ Add null terminator. */ ++ while (*src != '/' && *src != '\0') ++ { ++ if (dst < part + NAME_MAX) ++ *dst++ = *src; ++ else ++ return -1; ++ src++; ++ } ++ *dst = '\0'; ++ ++ /* Advance source pointer. */ ++ *srcp = src; ++ return 1; ++} ++ ++/* Resolves relative or absolute file NAME. ++ Returns true if successful, false on failure. ++ Stores the directory corresponding to the name into *DIRP, ++ and the file name part into BASENAME. */ ++static bool ++resolve_name (const char *name, ++ struct dir **dirp, char basename[NAME_MAX + 1]) ++{ ++ struct dir *dir = NULL; ++ struct inode *inode; ++ const char *cp; ++ char part[NAME_MAX + 1], next_part[NAME_MAX + 1]; ++ int ok; ++ ++ /* Find starting directory. */ ++ if (name[0] == '/' || thread_current ()->wd == NULL) ++ { ++ if (!dir_open_root (&dir)) ++ goto error; ++ } ++ else ++ { ++ if (!dir_reopen (thread_current ()->wd)) ++ goto error; ++ dir = thread_current ()->wd; ++ } ++ ++ /* Get first name part. */ ++ cp = name; ++ if (get_next_part (part, &cp) <= 0) ++ goto error; ++ ++ /* As long as another part follows the current one, ++ traverse down another directory. */ ++ while ((ok = get_next_part (next_part, &cp)) > 0) ++ { ++ if (!dir_lookup (dir, part, &inode)) ++ goto error; ++ ++ dir_close (dir); ++ if (!dir_open (inode, &dir)) ++ goto error; ++ ++ strlcpy (part, next_part, NAME_MAX + 1); ++ } ++ if (ok < 0) ++ goto error; ++ ++ /* Return our results. */ ++ *dirp = dir; ++ strlcpy (basename, part, NAME_MAX + 1); ++ return true; ++ ++ error: ++ /* Return failure. */ ++ dir_close (dir); ++ *dirp = NULL; ++ basename[0] = '\0'; ++ return false; + } + + /* Creates a file named NAME with the given INITIAL_SIZE. +@@ -44,16 +144,17 @@ filesys_done (void) + Fails if a file named NAME already exists, + or if internal memory allocation fails. */ + bool +-filesys_create (const char *name, off_t initial_size) ++filesys_create (const char *name, off_t initial_size, enum inode_type type) + { + struct dir *dir; ++ char basename[NAME_MAX + 1]; + disk_sector_t inode_sector = 0; +- bool success = (dir_open_root (&dir) +- && 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); ++ bool success = (resolve_name (name, &dir, basename) ++ && free_map_allocate (&inode_sector) ++ && inode_create (inode_sector, initial_size, type) ++ && dir_add (dir, basename, inode_sector)); ++ if (!success && inode_sector) ++ free_map_release (inode_sector); + dir_close (dir); + + return success; +@@ -64,17 +165,18 @@ filesys_create (const char *name, off_t + otherwise. + Fails if no file named NAME exists, + or if an internal memory allocation fails. */ +-struct file * ++struct inode * + filesys_open (const char *name) + { + struct dir *dir; ++ char basename[NAME_MAX + 1]; + struct inode *inode = NULL; + +- if (dir_open_root (&dir)) +- dir_lookup (dir, name, &inode); ++ if (resolve_name (name, &dir, basename)) ++ dir_lookup (dir, basename, &inode); + dir_close (dir); + +- return file_open (inode); ++ return inode; + } + + /* Deletes the file named NAME. +@@ -85,13 +187,54 @@ bool + filesys_remove (const char *name) + { + struct dir *dir = NULL; +- bool success = (dir_open_root (&dir) +- && dir_remove (dir, name)); ++ char basename[NAME_MAX + 1]; ++ bool success = false; ++ ++ if (resolve_name (name, &dir, basename)) ++ success = dir_remove (dir, basename); + dir_close (dir); + + return success; + } + ++/* Change current directory to NAME. ++ Return true if successful, false on failure. */ ++bool ++filesys_chdir (const char *name) ++{ ++ struct dir *dir; ++ ++ /* Find new directory. */ ++ if (*name == '\0') ++ return false; ++ else if (name[strspn (name, "/")] == '\0') ++ { ++ if (!dir_open_root (&dir)) ++ return false; ++ } ++ else ++ { ++ char basename[NAME_MAX + 1]; ++ struct inode *base_inode; ++ struct dir *base_dir; ++ if (!resolve_name (name, &dir, basename) ++ || !dir_lookup (dir, basename, &base_inode) ++ || !dir_open (base_inode, &base_dir)) ++ { ++ dir_close (dir); ++ return false; ++ } ++ dir_close (dir); ++ dir = base_dir; ++ } ++ ++ /* Change current directory. */ ++ dir_close (thread_current ()->wd); ++ thread_current ()->wd = dir; ++ ++ return true; ++} ++ + /* Prints a list of files in the filesystem to the system + console. + Returns true if successful, false on failure, +@@ -99,13 +242,9 @@ filesys_remove (const char *name) + bool + filesys_list (void) + { +- struct dir *dir = NULL; +- bool success = dir_open_root (&dir); +- if (success) +- dir_list (dir); +- dir_close (dir); ++ dir_list (thread_current ()->wd); + +- return success; ++ return true; + } + + static void must_succeed_function (int, bool) NO_INLINE; +@@ -128,8 +267,8 @@ filesys_self_test (void) + { + /* Create file and check that it contains zeros + throughout the created length. */ +- MUST_SUCCEED (filesys_create ("foo", sizeof s)); +- MUST_SUCCEED ((file = filesys_open ("foo")) != NULL); ++ MUST_SUCCEED (filesys_create ("foo", sizeof s, FILE_INODE)); ++ MUST_SUCCEED ((file = file_open (filesys_open ("foo"))) != NULL); + MUST_SUCCEED (file_read (file, s2, sizeof s2) == sizeof s2); + MUST_SUCCEED (memcmp (s2, zeros, sizeof s) == 0); + MUST_SUCCEED (file_tell (file) == sizeof s); +@@ -137,7 +276,7 @@ filesys_self_test (void) + file_close (file); + + /* Reopen file and write to it. */ +- MUST_SUCCEED ((file = filesys_open ("foo")) != NULL); ++ MUST_SUCCEED ((file = file_open (filesys_open ("foo"))) != NULL); + MUST_SUCCEED (file_write (file, s, sizeof s) == sizeof s); + MUST_SUCCEED (file_tell (file) == sizeof s); + MUST_SUCCEED (file_length (file) == sizeof s); +@@ -145,7 +284,7 @@ filesys_self_test (void) + + /* Reopen file and verify that it reads back correctly. + Delete file while open to check proper semantics. */ +- MUST_SUCCEED ((file = filesys_open ("foo")) != NULL); ++ MUST_SUCCEED ((file = file_open (filesys_open ("foo"))) != NULL); + MUST_SUCCEED (filesys_remove ("foo")); + MUST_SUCCEED (filesys_open ("foo") == NULL); + MUST_SUCCEED (file_read (file, s2, sizeof s) == sizeof s); +@@ -172,9 +311,13 @@ static void + do_format (void) + { + printf ("Formatting filesystem..."); ++ ++ /* Set up free map. */ + free_map_create (); +- if (!dir_create (ROOT_DIR_SECTOR, 16)) ++ ++ /* Set up root directory. */ ++ if (!inode_create (ROOT_DIR_SECTOR, 0, DIR_INODE)) + PANIC ("root directory creation failed"); +- free_map_close (); ++ + printf ("done.\n"); + } +diff -u src/filesys/filesys.h~ src/filesys/filesys.h +--- src/filesys/filesys.h~ 2005-06-16 21:50:20.000000000 -0700 ++++ src/filesys/filesys.h 2005-06-16 20:55:14.000000000 -0700 +@@ -3,6 +3,7 @@ + + #include + #include "filesys/off_t.h" ++#include "filesys/inode.h" + + /* Sectors of system file inodes. */ + #define FREE_MAP_SECTOR 0 /* Free map file inode sector. */ +@@ -13,8 +14,8 @@ extern struct disk *filesys_disk; + + 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_create (const char *name, off_t initial_size, enum inode_type); ++struct inode *filesys_open (const char *name); + bool filesys_remove (const char *name); + bool filesys_chdir (const char *name); + bool filesys_list (void); +diff -u src/filesys/free-map.c~ src/filesys/free-map.c +--- src/filesys/free-map.c~ 2005-06-16 21:50:20.000000000 -0700 ++++ src/filesys/free-map.c 2005-06-16 20:57:13.000000000 -0700 +@@ -3,15 +3,18 @@ + #include + #include "filesys/file.h" + #include "filesys/filesys.h" +-#include "filesys/inode.h" ++#include "threads/synch.h" + + static struct file *free_map_file; /* Free map file. */ + static struct bitmap *free_map; /* Free map, one bit per disk sector. */ ++static struct lock free_map_lock; /* Mutual exclusion. */ + + /* Initializes the free map. */ + void + free_map_init (void) + { ++ lock_init (&free_map_lock); ++ + free_map = bitmap_create (disk_size (filesys_disk)); + if (free_map == NULL) + PANIC ("bitmap creation failed--disk is too large"); +@@ -19,33 +22,32 @@ free_map_init (void) + 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 all sectors were ++/* Allocates a sector from the free map and stores it into ++ *SECTORP. ++ Return true if successful, false if all sectors were + available. */ + bool +-free_map_allocate (size_t cnt, disk_sector_t *sectorp) ++free_map_allocate (disk_sector_t *sectorp) + { +- disk_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) ++ size_t sector; ++ ++ lock_acquire (&free_map_lock); ++ sector = bitmap_scan_and_flip (free_map, 0, 1, false); ++ lock_release (&free_map_lock); ++ ++ if (sector != BITMAP_ERROR) + *sectorp = sector; + return sector != BITMAP_ERROR; + } + +-/* Makes CNT sectors starting at SECTOR available for use. */ ++/* Makes SECTOR available for use. */ + void +-free_map_release (disk_sector_t sector, size_t cnt) ++free_map_release (disk_sector_t sector) + { +- ASSERT (bitmap_all (free_map, sector, cnt)); +- bitmap_set_multiple (free_map, sector, cnt, false); +- bitmap_write (free_map, free_map_file); ++ lock_acquire (&free_map_lock); ++ ASSERT (bitmap_test (free_map, sector)); ++ bitmap_reset (free_map, sector); ++ lock_release (&free_map_lock); + } + + /* Opens the free map file and reads it from disk. */ +@@ -63,6 +65,8 @@ free_map_open (void) + void + free_map_close (void) + { ++ if (!bitmap_write (free_map, free_map_file)) ++ PANIC ("can't write free map"); + file_close (free_map_file); + } + +@@ -72,7 +76,7 @@ void + free_map_create (void) + { + /* Create inode. */ +- if (!inode_create (FREE_MAP_SECTOR, bitmap_file_size (free_map))) ++ if (!inode_create (FREE_MAP_SECTOR, bitmap_file_size (free_map), FILE_INODE)) + PANIC ("free map creation failed"); + + /* Write bitmap to file. */ +@@ -81,4 +85,5 @@ free_map_create (void) + PANIC ("can't open free map"); + if (!bitmap_write (free_map, free_map_file)) + PANIC ("can't write free map"); ++ file_close (free_map_file); + } +diff -u src/filesys/free-map.h~ src/filesys/free-map.h +--- src/filesys/free-map.h~ 2005-06-16 21:50:20.000000000 -0700 ++++ src/filesys/free-map.h 2005-06-16 20:48:04.000000000 -0700 +@@ -11,7 +11,7 @@ void free_map_create (void); + void free_map_open (void); + void free_map_close (void); + +-bool free_map_allocate (size_t, disk_sector_t *); +-void free_map_release (disk_sector_t, size_t); ++bool free_map_allocate (disk_sector_t *); ++void free_map_release (disk_sector_t); + + #endif /* filesys/free-map.h */ +diff -u src/filesys/fsutil.c~ src/filesys/fsutil.c +--- src/filesys/fsutil.c~ 2005-06-16 21:50:20.000000000 -0700 ++++ src/filesys/fsutil.c 2005-06-16 20:55:13.000000000 -0700 +@@ -30,7 +30,7 @@ fsutil_cat (char **argv) + char *buffer; + + printf ("Printing '%s' to the console...\n", filename); +- file = filesys_open (filename); ++ file = file_open (filesys_open (filename)); + if (file == NULL) + PANIC ("%s: open failed", filename); + buffer = palloc_get_page (PAL_ASSERT); +@@ -102,9 +102,9 @@ fsutil_put (char **argv) + PANIC ("%s: invalid file size %d", filename, size); + + /* Create destination file. */ +- if (!filesys_create (filename, size)) ++ if (!filesys_create (filename, size, FILE_INODE)) + PANIC ("%s: create failed", filename); +- dst = filesys_open (filename); ++ dst = file_open (filesys_open (filename)); + if (dst == NULL) + PANIC ("%s: open failed", filename); + +@@ -154,7 +154,7 @@ fsutil_get (char **argv) + PANIC ("couldn't allocate buffer"); + + /* Open source file. */ +- src = filesys_open (filename); ++ src = file_open (filesys_open (filename)); + if (src == NULL) + PANIC ("%s: open failed", filename); + size = file_length (src); +diff -u src/filesys/inode.c~ src/filesys/inode.c +--- src/filesys/inode.c~ 2005-06-16 21:50:20.000000000 -0700 ++++ src/filesys/inode.c 2005-06-16 21:11:24.000000000 -0700 +@@ -1,26 +1,41 @@ + #include "filesys/inode.h" ++#include + #include + #include + #include ++#include + #include ++#include "filesys/cache.h" + #include "filesys/filesys.h" + #include "filesys/free-map.h" + #include "threads/malloc.h" ++#include "threads/synch.h" + + /* Identifies an inode. */ + #define INODE_MAGIC 0x494e4f44 + ++#define DIRECT_CNT 123 ++#define INDIRECT_CNT 1 ++#define DBL_INDIRECT_CNT 1 ++#define SECTOR_CNT (DIRECT_CNT + INDIRECT_CNT + DBL_INDIRECT_CNT) ++ ++#define PTRS_PER_SECTOR ((off_t) (DISK_SECTOR_SIZE / sizeof (disk_sector_t))) ++#define INODE_SPAN ((DIRECT_CNT \ ++ + PTRS_PER_SECTOR * INDIRECT_CNT \ ++ + PTRS_PER_SECTOR * PTRS_PER_SECTOR * DBL_INDIRECT_CNT) \ ++ * DISK_SECTOR_SIZE) ++ + /* On-disk inode. + Must be exactly DISK_SECTOR_SIZE bytes long. */ + struct inode_disk + { +- disk_sector_t start; /* First data sector. */ ++ disk_sector_t sectors[SECTOR_CNT]; /* Sectors. */ ++ enum inode_type type; /* FILE_INODE or DIR_INODE. */ + 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 ++/* Returns the number of sectors to allocate for an indoe SIZE + bytes long. */ + static inline size_t + bytes_to_sectors (off_t size) +@@ -35,33 +49,29 @@ struct inode + disk_sector_t sector; /* Sector number of disk location. */ + int open_cnt; /* Number of openers. */ + bool removed; /* True if deleted, false otherwise. */ ++ ++ /* Denying writes. */ ++ struct lock deny_write_lock; /* Protects members below. */ ++ struct condition no_writers_cond; /* Signaled when no writers. */ + int deny_write_cnt; /* 0: writes ok, >0: deny writes. */ +- struct inode_disk data; /* Inode content. */ ++ int writer_cnt; /* Number of writers. */ + }; + +-/* Returns the disk sector that contains byte offset POS within +- INODE. +- Returns -1 if INODE does not contain data for a byte at offset +- POS. */ +-static disk_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 / DISK_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; + ++/* Controls access to open_inodes list. */ ++static struct lock open_inodes_lock; ++ ++static void deallocate_inode (const struct inode *); ++ + /* Initializes the inode module. */ + void + inode_init (void) + { + list_init (&open_inodes); ++ lock_init (&open_inodes_lock); + } + + /* Initializes an inode with LENGTH bytes of data and +@@ -70,35 +80,32 @@ inode_init (void) + Returns true if successful. + Returns false if memory or disk allocation fails. */ + bool +-inode_create (disk_sector_t sector, off_t length) ++inode_create (disk_sector_t sector, off_t length, enum inode_type type) + { +- struct inode_disk *disk_inode = NULL; +- bool success = false; ++ struct cache_block *block; ++ struct inode_disk *disk_inode; ++ bool success; + + ASSERT (length >= 0); ++ ++ block = cache_lock (sector, EXCLUSIVE); + ASSERT (sizeof *disk_inode == DISK_SECTOR_SIZE); ++ disk_inode = cache_zero (block); ++ disk_inode->type = type; ++ disk_inode->length = 0; ++ disk_inode->magic = INODE_MAGIC; ++ cache_dirty (block); ++ cache_unlock (block); + +- disk_inode = calloc (1, sizeof *disk_inode); +- if (disk_inode != NULL) ++ if (length > 0) + { +- size_t sectors = bytes_to_sectors (length); +- disk_inode->length = length; +- disk_inode->magic = INODE_MAGIC; +- if (free_map_allocate (sectors, &disk_inode->start)) +- { +- disk_write (filesys_disk, sector, disk_inode); +- if (sectors > 0) +- { +- static char zeros[DISK_SECTOR_SIZE]; +- size_t i; +- +- for (i = 0; i < sectors; i++) +- disk_write (filesys_disk, disk_inode->start + i, zeros); +- } +- success = true; +- } +- free (disk_inode); ++ struct inode *inode = inode_open (sector); ++ success = inode != NULL && inode_write_at (inode, "", 1, length - 1); ++ inode_close (inode); + } ++ else ++ success = true; ++ + return success; + } + +@@ -112,6 +119,7 @@ inode_open (disk_sector_t sector) + struct inode *inode; + + /* Check whether this inode is already open. */ ++ lock_acquire (&open_inodes_lock); + for (e = list_begin (&open_inodes); e != list_end (&open_inodes); + e = list_next (e)) + { +@@ -119,22 +127,26 @@ inode_open (disk_sector_t sector) + if (inode->sector == sector) + { + inode_reopen (inode); +- return inode; ++ goto done; + } + } + + /* Allocate memory. */ + inode = malloc (sizeof *inode); + if (inode == NULL) +- return NULL; ++ goto done; + + /* Initialize. */ + list_push_front (&open_inodes, &inode->elem); + inode->sector = sector; + inode->open_cnt = 1; + inode->deny_write_cnt = 0; ++ lock_init (&inode->deny_write_lock); ++ cond_init (&inode->no_writers_cond); + inode->removed = false; +- disk_read (filesys_disk, inode->sector, &inode->data); ++ ++ done: ++ lock_release (&open_inodes_lock); + return inode; + } + +@@ -147,6 +159,17 @@ inode_reopen (struct inode *inode) + return inode; + } + ++/* Returns the type of INODE. */ ++enum inode_type ++inode_get_type (const struct inode *inode) ++{ ++ struct cache_block *inode_block = cache_lock (inode->sector, NON_EXCLUSIVE); ++ struct inode_disk *disk_inode = cache_read (inode_block); ++ enum inode_type type = disk_inode->type; ++ cache_unlock (inode_block); ++ return type; ++} ++ + /* 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. */ +@@ -158,18 +181,59 @@ inode_close (struct inode *inode) + return; + + /* Release resources if this was the last opener. */ ++ lock_acquire (&open_inodes_lock); + if (--inode->open_cnt == 0) + { + /* Remove from inode list and release lock. */ + list_remove (&inode->elem); ++ lock_release (&open_inodes_lock); + + /* Deallocate blocks if removed. */ + if (inode->removed) +- free_map_release (inode->sector, +- bytes_to_sectors (inode->data.length)); +- ++ deallocate_inode (inode); + free (inode); + } ++ else ++ lock_release (&open_inodes_lock); ++} ++ ++/* Deallocates SECTOR and anything it points to recursively. ++ LEVEL is 2 if SECTOR is doubly indirect, ++ or 1 if SECTOR is indirect, ++ or 0 if SECTOR is a data sector. */ ++static void ++deallocate_recursive (disk_sector_t sector, int level) ++{ ++ if (level > 0) ++ { ++ struct cache_block *block = cache_lock (sector, EXCLUSIVE); ++ disk_sector_t *pointers = cache_read (block); ++ int i; ++ for (i = 0; i < PTRS_PER_SECTOR; i++) ++ if (pointers[i]) ++ deallocate_recursive (sector, level - 1); ++ cache_unlock (block); ++ } ++ ++ cache_free (sector); ++ free_map_release (sector); ++} ++ ++/* Deallocates the blocks allocated for INODE. */ ++static void ++deallocate_inode (const struct inode *inode) ++{ ++ struct cache_block *block = cache_lock (inode->sector, EXCLUSIVE); ++ struct inode_disk *disk_inode = cache_read (block); ++ int i; ++ for (i = 0; i < SECTOR_CNT; i++) ++ if (disk_inode->sectors[i]) ++ { ++ int level = (i >= DIRECT_CNT) + (i >= DIRECT_CNT + INDIRECT_CNT); ++ deallocate_recursive (disk_inode->sectors[i], level); ++ } ++ cache_unlock (block); ++ deallocate_recursive (inode->sector, 0); + } + + /* Marks INODE to be deleted when it is closed by the last caller who +@@ -181,6 +245,156 @@ inode_remove (struct inode *inode) + inode->removed = true; + } + ++/* Translates SECTOR_IDX into a sequence of block indexes in ++ OFFSETS and sets *OFFSET_CNT to the number of offsets. */ ++static void ++calculate_indices (off_t sector_idx, size_t offsets[], size_t *offset_cnt) ++{ ++ /* Handle direct blocks. */ ++ if (sector_idx < DIRECT_CNT) ++ { ++ offsets[0] = sector_idx; ++ *offset_cnt = 1; ++ return; ++ } ++ sector_idx -= DIRECT_CNT; ++ ++ /* Handle indirect blocks. */ ++ if (sector_idx < PTRS_PER_SECTOR * INDIRECT_CNT) ++ { ++ offsets[0] = DIRECT_CNT + sector_idx / PTRS_PER_SECTOR; ++ offsets[1] = sector_idx % PTRS_PER_SECTOR; ++ *offset_cnt = 2; ++ return; ++ } ++ sector_idx -= PTRS_PER_SECTOR * INDIRECT_CNT; ++ ++ /* Handle doubly indirect blocks. */ ++ if (sector_idx < DBL_INDIRECT_CNT * PTRS_PER_SECTOR * PTRS_PER_SECTOR) ++ { ++ offsets[0] = (DIRECT_CNT + INDIRECT_CNT ++ + sector_idx / (PTRS_PER_SECTOR * PTRS_PER_SECTOR)); ++ offsets[1] = sector_idx / PTRS_PER_SECTOR; ++ offsets[2] = sector_idx % PTRS_PER_SECTOR; ++ *offset_cnt = 3; ++ return; ++ } ++ NOT_REACHED (); ++} ++ ++/* Retrieves the data block for the given byte OFFSET in INODE, ++ setting *DATA_BLOCK to the block. ++ Returns true if successful, false on failure. ++ If ALLOCATE is false, then missing blocks will be successful ++ with *DATA_BLOCk set to a null pointer. ++ If ALLOCATE is true, then missing blocks will be allocated. ++ The block returned will be locked, normally non-exclusively, ++ but a newly allocated block will have an exclusive lock. */ ++static bool ++get_data_block (struct inode *inode, off_t offset, bool allocate, ++ struct cache_block **data_block) ++{ ++ disk_sector_t this_level_sector; ++ size_t offsets[3]; ++ size_t offset_cnt; ++ size_t level; ++ ++ ASSERT (offset >= 0); ++ ++ calculate_indices (offset / DISK_SECTOR_SIZE, offsets, &offset_cnt); ++ level = 0; ++ this_level_sector = inode->sector; ++ for (;;) ++ { ++ struct cache_block *this_level_block; ++ uint32_t *this_level_data; ++ ++ struct cache_block *next_level_block; ++ ++ /* Check whether the block for the next level is allocated. */ ++ this_level_block = cache_lock (this_level_sector, NON_EXCLUSIVE); ++ this_level_data = cache_read (this_level_block); ++ if (this_level_data[offsets[level]] != 0) ++ { ++ /* Yes, it's allocated. Advance to next level. */ ++ this_level_sector = this_level_data[offsets[level]]; ++ ++ if (++level == offset_cnt) ++ { ++ /* We hit the data block. ++ Do read-ahead. */ ++ if ((level == 0 && offsets[level] + 1 < DIRECT_CNT) ++ || (level > 0 && offsets[level] + 1 < PTRS_PER_SECTOR)) ++ { ++ uint32_t next_sector = this_level_data[offsets[level] + 1]; ++ if (next_sector && next_sector < disk_size (filesys_disk)) ++ cache_readahead (next_sector); ++ } ++ cache_unlock (this_level_block); ++ ++ /* Return block. */ ++ *data_block = cache_lock (this_level_sector, NON_EXCLUSIVE); ++ return true; ++ } ++ cache_unlock (this_level_block); ++ continue; ++ } ++ cache_unlock (this_level_block); ++ ++ /* No block is allocated. Nothing is locked. ++ If we're not allocating new blocks, then this is ++ "success" (with all-zero data). */ ++ if (!allocate) ++ { ++ *data_block = NULL; ++ return true; ++ } ++ ++ /* We need to allocate a new block. ++ Grab an exclusive lock on this level's block so we can ++ insert the new block. */ ++ this_level_block = cache_lock (this_level_sector, EXCLUSIVE); ++ this_level_data = cache_read (this_level_block); ++ ++ /* Since we released this level's block, someone else might ++ have allocated the block in the meantime. Recheck. */ ++ if (this_level_data[offsets[level]] != 0) ++ { ++ cache_unlock (this_level_block); ++ continue; ++ } ++ ++ /* Allocate the new block. */ ++ if (!free_map_allocate (&this_level_data[offsets[level]])) ++ { ++ cache_unlock (this_level_block); ++ *data_block = NULL; ++ return false; ++ } ++ cache_dirty (this_level_block); ++ ++ /* Lock and clear the new block. */ ++ next_level_block = cache_lock (this_level_data[offsets[level]], ++ EXCLUSIVE); ++ cache_zero (next_level_block); ++ ++ /* Release this level's block. No one else can access the ++ new block yet, because we have an exclusive lock on it. */ ++ cache_unlock (this_level_block); ++ ++ /* If this is the final level, then return the new block. */ ++ if (level == offset_cnt - 1) ++ { ++ *data_block = next_level_block; ++ return true; ++ } ++ ++ /* Otherwise, release the new block and go around again to ++ follow the new pointer. */ ++ cache_unlock (next_level_block); ++ } ++} ++ + /* 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. */ +@@ -189,13 +403,12 @@ inode_read_at (struct inode *inode, void + { + 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. */ +- disk_sector_t sector_idx = byte_to_sector (inode, offset); ++ /* Sector to read, starting byte offset within sector, sector data. */ + int sector_ofs = offset % DISK_SECTOR_SIZE; ++ struct cache_block *block; + + /* Bytes left in inode, bytes left in sector, lesser of the two. */ + off_t inode_left = inode_length (inode) - offset; +@@ -204,26 +417,16 @@ inode_read_at (struct inode *inode, void + + /* Number of bytes to actually copy out of this sector. */ + int chunk_size = size < min_left ? size : min_left; +- if (chunk_size <= 0) ++ if (chunk_size <= 0 || !get_data_block (inode, offset, false, &block)) + break; + +- if (sector_ofs == 0 && chunk_size == DISK_SECTOR_SIZE) +- { +- /* Read full sector directly into caller's buffer. */ +- disk_read (filesys_disk, sector_idx, buffer + bytes_read); +- } ++ if (block == NULL) ++ memset (buffer + bytes_read, 0, chunk_size); + else + { +- /* Read sector into bounce buffer, then partially copy +- into caller's buffer. */ +- if (bounce == NULL) +- { +- bounce = malloc (DISK_SECTOR_SIZE); +- if (bounce == NULL) +- break; +- } +- disk_read (filesys_disk, sector_idx, bounce); +- memcpy (buffer + bytes_read, bounce + sector_ofs, chunk_size); ++ const uint8_t *sector_data = cache_read (block); ++ memcpy (buffer + bytes_read, sector_data + sector_ofs, chunk_size); ++ cache_unlock (block); + } + + /* Advance. */ +@@ -231,75 +434,84 @@ inode_read_at (struct inode *inode, void + offset += chunk_size; + bytes_read += chunk_size; + } +- free (bounce); + + return bytes_read; + } + ++/* Extends INODE to be at least LENGTH bytes long. */ ++static void ++extend_file (struct inode *inode, off_t length) ++{ ++ if (length > inode_length (inode)) ++ { ++ struct cache_block *inode_block = cache_lock (inode->sector, EXCLUSIVE); ++ struct inode_disk *disk_inode = cache_read (inode_block); ++ if (length > disk_inode->length) ++ { ++ disk_inode->length = length; ++ cache_dirty (inode_block); ++ } ++ cache_unlock (inode_block); ++ } ++} ++ + /* 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.) */ ++ (Normally a write at end of file would extend the file, but ++ file 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; ++ /* Don't write if writes are denied. */ ++ lock_acquire (&inode->deny_write_lock); ++ if (inode->deny_write_cnt) ++ { ++ lock_release (&inode->deny_write_lock); ++ return 0; ++ } ++ inode->writer_cnt++; ++ lock_release (&inode->deny_write_lock); + + while (size > 0) + { +- /* Sector to write, starting byte offset within sector. */ +- off_t sector_idx = byte_to_sector (inode, offset); ++ /* Sector to write, starting byte offset within sector, sector data. */ + int sector_ofs = offset % DISK_SECTOR_SIZE; ++ struct cache_block *block; ++ uint8_t *sector_data; + +- /* Bytes left in inode, bytes left in sector, lesser of the two. */ +- off_t inode_left = inode_length (inode) - offset; ++ /* Bytes to max inode size, bytes left in sector, lesser of the two. */ ++ off_t inode_left = INODE_SPAN - offset; + int sector_left = DISK_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 == DISK_SECTOR_SIZE) +- { +- /* Write full sector directly to disk. */ +- disk_write (filesys_disk, sector_idx, buffer + bytes_written); +- } +- else +- { +- /* We need a bounce buffer. */ +- if (bounce == NULL) +- { +- bounce = malloc (DISK_SECTOR_SIZE); +- if (bounce == NULL) +- break; +- } ++ if (chunk_size <= 0 || !get_data_block (inode, offset, true, &block)) ++ 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) +- disk_read (filesys_disk, sector_idx, bounce); +- else +- memset (bounce, 0, DISK_SECTOR_SIZE); +- memcpy (bounce + sector_ofs, buffer + bytes_written, chunk_size); +- disk_write (filesys_disk, sector_idx, bounce); +- } ++ sector_data = cache_read (block); ++ memcpy (sector_data + sector_ofs, buffer + bytes_written, chunk_size); ++ cache_dirty (block); ++ cache_unlock (block); + + /* Advance. */ + size -= chunk_size; + offset += chunk_size; + bytes_written += chunk_size; + } +- free (bounce); ++ ++ extend_file (inode, offset); ++ ++ lock_acquire (&inode->deny_write_lock); ++ if (--inode->writer_cnt == 0) ++ cond_signal (&inode->no_writers_cond, &inode->deny_write_lock); ++ lock_release (&inode->deny_write_lock); + + return bytes_written; + } +@@ -309,8 +521,12 @@ inode_write_at (struct inode *inode, con + void + inode_deny_write (struct inode *inode) + { ++ lock_acquire (&inode->deny_write_lock); ++ while (inode->writer_cnt > 0) ++ cond_wait (&inode->no_writers_cond, &inode->deny_write_lock); ++ ASSERT (inode->deny_write_cnt < inode->open_cnt); + inode->deny_write_cnt++; +- ASSERT (inode->deny_write_cnt <= inode->open_cnt); ++ lock_release (&inode->deny_write_lock); + } + + /* Re-enables writes to INODE. +@@ -319,14 +535,33 @@ inode_deny_write (struct inode *inode) + void + inode_allow_write (struct inode *inode) + { ++ lock_acquire (&inode->deny_write_lock); + ASSERT (inode->deny_write_cnt > 0); + ASSERT (inode->deny_write_cnt <= inode->open_cnt); + inode->deny_write_cnt--; ++ lock_release (&inode->deny_write_lock); + } + + /* Returns the length, in bytes, of INODE's data. */ + off_t + inode_length (const struct inode *inode) + { +- return inode->data.length; ++ struct cache_block *inode_block = cache_lock (inode->sector, NON_EXCLUSIVE); ++ struct inode_disk *disk_inode = cache_read (inode_block); ++ off_t length = disk_inode->length; ++ cache_unlock (inode_block); ++ return length; ++} ++ ++/* Returns the number of openers. */ ++int ++inode_open_cnt (const struct inode *inode) ++{ ++ int open_cnt; ++ ++ lock_acquire (&open_inodes_lock); ++ open_cnt = inode->open_cnt; ++ lock_release (&open_inodes_lock); ++ ++ return open_cnt; + } +diff -u src/filesys/inode.h~ src/filesys/inode.h +--- src/filesys/inode.h~ 2005-06-16 21:50:20.000000000 -0700 ++++ src/filesys/inode.h 2005-06-16 20:53:47.000000000 -0700 +@@ -7,10 +7,18 @@ + + struct bitmap; + ++/* Type of an inode. */ ++enum inode_type ++ { ++ FILE_INODE, /* Ordinary file. */ ++ DIR_INODE /* Directory. */ ++ }; ++ + void inode_init (void); +-bool inode_create (disk_sector_t, off_t); ++bool inode_create (disk_sector_t, off_t, enum inode_type); + struct inode *inode_open (disk_sector_t); + struct inode *inode_reopen (struct inode *); ++enum inode_type inode_get_type (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); +@@ -18,5 +26,6 @@ off_t inode_write_at (struct inode *, co + void inode_deny_write (struct inode *); + void inode_allow_write (struct inode *); + off_t inode_length (const struct inode *); ++int inode_open_cnt (const struct inode *); + + #endif /* filesys/inode.h */ +diff -u src/lib/kernel/bitmap.h~ src/lib/kernel/bitmap.h +diff -u src/threads/init.c~ src/threads/init.c +--- src/threads/init.c~ 2005-06-14 14:04:06.000000000 -0700 ++++ src/threads/init.c 2005-06-16 15:09:31.000000000 -0700 +@@ -33,6 +33,8 @@ + #include "filesys/filesys.h" + #include "filesys/fsutil.h" + #endif ++#include "vm/frame.h" ++#include "vm/swap.h" + + /* Amount of physical memory, in 4 kB pages. */ + size_t ram_pages; +@@ -131,6 +133,9 @@ main (void) + filesys_init (format_filesys); + #endif + ++ frame_init (); ++ swap_init (); ++ + printf ("Boot complete.\n"); + + /* Run actions specified on kernel command line. */ +diff -u src/threads/interrupt.c~ src/threads/interrupt.c +--- src/threads/interrupt.c~ 2005-06-15 15:22:43.000000000 -0700 ++++ src/threads/interrupt.c 2005-06-16 15:09:31.000000000 -0700 +@@ -343,6 +343,8 @@ intr_handler (struct intr_frame *frame) + in_external_intr = true; + yield_on_return = false; + } ++ else ++ thread_current ()->user_esp = frame->esp; + + /* Invoke the interrupt's handler. + If there is no handler, invoke the unexpected interrupt +diff -u src/threads/thread.c~ src/threads/thread.c +--- src/threads/thread.c~ 2005-06-15 14:41:47.000000000 -0700 ++++ src/threads/thread.c 2005-06-16 15:09:31.000000000 -0700 +@@ -13,6 +13,7 @@ + #include "threads/synch.h" + #ifdef USERPROG + #include "userprog/process.h" ++#include "userprog/syscall.h" + #endif + + /* Random value for struct thread's `magic' member. +@@ -55,7 +56,8 @@ static void kernel_thread (thread_func * + 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 void init_thread (struct thread *, const char *name, int priority, ++ tid_t); + static bool is_thread (struct thread *) UNUSED; + static void *alloc_frame (struct thread *, size_t size); + static void schedule (void); +@@ -82,9 +84,8 @@ thread_init (void) + + /* Set up a thread structure for the running thread. */ + initial_thread = running_thread (); +- init_thread (initial_thread, "main", PRI_DEFAULT); ++ init_thread (initial_thread, "main", PRI_DEFAULT, 0); + initial_thread->status = THREAD_RUNNING; +- initial_thread->tid = allocate_tid (); + } + + /* Starts preemptive thread scheduling by enabling interrupts. +@@ -158,8 +159,8 @@ thread_create (const char *name, int pri + return TID_ERROR; + + /* Initialize thread. */ +- init_thread (t, name, priority); +- tid = t->tid = allocate_tid (); ++ init_thread (t, name, priority, allocate_tid ()); ++ tid = t->tid; + + /* Stack frame for kernel_thread(). */ + kf = alloc_frame (t, sizeof *kf); +@@ -252,16 +253,19 @@ thread_tid (void) + void + thread_exit (void) + { ++ struct thread *t = thread_current (); ++ + ASSERT (!intr_context ()); + ++ syscall_exit (); + #ifdef USERPROG + process_exit (); + #endif +- ++ + /* Just set our status to dying and schedule another process. + We will be destroyed during the call to schedule_tail(). */ + intr_disable (); +- thread_current ()->status = THREAD_DYING; ++ t->status = THREAD_DYING; + schedule (); + NOT_REACHED (); + } +@@ -390,17 +394,29 @@ is_thread (struct thread *t) + /* Does basic initialization of T as a blocked thread named + NAME. */ + static void +-init_thread (struct thread *t, const char *name, int priority) ++init_thread (struct thread *t, const char *name, int priority, tid_t tid) + { + ASSERT (t != NULL); + ASSERT (PRI_MIN <= priority && priority <= PRI_MAX); + ASSERT (name != NULL); + + memset (t, 0, sizeof *t); ++ t->tid = tid; + t->status = THREAD_BLOCKED; + strlcpy (t->name, name, sizeof t->name); + t->stack = (uint8_t *) t + PGSIZE; + t->priority = priority; ++ t->exit_code = -1; ++ t->wait_status = NULL; ++ list_init (&t->children); ++ sema_init (&t->timer_sema, 0); ++ t->pagedir = NULL; ++ t->pages = NULL; ++ t->bin_file = NULL; ++ list_init (&t->fds); ++ list_init (&t->mappings); ++ t->next_handle = 2; ++ t->wd = NULL; + t->magic = THREAD_MAGIC; + } + +diff -u src/threads/thread.h~ src/threads/thread.h +--- src/threads/thread.h~ 2005-06-15 14:49:06.000000000 -0700 ++++ src/threads/thread.h 2005-06-16 15:09:31.000000000 -0700 +@@ -2,8 +2,10 @@ + #define THREADS_THREAD_H + + #include ++#include + #include + #include ++#include "threads/synch.h" + + /* States in a thread's life cycle. */ + enum thread_status +@@ -89,18 +91,50 @@ struct thread + uint8_t *stack; /* Saved stack pointer. */ + int priority; /* Priority. */ + ++ /* Owned by process.c. */ ++ int exit_code; /* Exit code. */ ++ struct wait_status *wait_status; /* This process's completion status. */ ++ struct list children; /* Completion status of children. */ ++ + /* Shared between thread.c and synch.c. */ + struct list_elem elem; /* List element. */ + +-#ifdef USERPROG ++ /* Alarm clock. */ ++ int64_t wakeup_time; /* Time to wake this thread up. */ ++ struct list_elem timer_elem; /* Element in timer_wait_list. */ ++ struct semaphore timer_sema; /* Semaphore. */ ++ + /* Owned by userprog/process.c. */ + uint32_t *pagedir; /* Page directory. */ +-#endif ++ struct hash *pages; /* Page table. */ ++ struct file *bin_file; /* The binary executable. */ ++ ++ /* Owned by syscall.c. */ ++ struct list fds; /* List of file descriptors. */ ++ struct list mappings; /* Memory-mapped files. */ ++ int next_handle; /* Next handle value. */ ++ void *user_esp; /* User's stack pointer. */ ++ struct dir *wd; /* Working directory. */ + + /* Owned by thread.c. */ + unsigned magic; /* Detects stack overflow. */ + }; + ++/* Tracks the completion of a process. ++ Reference held by both the parent, in its `children' list, ++ and by the child, in its `wait_status' pointer. */ ++struct wait_status ++ { ++ struct list_elem elem; /* `children' list element. */ ++ struct lock lock; /* Protects ref_cnt. */ ++ int ref_cnt; /* 2=child and parent both alive, ++ 1=either child or parent alive, ++ 0=child and parent both dead. */ ++ tid_t tid; /* Child thread id. */ ++ int exit_code; /* Child exit code, if dead. */ ++ struct semaphore dead; /* 1=child alive, 0=child dead. */ ++ }; ++ + void thread_init (void); + void thread_start (void); + +diff -u src/userprog/exception.c~ src/userprog/exception.c +--- src/userprog/exception.c~ 2005-06-15 15:14:10.000000000 -0700 ++++ src/userprog/exception.c 2005-06-16 15:09:31.000000000 -0700 +@@ -4,6 +4,7 @@ + #include "userprog/gdt.h" + #include "threads/interrupt.h" + #include "threads/thread.h" ++#include "vm/page.h" + + /* Number of page faults processed. */ + static long long page_fault_cnt; +@@ -153,9 +154,14 @@ page_fault (struct intr_frame *f) + write = (f->error_code & PF_W) != 0; + user = (f->error_code & PF_U) != 0; + +- /* To implement virtual memory, delete the rest of the function +- body, and replace it with code that brings in the page to +- which fault_addr refers. */ ++ /* Allow the pager to try to handle it. */ ++ if (user && not_present) ++ { ++ if (!page_in (fault_addr)) ++ thread_exit (); ++ return; ++ } ++ + printf ("Page fault at %p: %s error %s page in %s context.\n", + fault_addr, + not_present ? "not present" : "rights violation", +diff -u src/userprog/pagedir.c~ src/userprog/pagedir.c +--- src/userprog/pagedir.c~ 2005-06-14 13:16:22.000000000 -0700 ++++ src/userprog/pagedir.c 2005-06-16 15:09:31.000000000 -0700 +@@ -35,15 +35,7 @@ pagedir_destroy (uint32_t *pd) + ASSERT (pd != base_page_dir); + for (pde = pd; pde < pd + pd_no (PHYS_BASE); pde++) + if (*pde & PG_P) +- { +- uint32_t *pt = pde_get_pt (*pde); +- uint32_t *pte; +- +- for (pte = pt; pte < pt + PGSIZE / sizeof *pte; pte++) +- if (*pte & PG_P) +- palloc_free_page (pte_get_page (*pte)); +- palloc_free_page (pt); +- } ++ palloc_free_page (pde_get_pt (*pde)); + palloc_free_page (pd); + } + +diff -u src/userprog/process.c~ src/userprog/process.c +--- src/userprog/process.c~ 2005-06-14 13:09:39.000000000 -0700 ++++ src/userprog/process.c 2005-06-16 15:09:31.000000000 -0700 +@@ -14,11 +14,26 @@ + #include "threads/init.h" + #include "threads/interrupt.h" + #include "threads/mmu.h" ++#include "threads/malloc.h" + #include "threads/palloc.h" + #include "threads/thread.h" ++#include "vm/page.h" ++#include "vm/frame.h" + + static thread_func execute_thread NO_RETURN; +-static bool load (const char *cmdline, void (**eip) (void), void **esp); ++static bool load (const char *cmd_line, void (**eip) (void), void **esp); ++ ++/* Data structure shared between process_execute() in the ++ invoking thread and execute_thread() in the newly invoked ++ thread. */ ++struct exec_info ++ { ++ const char *filename; /* Program to load. */ ++ struct semaphore load_done; /* "Up"ed when loading complete. */ ++ struct wait_status *wait_status; /* Child process. */ ++ struct dir *wd; /* Working directory. */ ++ bool success; /* Program successfully loaded? */ ++ }; + + /* Starts a new thread running a user program loaded from + FILENAME. The new thread may be scheduled (and may even exit) +@@ -27,41 +42,82 @@ static bool load (const char *cmdline, v + tid_t + process_execute (const char *filename) + { +- char *fn_copy; ++ struct dir *wd = thread_current ()->wd; ++ struct exec_info exec; ++ char thread_name[16]; ++ char *save_ptr; + tid_t tid; + +- /* Make a copy of FILENAME. +- Otherwise there's a race between the caller and load(). */ +- fn_copy = palloc_get_page (0); +- if (fn_copy == NULL) ++ /* Initialize exec_info. */ ++ exec.filename = filename; ++ if (wd) ++ { ++ dir_reopen (wd); ++ exec.wd = wd; ++ } ++ else if (!dir_open_root (&exec.wd)) + return TID_ERROR; +- strlcpy (fn_copy, filename, PGSIZE); ++ sema_init (&exec.load_done, 0); + + /* Create a new thread to execute FILENAME. */ +- tid = thread_create (filename, PRI_DEFAULT, execute_thread, fn_copy); +- if (tid == TID_ERROR) +- palloc_free_page (fn_copy); ++ strlcpy (thread_name, filename, sizeof thread_name); ++ strtok_r (thread_name, " ", &save_ptr); ++ tid = thread_create (thread_name, PRI_DEFAULT, execute_thread, &exec); ++ if (tid != TID_ERROR) ++ { ++ sema_down (&exec.load_done); ++ if (exec.success) ++ list_push_back (&thread_current ()->children, &exec.wait_status->elem); ++ else ++ { ++ tid = TID_ERROR; ++ /* Don't close exec.wd; child process will have done so. */ ++ } ++ } ++ else ++ dir_close (exec.wd); ++ + return tid; + } + + /* A thread function that loads a user process and starts it + running. */ + static void +-execute_thread (void *filename_) ++execute_thread (void *exec_) + { +- char *filename = filename_; ++ struct exec_info *exec = exec_; + struct intr_frame if_; + bool success; + ++ thread_current ()->wd = exec->wd; ++ + /* 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; +- success = load (filename, &if_.eip, &if_.esp); ++ success = load (exec->filename, &if_.eip, &if_.esp); ++ ++ /* Allocate wait_status. */ ++ if (success) ++ { ++ exec->wait_status = thread_current ()->wait_status ++ = malloc (sizeof *exec->wait_status); ++ success = exec->wait_status != NULL; ++ } + +- /* If load failed, quit. */ +- palloc_free_page (filename); ++ /* Initialize wait_status. */ ++ if (success) ++ { ++ lock_init (&exec->wait_status->lock); ++ exec->wait_status->ref_cnt = 2; ++ exec->wait_status->tid = thread_current ()->tid; ++ sema_init (&exec->wait_status->dead, 0); ++ } ++ ++ /* Notify parent thread and clean up. */ ++ exec->success = success; ++ sema_up (&exec->load_done); + if (!success) + thread_exit (); + +@@ -75,18 +131,47 @@ execute_thread (void *filename_) + NOT_REACHED (); + } + ++/* Releases one reference to CS and, if it is now unreferenced, ++ frees it. */ ++static void ++release_child (struct wait_status *cs) ++{ ++ int new_ref_cnt; ++ ++ lock_acquire (&cs->lock); ++ new_ref_cnt = --cs->ref_cnt; ++ lock_release (&cs->lock); ++ ++ if (new_ref_cnt == 0) ++ free (cs); ++} ++ + /* 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. +- +- This function will be implemented in problem 2-2. For now, it +- does nothing. */ ++ immediately, without waiting. */ + int +-process_wait (tid_t child_tid UNUSED) ++process_wait (tid_t child_tid) + { ++ struct thread *cur = thread_current (); ++ struct list_elem *e; ++ ++ for (e = list_begin (&cur->children); e != list_end (&cur->children); ++ e = list_next (e)) ++ { ++ struct wait_status *cs = list_entry (e, struct wait_status, elem); ++ if (cs->tid == child_tid) ++ { ++ int exit_code; ++ list_remove (e); ++ sema_down (&cs->dead); ++ exit_code = cs->exit_code; ++ release_child (cs); ++ return exit_code; ++ } ++ } + return -1; + } + +@@ -95,8 +180,35 @@ void + process_exit (void) + { + struct thread *cur = thread_current (); ++ struct list_elem *e, *next; + uint32_t *pd; + ++ printf ("%s: exit(%d)\n", cur->name, cur->exit_code); ++ ++ /* Notify parent that we're dead. */ ++ if (cur->wait_status != NULL) ++ { ++ struct wait_status *cs = cur->wait_status; ++ cs->exit_code = cur->exit_code; ++ sema_up (&cs->dead); ++ release_child (cs); ++ } ++ ++ /* Free entries of children list. */ ++ for (e = list_begin (&cur->children); e != list_end (&cur->children); ++ e = next) ++ { ++ struct wait_status *cs = list_entry (e, struct wait_status, elem); ++ next = list_remove (e); ++ release_child (cs); ++ } ++ ++ /* Destroy the page hash table. */ ++ page_exit (); ++ ++ /* Close executable (and allow writes). */ ++ file_close (cur->bin_file); ++ + /* Destroy the current process's page directory and switch back + to the kernel-only page directory. */ + pd = cur->pagedir; +@@ -194,20 +306,22 @@ struct Elf32_Phdr + #define PF_R 4 /* Readable. */ + + static bool load_segment (struct file *, const struct Elf32_Phdr *); +-static bool setup_stack (void **esp); ++static bool setup_stack (const char *cmd_line, void **esp); + + /* Loads an ELF executable from FILENAME 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 (const char *filename, void (**eip) (void), void **esp) ++load (const char *cmd_line, void (**eip) (void), void **esp) + { + struct thread *t = thread_current (); ++ char filename[NAME_MAX + 2]; + struct Elf32_Ehdr ehdr; + struct file *file = NULL; + off_t file_ofs; + bool success = false; ++ char *cp; + int i; + + /* Allocate and activate page directory. */ +@@ -216,13 +330,28 @@ load (const char *filename, void (**eip) + goto done; + process_activate (); + ++ /* Create page hash table. */ ++ t->pages = malloc (sizeof *t->pages); ++ if (t->pages == NULL) ++ goto done; ++ hash_init (t->pages, page_hash, page_less, NULL); ++ ++ /* Extract filename from command line. */ ++ while (*cmd_line == ' ') ++ cmd_line++; ++ strlcpy (filename, cmd_line, sizeof filename); ++ cp = strchr (filename, ' '); ++ if (cp != NULL) ++ *cp = '\0'; ++ + /* Open executable file. */ +- file = filesys_open (filename); ++ t->bin_file = file = file_open (filesys_open (filename)); + if (file == NULL) + { + printf ("load: %s: open failed\n", filename); + goto done; + } ++ file_deny_write (file); + + /* Read and verify executable header. */ + if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr +@@ -271,7 +400,7 @@ load (const char *filename, void (**eip) + } + + /* Set up stack. */ +- if (!setup_stack (esp)) ++ if (!setup_stack (cmd_line, esp)) + goto done; + + /* Start address. */ +@@ -280,15 +409,11 @@ load (const char *filename, void (**eip) + success = true; + + done: +- /* We arrive here whether the load is successful or not. */ +- file_close (file); + return success; + } + + /* load() helpers. */ + +-static bool install_page (void *upage, void *kpage); +- + /* Loads the segment described by PHDR from FILE into user + address space. Return true if successful, false otherwise. */ + static bool +@@ -296,6 +421,7 @@ load_segment (struct file *file, const s + { + void *start, *end; /* Page-rounded segment start and end. */ + uint8_t *upage; /* Iterator from start to end. */ ++ off_t file_offset; /* Offset into file. */ + off_t filesz_left; /* Bytes left of file data (as opposed to + zero-initialized bytes). */ + +@@ -303,7 +429,7 @@ load_segment (struct file *file, const s + commented out. You'll want to use it when implementing VM + to decide whether to page the segment from its executable or + from swap. */ +- //bool read_only = (phdr->p_flags & PF_W) == 0; ++ bool read_only = (phdr->p_flags & PF_W) == 0; + + ASSERT (file != NULL); + ASSERT (phdr != NULL); +@@ -332,69 +458,129 @@ load_segment (struct file *file, const s + || start == 0) + return false; + +- /* Load the segment page-by-page into memory. */ ++ /* Add the segment page-by-page to the hash table. */ + filesz_left = phdr->p_filesz + (phdr->p_vaddr & PGMASK); +- file_seek (file, ROUND_DOWN (phdr->p_offset, PGSIZE)); ++ file_offset = ROUND_DOWN (phdr->p_offset, PGSIZE); + for (upage = start; upage < (uint8_t *) end; upage += PGSIZE) + { +- /* We want to read min(PGSIZE, filesz_left) bytes from the +- file into the page and zero the rest. */ +- size_t read_bytes = filesz_left >= PGSIZE ? PGSIZE : filesz_left; +- size_t zero_bytes = PGSIZE - read_bytes; +- uint8_t *kpage = palloc_get_page (PAL_USER); +- if (kpage == NULL) ++ struct page *p = page_allocate (upage, read_only); ++ if (p == NULL) + return false; + +- /* Do the reading and zeroing. */ +- if (file_read (file, kpage, read_bytes) != (int) read_bytes) ++ if (filesz_left > 0) + { +- palloc_free_page (kpage); +- return false; +- } +- memset (kpage + read_bytes, 0, zero_bytes); +- filesz_left -= read_bytes; +- +- /* Add the page to the process's address space. */ +- if (!install_page (upage, kpage)) +- { +- palloc_free_page (kpage); +- return false; ++ size_t file_bytes = filesz_left >= PGSIZE ? PGSIZE : filesz_left; ++ p->file = file; ++ p->file_offset = file_offset; ++ p->file_bytes = file_bytes; ++ filesz_left -= file_bytes; ++ file_offset += file_bytes; + } + } + + return true; + } + +-/* Create a minimal stack by mapping a zeroed page at the top of +- user virtual memory. */ ++/* Reverse the order of the ARGC pointers to char in ARGV. */ ++static void ++reverse (int argc, char **argv) ++{ ++ for (; argc > 1; argc -= 2, argv++) ++ { ++ char *tmp = argv[0]; ++ argv[0] = argv[argc - 1]; ++ argv[argc - 1] = tmp; ++ } ++} ++ ++/* Pushes the SIZE bytes in BUF onto the stack in KPAGE, whose ++ page-relative stack pointer is *OFS, and then adjusts *OFS ++ appropriately. The bytes pushed are rounded to a 32-bit ++ boundary. ++ ++ If successful, returns a pointer to the newly pushed object. ++ On failure, returns a null pointer. */ ++static void * ++push (uint8_t *kpage, size_t *ofs, const void *buf, size_t size) ++{ ++ size_t padsize = ROUND_UP (size, sizeof (uint32_t)); ++ if (*ofs < padsize) ++ return NULL; ++ ++ *ofs -= padsize; ++ memcpy (kpage + *ofs + (padsize - size), buf, size); ++ return kpage + *ofs + (padsize - size); ++} ++ ++/* Sets up command line arguments in KPAGE, which will be mapped ++ to UPAGE in user space. The command line arguments are taken ++ from CMD_LINE, separated by spaces. Sets *ESP to the initial ++ stack pointer for the process. */ + static bool +-setup_stack (void **esp) ++init_cmd_line (uint8_t *kpage, uint8_t *upage, const char *cmd_line, ++ void **esp) + { +- uint8_t *kpage; +- bool success = false; ++ size_t ofs = PGSIZE; ++ char *const null = NULL; ++ char *cmd_line_copy; ++ char *karg, *saveptr; ++ int argc; ++ char **argv; ++ ++ /* Push command line string. */ ++ cmd_line_copy = push (kpage, &ofs, cmd_line, strlen (cmd_line) + 1); ++ if (cmd_line_copy == NULL) ++ return false; ++ ++ if (push (kpage, &ofs, &null, sizeof null) == NULL) ++ return false; + +- kpage = palloc_get_page (PAL_USER | PAL_ZERO); +- if (kpage != NULL) ++ /* Parse command line into arguments ++ and push them in reverse order. */ ++ argc = 0; ++ for (karg = strtok_r (cmd_line_copy, " ", &saveptr); karg != NULL; ++ karg = strtok_r (NULL, " ", &saveptr)) + { +- success = install_page (((uint8_t *) PHYS_BASE) - PGSIZE, kpage); +- if (success) +- *esp = PHYS_BASE; +- else +- palloc_free_page (kpage); ++ char *uarg = upage + (karg - (char *) kpage); ++ if (push (kpage, &ofs, &uarg, sizeof uarg) == NULL) ++ return false; ++ argc++; + } +- return success; ++ ++ /* Reverse the order of the command line arguments. */ ++ argv = (char **) (upage + ofs); ++ reverse (argc, (char **) (kpage + ofs)); ++ ++ /* Push argv, argc, "return address". */ ++ if (push (kpage, &ofs, &argv, sizeof argv) == NULL ++ || push (kpage, &ofs, &argc, sizeof argc) == NULL ++ || push (kpage, &ofs, &null, sizeof null) == NULL) ++ return false; ++ ++ /* Set initial stack pointer. */ ++ *esp = upage + ofs; ++ return true; + } + +-/* Adds a mapping from user virtual address UPAGE to kernel +- virtual address KPAGE to the page table. Fails if UPAGE is +- already mapped or if memory allocation fails. */ ++/* Create a minimal stack for T by mapping a page at the ++ top of user virtual memory. Fills in the page using CMD_LINE ++ and sets *ESP to the stack pointer. */ + static bool +-install_page (void *upage, void *kpage) ++setup_stack (const char *cmd_line, void **esp) + { +- 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, true)); ++ struct page *page = page_allocate (((uint8_t *) PHYS_BASE) - PGSIZE, false); ++ if (page != NULL) ++ { ++ page->frame = frame_alloc_and_lock (page); ++ if (page->frame != NULL) ++ { ++ bool ok; ++ page->read_only = false; ++ page->private = false; ++ ok = init_cmd_line (page->frame->base, page->addr, cmd_line, esp); ++ frame_unlock (page->frame); ++ return ok; ++ } ++ } ++ return false; + } +diff -u src/userprog/syscall.c~ src/userprog/syscall.c +--- src/userprog/syscall.c~ 2005-06-16 14:56:52.000000000 -0700 ++++ src/userprog/syscall.c 2005-06-16 15:09:31.000000000 -0700 +@@ -1,20 +1,594 @@ + #include "userprog/syscall.h" + #include ++#include + #include ++#include "userprog/process.h" ++#include "userprog/pagedir.h" ++#include "devices/kbd.h" ++#include "filesys/directory.h" ++#include "filesys/filesys.h" ++#include "filesys/file.h" ++#include "threads/init.h" + #include "threads/interrupt.h" ++#include "threads/malloc.h" ++#include "threads/mmu.h" ++#include "threads/palloc.h" + #include "threads/thread.h" +- ++#include "vm/page.h" ++ ++ ++static int sys_halt (void); ++static int sys_exit (int status); ++static int sys_exec (const char *ufile); ++static int sys_wait (tid_t); ++static int sys_create (const char *ufile, unsigned initial_size); ++static int sys_remove (const char *ufile); ++static int sys_open (const char *ufile); ++static int sys_filesize (int handle); ++static int sys_read (int handle, void *udst_, unsigned size); ++static int sys_write (int handle, void *usrc_, unsigned size); ++static int sys_seek (int handle, unsigned position); ++static int sys_tell (int handle); ++static int sys_close (int handle); ++static int sys_mmap (int handle, void *addr); ++static int sys_munmap (int mapping); ++static int sys_chdir (const char *udir); ++static int sys_mkdir (const char *udir); ++static int sys_lsdir (void); ++ + static void syscall_handler (struct intr_frame *); +- ++static void copy_in (void *, const void *, size_t); ++ + void + syscall_init (void) + { + intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall"); + } ++ ++/* System call handler. */ ++static void ++syscall_handler (struct intr_frame *f) ++{ ++ typedef int syscall_function (int, int, int); ++ ++ /* A system call. */ ++ struct syscall ++ { ++ size_t arg_cnt; /* Number of arguments. */ ++ syscall_function *func; /* Implementation. */ ++ }; ++ ++ /* Table of system calls. */ ++ static const struct syscall syscall_table[] = ++ { ++ {0, (syscall_function *) sys_halt}, ++ {1, (syscall_function *) sys_exit}, ++ {1, (syscall_function *) sys_exec}, ++ {1, (syscall_function *) sys_wait}, ++ {2, (syscall_function *) sys_create}, ++ {1, (syscall_function *) sys_remove}, ++ {1, (syscall_function *) sys_open}, ++ {1, (syscall_function *) sys_filesize}, ++ {3, (syscall_function *) sys_read}, ++ {3, (syscall_function *) sys_write}, ++ {2, (syscall_function *) sys_seek}, ++ {1, (syscall_function *) sys_tell}, ++ {1, (syscall_function *) sys_close}, ++ {2, (syscall_function *) sys_mmap}, ++ {1, (syscall_function *) sys_munmap}, ++ {1, (syscall_function *) sys_chdir}, ++ {1, (syscall_function *) sys_mkdir}, ++ {0, (syscall_function *) sys_lsdir}, ++ }; + ++ const struct syscall *sc; ++ unsigned call_nr; ++ int args[3]; ++ ++ /* Get the system call. */ ++ copy_in (&call_nr, f->esp, sizeof call_nr); ++ if (call_nr >= sizeof syscall_table / sizeof *syscall_table) ++ thread_exit (); ++ sc = syscall_table + call_nr; ++ ++ /* Get the system call arguments. */ ++ ASSERT (sc->arg_cnt <= sizeof args / sizeof *args); ++ memset (args, 0, sizeof args); ++ copy_in (args, (uint32_t *) f->esp + 1, sizeof *args * sc->arg_cnt); ++ ++ /* Execute the system call, ++ and set the return value. */ ++ f->eax = sc->func (args[0], args[1], args[2]); ++} ++ ++/* Copies SIZE bytes from user address USRC to kernel address ++ DST. ++ Call thread_exit() if any of the user accesses are invalid. */ + static void +-syscall_handler (struct intr_frame *f UNUSED) ++copy_in (void *dst_, const void *usrc_, size_t size) ++{ ++ uint8_t *dst = dst_; ++ const uint8_t *usrc = usrc_; ++ ++ while (size > 0) ++ { ++ size_t chunk_size = PGSIZE - pg_ofs (usrc); ++ if (chunk_size > size) ++ chunk_size = size; ++ ++ if (!page_lock (usrc, false)) ++ thread_exit (); ++ memcpy (dst, usrc, chunk_size); ++ page_unlock (usrc); ++ ++ dst += chunk_size; ++ usrc += chunk_size; ++ size -= chunk_size; ++ } ++} ++ ++/* Creates a copy of user string US in kernel memory ++ and returns it as a page that must be freed with ++ palloc_free_page(). ++ Truncates the string at PGSIZE bytes in size. ++ Call thread_exit() if any of the user accesses are invalid. */ ++static char * ++copy_in_string (const char *us) ++{ ++ char *ks; ++ char *upage; ++ size_t length; ++ ++ ks = palloc_get_page (0); ++ if (ks == NULL) ++ thread_exit (); ++ ++ length = 0; ++ for (;;) ++ { ++ upage = pg_round_down (us); ++ if (!page_lock (upage, false)) ++ goto lock_error; ++ ++ for (; us < upage + PGSIZE; us++) ++ { ++ ks[length++] = *us; ++ if (*us == '\0') ++ { ++ page_unlock (upage); ++ return ks; ++ } ++ else if (length >= PGSIZE) ++ goto too_long_error; ++ } ++ ++ page_unlock (upage); ++ } ++ ++ too_long_error: ++ page_unlock (upage); ++ lock_error: ++ palloc_free_page (ks); ++ thread_exit (); ++} ++ ++/* Halt system call. */ ++static int ++sys_halt (void) ++{ ++ power_off (); ++} ++ ++/* Exit system call. */ ++static int ++sys_exit (int exit_code) ++{ ++ thread_current ()->exit_code = exit_code; ++ thread_exit (); ++ NOT_REACHED (); ++} ++ ++/* Exec system call. */ ++static int ++sys_exec (const char *ufile) ++{ ++ tid_t tid; ++ char *kfile = copy_in_string (ufile); ++ ++ tid = process_execute (kfile); ++ ++ palloc_free_page (kfile); ++ ++ return tid; ++} ++ ++/* Wait system call. */ ++static int ++sys_wait (tid_t child) ++{ ++ return process_wait (child); ++} ++ ++/* Create system call. */ ++static int ++sys_create (const char *ufile, unsigned initial_size) ++{ ++ char *kfile = copy_in_string (ufile); ++ bool ok = filesys_create (kfile, initial_size, FILE_INODE); ++ palloc_free_page (kfile); ++ ++ return ok; ++} ++ ++/* Remove system call. */ ++static int ++sys_remove (const char *ufile) ++{ ++ char *kfile = copy_in_string (ufile); ++ bool ok = filesys_remove (kfile); ++ palloc_free_page (kfile); ++ ++ return ok; ++} ++ ++/* A file descriptor, for binding a file handle to a file. */ ++struct file_descriptor ++ { ++ struct list_elem elem; /* List element. */ ++ struct file *file; /* File. */ ++ int handle; /* File handle. */ ++ }; ++ ++/* Open system call. */ ++static int ++sys_open (const char *ufile) ++{ ++ char *kfile = copy_in_string (ufile); ++ struct file_descriptor *fd; ++ int handle = -1; ++ ++ fd = malloc (sizeof *fd); ++ if (fd != NULL) ++ { ++ fd->file = file_open (filesys_open (kfile)); ++ if (fd->file != NULL) ++ { ++ struct thread *cur = thread_current (); ++ handle = fd->handle = cur->next_handle++; ++ list_push_front (&cur->fds, &fd->elem); ++ } ++ else ++ free (fd); ++ } ++ ++ palloc_free_page (kfile); ++ return handle; ++} ++ ++/* Returns the file descriptor associated with the given handle. ++ Terminates the process if HANDLE is not associated with an ++ open file. */ ++static struct file_descriptor * ++lookup_fd (int handle) ++{ ++ struct thread *cur = thread_current (); ++ struct list_elem *e; ++ ++ for (e = list_begin (&cur->fds); e != list_end (&cur->fds); ++ e = list_next (e)) ++ { ++ struct file_descriptor *fd; ++ fd = list_entry (e, struct file_descriptor, elem); ++ if (fd->handle == handle) ++ return fd; ++ } ++ ++ thread_exit (); ++} ++ ++/* Filesize system call. */ ++static int ++sys_filesize (int handle) ++{ ++ struct file_descriptor *fd = lookup_fd (handle); ++ int size; ++ ++ size = file_length (fd->file); ++ ++ return size; ++} ++ ++/* Read system call. */ ++static int ++sys_read (int handle, void *udst_, unsigned size) ++{ ++ uint8_t *udst = udst_; ++ struct file_descriptor *fd; ++ int bytes_read = 0; ++ ++ /* Look up file descriptor. */ ++ if (handle != STDIN_FILENO) ++ fd = lookup_fd (handle); ++ ++ while (size > 0) ++ { ++ /* How much to read into this page? */ ++ size_t page_left = PGSIZE - pg_ofs (udst); ++ size_t read_amt = size < page_left ? size : page_left; ++ off_t retval; ++ ++ /* Check that touching this page is okay. */ ++ if (!page_lock (udst, true)) ++ thread_exit (); ++ ++ /* Read from file into page. */ ++ if (handle != STDIN_FILENO) ++ { ++ retval = file_read (fd->file, udst, read_amt); ++ if (retval < 0) ++ { ++ if (bytes_read == 0) ++ bytes_read = -1; ++ break; ++ } ++ bytes_read += retval; ++ } ++ else ++ { ++ size_t i; ++ ++ for (i = 0; i < read_amt; i++) ++ udst[i] = kbd_getc (); ++ bytes_read = read_amt; ++ } ++ ++ /* Release page. */ ++ page_unlock (udst); ++ ++ /* If it was a short read we're done. */ ++ if (retval != (off_t) read_amt) ++ break; ++ ++ /* Advance. */ ++ udst += retval; ++ size -= retval; ++ } ++ ++ return bytes_read; ++} ++ ++/* Write system call. */ ++static int ++sys_write (int handle, void *usrc_, unsigned size) + { +- printf ("system call!\n"); ++ uint8_t *usrc = usrc_; ++ struct file_descriptor *fd = NULL; ++ int bytes_written = 0; ++ ++ /* Lookup up file descriptor. */ ++ if (handle != STDOUT_FILENO) ++ fd = lookup_fd (handle); ++ ++ while (size > 0) ++ { ++ /* How much bytes to write to this page? */ ++ size_t page_left = PGSIZE - pg_ofs (usrc); ++ size_t write_amt = size < page_left ? size : page_left; ++ off_t retval; ++ ++ /* Check that we can touch this user page. */ ++ if (!page_lock (usrc, false)) ++ thread_exit (); ++ ++ /* Do the write. */ ++ if (handle == STDOUT_FILENO) ++ { ++ putbuf (usrc, write_amt); ++ retval = write_amt; ++ } ++ else ++ retval = file_write (fd->file, usrc, write_amt); ++ ++ /* Release user page. */ ++ page_unlock (usrc); ++ ++ /* Handle return value. */ ++ if (retval < 0) ++ { ++ if (bytes_written == 0) ++ bytes_written = -1; ++ break; ++ } ++ bytes_written += retval; ++ ++ /* If it was a short write we're done. */ ++ if (retval != (off_t) write_amt) ++ break; ++ ++ /* Advance. */ ++ usrc += retval; ++ size -= retval; ++ } ++ ++ return bytes_written; ++} ++ ++/* Seek system call. */ ++static int ++sys_seek (int handle, unsigned position) ++{ ++ if ((off_t) position >= 0) ++ file_seek (lookup_fd (handle)->file, position); ++ return 0; ++} ++ ++/* Tell system call. */ ++static int ++sys_tell (int handle) ++{ ++ return file_tell (lookup_fd (handle)->file); ++} ++ ++/* Close system call. */ ++static int ++sys_close (int handle) ++{ ++ struct file_descriptor *fd = lookup_fd (handle); ++ file_close (fd->file); ++ list_remove (&fd->elem); ++ free (fd); ++ return 0; ++} ++ ++/* Binds a mapping id to a region of memory and a file. */ ++struct mapping ++ { ++ struct list_elem elem; /* List element. */ ++ int handle; /* Mapping id. */ ++ struct file *file; /* File. */ ++ uint8_t *base; /* Start of memory mapping. */ ++ size_t page_cnt; /* Number of pages mapped. */ ++ }; ++ ++/* Returns the file descriptor associated with the given handle. ++ Terminates the process if HANDLE is not associated with a ++ memory mapping. */ ++static struct mapping * ++lookup_mapping (int handle) ++{ ++ struct thread *cur = thread_current (); ++ struct list_elem *e; ++ ++ for (e = list_begin (&cur->mappings); e != list_end (&cur->mappings); ++ e = list_next (e)) ++ { ++ struct mapping *m = list_entry (e, struct mapping, elem); ++ if (m->handle == handle) ++ return m; ++ } ++ + thread_exit (); + } ++ ++/* Remove mapping M from the virtual address space, ++ writing back any pages that have changed. */ ++static void ++unmap (struct mapping *m) ++{ ++ list_remove (&m->elem); ++ while (m->page_cnt-- > 0) ++ { ++ page_deallocate (m->base); ++ m->base += PGSIZE; ++ } ++ file_close (m->file); ++ free (m); ++} ++ ++/* Mmap system call. */ ++static int ++sys_mmap (int handle, void *addr) ++{ ++ struct file_descriptor *fd = lookup_fd (handle); ++ struct mapping *m = malloc (sizeof *m); ++ size_t offset; ++ off_t length; ++ ++ if (m == NULL || addr == NULL || pg_ofs (addr) != 0) ++ return -1; ++ ++ m->handle = thread_current ()->next_handle++; ++ m->file = file_reopen (fd->file); ++ if (m->file == NULL) ++ { ++ free (m); ++ return -1; ++ } ++ m->base = addr; ++ m->page_cnt = 0; ++ list_push_front (&thread_current ()->mappings, &m->elem); ++ ++ offset = 0; ++ length = file_length (m->file); ++ while (length > 0) ++ { ++ struct page *p = page_allocate ((uint8_t *) addr + offset, false); ++ if (p == NULL) ++ { ++ unmap (m); ++ return -1; ++ } ++ p->private = false; ++ p->file = m->file; ++ p->file_offset = offset; ++ p->file_bytes = length >= PGSIZE ? PGSIZE : length; ++ offset += p->file_bytes; ++ length -= p->file_bytes; ++ m->page_cnt++; ++ } ++ ++ return m->handle; ++} ++ ++/* Munmap system call. */ ++static int ++sys_munmap (int mapping) ++{ ++ unmap (lookup_mapping (mapping)); ++ return 0; ++} ++ ++/* Chdir system call. */ ++static int ++sys_chdir (const char *udir) ++{ ++ char *kdir = copy_in_string (udir); ++ bool ok = filesys_chdir (kdir); ++ palloc_free_page (kdir); ++ return ok; ++} ++ ++/* Mkdir system call. */ ++static int ++sys_mkdir (const char *udir) ++{ ++ char *kdir = copy_in_string (udir); ++ bool ok = filesys_create (kdir, 0, DIR_INODE); ++ palloc_free_page (kdir); ++ ++ return ok; ++} ++ ++/* Lsdir system call. */ ++static int ++sys_lsdir (void) ++{ ++ dir_list (thread_current ()->wd); ++ return 0; ++} ++ ++/* On thread exit, close all open files and unmap all mappings. */ ++void ++syscall_exit (void) ++{ ++ struct thread *cur = thread_current (); ++ struct list_elem *e, *next; ++ ++ for (e = list_begin (&cur->fds); e != list_end (&cur->fds); e = next) ++ { ++ struct file_descriptor *fd = list_entry (e, struct file_descriptor, elem); ++ next = list_next (e); ++ file_close (fd->file); ++ free (fd); ++ } ++ ++ for (e = list_begin (&cur->mappings); e != list_end (&cur->mappings); ++ e = next) ++ { ++ struct mapping *m = list_entry (e, struct mapping, elem); ++ next = list_next (e); ++ unmap (m); ++ } ++ ++ dir_close (cur->wd); ++} +diff -u src/userprog/syscall.h~ src/userprog/syscall.h +--- src/userprog/syscall.h~ 2004-09-05 22:38:45.000000000 -0700 ++++ src/userprog/syscall.h 2005-06-16 15:09:31.000000000 -0700 +@@ -2,5 +2,6 @@ + #define USERPROG_SYSCALL_H + + void syscall_init (void); ++void syscall_exit (void); + + #endif /* userprog/syscall.h */ +diff -u src/vm/frame.c~ src/vm/frame.c +--- src/vm/frame.c~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/vm/frame.c 2005-06-16 15:09:31.000000000 -0700 +@@ -0,0 +1,161 @@ ++#include "vm/frame.h" ++#include ++#include "vm/page.h" ++#include "devices/timer.h" ++#include "threads/init.h" ++#include "threads/malloc.h" ++#include "threads/mmu.h" ++#include "threads/palloc.h" ++#include "threads/synch.h" ++ ++static struct frame *frames; ++static size_t frame_cnt; ++ ++static struct lock scan_lock; ++static size_t hand; ++ ++/* Initialize the frame manager. */ ++void ++frame_init (void) ++{ ++ void *base; ++ ++ lock_init (&scan_lock); ++ ++ frames = malloc (sizeof *frames * ram_pages); ++ if (frames == NULL) ++ PANIC ("out of memory allocating page frames"); ++ ++ while ((base = palloc_get_page (PAL_USER)) != NULL) ++ { ++ struct frame *f = &frames[frame_cnt++]; ++ lock_init (&f->lock); ++ f->base = base; ++ f->page = NULL; ++ } ++} ++ ++/* Tries to allocate and lock a frame for PAGE. ++ Returns the frame if successful, false on failure. */ ++static struct frame * ++try_frame_alloc_and_lock (struct page *page) ++{ ++ size_t i; ++ ++ lock_acquire (&scan_lock); ++ ++ /* Find a free frame. */ ++ for (i = 0; i < frame_cnt; i++) ++ { ++ struct frame *f = &frames[i]; ++ if (!lock_try_acquire (&f->lock)) ++ continue; ++ if (f->page == NULL) ++ { ++ f->page = page; ++ lock_release (&scan_lock); ++ return f; ++ } ++ lock_release (&f->lock); ++ } ++ ++ /* No free frame. Find a frame to evict. */ ++ for (i = 0; i < frame_cnt * 2; i++) ++ { ++ /* Get a frame. */ ++ struct frame *f = &frames[hand]; ++ if (++hand >= frame_cnt) ++ hand = 0; ++ ++ if (!lock_try_acquire (&f->lock)) ++ continue; ++ ++ if (f->page == NULL) ++ { ++ f->page = page; ++ lock_release (&scan_lock); ++ return f; ++ } ++ ++ if (page_accessed_recently (f->page)) ++ { ++ lock_release (&f->lock); ++ continue; ++ } ++ ++ lock_release (&scan_lock); ++ ++ /* Evict this frame. */ ++ if (!page_out (f->page)) ++ { ++ lock_release (&f->lock); ++ return NULL; ++ } ++ ++ f->page = page; ++ return f; ++ } ++ ++ lock_release (&scan_lock); ++ return NULL; ++} ++ ++/* Tries really hard to allocate and lock a frame for PAGE. ++ Returns the frame if successful, false on failure. */ ++struct frame * ++frame_alloc_and_lock (struct page *page) ++{ ++ size_t try; ++ ++ for (try = 0; try < 3; try++) ++ { ++ struct frame *f = try_frame_alloc_and_lock (page); ++ if (f != NULL) ++ { ++ ASSERT (lock_held_by_current_thread (&f->lock)); ++ return f; ++ } ++ timer_msleep (1000); ++ } ++ ++ return NULL; ++} ++ ++/* Locks P's frame into memory, if it has one. ++ Upon return, p->frame will not change until P is unlocked. */ ++void ++frame_lock (struct page *p) ++{ ++ /* A frame can be asynchronously removed, but never inserted. */ ++ struct frame *f = p->frame; ++ if (f != NULL) ++ { ++ lock_acquire (&f->lock); ++ if (f != p->frame) ++ { ++ lock_release (&f->lock); ++ ASSERT (p->frame == NULL); ++ } ++ } ++} ++ ++/* Releases frame F for use by another page. ++ F must be locked for use by the current process. ++ Any data in F is lost. */ ++void ++frame_free (struct frame *f) ++{ ++ ASSERT (lock_held_by_current_thread (&f->lock)); ++ ++ f->page = NULL; ++ lock_release (&f->lock); ++} ++ ++/* Unlocks frame F, allowing it to be evicted. ++ F must be locked for use by the current process. */ ++void ++frame_unlock (struct frame *f) ++{ ++ ASSERT (lock_held_by_current_thread (&f->lock)); ++ lock_release (&f->lock); ++} +diff -u src/vm/frame.h~ src/vm/frame.h +--- src/vm/frame.h~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/vm/frame.h 2005-06-16 15:09:31.000000000 -0700 +@@ -0,0 +1,23 @@ ++#ifndef VM_FRAME_H ++#define VM_FRAME_H ++ ++#include ++#include "threads/synch.h" ++ ++/* A physical frame. */ ++struct frame ++ { ++ struct lock lock; /* Prevent simultaneous access. */ ++ void *base; /* Kernel virtual base address. */ ++ struct page *page; /* Mapped process page, if any. */ ++ }; ++ ++void frame_init (void); ++ ++struct frame *frame_alloc_and_lock (struct page *); ++void frame_lock (struct page *); ++ ++void frame_free (struct frame *); ++void frame_unlock (struct frame *); ++ ++#endif /* vm/frame.h */ +diff -u src/vm/page.c~ src/vm/page.c +--- src/vm/page.c~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/vm/page.c 2005-06-16 15:09:31.000000000 -0700 +@@ -0,0 +1,297 @@ ++#include "vm/page.h" ++#include ++#include ++#include "vm/frame.h" ++#include "vm/swap.h" ++#include "filesys/file.h" ++#include "threads/malloc.h" ++#include "threads/mmu.h" ++#include "threads/thread.h" ++#include "userprog/pagedir.h" ++ ++/* Maximum size of process stack, in bytes. */ ++#define STACK_MAX (1024 * 1024) ++ ++/* Destroys the current process's page table. */ ++void ++page_exit (void) ++{ ++ struct hash *h; ++ struct hash_iterator i; ++ ++ h = thread_current ()->pages; ++ if (h == NULL) ++ return; ++ ++ hash_first (&i, h); ++ hash_next (&i); ++ while (hash_cur (&i)) ++ { ++ struct page *p = hash_entry (hash_cur (&i), struct page, hash_elem); ++ hash_next (&i); ++ frame_lock (p); ++ if (p->frame) ++ frame_free (p->frame); ++ free (p); ++ } ++ hash_destroy (h); ++} ++ ++/* Returns the page containing the given virtual ADDRESS, ++ or a null pointer if no such page exists. ++ Allocates stack pages as necessary. */ ++static struct page * ++page_for_addr (const void *address) ++{ ++ if (address < PHYS_BASE) ++ { ++ struct page p; ++ struct hash_elem *e; ++ ++ /* Find existing page. */ ++ p.addr = (void *) pg_round_down (address); ++ e = hash_find (thread_current ()->pages, &p.hash_elem); ++ if (e != NULL) ++ return hash_entry (e, struct page, hash_elem); ++ ++ /* No page. Expand stack? */ ++ if (address >= PHYS_BASE - STACK_MAX ++ && address >= thread_current ()->user_esp - 32) ++ return page_allocate ((void *) address, false); ++ } ++ return NULL; ++} ++ ++/* Locks a frame for page P and pages it in. ++ Returns true if successful, false on failure. */ ++static bool ++do_page_in (struct page *p) ++{ ++ /* Get a frame for the page. */ ++ p->frame = frame_alloc_and_lock (p); ++ if (p->frame == NULL) ++ return false; ++ ++ /* Copy data into the frame. */ ++ if (p->sector != (disk_sector_t) -1) ++ { ++ /* Get data from swap. */ ++ swap_in (p); ++ } ++ else if (p->file != NULL) ++ { ++ /* Get data from file. */ ++ off_t read_bytes = file_read_at (p->file, p->frame->base, ++ p->file_bytes, p->file_offset); ++ off_t zero_bytes = PGSIZE - read_bytes; ++ memset (p->frame->base + read_bytes, 0, zero_bytes); ++ if (read_bytes != p->file_bytes) ++ printf ("bytes read (%"PROTd") != bytes requested (%"PROTd")\n", ++ read_bytes, p->file_bytes); ++ } ++ else ++ { ++ /* Provide all-zero page. */ ++ memset (p->frame->base, 0, PGSIZE); ++ } ++ ++ return true; ++} ++ ++/* Faults in the page containing FAULT_ADDR. ++ Returns true if successful, false on failure. */ ++bool ++page_in (void *fault_addr) ++{ ++ struct page *p; ++ bool success; ++ ++ /* Can't handle page faults without a hash table. */ ++ if (thread_current ()->pages == NULL) ++ return false; ++ ++ p = page_for_addr (fault_addr); ++ if (p == NULL) ++ return false; ++ ++ frame_lock (p); ++ if (p->frame == NULL) ++ { ++ if (!do_page_in (p)) ++ return false; ++ } ++ ASSERT (lock_held_by_current_thread (&p->frame->lock)); ++ ++ /* Install frame into page table. */ ++ success = pagedir_set_page (thread_current ()->pagedir, p->addr, ++ p->frame->base, !p->read_only); ++ ++ /* Release frame. */ ++ frame_unlock (p->frame); ++ ++ return success; ++} ++ ++/* Evicts page P. ++ P must have a locked frame. ++ Return true if successful, false on failure. */ ++bool ++page_out (struct page *p) ++{ ++ bool dirty; ++ bool ok; ++ ++ ASSERT (p->frame != NULL); ++ ASSERT (lock_held_by_current_thread (&p->frame->lock)); ++ ++ /* Mark page not present in page table, forcing accesses by the ++ process to fault. This must happen before checking the ++ dirty bit, to prevent a race with the process dirtying the ++ page. */ ++ pagedir_clear_page (p->thread->pagedir, p->addr); ++ ++ /* Has the frame been modified? */ ++ dirty = pagedir_is_dirty (p->thread->pagedir, p->addr); ++ ++ /* Write frame contents to disk if necessary. */ ++ if (p->file != NULL) ++ { ++ if (dirty) ++ { ++ if (p->private) ++ ok = swap_out (p); ++ else ++ ok = file_write_at (p->file, p->frame->base, p->file_bytes, ++ p->file_offset) == p->file_bytes; ++ } ++ else ++ ok = true; ++ } ++ else ++ ok = swap_out (p); ++ if (ok) ++ { ++ //memset (p->frame->base, 0xcc, PGSIZE); ++ p->frame = NULL; ++ } ++ return ok; ++} ++ ++/* Returns true if page P's data has been accessed recently, ++ false otherwise. ++ P must have a frame locked into memory. */ ++bool ++page_accessed_recently (struct page *p) ++{ ++ bool was_accessed; ++ ++ ASSERT (p->frame != NULL); ++ ASSERT (lock_held_by_current_thread (&p->frame->lock)); ++ ++ was_accessed = pagedir_is_accessed (p->thread->pagedir, p->addr); ++ if (was_accessed) ++ pagedir_set_accessed (p->thread->pagedir, p->addr, false); ++ return was_accessed; ++} ++ ++/* Adds a mapping for user virtual address VADDR to the page hash ++ table. Fails if VADDR is already mapped or if memory ++ allocation fails. */ ++struct page * ++page_allocate (void *vaddr, bool read_only) ++{ ++ struct thread *t = thread_current (); ++ struct page *p = malloc (sizeof *p); ++ if (p != NULL) ++ { ++ p->addr = pg_round_down (vaddr); ++ ++ p->read_only = read_only; ++ p->private = !read_only; ++ ++ p->frame = NULL; ++ ++ p->sector = (disk_sector_t) -1; ++ ++ p->file = NULL; ++ p->file_offset = 0; ++ p->file_bytes = 0; ++ ++ p->thread = thread_current (); ++ ++ if (hash_insert (t->pages, &p->hash_elem) != NULL) ++ { ++ /* Already mapped. */ ++ free (p); ++ p = NULL; ++ } ++ } ++ return p; ++} ++ ++/* Evicts the page containing address VADDR ++ and removes it from the page table. */ ++void ++page_deallocate (void *vaddr) ++{ ++ struct page *p = page_for_addr (vaddr); ++ ASSERT (p != NULL); ++ frame_lock (p); ++ if (p->frame) ++ { ++ struct frame *f = p->frame; ++ if (p->file && !p->private) ++ page_out (p); ++ frame_free (f); ++ } ++ hash_delete (thread_current ()->pages, &p->hash_elem); ++ free (p); ++} ++ ++/* Returns a hash value for the page that E refers to. */ ++unsigned ++page_hash (const struct hash_elem *e, void *aux UNUSED) ++{ ++ const struct page *p = hash_entry (e, struct page, hash_elem); ++ return ((uintptr_t) p->addr) >> PGBITS; ++} ++ ++/* 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; ++} ++ ++/* Tries to lock the page containing ADDR into physical memory. ++ If WILL_WRITE is true, the page must be writeable; ++ otherwise it may be read-only. ++ Returns true if successful, false on failure. */ ++bool ++page_lock (const void *addr, bool will_write) ++{ ++ struct page *p = page_for_addr (addr); ++ if (p == NULL || (p->read_only && will_write)) ++ return false; ++ ++ frame_lock (p); ++ if (p->frame == NULL) ++ return (do_page_in (p) ++ && pagedir_set_page (thread_current ()->pagedir, p->addr, ++ p->frame->base, !p->read_only)); ++ else ++ return true; ++} ++ ++/* Unlocks a page locked with page_lock(). */ ++void ++page_unlock (const void *addr) ++{ ++ struct page *p = page_for_addr (addr); ++ ASSERT (p != NULL); ++ frame_unlock (p->frame); ++} +diff -u src/vm/page.h~ src/vm/page.h +--- src/vm/page.h~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/vm/page.h 2005-06-16 15:09:31.000000000 -0700 +@@ -0,0 +1,50 @@ ++#ifndef VM_PAGE_H ++#define VM_PAGE_H ++ ++#include ++#include "devices/disk.h" ++#include "filesys/off_t.h" ++#include "threads/synch.h" ++ ++/* Virtual page. */ ++struct page ++ { ++ /* Immutable members. */ ++ void *addr; /* User virtual address. */ ++ bool read_only; /* Read-only page? */ ++ struct thread *thread; /* Owning thread. */ ++ ++ /* Accessed only in owning process context. */ ++ struct hash_elem hash_elem; /* struct thread `pages' hash element. */ ++ ++ /* Set only in owning process context with frame->frame_lock held. ++ Cleared only with scan_lock and frame->frame_lock held. */ ++ struct frame *frame; /* Page frame. */ ++ ++ /* Swap information, protected by frame->frame_lock. */ ++ disk_sector_t sector; /* Starting sector of swap area, or -1. */ ++ ++ /* Memory-mapped file information, protected by frame->frame_lock. */ ++ bool private; /* False to write back to file, ++ true to write back to swap. */ ++ struct file *file; /* File. */ ++ off_t file_offset; /* Offset in file. */ ++ off_t file_bytes; /* Bytes to read/write, 1...PGSIZE. */ ++ }; ++ ++void page_exit (void); ++ ++struct page *page_allocate (void *, bool read_only); ++void page_deallocate (void *vaddr); ++ ++bool page_in (void *fault_addr); ++bool page_out (struct page *); ++bool page_accessed_recently (struct page *); ++ ++bool page_lock (const void *, bool will_write); ++void page_unlock (const void *); ++ ++hash_hash_func page_hash; ++hash_less_func page_less; ++ ++#endif /* vm/page.h */ +diff -u src/vm/swap.c~ src/vm/swap.c +--- src/vm/swap.c~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/vm/swap.c 2005-06-16 15:09:31.000000000 -0700 +@@ -0,0 +1,85 @@ ++#include "vm/swap.h" ++#include ++#include ++#include ++#include "vm/frame.h" ++#include "vm/page.h" ++#include "devices/disk.h" ++#include "threads/mmu.h" ++#include "threads/synch.h" ++ ++/* The swap disk. */ ++static struct disk *swap_disk; ++ ++/* Used swap pages. */ ++static struct bitmap *swap_bitmap; ++ ++/* Protects swap_bitmap. */ ++static struct lock swap_lock; ++ ++/* Number of sectors per page. */ ++#define PAGE_SECTORS (PGSIZE / DISK_SECTOR_SIZE) ++ ++/* Sets up swap. */ ++void ++swap_init (void) ++{ ++ swap_disk = disk_get (1, 1); ++ if (swap_disk == NULL) ++ { ++ printf ("no swap disk--swap disabled\n"); ++ swap_bitmap = bitmap_create (0); ++ } ++ else ++ swap_bitmap = bitmap_create (disk_size (swap_disk) / PAGE_SECTORS); ++ if (swap_bitmap == NULL) ++ PANIC ("couldn't create swap bitmap"); ++ lock_init (&swap_lock); ++} ++ ++/* Swaps in page P, which must have a locked frame ++ (and be swapped out). */ ++void ++swap_in (struct page *p) ++{ ++ size_t i; ++ ++ ASSERT (p->frame != NULL); ++ ASSERT (lock_held_by_current_thread (&p->frame->lock)); ++ ASSERT (p->sector != (disk_sector_t) -1); ++ ++ for (i = 0; i < PAGE_SECTORS; i++) ++ disk_read (swap_disk, p->sector + i, ++ p->frame->base + i * DISK_SECTOR_SIZE); ++ bitmap_reset (swap_bitmap, p->sector / PAGE_SECTORS); ++ p->sector = (disk_sector_t) -1; ++} ++ ++/* Swaps out page P, which must have a locked frame. */ ++bool ++swap_out (struct page *p) ++{ ++ size_t slot; ++ size_t i; ++ ++ ASSERT (p->frame != NULL); ++ ASSERT (lock_held_by_current_thread (&p->frame->lock)); ++ ++ lock_acquire (&swap_lock); ++ slot = bitmap_scan_and_flip (swap_bitmap, 0, 1, false); ++ lock_release (&swap_lock); ++ if (slot == BITMAP_ERROR) ++ return false; ++ ++ p->sector = slot * PAGE_SECTORS; ++ for (i = 0; i < PAGE_SECTORS; i++) ++ disk_write (swap_disk, p->sector + i, ++ p->frame->base + i * DISK_SECTOR_SIZE); ++ ++ p->private = false; ++ p->file = NULL; ++ p->file_offset = 0; ++ p->file_bytes = 0; ++ ++ return true; ++} +diff -u src/vm/swap.h~ src/vm/swap.h +--- src/vm/swap.h~ 1969-12-31 16:00:00.000000000 -0800 ++++ src/vm/swap.h 2005-06-16 15:09:31.000000000 -0700 +@@ -0,0 +1,11 @@ ++#ifndef VM_SWAP_H ++#define VM_SWAP_H 1 ++ ++#include ++ ++struct page; ++void swap_init (void); ++void swap_in (struct page *); ++bool swap_out (struct page *); ++ ++#endif /* vm/swap.h */ diff --git a/src/LICENSE b/src/LICENSE index 30e57e7..d6e8392 100644 --- a/src/LICENSE +++ b/src/LICENSE @@ -1,4 +1,34 @@ -Code derived from Nachos is subject to the following license: +Most of Pintos is subject to the following license: + + Copyright 2004 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. @@ -24,8 +54,8 @@ Code derived from Nachos is subject to the following license: MODIFICATIONS. */ -Code derived from MIT's 6.828 course code is subject to the following -license: +Also, code derived from MIT's 6.828 course code is subject to the +following license: /* * Copyright (C) 1997 Massachusetts Institute of Technology @@ -62,28 +92,3 @@ license: * holders listed in the AUTHORS file. The rest of this file is covered by * the copyright notices, if any, listed below. */ - -Other code, and modifications to the above code made for this project, -is subject to the following license: - -Copyright 2004 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. diff --git a/src/Make.config b/src/Make.config index 27f8328..8744f0d 100644 --- a/src/Make.config +++ b/src/Make.config @@ -2,6 +2,8 @@ SHELL = /bin/sh +VPATH = $(SRCDIR) + # Binary utilities. # If the host appears to be x86, use the normal tools. # Otherwise assume cross-tools are installed as i386-elf-*. @@ -16,15 +18,13 @@ LD = i386-elf-ld OBJCOPY = i386-elf-objcopy endif -# Other utilities. -DD = dd -RM = rm -CAT = cat - # Compiler and assembler invocation. +DEFINES = WARNINGS = -Wall -W -Wstrict-prototypes -Wmissing-prototypes -Wsystem-headers CFLAGS = -g -MMD -msoft-float -masm=intel +CPPFLAGS = -nostdinc -I$(SRCDIR) -I- -I$(SRCDIR)/lib ASFLAGS = -Wa,--gstabs -MMD +LDFLAGS = %.o: %.c $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) $(WARNINGS) $(DEFINES) diff --git a/src/Makefile b/src/Makefile index 326e774..6dd4610 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,17 +1,20 @@ -SUBDIRS = threads userprog vm filesys +BUILD_SUBDIRS = threads userprog vm filesys all:: - @echo "Run 'make' in subdirectories $(SUBDIRS)." + @echo "Run 'make' in subdirectories: $(BUILD_SUBDIRS)." @echo "This top-level make has only 'clean' targets." +CLEAN_SUBDIRS = $(BUILD_SUBDIRS) examples + clean:: - for d in $(SUBDIRS) tests; do $(MAKE) -C $$d $@; done + for d in $(CLEAN_SUBDIRS); do $(MAKE) -C $$d $@; done rm -f TAGS tags distclean:: clean find . -name '*~' -exec rm '{}' \; -TAGS_SOURCES = `find \( -name tests -o -name build \) -prune -o -name \*.[chS] -print` +TAGS_SUBDIRS = $(BUILD_SUBDIRS) devices lib +TAGS_SOURCES = `find $(TAGS_SUBDIRS) -name \*.[chS] -print` TAGS:: etags --members $(TAGS_SOURCES) diff --git a/src/Makefile.build b/src/Makefile.build index e124011..cf0f9b6 100644 --- a/src/Makefile.build +++ b/src/Makefile.build @@ -1,13 +1,15 @@ # -*- makefile -*- -include ../Make.vars -include ../../Make.config +SRCDIR = ../.. + +all: os.dsk -VPATH = ../.. +include ../../Make.config +include ../Make.vars +include ../../tests/Make.tests # Compiler and assembler options. -DEFINES += -DKERNEL -CPPFLAGS = -nostdinc -I../.. -I- -I../../lib -I../../lib/kernel +os.dsk: CPPFLAGS += -I$(SRCDIR)/lib/kernel # Core kernel. threads_SRC = threads/init.c # Main program. @@ -19,7 +21,6 @@ threads_SRC += threads/synch.c # Synchronization. threads_SRC += threads/palloc.c # Page allocator. threads_SRC += threads/malloc.c # Subpage allocator. threads_SRC += threads/start.S # Startup code. -threads_SRC += threads/test.c # Test code. # Device driver code. devices_SRC = devices/timer.c # Timer device. @@ -37,10 +38,11 @@ lib_SRC += lib/stdlib.c # Utility functions. lib_SRC += lib/string.c # String functions. # Kernel-specific library code. -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(). +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. @@ -55,17 +57,16 @@ userprog_SRC += userprog/tss.c # TSS management. # 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,$(SUBDIRS),$($(subst /,_,$(dir))_SRC)) +SOURCES = $(foreach dir,$(KERNEL_SUBDIRS),$($(dir)_SRC)) OBJECTS = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SOURCES))) DEPENDS = $(patsubst %.o,%.d,$(OBJECTS)) -all: os.dsk - threads/kernel.lds.s: CPPFLAGS += -P threads/kernel.lds.s: threads/kernel.lds.S threads/loader.h @@ -74,8 +75,8 @@ kernel.o: threads/kernel.lds.s $(OBJECTS) kernel.bin: kernel.o $(OBJCOPY) -O binary -R .note -R .comment -S $< $@.tmp - $(DD) if=$@.tmp of=$@ bs=4096 conv=sync - $(RM) $@.tmp + dd if=$@.tmp of=$@ bs=4096 conv=sync + rm $@.tmp threads/loader.o: threads/loader.S kernel.bin $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) -DKERNEL_LOAD_PAGES=`perl -e 'print +(-s "kernel.bin") / 4096;'` @@ -84,15 +85,15 @@ loader.bin: threads/loader.o $(LD) -N -e start -Ttext 0x7c00 --oformat binary -o $@ $< os.dsk: loader.bin kernel.bin - $(CAT) $^ > $@ + cat $^ > $@ -clean: - $(RM) -f $(OBJECTS) $(DEPENDS) - $(RM) -f threads/loader.o - $(RM) -f kernel.o kernel.lds.s - $(RM) -f kernel.bin loader.bin +clean:: + rm -f $(OBJECTS) $(DEPENDS) + rm -f threads/loader.o threads/kernel.lds.s threads/loader.d + rm -f kernel.o kernel.lds.s + rm -f kernel.bin loader.bin os.dsk -Makefile: ../../Makefile.build +Makefile: $(SRCDIR)/Makefile.build cp $< $@ -include $(DEPENDS) diff --git a/src/Makefile.kernel b/src/Makefile.kernel index e31d513..162a411 100644 --- a/src/Makefile.kernel +++ b/src/Makefile.kernel @@ -1,18 +1,20 @@ # -*- makefile -*- +all: + include Make.vars -BUILD_SUBDIRS = $(addprefix build/, $(SUBDIRS)) -all: dirs - $(MAKE) -C build +DIRS = $(sort $(addprefix build/,$(KERNEL_SUBDIRS) $(TEST_SUBDIRS) lib/user)) -dirs: build build/Makefile $(BUILD_SUBDIRS) -build: - mkdir $@ +all grade check: $(DIRS) build/Makefile + cd build && $(MAKE) $@ +$(DIRS): + mkdir -p $@ build/Makefile: ../Makefile.build cp $< $@ -$(BUILD_SUBDIRS): - mkdir $@ + +build/%: $(DIRS) build/Makefile + cd build && $(MAKE) $* clean: rm -rf build diff --git a/src/Makefile.userprog b/src/Makefile.userprog index 2ecb8e7..72e35ce 100644 --- a/src/Makefile.userprog +++ b/src/Makefile.userprog @@ -2,47 +2,45 @@ include $(SRCDIR)/Make.config -SHELL = /bin/sh - -VPATH = $(SRCDIR) - -DEFINES = -DPINTOS -DUSER -CPPFLAGS = -nostdinc -I$(SRCDIR) -I- -I$(SRCDIR)/lib -I$(SRCDIR)/lib/user -I. +$(PROGS): CPPFLAGS += -I$(SRCDIR)/lib/user -I. # Linker flags. -LDFLAGS = -nostdlib -static -Wl,-T,$(LDSCRIPT) -LDLIBS = $(shell $(CC) -print-libgcc-file-name) -LDSCRIPT = $(SRCDIR)/lib/user/normal.lds +$(PROGS): LDFLAGS = -nostdlib -static -Wl,-T,$(LDSCRIPT) +$(PROGS): LDLIBS = $(shell $(CC) -print-libgcc-file-name) +$(PROGS): LDSCRIPT = $(SRCDIR)/lib/user/normal.lds # Uncomment the following line to round up section sizes # to full pages (for debugging only). -#LDSCRIPT = $(SRCDIR)/lib/user/fullpage.lds - -# C library sources linked into every test program. -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 # atoi() -LIB_SRC += lib/string.c # String functions. -LIB_SRC += lib/user/syscall.c # System calls. -LIB_SRC += lib/user/console.c # Console code. - -LIB_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(LIB_SRC))) +#$(PROGS): LDSCRIPT = $(SRCDIR)/lib/user/fullpage.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. + +# 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),$($(subst -,_,$(prog))_SRC)) +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 -$(2)_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$($(2)_SRC))) -$(1): $$($(2)_OBJ) $$(LIB) $$(LDSCRIPT) - $$(CC) $$(LDFLAGS) $$($(2)_OBJ) $$(LIB) $$(LDLIBS) -o $$@ +$(1)_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$($(1)_SRC))) +$(1): $$($(1)_OBJ) $$(LIB) $$(LDSCRIPT) + $$(CC) $$(LDFLAGS) $$($(1)_OBJ) $$(LIB) $$(LDLIBS) -o $$@ endef -$(foreach prog,$(PROGS),$(eval $(call TEMPLATE,$(prog),$(subst -,_,$(prog))))) +$(foreach prog,$(PROGS),$(eval $(call TEMPLATE,$(prog)))) libc.a: $(LIB_OBJ) rm -f $@ diff --git a/src/devices/disk.c b/src/devices/disk.c index 1810fc4..0a13174 100644 --- a/src/devices/disk.c +++ b/src/devices/disk.c @@ -123,9 +123,9 @@ disk_init (void) default: NOT_REACHED (); } - lock_init (&c->lock, c->name); + lock_init (&c->lock); c->expecting_interrupt = false; - sema_init (&c->completion_wait, 0, c->name); + sema_init (&c->completion_wait, 0); /* Initialize devices. */ for (dev_no = 0; dev_no < 2; dev_no++) @@ -142,7 +142,7 @@ disk_init (void) } /* Register interrupt handler. */ - intr_register (c->irq, 0, INTR_OFF, interrupt_handler, c->name); + intr_register_ext (c->irq, interrupt_handler, c->name); /* Reset hardware. */ reset_channel (c); diff --git a/src/devices/intq.c b/src/devices/intq.c index 8754c85..028dca4 100644 --- a/src/devices/intq.c +++ b/src/devices/intq.c @@ -6,12 +6,11 @@ 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, naming it NAME (for debugging - purposes). */ +/* Initializes interrupt queue Q. */ void -intq_init (struct intq *q, const char *name) +intq_init (struct intq *q) { - lock_init (&q->lock, name); + lock_init (&q->lock); q->not_full = q->not_empty = NULL; q->head = q->tail = 0; } @@ -87,7 +86,7 @@ next (int pos) /* 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, struct thread **waiter) +wait (struct intq *q UNUSED, struct thread **waiter) { ASSERT (!intr_context ()); ASSERT (intr_get_level () == INTR_OFF); @@ -103,7 +102,7 @@ wait (struct intq *q, struct thread **waiter) thread is waiting for the condition, wakes it up and resets the waiting thread. */ static void -signal (struct intq *q, struct thread **waiter) +signal (struct intq *q UNUSED, struct thread **waiter) { ASSERT (intr_get_level () == INTR_OFF); ASSERT ((waiter == &q->not_empty && !intq_empty (q)) diff --git a/src/devices/intq.h b/src/devices/intq.h index 2b3edea..7d5054f 100644 --- a/src/devices/intq.h +++ b/src/devices/intq.h @@ -34,7 +34,7 @@ struct intq int tail; /* Old data is read here. */ }; -void intq_init (struct intq *, const char *); +void intq_init (struct intq *); bool intq_empty (const struct intq *); bool intq_full (const struct intq *); uint8_t intq_getc (struct intq *); diff --git a/src/devices/kbd.c b/src/devices/kbd.c index a264984..1ddf3e2 100644 --- a/src/devices/kbd.c +++ b/src/devices/kbd.c @@ -34,8 +34,8 @@ static intr_handler_func keyboard_interrupt; void kbd_init (void) { - intq_init (&buffer, "keyboard"); - intr_register (0x21, 0, INTR_OFF, keyboard_interrupt, "8042 Keyboard"); + intq_init (&buffer); + intr_register_ext (0x21, keyboard_interrupt, "8042 Keyboard"); } /* Retrieves a key from the keyboard buffer. diff --git a/src/devices/serial.c b/src/devices/serial.c index aca5b48..d92a87c 100644 --- a/src/devices/serial.c +++ b/src/devices/serial.c @@ -65,7 +65,7 @@ serial_init_poll (void) outb (FCR_REG, 0); /* Disable FIFO. */ set_serial (115200); /* 115.2 kbps, N-8-1. */ outb (MCR_REG, MCR_OUT2); /* Turn on OUT2 output line. */ - intq_init (&txq, "serial xmit"); + intq_init (&txq); mode = POLL; } @@ -76,7 +76,7 @@ void serial_init_queue (void) { ASSERT (mode == POLL); - intr_register (0x20 + 4, 0, INTR_OFF, serial_interrupt, "serial"); + intr_register_ext (0x20 + 4, serial_interrupt, "serial"); mode = QUEUE; } diff --git a/src/devices/timer.c b/src/devices/timer.c index 9e1b498..24eede0 100644 --- a/src/devices/timer.c +++ b/src/devices/timer.c @@ -16,9 +16,6 @@ #error TIMER_FREQ <= 1000 recommended #endif -/* Number of time ticks to elapse between process yields. */ -#define TIME_SLICE 1 - /* Number of timer ticks since OS booted. */ static volatile int64_t ticks; @@ -45,7 +42,7 @@ timer_init (void) outb (0x40, count & 0xff); outb (0x40, count >> 8); - intr_register (0x20, 0, INTR_OFF, timer_interrupt, "8254 Timer"); + intr_register_ext (0x20, timer_interrupt, "8254 Timer"); } /* Calibrates loops_per_tick, used to implement brief delays. */ @@ -135,8 +132,6 @@ timer_interrupt (struct intr_frame *args UNUSED) { ticks++; thread_tick (); - if (ticks % TIME_SLICE == 0) - intr_yield_on_return (); } /* Returns true if LOOPS iterations waits for more than one timer diff --git a/src/tests/userprog/.cvsignore b/src/examples/.cvsignore similarity index 94% rename from src/tests/userprog/.cvsignore rename to src/examples/.cvsignore index dda06da..d5c74d7 100644 --- a/src/tests/userprog/.cvsignore +++ b/src/examples/.cvsignore @@ -1,4 +1,3 @@ -*.d bubsort echo halt diff --git a/src/tests/userprog/Makefile b/src/examples/Makefile similarity index 97% rename from src/tests/userprog/Makefile rename to src/examples/Makefile index 8f7efe5..ae1df0f 100644 --- a/src/tests/userprog/Makefile +++ b/src/examples/Makefile @@ -1,4 +1,4 @@ -SRCDIR = ../.. +SRCDIR = .. # Test programs to compile, and a list of sources for each. # To add a new test, put its name on the PROGS list diff --git a/src/tests/userprog/bubsort.c b/src/examples/bubsort.c similarity index 100% rename from src/tests/userprog/bubsort.c rename to src/examples/bubsort.c diff --git a/src/tests/userprog/echo.c b/src/examples/echo.c similarity index 100% rename from src/tests/userprog/echo.c rename to src/examples/echo.c diff --git a/src/examples/halt.c b/src/examples/halt.c new file mode 100644 index 0000000..bad7250 --- /dev/null +++ b/src/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/src/tests/userprog/insult.c b/src/examples/insult.c similarity index 100% rename from src/tests/userprog/insult.c rename to src/examples/insult.c diff --git a/src/examples/lib/.cvsignore b/src/examples/lib/.cvsignore new file mode 100644 index 0000000..e69de29 diff --git a/src/examples/lib/user/.dummy b/src/examples/lib/user/.dummy new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/userprog/lineup.c b/src/examples/lineup.c similarity index 100% rename from src/tests/userprog/lineup.c rename to src/examples/lineup.c diff --git a/src/tests/userprog/ls.c b/src/examples/ls.c similarity index 100% rename from src/tests/userprog/ls.c rename to src/examples/ls.c diff --git a/src/tests/userprog/matmult.c b/src/examples/matmult.c similarity index 100% rename from src/tests/userprog/matmult.c rename to src/examples/matmult.c diff --git a/src/tests/userprog/mkdir.c b/src/examples/mkdir.c similarity index 100% rename from src/tests/userprog/mkdir.c rename to src/examples/mkdir.c diff --git a/src/tests/userprog/recursor.c b/src/examples/recursor.c similarity index 100% rename from src/tests/userprog/recursor.c rename to src/examples/recursor.c diff --git a/src/tests/userprog/shell.c b/src/examples/shell.c similarity index 100% rename from src/tests/userprog/shell.c rename to src/examples/shell.c diff --git a/src/filesys/Make.vars b/src/filesys/Make.vars index 5899e51..5a010be 100644 --- a/src/filesys/Make.vars +++ b/src/filesys/Make.vars @@ -1,6 +1,11 @@ -DEFINES = -DUSERPROG -DFILESYS -SUBDIRS = threads devices lib lib/kernel userprog filesys +# -*- makefile -*- -# Comment out the two lines below to disable VM. -DEFINES += -DVM -SUBDIRS += vm +os.dsk: DEFINES = -DUSERPROG -DFILESYS + +KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys +TEST_SUBDIRS = tests/userprog tests/filesys/base tests/filesys/extended + +# Uncomment the lines below to enable VM. +#os.dsk: DEFINES += -DVM +#KERNEL_SUBDIRS += vm +#TEST_SUBDIRS += tests/vm diff --git a/src/filesys/directory.c b/src/filesys/directory.c index fd15902..5f7dd9e 100644 --- a/src/filesys/directory.c +++ b/src/filesys/directory.c @@ -1,190 +1,218 @@ #include "filesys/directory.h" #include #include -#include "filesys/file.h" -#include "filesys/fsutil.h" +#include +#include "filesys/filesys.h" +#include "filesys/inode.h" #include "threads/malloc.h" /* A directory. */ struct dir { - size_t entry_cnt; /* Number of entries. */ - struct dir_entry *entries; /* Array of entries. */ + struct inode *inode; /* Backing store. */ }; /* A single directory entry. */ struct dir_entry { - bool in_use; /* In use or free? */ - char name[NAME_MAX + 1]; /* Null terminated file name. */ disk_sector_t inode_sector; /* Sector number of header. */ + char name[NAME_MAX + 1]; /* Null terminated file name. */ + bool in_use; /* In use or free? */ }; -/* Returns a new directory that holds ENTRY_CNT entries, if - successful, or a null pointer if memory is unavailable. */ -struct dir * -dir_create (size_t entry_cnt) +/* Creates a directory with space for ENTRY_CNT entries in the + given SECTOR. Returns true if successful, false on failure. */ +bool +dir_create (disk_sector_t sector, size_t entry_cnt) { - struct dir *d = malloc (sizeof *d); - if (d != NULL) - { - d->entry_cnt = entry_cnt; - d->entries = calloc (1, sizeof *d->entries * entry_cnt); - if (d->entries != NULL) - return d; - free (d); - } - return NULL; + return inode_create (sector, entry_cnt * sizeof (struct dir_entry)); } -/* Returns the size, in bytes, of a directory with ENTRY_CNT - entries. */ -size_t -dir_size (size_t entry_cnt) +/* Opens the directory in the given INODE, of which it takes + ownership, and sets *DIRP to the new directory or a null + pointer on failure. Return true if successful, false on + failure. */ +bool +dir_open (struct inode *inode, struct dir **dirp) { - return entry_cnt * sizeof (struct dir_entry); -} + struct dir *dir = NULL; + + ASSERT (dirp != NULL); -/* Destroys D and frees associated resources. */ -void -dir_destroy (struct dir *d) -{ - if (d != NULL) + if (inode != NULL) { - free (d->entries); - free (d); + dir = malloc (sizeof *dir); + if (dir != NULL) + dir->inode = inode; } + + *dirp = dir; + if (dir == NULL) + inode_close (inode); + return dir != NULL; } -/* Reads D from FILE. - D must have already been initialized, to the correct number of - entries, with dir_init(). */ -void -dir_read (struct dir *d, struct file *file) +/* Opens the root directory and sets *DIRP to it or to a null + pointer on failure. Return true if successful, false on + failure. */ +bool +dir_open_root (struct dir **dirp) { - ASSERT (d != NULL); - ASSERT (file != NULL); - ASSERT (file_length (file) >= (off_t) dir_size (d->entry_cnt)); - - file_read_at (file, d->entries, dir_size (d->entry_cnt), 0); + return dir_open (inode_open (ROOT_DIR_SECTOR), dirp); } -/* Writes D to FILE. - D must have already been initialized, to the correct number of - entries, with dir_init(). */ +/* Destroys DIR and frees associated resources. */ void -dir_write (struct dir *d, struct file *file) +dir_close (struct dir *dir) { - file_write_at (file, d->entries, dir_size (d->entry_cnt), 0); + if (dir != NULL) + { + inode_close (dir->inode); + free (dir); + } } -/* Searches D for a file named NAME. - If successful, returns the file's entry; - otherwise, returns a null pointer. */ -static struct dir_entry * -lookup (const struct dir *d, const char *name) +/* 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) { - size_t i; + struct dir_entry e; + size_t ofs; - ASSERT (d != NULL); + ASSERT (dir != NULL); ASSERT (name != NULL); - if (strlen (name) > NAME_MAX) - return NULL; - - for (i = 0; i < d->entry_cnt; i++) - { - struct dir_entry *e = &d->entries[i]; - if (e->in_use && !strcmp (name, e->name)) - return e; - } - return 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 D for a file named NAME +/* Searches DIR for a file with the given NAME and returns true if one exists, false otherwise. - If INODE_SECTOR is nonnull, then on success *INODE_SECTOR - is set to the sector that contains the file's inode. */ + 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 *d, const char *name, - disk_sector_t *inode_sector) +dir_lookup (const struct dir *dir, const char *name, + struct inode **inode) { - const struct dir_entry *e; + struct dir_entry e; - ASSERT (d != NULL); + ASSERT (dir != NULL); ASSERT (name != NULL); - - e = lookup (d, name); - if (e != NULL) - { - if (inode_sector != NULL) - *inode_sector = e->inode_sector; - return true; - } + + if (lookup (dir, name, &e, NULL)) + *inode = inode_open (e.inode_sector); else - return false; + *inode = NULL; + + return *inode != NULL; } -/* Adds a file named NAME to D, which must not already contain a +/* 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 if D has no free - directory entries. */ + Fails if NAME is invalid (i.e. too long) or a disk or memory + error occurs. */ bool -dir_add (struct dir *d, const char *name, disk_sector_t inode_sector) +dir_add (struct dir *dir, const char *name, disk_sector_t inode_sector) { - size_t i; + struct dir_entry e; + off_t ofs; + bool success = false; - ASSERT (d != NULL); + ASSERT (dir != NULL); ASSERT (name != NULL); - ASSERT (lookup (d, name) == NULL); + /* Check NAME for validity. */ if (*name == '\0' || strlen (name) > NAME_MAX) return false; - for (i = 0; i < d->entry_cnt; i++) - { - struct dir_entry *e = &d->entries[i]; - if (!e->in_use) - { - e->in_use = true; - strlcpy (e->name, name, sizeof e->name); - e->inode_sector = inode_sector; - return true; - } - } - 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 D. +/* 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 *d, const char *name) +dir_remove (struct dir *dir, const char *name) { - struct dir_entry *e; + struct dir_entry e; + struct inode *inode = NULL; + bool success = false; + off_t ofs; - ASSERT (d != NULL); + ASSERT (dir != NULL); ASSERT (name != NULL); - e = lookup (d, name); - if (e != NULL) - { - e->in_use = false; - return true; - } - else - return false; + /* 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; } -/* Prints the names of the files in D to the system console. */ +/* Prints the names of the files in DIR to the system console. */ void -dir_list (const struct dir *d) +dir_list (const struct dir *dir) { - struct dir_entry *e; + struct dir_entry e; + size_t ofs; - for (e = d->entries; e < d->entries + d->entry_cnt; e++) - if (e->in_use) - printf ("%s\n", e->name); + for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e; + ofs += sizeof e) + if (e.in_use) + printf ("%s\n", e.name); } diff --git a/src/filesys/directory.h b/src/filesys/directory.h index c773887..abc3029 100644 --- a/src/filesys/directory.h +++ b/src/filesys/directory.h @@ -11,13 +11,13 @@ retained, but much longer full path names must be allowed. */ #define NAME_MAX 14 -struct file; -struct dir *dir_create (size_t entry_cnt); -size_t dir_size (size_t entry_cnt); -void dir_destroy (struct dir *); -void dir_read (struct dir *, struct file *); -void dir_write (struct dir *, struct file *); -bool dir_lookup (const struct dir *, const char *name, disk_sector_t *); +struct inode; +struct dir; +bool dir_create (disk_sector_t sector, size_t entry_cnt); +bool dir_open (struct inode *, struct dir **); +bool dir_open_root (struct dir **); +void dir_close (struct dir *); +bool dir_lookup (const struct dir *, const char *name, struct inode **); bool dir_add (struct dir *, const char *name, disk_sector_t); bool dir_remove (struct dir *, const char *name); void dir_list (const struct dir *); diff --git a/src/filesys/file.c b/src/filesys/file.c index a0028ec..029d8ce 100644 --- a/src/filesys/file.c +++ b/src/filesys/file.c @@ -1,171 +1,136 @@ #include "filesys/file.h" #include -#include -#include "filesys/directory.h" #include "filesys/inode.h" -#include "filesys/filesys.h" #include "threads/malloc.h" /* An open file. */ struct file { struct inode *inode; /* File's inode. */ - uint8_t *bounce; /* Bounce buffer for reads and writes. */ off_t pos; /* Current position. */ + bool deny_write; /* Has file_deny_write() been called? */ }; -/* Opens and returns the file whose inode is in sector - INODE_SECTOR. Returns a null pointer if unsuccessful. */ +/* 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 (disk_sector_t inode_sector) +file_open (struct inode *inode) { struct file *file = calloc (1, sizeof *file); - if (file == NULL) - return NULL; - - file->inode = inode_open (inode_sector); - file->bounce = malloc (DISK_SECTOR_SIZE); - file->pos = 0; - if (file->inode == NULL || file->bounce == NULL) + if (inode != NULL && file != NULL) { - inode_close (file->inode); - free (file->bounce); - return NULL; + file->inode = inode; + file->pos = 0; + file->deny_write = false; + return file; + } + else + { + inode_close (inode); + free (file); + return NULL; } +} - return file; +/* 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) - return; - - inode_close (file->inode); - free (file->bounce); - free (file); + if (file != NULL) + { + file_allow_write (file); + inode_close (file->inode); + free (file); + } } /* Reads SIZE bytes from FILE into BUFFER, - starting at the file's current position, - and advances the current position. + 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. */ + 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 = file_read_at (file, buffer, size, file->pos); + 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. - The file's current position is unaffected Returns the number of bytes actually read, - which may be less than SIZE if end of file is reached. */ + 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) +file_read_at (struct file *file, void *buffer, off_t size, off_t file_ofs) { - uint8_t *buffer = buffer_; - off_t bytes_read = 0; - - while (size > 0) - { - /* Disk sector to read, starting byte offset within sector. */ - disk_sector_t sector_idx; - int sector_ofs = file_ofs % DISK_SECTOR_SIZE; - - /* Bytes left in file, bytes left in sector, lesser of the two. */ - off_t file_left = inode_length (file->inode) - file_ofs; - int sector_left = DISK_SECTOR_SIZE - sector_ofs; - int min_left = file_left < sector_left ? file_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; - - /* Read sector into bounce buffer, then copy into caller's - buffer. */ - sector_idx = inode_byte_to_sector (file->inode, file_ofs); - disk_read (filesys_disk, sector_idx, file->bounce); - memcpy (buffer + bytes_read, file->bounce + sector_ofs, chunk_size); - - /* Advance. */ - size -= chunk_size; - file_ofs += chunk_size; - bytes_read += chunk_size; - } - - return bytes_read; + return inode_read_at (file->inode, buffer, size, file_ofs); } /* Writes SIZE bytes from BUFFER into FILE, - starting at the file's current position, - and advances the current position. + 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.) */ + 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 = file_write_at (file, buffer, size, file->pos); + 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. - The file's current position is unaffected 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.) */ + not yet implemented.) + The file's current position is unaffected. */ off_t -file_write_at (struct file *file, const void *buffer_, off_t size, +file_write_at (struct file *file, const void *buffer, off_t size, off_t file_ofs) { - const uint8_t *buffer = buffer_; - off_t bytes_written = 0; + return inode_write_at (file->inode, buffer, size, file_ofs); +} - while (size > 0) +/* 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) { - /* Sector to write, starting byte offset within sector. */ - off_t sector_idx; - int sector_ofs = file_ofs % DISK_SECTOR_SIZE; - - /* Bytes left in file, bytes left in sector, lesser of the two. */ - off_t file_left = inode_length (file->inode) - file_ofs; - int sector_left = DISK_SECTOR_SIZE - sector_ofs; - int min_left = file_left < sector_left ? file_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 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. */ - sector_idx = inode_byte_to_sector (file->inode, file_ofs); - if (sector_ofs > 0 || chunk_size < sector_left) - disk_read (filesys_disk, sector_idx, file->bounce); - else - memset (file->bounce, 0, DISK_SECTOR_SIZE); - memcpy (file->bounce + sector_ofs, buffer + bytes_written, chunk_size); - disk_write (filesys_disk, sector_idx, file->bounce); - - /* Advance. */ - size -= chunk_size; - file_ofs += chunk_size; - bytes_written += chunk_size; + file->deny_write = true; + inode_deny_write (file->inode); } +} - return bytes_written; +/* 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. */ @@ -176,14 +141,14 @@ file_length (struct file *file) return inode_length (file->inode); } -/* Sets the current position in FILE to an offset of FILE_OFS - bytes from the start of the file. */ +/* Sets the current position in FILE to NEW_POS bytes from the + start of the file. */ void -file_seek (struct file *file, off_t file_ofs) +file_seek (struct file *file, off_t new_pos) { ASSERT (file != NULL); - ASSERT (file_ofs >= 0); - file->pos = file_ofs; + ASSERT (new_pos >= 0); + file->pos = new_pos; } /* Returns the current position in FILE as a byte offset from the diff --git a/src/filesys/file.h b/src/filesys/file.h index c28201d..dc05332 100644 --- a/src/filesys/file.h +++ b/src/filesys/file.h @@ -1,19 +1,28 @@ #ifndef FILESYS_FILE_H #define FILESYS_FILE_H -#include -#include "devices/disk.h" #include "filesys/off_t.h" -struct file *file_open (disk_sector_t); +struct inode; + +/* Opening and closing files. */ +struct file *file_open (struct inode *); +struct file *file_reopen (struct file *); void file_close (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); -off_t file_length (struct file *); + +/* 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 *); -void file_remove (struct file *); +off_t file_length (struct file *); #endif /* filesys/file.h */ diff --git a/src/filesys/filesys.c b/src/filesys/filesys.c index b77d271..0ca8e9e 100644 --- a/src/filesys/filesys.c +++ b/src/filesys/filesys.c @@ -1,112 +1,16 @@ -/* 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 "filesys/filesys.h" -#include #include #include #include #include "filesys/file.h" +#include "filesys/free-map.h" #include "filesys/inode.h" #include "filesys/directory.h" #include "devices/disk.h" -/* Filesystem. - - For the purposes of the "user processes" and "virtual memory" - assignments (projects 2 and 3), please treat all the code in - the filesys directory as a black box. No changes should be - needed. For those projects, a single lock external to the - filesystem code suffices. - - The filesystem consists of a set of files. Each file has a - header called an `index node' or `inode', represented by - struct inode, that is stored by itself in a single sector (see - inode.h). The header contains the file's length in bytes and - an array that lists the sector numbers for the file's - contents. - - Two files are special. The first special file is the free - map, whose inode is always stored in sector 0 - (FREE_MAP_SECTOR). The free map stores a bitmap (see - lib/bitmap.h) that contains one bit for each sector on the - disk. Each bit that corresponds to a sector within a file is - set to true, and the other bits, which are not part of any - file, are set to false. - - The second special file is the root directory, whose inode is - always stored in sector 1 (ROOT_DIR_SECTOR). The root - directory file stores an array of `struct dir_entry' (see - directory.h), each of which, if it is in use, associates a - filename with the sector of the file's inode. - - The filesystem implemented here has the following limitations: - - - No synchronization. Concurrent accesses will interfere - with one another, so external synchronization is needed. - - - File size is fixed at creation time. Because the root - directory is represented as a file, the number of files - that may be created is also limited. - - - File data is allocated as a single extent, so that - external fragmentation can become a serious problem as a - file system is used over time. - - - No subdirectories. - - - Filenames limited to 14 characters. - - - A system crash mid-operation may corrupt the disk in a way - that cannot be repaired automatically. No `fsck' tool is - provided in any case. - - However one important feature is included: - - - Unix-like semantics for filesys_remove() are implemented. - That is, if a file is open when it is removed, its blocks - are not deallocated and it may still be accessed by the - threads that have it open until the last one closes it. */ - -/* 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. */ - -/* Root directory. */ -#define NUM_DIR_ENTRIES 10 /* Maximum number of directory entries. */ - /* The disk that contains the filesystem. */ struct disk *filesys_disk; -/* The free map and root directory files. - These files are opened by filesys_init() and never closed. */ -struct file *free_map_file, *root_dir_file; - static void do_format (void); /* Initializes the filesystem module. @@ -114,32 +18,27 @@ static void do_format (void); void filesys_init (bool format) { - inode_init (); - filesys_disk = disk_get (0, 1); if (filesys_disk == NULL) PANIC ("hd0:1 (hdb) not present, filesystem initialization failed"); + inode_init (); + free_map_init (); + if (format) do_format (); - - free_map_file = file_open (FREE_MAP_SECTOR); - if (free_map_file == NULL) - PANIC ("can't open free map file"); - root_dir_file = file_open (ROOT_DIR_SECTOR); - if (root_dir_file == NULL) - PANIC ("can't open root dir file"); + + free_map_open (); } /* Shuts down the filesystem module, writing any unwritten data - to disk. - Currently there's nothing to do. You'll need to add code here - when you implement write-behind caching. */ + 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, @@ -147,52 +46,20 @@ filesys_done (void) bool filesys_create (const char *name, off_t initial_size) { - struct dir *dir = NULL; - struct bitmap *free_map = NULL; - disk_sector_t inode_sector; - bool success = false; - - /* Read the root directory. */ - dir = dir_create (NUM_DIR_ENTRIES); - if (dir == NULL) - goto done; - dir_read (dir, root_dir_file); - if (dir_lookup (dir, name, NULL)) - goto done; - - /* Allocate a block for the inode. */ - free_map = bitmap_create (disk_size (filesys_disk)); - if (free_map == NULL) - goto done; - bitmap_read (free_map, free_map_file); - inode_sector = bitmap_scan_and_flip (free_map, 0, 1, false); - if (inode_sector == BITMAP_ERROR) - goto done; - - /* Add the file to the directory. */ - if (!dir_add (dir, name, inode_sector)) - goto done; - - /* Allocate space for the file. */ - if (!inode_create (free_map, inode_sector, initial_size)) - goto done; - - /* Write everything back. */ - dir_write (dir, root_dir_file); - bitmap_write (free_map, free_map_file); - - success = true; - - /* Clean up. */ - done: - bitmap_destroy (free_map); - dir_destroy (dir); + struct dir *dir; + disk_sector_t inode_sector = 0; + bool success = (dir_open_root (&dir) + && 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 a file named NAME and initializes FILE for usage with - the file_*() functions declared in file.h. +/* 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, @@ -200,22 +67,14 @@ filesys_create (const char *name, off_t initial_size) struct file * filesys_open (const char *name) { - struct dir *dir = NULL; - struct file *file = NULL; - disk_sector_t inode_sector; - - dir = dir_create (NUM_DIR_ENTRIES); - if (dir == NULL) - goto done; - - dir_read (dir, root_dir_file); - if (dir_lookup (dir, name, &inode_sector)) - file = file_open (inode_sector); + struct dir *dir; + struct inode *inode = NULL; - done: - dir_destroy (dir); + if (dir_open_root (&dir)) + dir_lookup (dir, name, &inode); + dir_close (dir); - return file; + return file_open (inode); } /* Deletes the file named NAME. @@ -226,35 +85,9 @@ bool filesys_remove (const char *name) { struct dir *dir = NULL; - struct inode *inode; - disk_sector_t inode_sector; - bool success = false; - - /* Read the root directory. */ - dir = dir_create (NUM_DIR_ENTRIES); - if (dir == NULL) - goto done; - dir_read (dir, root_dir_file); - if (!dir_lookup (dir, name, &inode_sector)) - goto done; - - /* Open the inode and delete it. */ - inode = inode_open (inode_sector); - if (inode == NULL) - goto done; - inode_remove (inode); - inode_close (inode); - - /* Remove file from root directory and write directory back to - disk. */ - dir_remove (dir, name); - dir_write (dir, root_dir_file); - - success = true; - - /* Clean up. */ - done: - dir_destroy (dir); + bool success = (dir_open_root (&dir) + && dir_remove (dir, name)); + dir_close (dir); return success; } @@ -266,16 +99,15 @@ filesys_remove (const char *name) bool filesys_list (void) { - struct dir *dir = dir_create (NUM_DIR_ENTRIES); - if (dir == NULL) - return false; - dir_read (dir, root_dir_file); - dir_list (dir); - dir_destroy (dir); + struct dir *dir = NULL; + bool success = dir_open_root (&dir); + if (success) + dir_list (dir); + dir_close (dir); - return true; + return success; } - + static void must_succeed_function (int, bool) NO_INLINE; #define MUST_SUCCEED(EXPR) must_succeed_function (__LINE__, EXPR) @@ -325,52 +157,6 @@ filesys_self_test (void) printf ("filesys: self test ok\n"); } - -/* Formats the filesystem. */ -static void -do_format (void) -{ - struct bitmap *free_map; - struct dir *dir; - - printf ("Formatting filesystem..."); - - /* Create the initial bitmap and reserve sectors for the - free map and root directory inodes. */ - free_map = bitmap_create (disk_size (filesys_disk)); - if (free_map == NULL) - PANIC ("bitmap creation failed--disk is too large"); - bitmap_mark (free_map, FREE_MAP_SECTOR); - bitmap_mark (free_map, ROOT_DIR_SECTOR); - - /* Allocate free map and root dir files. */ - if (!inode_create (free_map, FREE_MAP_SECTOR, bitmap_file_size (free_map))) - PANIC ("free map creation failed--disk is too large"); - if (!inode_create (free_map, ROOT_DIR_SECTOR, dir_size (NUM_DIR_ENTRIES))) - PANIC ("root directory creation failed"); - - /* Write out the free map now that we have space reserved - for it. */ - free_map_file = file_open (FREE_MAP_SECTOR); - if (free_map_file == NULL) - PANIC ("can't open free map file"); - bitmap_write (free_map, free_map_file); - bitmap_destroy (free_map); - file_close (free_map_file); - - /* Write out the root directory in the same way. */ - root_dir_file = file_open (ROOT_DIR_SECTOR); - if (root_dir_file == NULL) - PANIC ("can't open root directory"); - dir = dir_create (NUM_DIR_ENTRIES); - if (dir == NULL) - PANIC ("can't initialize root directory"); - dir_write (dir, root_dir_file); - dir_destroy (dir); - file_close (root_dir_file); - - printf ("done.\n"); -} /* If SUCCESS is false, panics with an error complaining about LINE_NO. */ @@ -380,3 +166,15 @@ must_succeed_function (int line_no, bool success) if (!success) PANIC ("filesys_self_test: operation failed on line %d", line_no); } + +/* Formats the filesystem. */ +static void +do_format (void) +{ + printf ("Formatting filesystem..."); + free_map_create (); + if (!dir_create (ROOT_DIR_SECTOR, 16)) + PANIC ("root directory creation failed"); + free_map_close (); + printf ("done.\n"); +} diff --git a/src/filesys/filesys.h b/src/filesys/filesys.h index 064c577..e87cbea 100644 --- a/src/filesys/filesys.h +++ b/src/filesys/filesys.h @@ -4,18 +4,19 @@ #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. */ + /* Disk used for filesystem. */ extern struct disk *filesys_disk; -/* The free map file, opened by filesys_init() and never - closed. */ -extern struct file *free_map_file; - 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); +bool filesys_chdir (const char *name); bool filesys_list (void); void filesys_self_test (void); diff --git a/src/filesys/free-map.c b/src/filesys/free-map.c new file mode 100644 index 0000000..1cd9175 --- /dev/null +++ b/src/filesys/free-map.c @@ -0,0 +1,84 @@ +#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 disk sector. */ + +/* Initializes the free map. */ +void +free_map_init (void) +{ + free_map = bitmap_create (disk_size (filesys_disk)); + if (free_map == NULL) + PANIC ("bitmap creation failed--disk 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 all sectors were + available. */ +bool +free_map_allocate (size_t cnt, disk_sector_t *sectorp) +{ + disk_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 (disk_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/src/filesys/free-map.h b/src/filesys/free-map.h new file mode 100644 index 0000000..ce08f5c --- /dev/null +++ b/src/filesys/free-map.h @@ -0,0 +1,17 @@ +#ifndef FILESYS_FREE_MAP_H +#define FILESYS_FREE_MAP_H + +#include +#include +#include "devices/disk.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, disk_sector_t *); +void free_map_release (disk_sector_t, size_t); + +#endif /* filesys/free-map.h */ diff --git a/src/filesys/fsutil.c b/src/filesys/fsutil.c index 81466c2..8ef5d98 100644 --- a/src/filesys/fsutil.c +++ b/src/filesys/fsutil.c @@ -1,48 +1,105 @@ #include "filesys/fsutil.h" #include -#include #include #include #include #include "filesys/file.h" #include "filesys/filesys.h" +#include "devices/disk.h" #include "threads/mmu.h" +#include "threads/malloc.h" #include "threads/palloc.h" -/* Destination filename and size for copy-in operations. */ -char *fsutil_copyin_file; -int fsutil_copyin_size; +/* List files in the root directory. */ +void +fsutil_ls (char **argv UNUSED) +{ + printf ("Files in the root directory:\n"); + filesys_list (); + printf ("End of listing.\n"); +} -/* Source filename for copy-out operations. */ -char *fsutil_copyout_file; +/* Prints the contents of file ARGV[1] to the system console as + hex and ASCII. */ +void +fsutil_cat (char **argv) +{ + const char *filename = argv[1]; + + struct file *file; + char *buffer; -/* Name of a file print to print to console. */ -char *fsutil_print_file; + printf ("Printing '%s' to the console...\n", filename); + file = filesys_open (filename); + if (file == NULL) + PANIC ("%s: open failed", filename); + 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; -/* Name of a file to delete. */ -char *fsutil_remove_file; + hex_dump (pos, buffer, n, true); + } + palloc_free_page (buffer); + file_close (file); +} + +/* Deletes file ARGV[1]. */ +void +fsutil_rm (char **argv) +{ + const char *filename = argv[1]; + + printf ("Deleting '%s'...\n", filename); + if (!filesys_remove (filename)) + PANIC ("%s: delete failed\n", filename); +} + +/* Copies from the "scratch" disk, hdc or hd1:0 to file ARGV[1] + in the filesystem. -/* List all files in the filesystem to the system console? */ -bool fsutil_list_files; + The current sector on the scratch disk must begin with the + string "PUT\0" followed by a 32-bit little-endian integer + indicating the file size in bytes. Subsequent sectors hold + the file content. -/* Copies from the "scratch" disk, hdc or hd1:0, - to a file named FILENAME in the filesystem. - The file will be SIZE bytes in length. */ -static void -copy_in (const char *filename, off_t size) + The first call to this function will read starting at the + beginning of the scratch disk. Later calls advance across the + disk. This disk position is independent of that used for + fsutil_get(), so all `put's should precede all `get's. */ +void +fsutil_put (char **argv) { + static disk_sector_t sector = 0; + + const char *filename = argv[1]; struct disk *src; struct file *dst; - disk_sector_t sector; + off_t size; void *buffer; - /* Open source disk. */ + printf ("Putting '%s' into the file system...\n", filename); + + /* Allocate buffer. */ + buffer = malloc (DISK_SECTOR_SIZE); + if (buffer == NULL) + PANIC ("couldn't allocate buffer"); + + /* Open source disk and read file size. */ src = disk_get (1, 0); if (src == NULL) PANIC ("couldn't open source disk (hdc or hd1:0)"); - if (size > (off_t) disk_size (src) * DISK_SECTOR_SIZE) - PANIC ("source disk is too small for %lld-byte file", - (unsigned long long) size); + + /* Read file size. */ + disk_read (src, sector++, buffer); + if (memcmp (buffer, "PUT", 4)) + PANIC ("%s: missing PUT signature on scratch disk", filename); + size = ((int32_t *) buffer)[1]; + if (size < 0) + PANIC ("%s: invalid file size %d", filename, size); /* Create destination file. */ if (!filesys_create (filename, size)) @@ -52,36 +109,49 @@ copy_in (const char *filename, off_t size) PANIC ("%s: open failed", filename); /* Do copy. */ - buffer = palloc_get_page (PAL_ASSERT); - sector = 0; while (size > 0) { int chunk_size = size > DISK_SECTOR_SIZE ? DISK_SECTOR_SIZE : size; disk_read (src, sector++, buffer); if (file_write (dst, buffer, chunk_size) != chunk_size) - PANIC ("%s: write failed with %lld bytes unwritten", - filename, (unsigned long long) size); + PANIC ("%s: write failed with %"PROTd" bytes unwritten", + filename, size); size -= chunk_size; } - palloc_free_page (buffer); + /* Finish up. */ file_close (dst); + free (buffer); } -/* Copies FILENAME from the file system to the scratch disk. - The first four bytes of the first sector in the disk - receive the file's size in bytes as a little-endian integer. - The second and subsequent sectors receive the file's data. */ -static void -copy_out (const char *filename) +/* Copies file FILENAME from the file system to the scratch disk. + + The current sector on the scratch disk will receive "GET\0" + followed by the file's size in bytes as a 32-bit, + little-endian integer. Subsequent sectors receive the file's + data. + + The first call to this function will write starting at the + beginning of the scratch disk. Later calls advance across the + disk. This disk position is independent of that used for + fsutil_put(), so all `put's should precede all `get's. */ +void +fsutil_get (char **argv) { + static disk_sector_t sector = 0; + + const char *filename = argv[1]; void *buffer; struct file *src; struct disk *dst; off_t size; - disk_sector_t sector; - buffer = palloc_get_page (PAL_ASSERT | PAL_ZERO); + printf ("Getting '%s' from the file system...\n", filename); + + /* Allocate buffer. */ + buffer = malloc (DISK_SECTOR_SIZE); + if (buffer == NULL) + PANIC ("couldn't allocate buffer"); /* Open source file. */ src = filesys_open (filename); @@ -93,76 +163,27 @@ copy_out (const char *filename) dst = disk_get (1, 0); if (dst == NULL) PANIC ("couldn't open target disk (hdc or hd1:0)"); - if (size + DISK_SECTOR_SIZE > (off_t) disk_size (dst) * DISK_SECTOR_SIZE) - PANIC ("target disk is too small for %lld-byte file", - (unsigned long long) size); /* Write size to sector 0. */ - *(uint32_t *) buffer = size; - disk_write (dst, 0, buffer); + memset (buffer, 0, DISK_SECTOR_SIZE); + memcpy (buffer, "GET", 4); + ((int32_t *) buffer)[1] = size; + disk_write (dst, sector++, buffer); /* Do copy. */ - sector = 1; while (size > 0) { int chunk_size = size > DISK_SECTOR_SIZE ? DISK_SECTOR_SIZE : size; + if (sector >= disk_size (dst)) + PANIC ("%s: out of space on scratch disk", filename); if (file_read (src, buffer, chunk_size) != chunk_size) - PANIC ("%s: read failed with %lld bytes unread", - filename, (unsigned long long) size); + PANIC ("%s: read failed with %"PROTd" bytes unread", filename, size); + memset (buffer + chunk_size, 0, DISK_SECTOR_SIZE - chunk_size); disk_write (dst, sector++, buffer); size -= chunk_size; } - palloc_free_page (buffer); + /* Finish up. */ file_close (src); -} - -/* Executes the filesystem operations described by the variables - declared in fsutil.h. */ -void -fsutil_run (void) -{ - if (fsutil_copyin_file != NULL) - copy_in (fsutil_copyin_file, fsutil_copyin_size); - - if (fsutil_copyout_file != NULL) - copy_out (fsutil_copyout_file); - - if (fsutil_print_file != NULL) - fsutil_print (fsutil_print_file); - - if (fsutil_remove_file != NULL) - { - if (filesys_remove (fsutil_remove_file)) - printf ("%s: removed\n", fsutil_remove_file); - else - PANIC ("%s: remove failed\n", fsutil_remove_file); - } - - if (fsutil_list_files) - filesys_list (); -} - -/* Prints the contents of file FILENAME to the system console as - hex and ASCII. */ -void -fsutil_print (const char *filename) -{ - struct file *file; - char *buffer; - - file = filesys_open (filename); - if (file == NULL) - PANIC ("%s: open failed", filename); - buffer = palloc_get_page (PAL_ASSERT); - for (;;) - { - off_t n = file_read (file, buffer, PGSIZE); - if (n == 0) - break; - - hex_dump (0, buffer, n, true); - } - palloc_free_page (buffer); - file_close (file); + free (buffer); } diff --git a/src/filesys/fsutil.h b/src/filesys/fsutil.h index 307e37d..abebfe2 100644 --- a/src/filesys/fsutil.h +++ b/src/filesys/fsutil.h @@ -1,16 +1,10 @@ #ifndef FILESYS_FSUTIL_H #define FILESYS_FSUTIL_H -#include - -extern char *fsutil_copyin_file; -extern int fsutil_copyin_size; -extern char *fsutil_copyout_file; -extern char *fsutil_print_file; -extern char *fsutil_remove_file; -extern bool fsutil_list_files; - -void fsutil_run (void); -void fsutil_print (const char *filename); +void fsutil_ls (char **argv); +void fsutil_cat (char **argv); +void fsutil_rm (char **argv); +void fsutil_put (char **argv); +void fsutil_get (char **argv); #endif /* filesys/fsutil.h */ diff --git a/src/filesys/inode.c b/src/filesys/inode.c index 749af37..bd68167 100644 --- a/src/filesys/inode.c +++ b/src/filesys/inode.c @@ -1,22 +1,26 @@ #include "filesys/inode.h" -#include #include #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 DISK_SECTOR_SIZE bytes long. */ struct inode_disk { + disk_sector_t start; /* First data sector. */ off_t length; /* File size in bytes. */ - disk_sector_t start; /* Starting sector. */ - uint32_t unused[126]; /* Unused padding. */ + unsigned magic; /* Magic number. */ + uint32_t unused[125]; /* Not used. */ }; -/* Returns the number of sectors to allocate for a file SIZE +/* Returns the number of sectors to allocate for an inode SIZE bytes long. */ static inline size_t bytes_to_sectors (off_t size) @@ -31,15 +35,28 @@ struct inode disk_sector_t sector; /* Sector number of disk location. */ int open_cnt; /* Number of openers. */ bool removed; /* True if deleted, false otherwise. */ - struct inode_disk data; /* On-disk data. */ + int deny_write_cnt; /* 0: writes ok, >0: deny writes. */ + struct inode_disk data; /* Inode content. */ }; +/* Returns the disk sector that contains byte offset POS within + INODE. + Returns -1 if INODE does not contain data for a byte at offset + POS. */ +static disk_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 / DISK_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; -static void deallocate_inode (const struct inode *); - /* Initializes the inode module. */ void inode_init (void) @@ -47,42 +64,42 @@ inode_init (void) list_init (&open_inodes); } -/* Initializes an inode for a file LENGTH bytes in size and +/* Initializes an inode with LENGTH bytes of data and writes the new inode to sector SECTOR on the file system - disk. Allocates sectors for the file from FREE_MAP. + disk. Returns true if successful. - Returns false if memory or disk allocation fails. FREE_MAP - may be modified regardless. */ + Returns false if memory or disk allocation fails. */ bool -inode_create (struct bitmap *free_map, disk_sector_t sector, off_t length) +inode_create (disk_sector_t sector, off_t length) { - static const char zero_sector[DISK_SECTOR_SIZE]; - struct inode_disk *idx; - size_t start; - size_t i; + struct inode_disk *disk_inode = NULL; + bool success = false; - ASSERT (free_map != NULL); ASSERT (length >= 0); + ASSERT (sizeof *disk_inode == DISK_SECTOR_SIZE); - /* Allocate sectors. */ - start = bitmap_scan_and_flip (free_map, 0, bytes_to_sectors (length), false); - if (start == BITMAP_ERROR) - return false; - - /* Create inode. */ - idx = calloc (sizeof *idx, 1); - if (idx == NULL) - return false; - idx->length = length; - idx->start = start; - - /* Commit to disk. */ - disk_write (filesys_disk, sector, idx); - for (i = 0; i < bytes_to_sectors (length); i++) - disk_write (filesys_disk, idx->start + i, zero_sector); - - free (idx); - return true; + 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)) + { + disk_write (filesys_disk, sector, disk_inode); + if (sectors > 0) + { + static char zeros[DISK_SECTOR_SIZE]; + size_t i; + + for (i = 0; i < sectors; i++) + disk_write (filesys_disk, disk_inode->start + i, zeros); + } + success = true; + } + free (disk_inode); + } + return success; } /* Reads an inode from SECTOR @@ -92,119 +109,224 @@ struct inode * inode_open (disk_sector_t sector) { struct list_elem *e; - struct inode *idx; + struct inode *inode; - /* Check whether this inode is already open. - (A hash table would be better, but the Pintos base code - avoids using the hash table so that users are free to modify - it at will.) */ + /* Check whether this inode is already open. */ for (e = list_begin (&open_inodes); e != list_end (&open_inodes); e = list_next (e)) { - idx = list_entry (e, struct inode, elem); - if (idx->sector == sector) + inode = list_entry (e, struct inode, elem); + if (inode->sector == sector) { - idx->open_cnt++; - return idx; + inode_reopen (inode); + return inode; } } /* Allocate memory. */ - idx = calloc (1, sizeof *idx); - if (idx == NULL) + inode = malloc (sizeof *inode); + if (inode == NULL) return NULL; /* Initialize. */ - list_push_front (&open_inodes, &idx->elem); - idx->sector = sector; - idx->open_cnt = 1; - idx->removed = false; - - /* Read from disk. */ - ASSERT (sizeof idx->data == DISK_SECTOR_SIZE); - disk_read (filesys_disk, sector, &idx->data); + list_push_front (&open_inodes, &inode->elem); + inode->sector = sector; + inode->open_cnt = 1; + inode->deny_write_cnt = 0; + inode->removed = false; + disk_read (filesys_disk, inode->sector, &inode->data); + return inode; +} - return idx; +/* Reopens and returns INODE. */ +struct inode * +inode_reopen (struct inode *inode) +{ + if (inode != NULL) + inode->open_cnt++; + return inode; } -/* Closes inode IDX and writes it to disk. - If this was the last reference to IDX, frees its memory. - If IDX was also a removed inode, frees its blocks. */ +/* 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 *idx) +inode_close (struct inode *inode) { /* Ignore null pointer. */ - if (idx == NULL) + if (inode == NULL) return; /* Release resources if this was the last opener. */ - if (--idx->open_cnt == 0) + if (--inode->open_cnt == 0) { + /* Remove from inode list and release lock. */ + list_remove (&inode->elem); + /* Deallocate blocks if removed. */ - if (idx->removed) - deallocate_inode (idx); + if (inode->removed) + free_map_release (inode->sector, + bytes_to_sectors (inode->data.length)); - /* Remove from inode list and free memory. */ - list_remove (&idx->elem); - free (idx); + free (inode); } } -/* Deallocates the blocks allocated for IDX. */ -static void -deallocate_inode (const struct inode *idx) +/* 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) { - struct bitmap *free_map = bitmap_create (disk_size (filesys_disk)); - if (free_map != NULL) + uint8_t *buffer = buffer_; + off_t bytes_read = 0; + uint8_t *bounce = NULL; + + while (size > 0) { - bitmap_read (free_map, free_map_file); - bitmap_reset (free_map, idx->sector); - bitmap_set_multiple (free_map, idx->data.start, - bytes_to_sectors (idx->data.length), false); - bitmap_write (free_map, free_map_file); - bitmap_destroy (free_map); + /* Disk sector to read, starting byte offset within sector. */ + disk_sector_t sector_idx = byte_to_sector (inode, offset); + int sector_ofs = offset % DISK_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 = DISK_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 == DISK_SECTOR_SIZE) + { + /* Read full sector directly into caller's buffer. */ + disk_read (filesys_disk, sector_idx, buffer + bytes_read); + } + else + { + /* Read sector into bounce buffer, then partially copy + into caller's buffer. */ + if (bounce == NULL) + { + bounce = malloc (DISK_SECTOR_SIZE); + if (bounce == NULL) + break; + } + disk_read (filesys_disk, sector_idx, bounce); + memcpy (buffer + bytes_read, bounce + sector_ofs, chunk_size); + } + + /* Advance. */ + size -= chunk_size; + offset += chunk_size; + bytes_read += chunk_size; } - else - printf ("inode_close(): can't free blocks"); + free (bounce); + + return bytes_read; } -/* Marks IDX to be deleted when it is closed by the last caller who - has it open. */ -void -inode_remove (struct inode *idx) +/* 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) { - ASSERT (idx != NULL); - idx->removed = true; + 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. */ + off_t sector_idx = byte_to_sector (inode, offset); + int sector_ofs = offset % DISK_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 = DISK_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 == DISK_SECTOR_SIZE) + { + /* Write full sector directly to disk. */ + disk_write (filesys_disk, sector_idx, buffer + bytes_written); + } + else + { + /* We need a bounce buffer. */ + if (bounce == NULL) + { + bounce = malloc (DISK_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) + disk_read (filesys_disk, sector_idx, bounce); + else + memset (bounce, 0, DISK_SECTOR_SIZE); + memcpy (bounce + sector_ofs, buffer + bytes_written, chunk_size); + disk_write (filesys_disk, sector_idx, bounce); + } + + /* Advance. */ + size -= chunk_size; + offset += chunk_size; + bytes_written += chunk_size; + } + free (bounce); + + return bytes_written; } -/* Returns the disk sector that contains byte offset POS within - the file with inode IDX. - Returns -1 if IDX does not contain data for a byte at offset - POS. */ -disk_sector_t -inode_byte_to_sector (const struct inode *idx, off_t pos) +/* Disables writes to INODE. + May be called at most once per inode opener. */ +void +inode_deny_write (struct inode *inode) { - ASSERT (idx != NULL); - ASSERT (pos < idx->data.length); - return idx->data.start + pos / DISK_SECTOR_SIZE; + inode->deny_write_cnt++; + ASSERT (inode->deny_write_cnt <= inode->open_cnt); } -/* Returns the length, in bytes, of the file with inode IDX. */ -off_t -inode_length (const struct inode *idx) +/* 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 (idx != NULL); - return idx->data.length; + ASSERT (inode->deny_write_cnt > 0); + ASSERT (inode->deny_write_cnt <= inode->open_cnt); + inode->deny_write_cnt--; } -/* Prints a representation of IDX to the system console. */ -void -inode_print (const struct inode *idx) +/* Returns the length, in bytes, of INODE's data. */ +off_t +inode_length (const struct inode *inode) { - ASSERT (idx != NULL); - printf ("Inode %"PRDSNu": %"PRDSNu" bytes, " - "%zu sectors starting at %"PRDSNu"\n", - idx->sector, idx->data.length, - (size_t) DIV_ROUND_UP (idx->data.length, DISK_SECTOR_SIZE), - idx->data.start); + return inode->data.length; } diff --git a/src/filesys/inode.h b/src/filesys/inode.h index 296ffd1..e8aac13 100644 --- a/src/filesys/inode.h +++ b/src/filesys/inode.h @@ -8,12 +8,15 @@ struct bitmap; void inode_init (void); -bool inode_create (struct bitmap *, disk_sector_t, off_t); +bool inode_create (disk_sector_t, off_t); struct inode *inode_open (disk_sector_t); +struct inode *inode_reopen (struct inode *); void inode_close (struct inode *); void inode_remove (struct inode *); -disk_sector_t inode_byte_to_sector (const struct inode *, off_t); +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 *); -void inode_print (const struct inode *); #endif /* filesys/inode.h */ diff --git a/src/filesys/off_t.h b/src/filesys/off_t.h index 55ff174..9caff4d 100644 --- a/src/filesys/off_t.h +++ b/src/filesys/off_t.h @@ -8,4 +8,8 @@ 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/src/lib/debug.c b/src/lib/debug.c index 66e9119..6d7c9e1 100644 --- a/src/lib/debug.c +++ b/src/lib/debug.c @@ -4,53 +4,6 @@ #include #include #include -#ifdef KERNEL -#include "threads/init.h" -#include "threads/interrupt.h" -#include "devices/serial.h" -#else -#include -#endif - -/* Halts the OS or 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; - -#ifdef KERNEL - intr_disable (); -#endif - -#ifdef KERNEL - printf ("Kernel PANIC at %s:%d in %s(): ", file, line, function); -#else - printf ("User process panic at %s:%d in %s(): ", file, line, function); -#endif - - va_start (args, message); - vprintf (message, args); - printf ("\n"); - va_end (args); - - debug_backtrace (); - - 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"); - -#ifdef KERNEL - serial_flush (); - if (power_off_when_done) - power_off (); - for (;;); -#else - exit (1); -#endif -} /* Prints the call stack, that is, a list of addresses, one in each of the functions we are nested within. gdb or addr2line @@ -59,6 +12,7 @@ debug_panic (const char *file, int line, const char *function, void debug_backtrace (void) { + static bool explained; void **frame; printf ("Call stack:"); @@ -67,4 +21,12 @@ debug_backtrace (void) 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/src/lib/debug.h b/src/lib/debug.h index 3ab800b..3218ab6 100644 --- a/src/lib/debug.h +++ b/src/lib/debug.h @@ -13,7 +13,6 @@ function name, plus a user-specific message. */ #define PANIC(...) debug_panic (__FILE__, __LINE__, __func__, __VA_ARGS__) -void debug_enable (char *classes); void debug_panic (const char *file, int line, const char *function, const char *message, ...) PRINTF_FORMAT (4, 5) NO_RETURN; void debug_backtrace (void); diff --git a/src/lib/kernel/bitmap.c b/src/lib/kernel/bitmap.c index abbb85d..df23cc3 100644 --- a/src/lib/kernel/bitmap.c +++ b/src/lib/kernel/bitmap.c @@ -69,6 +69,8 @@ last_mask (const struct bitmap *b) return last_bits ? ((elem_type) 1 << last_bits) - 1 : (elem_type) -1; } +/* Creation and destruction. */ + /* Initializes B to be a bitmap of BIT_CNT bits and sets all of its bits to false. Returns true if success, false if memory allocation @@ -91,6 +93,30 @@ bitmap_create (size_t bit_cnt) 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_preallocated(). */ @@ -103,6 +129,8 @@ bitmap_destroy (struct bitmap *b) free (b); } } + +/* Bitmap size. */ /* Returns the number of bits in B. */ size_t @@ -110,6 +138,8 @@ 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 @@ -123,29 +153,6 @@ bitmap_set (struct bitmap *b, size_t idx, bool value) bitmap_reset (b, idx); } -/* 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); -} - /* Atomically sets the bit numbered BIT_IDX in B to true. */ void bitmap_mark (struct bitmap *b, size_t bit_idx) @@ -195,59 +202,30 @@ bitmap_test (const struct bitmap *b, size_t idx) ASSERT (idx < b->bit_cnt); return (b->bits[elem_idx (idx)] & bit_mask (idx)) != 0; } + +/* Setting and testing multiple bits. */ -/* Returns true if any of the CNT bits starting at START is set - to VALUE. */ -static bool -contains (const struct bitmap *b, size_t start, size_t cnt, bool value) +/* Sets all bits in B to VALUE. */ +void +bitmap_set_all (struct bitmap *b, 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; + bitmap_set_multiple (b, 0, bitmap_size (b), value); } -/* 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) +/* 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); - if (cnt <= b->bit_cnt) - { - size_t last = b->bit_cnt - cnt; - size_t i; - for (i = start; i <= last; i++) - if (!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; + for (i = 0; i < cnt; i++) + bitmap_set (b, start + i, value); } /* Returns the number of bits in B between START and START + CNT, @@ -268,12 +246,29 @@ bitmap_count (const struct bitmap *b, size_t start, size_t cnt, bool value) 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 contains (b, start, cnt, true); + return bitmap_contains (b, start, cnt, true); } /* Returns true if no bits in B between START and START + CNT, @@ -281,7 +276,7 @@ bitmap_any (const struct bitmap *b, size_t start, size_t cnt) bool bitmap_none (const struct bitmap *b, size_t start, size_t cnt) { - return !contains (b, start, cnt, true); + return !bitmap_contains (b, start, cnt, true); } /* Returns true if every bit in B between START and START + CNT, @@ -289,8 +284,49 @@ bitmap_none (const struct bitmap *b, size_t start, size_t cnt) bool bitmap_all (const struct bitmap *b, size_t start, size_t cnt) { - return !contains (b, start, cnt, false); + 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. */ @@ -300,24 +336,32 @@ bitmap_file_size (const struct bitmap *b) return byte_cnt (b->bit_cnt); } -/* Reads FILE into B, ignoring errors. */ -void +/* 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) { - file_read_at (file, b->bits, byte_cnt (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 FILE to B, ignoring errors. */ -void +/* Writes B to FILE. Return true if successful, false + otherwise. */ +bool bitmap_write (const struct bitmap *b, struct file *file) { - file_write_at (file, b->bits, byte_cnt (b->bit_cnt), 0); + 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 @@ -326,27 +370,3 @@ bitmap_dump (const struct bitmap *b) hex_dump (0, b->bits, byte_cnt (b->bit_cnt), false); } -/* Returns the number of bytes required to accomodate a bitmap - with BIT_CNT bits. */ -size_t -bitmap_needed_bytes (size_t bit_cnt) -{ - return sizeof (struct bitmap) + byte_cnt (bit_cnt); -} - -/* 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_preallocated (size_t bit_cnt, void *block, size_t block_size) -{ - struct bitmap *b = block; - - ASSERT (block_size >= bitmap_needed_bytes (bit_cnt)); - - b->bit_cnt = bit_cnt; - b->bits = (elem_type *) (b + 1); - bitmap_set_all (b, false); - return b; -} - diff --git a/src/lib/kernel/bitmap.h b/src/lib/kernel/bitmap.h index 1c66c2a..a50593c 100644 --- a/src/lib/kernel/bitmap.h +++ b/src/lib/kernel/bitmap.h @@ -3,45 +3,49 @@ #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_set_all (struct bitmap *, bool); -void bitmap_set_multiple (struct bitmap *, size_t start, size_t cnt, 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); -#define BITMAP_ERROR ((size_t) -1) -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); - +/* 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 *); -void bitmap_read (struct bitmap *, struct file *); -void bitmap_write (const struct bitmap *, struct file *); +bool bitmap_read (struct bitmap *, struct file *); +bool bitmap_write (const struct bitmap *, struct file *); #endif +/* Debugging. */ void bitmap_dump (const struct bitmap *); -size_t bitmap_needed_bytes (size_t bit_cnt); -struct bitmap *bitmap_create_preallocated (size_t bit_cnt, - void *, size_t byte_cnt); - #endif /* lib/kernel/bitmap.h */ diff --git a/src/lib/kernel/console.c b/src/lib/kernel/console.c index d09801d..865b13b 100644 --- a/src/lib/kernel/console.c +++ b/src/lib/kernel/console.c @@ -49,7 +49,7 @@ static int64_t write_cnt; void console_init (void) { - lock_init (&console_lock, "console"); + lock_init (&console_lock); } /* Prints console statistics. */ @@ -85,6 +85,14 @@ release_console (void) } } +/* Returns true if the current thread has the console lock, + false otherwise. */ +static bool +console_locked_by_current_thread (void) +{ + return intr_context () || 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. */ @@ -150,6 +158,7 @@ vprintf_helper (char c, void *char_cnt_) static void putchar_unlocked (uint8_t c) { + ASSERT (console_locked_by_current_thread ()); write_cnt++; serial_putc (c); vga_putc (c); diff --git a/src/lib/kernel/debug.c b/src/lib/kernel/debug.c new file mode 100644 index 0000000..f6fb9dd --- /dev/null +++ b/src/lib/kernel/debug.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include +#include "threads/init.h" +#include "threads/interrupt.h" +#include "devices/serial.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 (); + + 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 (); + if (power_off_when_done) + power_off (); + for (;;); +} diff --git a/src/lib/kernel/list.c b/src/lib/kernel/list.c index 1a1e24d..0b37f33 100644 --- a/src/lib/kernel/list.c +++ b/src/lib/kernel/list.c @@ -31,6 +31,9 @@ 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) diff --git a/src/lib/kernel/stdio.h b/src/lib/kernel/stdio.h new file mode 100644 index 0000000..3e5bae9 --- /dev/null +++ b/src/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/src/lib/stdio.h b/src/lib/stdio.h index 31be6d8..8288ff0 100644 --- a/src/lib/stdio.h +++ b/src/lib/stdio.h @@ -7,6 +7,10 @@ #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 @@ -20,13 +24,6 @@ int putchar (int); int puts (const char *); /* Nonstandard functions. */ -#ifdef KERNEL -void putbuf (const char *, size_t); -#endif -#ifdef USER -int hprintf (int, const char *, ...) PRINTF_FORMAT (2, 3); -int vhprintf (int, const char *, va_list) PRINTF_FORMAT (2, 0); -#endif void hex_dump (uintptr_t ofs, const void *, size_t size, bool ascii); /* Internal functions. */ diff --git a/src/lib/string.c b/src/lib/string.c index 89fa1f0..01e4ecb 100644 --- a/src/lib/string.c +++ b/src/lib/string.c @@ -1,8 +1,4 @@ #include - -#ifdef KERNEL -#define NDEBUG -#endif #include /* Copies SIZE bytes from SRC to DST, which must not overlap. diff --git a/src/lib/user/debug.c b/src/lib/user/debug.c new file mode 100644 index 0000000..6b71fc6 --- /dev/null +++ b/src/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/src/lib/user/normal.lds b/src/lib/user/normal.lds index e4ca5db..cc6d6c0 100644 --- a/src/lib/user/normal.lds +++ b/src/lib/user/normal.lds @@ -5,6 +5,7 @@ ENTRY(_start) SECTIONS { /* Read-only sections, merged into text segment: */ + __executable_start = 0x08048000 + SIZEOF_HEADERS; . = 0x08048000 + SIZEOF_HEADERS; .text : { *(.text) } = 0x90 .rodata : { *(.rodata) } diff --git a/src/lib/user/stdio.h b/src/lib/user/stdio.h new file mode 100644 index 0000000..b9f3cc6 --- /dev/null +++ b/src/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/src/lib/user/syscall.c b/src/lib/user/syscall.c index 078d796..6cc02d1 100644 --- a/src/lib/user/syscall.c +++ b/src/lib/user/syscall.c @@ -147,10 +147,10 @@ mmap (int fd, void *addr) return syscall2 (SYS_mmap, fd, addr); } -bool +void munmap (mapid_t mapid) { - return syscall1 (SYS_munmap, mapid); + syscall1 (SYS_munmap, mapid); } bool diff --git a/src/lib/user/syscall.h b/src/lib/user/syscall.h index c294108..d03eae1 100644 --- a/src/lib/user/syscall.h +++ b/src/lib/user/syscall.h @@ -24,7 +24,7 @@ void seek (int fd, unsigned position); unsigned tell (int fd); void close (int fd); mapid_t mmap (int fd, void *addr); -bool munmap (mapid_t); +void munmap (mapid_t); bool chdir (const char *dir); bool mkdir (const char *dir); void lsdir (void); diff --git a/src/misc/TODO b/src/misc/TODO deleted file mode 100644 index 60dacf9..0000000 --- a/src/misc/TODO +++ /dev/null @@ -1,39 +0,0 @@ -* Cygwin support: - - PE linker scripts? Doesn't seem to support ELF ones. - - .S files need _ prefixes on symbols. - -* Filesystem dumps should include filehdrs? - -* Cross-compile notes: - -PINTOSROOT=$HOME/private/pintos - -cd /tmp -bzcat ~/binutils-2.15.tar.bz2 | tar x -tar xzf ~/newlib-1.12.0.tar.gz -bzcat ~/gcc-core-3.3.5.tar.bz2 | tar x -tar xzf ~/gdb-6.2.1.tar.gz - -PATH=$PATH:/usr/class/cs140/i386/bin - -cd /tmp/binutils-2.15 -mkdir i386 -cd i386 -../configure --target=i386-elf --prefix=/usr/class/cs140/i386 -make LDFLAGS=-lintl -make install - -cd /tmp/gcc-3.3.5 -patch gcc/config/elfos.h < $PINTOSROOT/src/misc/gcc-3.3.5.patch -mkdir i386 -cd i386 -../configure --target=i386-elf --prefix=/usr/class/cs140/i386 --with-gnu-as --with-as=/usr/class/cs140/i386/bin/i386-elf-as --with-gnu-ld --with-ld=/usr/class/cs140/i386/bin/i386-elf-ld --with-headers=/tmp/newlib-1.12.0/newlib/libc/include --with-newlib -make -make install - -cd /tmp/gdb-6.2.1 -mkdir i386 -cd i386 -../configure --target=i386-elf --prefix=/usr/class/cs140/i386 --disable-tui -make LDFLAGS=-lintl -make install diff --git a/src/misc/bochs-2.1.1.patch b/src/misc/bochs-2.1.1.patch index 42fafc5..3d542f6 100644 --- a/src/misc/bochs-2.1.1.patch +++ b/src/misc/bochs-2.1.1.patch @@ -38,7 +38,8 @@ Bochs we make available on the elaines: cd /tmp && tar xzf ~/bochs-2.1.1.tar.gz && cd bochs-2.1.1 patch -p1 < $PINTOSROOT/src/misc/bochs-2.1.1.patch -CFGOPTS="--with-x --with-x11 --with-term --with-nogui --prefix=/usr/class/cs140/i386" +PREFIX="/usr/class/cs140/`uname -m`" +CFGOPTS="--with-x --with-x11 --with-term --with-nogui --prefix=$PREFIX" (mkdir plain && cd plain && ../configure $CFGOPTS && @@ -48,12 +49,12 @@ CFGOPTS="--with-x --with-x11 --with-term --with-nogui --prefix=/usr/class/cs140/ cd with-gdb && ../configure --enable-gdb-stub $CFGOPTS && make && - cp bochs /usr/class/cs140/i386/bin/bochs-gdb) + cp bochs $PREFIX/bin/bochs-gdb) (mkdir with-dbg && cd with-dbg && ../configure --enable-debugger $CFGOPTS && make && - cp bochs /usr/class/cs140/i386/bin/bochs-dbg) + cp bochs $PREFIX/bin/bochs-dbg) diff -urp orig/bochs-2.1.1/Makefile.in bochs-2.1.1/Makefile.in --- orig/bochs-2.1.1/Makefile.in 2004-02-11 14:28:02.000000000 -0800 @@ -162,9 +163,9 @@ diff -urp orig/bochs-2.1.1/gdbstub.cc bochs-2.1.1/gdbstub.cc put_reply(obuf); break; -diff -u tmp/bochs-2.1.1/iodev/serial.cc bochs-2.1.1/iodev/serial.cc ---- tmp/bochs-2.1.1/iodev/serial.cc 2004-02-11 14:28:54.000001000 -0800 -+++ bochs-2.1.1/iodev/serial.cc 2004-09-14 23:02:04.000001000 -0700 +diff -urp bochs-2.1.1-upstream/iodev/serial.cc bochs-2.1.1/iodev/serial.cc +--- tmp/bochs-2.1.1/iodev/serial.cc 2004-02-11 14:28:54.000000000 -0800 ++++ bochs-2.1.1/iodev/serial.cc 2005-06-01 20:26:01.000000000 -0700 @@ -53,7 +53,7 @@ #endif #endif @@ -174,7 +175,12 @@ diff -u tmp/bochs-2.1.1/iodev/serial.cc bochs-2.1.1/iodev/serial.cc #define SERIAL_ENABLE #endif -@@ -122,6 +122,7 @@ +@@ -118,10 +118,11 @@ bx_serial_c::init(void) + + #ifdef SERIAL_ENABLE + if (strlen(bx_options.com[0].Odev->getptr ()) > 0) { +- tty_id = open(bx_options.com[0].Odev->getptr (), O_RDWR|O_NONBLOCK,600); ++ tty_id = open(bx_options.com[0].Odev->getptr (), O_RDWR,600); if (tty_id < 0) BX_PANIC(("open of %s (%s) failed\n", "com1", bx_options.com[0].Odev->getptr ())); @@ -182,7 +188,7 @@ diff -u tmp/bochs-2.1.1/iodev/serial.cc bochs-2.1.1/iodev/serial.cc BX_DEBUG(("tty_id: %d",tty_id)); tcgetattr(tty_id, &term_orig); bcopy((caddr_t) &term_orig, (caddr_t) &term_new, sizeof(struct termios)); -@@ -145,6 +146,7 @@ +@@ -145,6 +148,7 @@ bx_serial_c::init(void) term_new.c_cc[VTIME] = 0; //term_new.c_iflag |= IXOFF; tcsetattr(tty_id, TCSAFLUSH, &term_new); @@ -190,6 +196,15 @@ diff -u tmp/bochs-2.1.1/iodev/serial.cc bochs-2.1.1/iodev/serial.cc } #endif /* def SERIAL_ENABLE */ // nothing for now +@@ -955,7 +968,7 @@ bx_serial_c::rx_timer(void) + } + if (rdy) { + chbuf = data; +-#elif defined(SERIAL_ENABLE) ++#elif 0 && defined(SERIAL_ENABLE) + if ((tty_id >= 0) && (select(tty_id + 1, &fds, NULL, NULL, &tval) == 1)) { + (void) read(tty_id, &chbuf, 1); + BX_DEBUG(("read: '%c'",chbuf)); diff -urp bochs-2.1.1.orig/bochs.h bochs-2.1.1/bochs.h --- bochs-2.1.1.orig/bochs.h 2004-02-11 14:28:03.000000000 -0800 diff --git a/src/misc/gcc-3.3.5.patch b/src/misc/gcc-3.3.5.patch deleted file mode 100644 index 6954636..0000000 --- a/src/misc/gcc-3.3.5.patch +++ /dev/null @@ -1,20 +0,0 @@ -GCC 3.3.5 has a bug in the i386-elf target: it fails to emit -.intel_syntax in assembly files if -masm=intel is passed on the -command line. This is because elfos.h overrides the ASM_FILE_START -provided by gcc/config/att.h, which emits that directive, with a -version that does not. This patch covers up the problem. - ---- gcc/config/elfos.h~ 2005-01-03 21:14:58.312309000 -0800 -+++ gcc/config/elfos.h 2005-01-03 21:03:51.758598000 -0800 -@@ -97,9 +97,11 @@ - directive for any specific target, you should override this definition - in the target-specific file which includes this one. */ - -+#if 0 - #undef ASM_FILE_START - #define ASM_FILE_START(FILE) \ - output_file_directive ((FILE), main_input_filename) -+#endif - - /* This is how to allocate empty space in some section. The .zero - pseudo-op is used for this on most svr4 assemblers. */ diff --git a/src/misc/gcc-3.3.6.patch b/src/misc/gcc-3.3.6.patch new file mode 100644 index 0000000..a2ba6b7 --- /dev/null +++ b/src/misc/gcc-3.3.6.patch @@ -0,0 +1,61 @@ +GCC 3.3.6 has a bug in the i386-elf target: it fails to emit +.intel_syntax in assembly files if -masm=intel is passed on the +command line. This is because elfos.h overrides the ASM_FILE_START +provided by gcc/config/att.h, which emits that directive, with a +version that does not. This patch covers up the problem. + +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.16.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.16.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.16 +mkdir i386 +cd i386 +../configure --target=i386-elf --prefix=$PREFIX +make LDFLAGS=-lintl +make install + +cd $TMP/gcc-3.3.6 +patch gcc/config/elfos.h < $PINTOSROOT/src/misc/gcc-3.3.6.patch +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 + +--- gcc/config/elfos.h~ 2005-01-03 21:14:58.312309000 -0800 ++++ gcc/config/elfos.h 2005-01-03 21:03:51.758598000 -0800 +@@ -97,9 +97,11 @@ + directive for any specific target, you should override this definition + in the target-specific file which includes this one. */ + ++#if 0 + #undef ASM_FILE_START + #define ASM_FILE_START(FILE) \ + output_file_directive ((FILE), main_input_filename) ++#endif + + /* This is how to allocate empty space in some section. The .zero + pseudo-op is used for this on most svr4 assemblers. */ diff --git a/src/tests/Make.tests b/src/tests/Make.tests new file mode 100644 index 0000000..b7c1432 --- /dev/null +++ b/src/tests/Make.tests @@ -0,0 +1,62 @@ +# -*- makefile -*- + +include $(patsubst %,$(SRCDIR)/%/Make.tests,$(TEST_SUBDIRS)) + +PROGS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_PROGS)) +TESTS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_TESTS)) + +include ../../Makefile.userprog + +PINTOSFLAGS += -v $(foreach file,$(PUTFILES),-p $(file) -a $(notdir $(file))) +KERNELFLAGS += -q +PINTOS = pintos $(PINTOSOPTS) $(PINTOSFLAGS) -- $(KERNELFLAGS) + +OUTPUTS = $(addsuffix .output,$(TESTS)) +ERRORS = $(addsuffix .errors,$(TESTS)) +RESULTS = $(addsuffix .result,$(TESTS)) + +clean:: + rm -f $(OUTPUTS) $(ERRORS) $(RESULTS) + +grade:: ../rubric.txt results + @pass=; \ + for d in $(TESTS); do \ + if echo PASS | cmp -s $$d.result -; then \ + pass="$$pass $$d"; \ + fi \ + done; \ + $(SRCDIR)/tests/make-grade $< $$pass + +check:: results + @f=0; \ + n=0; \ + echo "Test summary:"; \ + for d in $(TESTS); do \ + if echo PASS | cmp -s $$d.result -; then \ + echo "pass $$d"; \ + else \ + echo "FAIL $$d"; \ + f=`expr $$f + 1`; \ + fi; \ + n=`expr $$n + 1`; \ + done; \ + if [ $$f = 0 ]; then \ + echo "All $$n tests passed."; \ + else \ + echo "$$f of $$n tests failed."; \ + exit 1; \ + fi + +results:: $(RESULTS) +outputs:: $(OUTPUTS) + +$(foreach prog,$(PROGS),$(eval $(prog).output: $(prog))) +$(foreach test,$(TESTS),$(eval $(test).output: $($(test)_PUTFILES))) +tests/%.output: RUNCMD = $(if $($*_ARGS),run '$(*F) $($*_ARGS)',run $(*F)) +tests/%.output: RUNREDIR = 2>$*.errors $(if $(VERBOSE),|tee,>) $*.output +tests/%.output: RUNTEST = $(PINTOS) $(RUNCMD) $(RUNREDIR) +%.output: os.dsk + $(RUNTEST) + +%.result: %.ck %.output + perl -I$(SRCDIR) $< $* $@ diff --git a/src/tests/Makefile b/src/tests/Makefile deleted file mode 100644 index 764eae6..0000000 --- a/src/tests/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -all:: - $(MAKE) -C userprog - -clean:: - $(MAKE) -C userprog clean - -distclean:: clean - find . -name '*~' -exec rm '{}' \; diff --git a/grading/lib/arc4.c b/src/tests/arc4.c similarity index 97% rename from grading/lib/arc4.c rename to src/tests/arc4.c index 26798d3..b033cc6 100644 --- a/grading/lib/arc4.c +++ b/src/tests/arc4.c @@ -1,5 +1,5 @@ #include -#include "../lib/arc4.h" +#include "tests/arc4.h" /* Swap bytes. */ static inline void diff --git a/grading/lib/arc4.h b/src/tests/arc4.h similarity index 78% rename from grading/lib/arc4.h rename to src/tests/arc4.h index ccb260c..61c533a 100644 --- a/grading/lib/arc4.h +++ b/src/tests/arc4.h @@ -1,5 +1,5 @@ -#ifndef ARC4_H -#define ARC4_H +#ifndef TESTS_ARC4_H +#define TESTS_ARC4_H #include #include @@ -14,5 +14,4 @@ struct arc4 void arc4_init (struct arc4 *, const void *, size_t); void arc4_crypt (struct arc4 *, void *, size_t); - -#endif /* arc4.h */ +#endif /* tests/arc4.h */ diff --git a/src/tests/arc4.pm b/src/tests/arc4.pm new file mode 100644 index 0000000..df19216 --- /dev/null +++ b/src/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/grading/lib/cksum.c b/src/tests/cksum.c similarity index 90% rename from grading/lib/cksum.c rename to src/tests/cksum.c index b38a5b1..92a2995 100644 --- a/grading/lib/cksum.c +++ b/src/tests/cksum.c @@ -1,6 +1,7 @@ /* crctab[] and cksum() are from the `cksum' entry in SUSv3. */ -#include "../lib/cksum.h" +#include +#include "tests/cksum.h" static unsigned long crctab[] = { 0x00000000, @@ -58,18 +59,23 @@ static unsigned long crctab[] = { }; /* This is the algorithm used by the Posix `cksum' utility. */ -unsigned long cksum(const unsigned char *b, size_t n) +unsigned long +cksum (const void *b_, size_t n) { - register unsigned i, c, s = 0; - for (i = n; i > 0; --i) { - c = (unsigned)(*b++); - s = (s << 8) ^ crctab[(s >> 24) ^ c]; - } - while (n != 0) { - c = n & 0377; - n >>= 8; - s = (s << 8) ^ crctab[(s >> 24) ^ c]; - } + 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; } diff --git a/src/tests/cksum.h b/src/tests/cksum.h new file mode 100644 index 0000000..23a1fe9 --- /dev/null +++ b/src/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/src/tests/cksum.pm b/src/tests/cksum.pm new file mode 100644 index 0000000..73be5f2 --- /dev/null +++ b/src/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/src/tests/filesys/base/Make.tests b/src/tests/filesys/base/Make.tests new file mode 100644 index 0000000..8f008ff --- /dev/null +++ b/src/tests/filesys/base/Make.tests @@ -0,0 +1,16 @@ +# -*- 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 diff --git a/grading/filesys/child-syn-read.c b/src/tests/filesys/base/child-syn-read.c similarity index 87% rename from grading/filesys/child-syn-read.c rename to src/tests/filesys/base/child-syn-read.c index 5bd739e..8f3ccc4 100644 --- a/grading/filesys/child-syn-read.c +++ b/src/tests/filesys/base/child-syn-read.c @@ -2,10 +2,8 @@ #include #include #include -#include "fslib.h" -#include "syn-read.h" - -const char test_name[128] = "child-syn-read"; +#include "tests/lib.h" +#include "tests/filesys/base/syn-read.h" static char buf[BUF_SIZE]; diff --git a/grading/filesys/child-syn-wrt.c b/src/tests/filesys/base/child-syn-wrt.c similarity index 87% rename from grading/filesys/child-syn-wrt.c rename to src/tests/filesys/base/child-syn-wrt.c index 0a43f5a..43ca2a3 100644 --- a/grading/filesys/child-syn-wrt.c +++ b/src/tests/filesys/base/child-syn-wrt.c @@ -1,10 +1,8 @@ #include #include #include -#include "fslib.h" -#include "syn-write.h" - -const char test_name[] = "child-syn-wrt"; +#include "tests/lib.h" +#include "tests/filesys/base/syn-write.h" char buf[BUF_SIZE]; diff --git a/grading/filesys/full.inc b/src/tests/filesys/base/full.inc similarity index 79% rename from grading/filesys/full.inc rename to src/tests/filesys/base/full.inc index 25ffe71..38a0396 100644 --- a/grading/filesys/full.inc +++ b/src/tests/filesys/base/full.inc @@ -1,6 +1,7 @@ /* -*- c -*- */ -#include "fslib.h" +#include "tests/filesys/seq-test.h" +#include "tests/main.h" static char buf[TEST_SIZE]; diff --git a/src/tests/filesys/base/lg-create.c b/src/tests/filesys/base/lg-create.c new file mode 100644 index 0000000..e84866e --- /dev/null +++ b/src/tests/filesys/base/lg-create.c @@ -0,0 +1,2 @@ +#define TEST_SIZE 75678 +#include "tests/filesys/create.inc" diff --git a/src/tests/filesys/base/lg-create.ck b/src/tests/filesys/base/lg-create.ck new file mode 100644 index 0000000..10f3097 --- /dev/null +++ b/src/tests/filesys/base/lg-create.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/filesys/base/lg-full.c b/src/tests/filesys/base/lg-full.c new file mode 100644 index 0000000..50a7ffa --- /dev/null +++ b/src/tests/filesys/base/lg-full.c @@ -0,0 +1,2 @@ +#define TEST_SIZE 75678 +#include "tests/filesys/base/full.inc" diff --git a/src/tests/filesys/base/lg-full.ck b/src/tests/filesys/base/lg-full.ck new file mode 100644 index 0000000..a380594 --- /dev/null +++ b/src/tests/filesys/base/lg-full.ck @@ -0,0 +1,15 @@ +# -*- 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 diff --git a/src/tests/filesys/base/lg-random.c b/src/tests/filesys/base/lg-random.c new file mode 100644 index 0000000..c3027cc --- /dev/null +++ b/src/tests/filesys/base/lg-random.c @@ -0,0 +1,3 @@ +#define BLOCK_SIZE 512 +#define TEST_SIZE (512 * 150) +#include "tests/filesys/base/random.inc" diff --git a/src/tests/filesys/base/lg-random.ck b/src/tests/filesys/base/lg-random.ck new file mode 100644 index 0000000..4d6d752 --- /dev/null +++ b/src/tests/filesys/base/lg-random.ck @@ -0,0 +1,13 @@ +# -*- 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 diff --git a/src/tests/filesys/base/lg-seq-block.c b/src/tests/filesys/base/lg-seq-block.c new file mode 100644 index 0000000..568fbb7 --- /dev/null +++ b/src/tests/filesys/base/lg-seq-block.c @@ -0,0 +1,3 @@ +#define TEST_SIZE 75678 +#define BLOCK_SIZE 513 +#include "tests/filesys/base/seq-block.inc" diff --git a/src/tests/filesys/base/lg-seq-block.ck b/src/tests/filesys/base/lg-seq-block.ck new file mode 100644 index 0000000..8c979c7 --- /dev/null +++ b/src/tests/filesys/base/lg-seq-block.ck @@ -0,0 +1,15 @@ +# -*- 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 diff --git a/src/tests/filesys/base/lg-seq-random.c b/src/tests/filesys/base/lg-seq-random.c new file mode 100644 index 0000000..2840c7f --- /dev/null +++ b/src/tests/filesys/base/lg-seq-random.c @@ -0,0 +1,2 @@ +#define TEST_SIZE 75678 +#include "tests/filesys/base/seq-random.inc" diff --git a/src/tests/filesys/base/lg-seq-random.ck b/src/tests/filesys/base/lg-seq-random.ck new file mode 100644 index 0000000..56fd998 --- /dev/null +++ b/src/tests/filesys/base/lg-seq-random.ck @@ -0,0 +1,15 @@ +# -*- 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 diff --git a/grading/filesys/random.inc b/src/tests/filesys/base/random.inc similarity index 89% rename from grading/filesys/random.inc rename to src/tests/filesys/base/random.inc index 895b658..e297854 100644 --- a/grading/filesys/random.inc +++ b/src/tests/filesys/base/random.inc @@ -4,7 +4,8 @@ #include #include #include -#include "fslib.h" +#include "tests/lib.h" +#include "tests/main.h" #if TEST_SIZE % BLOCK_SIZE != 0 #error TEST_SIZE must be a multiple of BLOCK_SIZE @@ -37,7 +38,7 @@ test_main (void) { size_t ofs = BLOCK_SIZE * order[i]; seek (fd, ofs); - if (write (fd, buf + ofs, BLOCK_SIZE) <= 0) + if (write (fd, buf + ofs, BLOCK_SIZE) != BLOCK_SIZE) fail ("write %d bytes at offset %zu failed", (int) BLOCK_SIZE, ofs); } @@ -48,7 +49,7 @@ test_main (void) char block[BLOCK_SIZE]; size_t ofs = BLOCK_SIZE * order[i]; seek (fd, ofs); - if (read (fd, block, BLOCK_SIZE) <= 0) + 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, filename); } diff --git a/grading/filesys/seq-block.inc b/src/tests/filesys/base/seq-block.inc similarity index 80% rename from grading/filesys/seq-block.inc rename to src/tests/filesys/base/seq-block.inc index 9dc5d31..d4c1f57 100644 --- a/grading/filesys/seq-block.inc +++ b/src/tests/filesys/base/seq-block.inc @@ -1,6 +1,7 @@ /* -*- c -*- */ -#include "fslib.h" +#include "tests/filesys/seq-test.h" +#include "tests/main.h" static char buf[TEST_SIZE]; diff --git a/grading/filesys/seq-random.inc b/src/tests/filesys/base/seq-random.inc similarity index 82% rename from grading/filesys/seq-random.inc rename to src/tests/filesys/base/seq-random.inc index e22ba89..a4da4c5 100644 --- a/grading/filesys/seq-random.inc +++ b/src/tests/filesys/base/seq-random.inc @@ -1,7 +1,8 @@ /* -*- c -*- */ #include -#include "fslib.h" +#include "tests/filesys/seq-test.h" +#include "tests/main.h" static char buf[TEST_SIZE]; diff --git a/src/tests/filesys/base/sm-create.c b/src/tests/filesys/base/sm-create.c new file mode 100644 index 0000000..522dbd0 --- /dev/null +++ b/src/tests/filesys/base/sm-create.c @@ -0,0 +1,2 @@ +#define TEST_SIZE 5678 +#include "tests/filesys/create.inc" diff --git a/src/tests/filesys/base/sm-create.ck b/src/tests/filesys/base/sm-create.ck new file mode 100644 index 0000000..d4a6317 --- /dev/null +++ b/src/tests/filesys/base/sm-create.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/filesys/base/sm-full.c b/src/tests/filesys/base/sm-full.c new file mode 100644 index 0000000..10b5703 --- /dev/null +++ b/src/tests/filesys/base/sm-full.c @@ -0,0 +1,2 @@ +#define TEST_SIZE 5678 +#include "tests/filesys/base/full.inc" diff --git a/grading/filesys/lg-full.exp b/src/tests/filesys/base/sm-full.ck similarity index 54% rename from grading/filesys/lg-full.exp rename to src/tests/filesys/base/sm-full.ck index 44c99ee..6fa4805 100644 --- a/grading/filesys/lg-full.exp +++ b/src/tests/filesys/base/sm-full.ck @@ -1,8 +1,15 @@ +# -*- 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 diff --git a/src/tests/filesys/base/sm-random.c b/src/tests/filesys/base/sm-random.c new file mode 100644 index 0000000..3639a16 --- /dev/null +++ b/src/tests/filesys/base/sm-random.c @@ -0,0 +1,3 @@ +#define BLOCK_SIZE 13 +#define TEST_SIZE (13 * 123) +#include "tests/filesys/base/random.inc" diff --git a/grading/filesys/lg-random.exp b/src/tests/filesys/base/sm-random.ck similarity index 63% rename from grading/filesys/lg-random.exp rename to src/tests/filesys/base/sm-random.ck index 37adb48..29b2fb7 100644 --- a/grading/filesys/lg-random.exp +++ b/src/tests/filesys/base/sm-random.ck @@ -1,3 +1,8 @@ +# -*- 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" @@ -5,3 +10,4 @@ (sm-random) read "bazzle" in random order (sm-random) close "bazzle" (sm-random) end +EOF diff --git a/src/tests/filesys/base/sm-seq-block.c b/src/tests/filesys/base/sm-seq-block.c new file mode 100644 index 0000000..5196eac --- /dev/null +++ b/src/tests/filesys/base/sm-seq-block.c @@ -0,0 +1,3 @@ +#define TEST_SIZE 5678 +#define BLOCK_SIZE 513 +#include "tests/filesys/base/seq-block.inc" diff --git a/grading/filesys/lg-seq-block.exp b/src/tests/filesys/base/sm-seq-block.ck similarity index 59% rename from grading/filesys/lg-seq-block.exp rename to src/tests/filesys/base/sm-seq-block.ck index f7b4ccc..1556668 100644 --- a/grading/filesys/lg-seq-block.exp +++ b/src/tests/filesys/base/sm-seq-block.ck @@ -1,8 +1,15 @@ +# -*- 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 diff --git a/src/tests/filesys/base/sm-seq-random.c b/src/tests/filesys/base/sm-seq-random.c new file mode 100644 index 0000000..84b6bd3 --- /dev/null +++ b/src/tests/filesys/base/sm-seq-random.c @@ -0,0 +1,2 @@ +#define TEST_SIZE 5678 +#include "tests/filesys/base/seq-random.inc" diff --git a/grading/filesys/lg-seq-random.exp b/src/tests/filesys/base/sm-seq-random.ck similarity index 60% rename from grading/filesys/lg-seq-random.exp rename to src/tests/filesys/base/sm-seq-random.ck index 9432552..47f9d88 100644 --- a/grading/filesys/lg-seq-random.exp +++ b/src/tests/filesys/base/sm-seq-random.ck @@ -1,8 +1,15 @@ +# -*- 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 diff --git a/grading/filesys/syn-read.c b/src/tests/filesys/base/syn-read.c similarity index 86% rename from grading/filesys/syn-read.c rename to src/tests/filesys/base/syn-read.c index 57fd4cf..27154db 100644 --- a/grading/filesys/syn-read.c +++ b/src/tests/filesys/base/syn-read.c @@ -1,10 +1,9 @@ #include #include #include -#include "fslib.h" -#include "syn-read.h" - -const char test_name[] = "syn-read"; +#include "tests/lib.h" +#include "tests/main.h" +#include "tests/filesys/base/syn-read.h" static char buf[BUF_SIZE]; diff --git a/grading/filesys/syn-read.exp b/src/tests/filesys/base/syn-read.ck similarity index 91% rename from grading/filesys/syn-read.exp rename to src/tests/filesys/base/syn-read.ck index 730a97b..7bda1aa 100644 --- a/grading/filesys/syn-read.exp +++ b/src/tests/filesys/base/syn-read.ck @@ -1,3 +1,8 @@ +# -*- 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" @@ -24,3 +29,4 @@ (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 diff --git a/src/tests/filesys/base/syn-read.h b/src/tests/filesys/base/syn-read.h new file mode 100644 index 0000000..e8dad42 --- /dev/null +++ b/src/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 filename[] = "data"; + +#endif /* tests/filesys/base/syn-read.h */ diff --git a/grading/filesys/syn-remove.c b/src/tests/filesys/base/syn-remove.c similarity index 84% rename from grading/filesys/syn-remove.c rename to src/tests/filesys/base/syn-remove.c index a5fb86b..7f0cb61 100644 --- a/grading/filesys/syn-remove.c +++ b/src/tests/filesys/base/syn-remove.c @@ -1,9 +1,8 @@ #include #include #include -#include "fslib.h" - -const char test_name[] = "syn-remove"; +#include "tests/lib.h" +#include "tests/main.h" char buf1[1234]; char buf2[1234]; @@ -14,7 +13,7 @@ test_main (void) const char *filename = "deleteme"; int fd; - CHECK (create (filename, 0), "create \"%s\"", filename); + CHECK (create (filename, sizeof buf1), "create \"%s\"", filename); CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename); CHECK (remove (filename), "remove \"%s\"", filename); random_bytes (buf1, sizeof buf1); diff --git a/grading/filesys/syn-remove.exp b/src/tests/filesys/base/syn-remove.ck similarity index 68% rename from grading/filesys/syn-remove.exp rename to src/tests/filesys/base/syn-remove.ck index a7c56ca..79a5cb7 100644 --- a/grading/filesys/syn-remove.exp +++ b/src/tests/filesys/base/syn-remove.ck @@ -1,3 +1,8 @@ +# -*- 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" @@ -7,3 +12,4 @@ (syn-remove) read "deleteme" (syn-remove) close "deleteme" (syn-remove) end +EOF diff --git a/grading/filesys/syn-write.c b/src/tests/filesys/base/syn-write.c similarity index 86% rename from grading/filesys/syn-write.c rename to src/tests/filesys/base/syn-write.c index b21edce..5147a57 100644 --- a/grading/filesys/syn-write.c +++ b/src/tests/filesys/base/syn-write.c @@ -2,10 +2,9 @@ #include #include #include -#include "fslib.h" -#include "syn-write.h" - -const char test_name[] = "syn-write"; +#include "tests/filesys/base/syn-write.h" +#include "tests/lib.h" +#include "tests/main.h" char buf1[BUF_SIZE]; char buf2[BUF_SIZE]; diff --git a/grading/filesys/syn-write.exp b/src/tests/filesys/base/syn-write.ck similarity index 91% rename from grading/filesys/syn-write.exp rename to src/tests/filesys/base/syn-write.ck index e4d9816..da52dd8 100644 --- a/grading/filesys/syn-write.exp +++ b/src/tests/filesys/base/syn-write.ck @@ -1,3 +1,8 @@ +# -*- 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" @@ -23,3 +28,4 @@ (syn-write) open "stuff" (syn-write) read "stuff" (syn-write) end +EOF diff --git a/grading/filesys/syn-write.h b/src/tests/filesys/base/syn-write.h similarity index 50% rename from grading/filesys/syn-write.h rename to src/tests/filesys/base/syn-write.h index 17776ff..2f16765 100644 --- a/grading/filesys/syn-write.h +++ b/src/tests/filesys/base/syn-write.h @@ -1,4 +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 filename[] = "stuff"; + +#endif /* tests/filesys/base/syn-write.h */ diff --git a/grading/filesys/create.inc b/src/tests/filesys/create.inc similarity index 83% rename from grading/filesys/create.inc rename to src/tests/filesys/create.inc index 71b52fb..57858cb 100644 --- a/grading/filesys/create.inc +++ b/src/tests/filesys/create.inc @@ -1,8 +1,8 @@ /* -*- c -*- */ -#include #include -#include "fslib.h" +#include "tests/lib.h" +#include "tests/main.h" static char buf[TEST_SIZE]; diff --git a/src/tests/filesys/extended/Make.tests b/src/tests/filesys/extended/Make.tests new file mode 100644 index 0000000..9f03c6e --- /dev/null +++ b/src/tests/filesys/extended/Make.tests @@ -0,0 +1,21 @@ +# -*- makefile -*- + +tests/filesys/extended_TESTS = $(addprefix \ +tests/filesys/extended/,dir-empty-name dir-lsdir dir-mk-tree \ +dir-mk-vine dir-mkdir dir-open dir-over-file dir-rm-cwd-cd dir-rm-cwd \ +dir-rm-parent dir-rm-root dir-rm-tree dir-rm-vine dir-rmdir \ +dir-under-file 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-too-big grow-two-files syn-rw) + +tests/filesys/extended_PROGS = $(tests/filesys/extended_TESTS) \ +tests/filesys/extended/child-syn-rw + +$(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)) +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 diff --git a/grading/filesys/child-syn-rw.c b/src/tests/filesys/extended/child-syn-rw.c similarity index 88% rename from grading/filesys/child-syn-rw.c rename to src/tests/filesys/extended/child-syn-rw.c index 7f0137a..6334bda 100644 --- a/grading/filesys/child-syn-rw.c +++ b/src/tests/filesys/extended/child-syn-rw.c @@ -1,12 +1,10 @@ #include -#include #include -#include #include -#include "fslib.h" -#include "syn-rw.h" +#include "tests/filesys/extended/syn-rw.h" +#include "tests/lib.h" -const char test_name[128] = "child-syn-rw"; +const char *test_name = "child-syn-rw"; static char buf1[BUF_SIZE]; static char buf2[BUF_SIZE]; diff --git a/grading/filesys/dir-empty-name.c b/src/tests/filesys/extended/dir-empty-name.c similarity index 63% rename from grading/filesys/dir-empty-name.c rename to src/tests/filesys/extended/dir-empty-name.c index 9d423c8..d1f5132 100644 --- a/grading/filesys/dir-empty-name.c +++ b/src/tests/filesys/extended/dir-empty-name.c @@ -1,7 +1,6 @@ #include -#include "fslib.h" - -const char test_name[] = "dir-empty-name"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/src/tests/filesys/extended/dir-empty-name.ck b/src/tests/filesys/extended/dir-empty-name.ck new file mode 100644 index 0000000..c2d87f8 --- /dev/null +++ b/src/tests/filesys/extended/dir-empty-name.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-empty-name) begin +(dir-empty-name) create "" (must return false) +(dir-empty-name) end +EOF diff --git a/grading/filesys/dir-lsdir.c b/src/tests/filesys/extended/dir-lsdir.c similarity index 51% rename from grading/filesys/dir-lsdir.c rename to src/tests/filesys/extended/dir-lsdir.c index 9c54bb1..1896de3 100644 --- a/grading/filesys/dir-lsdir.c +++ b/src/tests/filesys/extended/dir-lsdir.c @@ -1,7 +1,6 @@ #include -#include "fslib.h" - -const char test_name[] = "dir-lsdir"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/src/tests/filesys/extended/dir-lsdir.ck b/src/tests/filesys/extended/dir-lsdir.ck new file mode 100644 index 0000000..0ad947c --- /dev/null +++ b/src/tests/filesys/extended/dir-lsdir.ck @@ -0,0 +1,38 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks (@output); +@output = get_core_output (@output); + +my ($begin); +for my $i (0...$#output) { + $begin = $i, last if $output[$i] eq '(dir-lsdir) begin'; +} +fail "\"(dir-lsdir) begin\" does not appear in output\n" if !defined $begin; + +my ($end); +for my $i (0...$#output) { + $end = $i, last if $output[$i] eq '(dir-lsdir) end'; +} +fail "\"(dir-lsdir) end\" does not appear in output\n" if !defined $end; +fail "\"begin\" follows \"end\" in output\n" if $begin > $end; + +my (%count); +for my $fn (@output[$begin + 1...$end - 1]) { + $fn =~ s/\s+$//; + fail "Unexpected file \"$fn\" in lsdir output\n" + unless grep ($_ eq $fn, qw (. .. dir-lsdir)); + fail "File \"$fn\" listed twice in lsdir output\n" + if $count{$fn}; + $count{$fn}++; +} +fail "No files in lsdir output\n" if scalar (keys (%count)) == 0; +fail "File \"dir-lsdir\" missing from lsdir output\n" + if !$count{"dir-lsdir"}; + +pass; diff --git a/src/tests/filesys/extended/dir-mk-tree.c b/src/tests/filesys/extended/dir-mk-tree.c new file mode 100644 index 0000000..2b5907d --- /dev/null +++ b/src/tests/filesys/extended/dir-mk-tree.c @@ -0,0 +1,9 @@ +#include "tests/filesys/extended/mk-tree.h" +#include "tests/main.h" + +void +test_main (void) +{ + make_tree (4, 3, 3, 4); +} + diff --git a/grading/filesys/dir-mk-tree.exp b/src/tests/filesys/extended/dir-mk-tree.ck similarity index 56% rename from grading/filesys/dir-mk-tree.exp rename to src/tests/filesys/extended/dir-mk-tree.ck index 5558a16..8d77145 100644 --- a/grading/filesys/dir-mk-tree.exp +++ b/src/tests/filesys/extended/dir-mk-tree.ck @@ -1,5 +1,11 @@ +# -*- 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 diff --git a/grading/filesys/dir-mk-vine.c b/src/tests/filesys/extended/dir-mk-vine.c similarity index 87% rename from grading/filesys/dir-mk-vine.c rename to src/tests/filesys/extended/dir-mk-vine.c index 3bed83e..e873238 100644 --- a/grading/filesys/dir-mk-vine.c +++ b/src/tests/filesys/extended/dir-mk-vine.c @@ -1,7 +1,6 @@ #include -#include "fslib.h" - -const char test_name[] = "dir-mk-vine"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/grading/filesys/dir-mk-vine.exp b/src/tests/filesys/extended/dir-mk-vine.ck similarity index 84% rename from grading/filesys/dir-mk-vine.exp rename to src/tests/filesys/extended/dir-mk-vine.ck index b555631..1f9db74 100644 --- a/grading/filesys/dir-mk-vine.exp +++ b/src/tests/filesys/extended/dir-mk-vine.ck @@ -1,3 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); (dir-mk-vine) begin (dir-mk-vine) mkdir "0" (dir-mk-vine) chdir "0" @@ -23,3 +28,4 @@ (dir-mk-vine) chdir "/" (dir-mk-vine) open "/0/1/2/3/4/5/6/7/8/9/test" (dir-mk-vine) end +EOF diff --git a/grading/filesys/dir-mkdir.c b/src/tests/filesys/extended/dir-mkdir.c similarity index 78% rename from grading/filesys/dir-mkdir.c rename to src/tests/filesys/extended/dir-mkdir.c index 890af9d..a031ba0 100644 --- a/grading/filesys/dir-mkdir.c +++ b/src/tests/filesys/extended/dir-mkdir.c @@ -1,7 +1,6 @@ #include -#include "fslib.h" - -const char test_name[] = "dir-mkdir"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/grading/filesys/dir-mkdir.exp b/src/tests/filesys/extended/dir-mkdir.ck similarity index 51% rename from grading/filesys/dir-mkdir.exp rename to src/tests/filesys/extended/dir-mkdir.ck index 6dcd01c..d4e062b 100644 --- a/grading/filesys/dir-mkdir.exp +++ b/src/tests/filesys/extended/dir-mkdir.ck @@ -1,6 +1,12 @@ +# -*- 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 diff --git a/grading/filesys/dir-open.c b/src/tests/filesys/extended/dir-open.c similarity index 86% rename from grading/filesys/dir-open.c rename to src/tests/filesys/extended/dir-open.c index 37a400b..9bfe2e0 100644 --- a/grading/filesys/dir-open.c +++ b/src/tests/filesys/extended/dir-open.c @@ -1,7 +1,6 @@ #include -#include "fslib.h" - -const char test_name[] = "dir-open"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/grading/filesys/dir-open.exp b/src/tests/filesys/extended/dir-open.ck similarity index 66% rename from grading/filesys/dir-open.exp rename to src/tests/filesys/extended/dir-open.ck index bdb6b67..09c0083 100644 --- a/grading/filesys/dir-open.exp +++ b/src/tests/filesys/extended/dir-open.ck @@ -1,11 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF', <<'EOF']); (dir-open) begin (dir-open) mkdir "xyzzy" (dir-open) open "xyzzy" (dir-open) open returned -1 -- ok (dir-open) end ---OR-- +EOF (dir-open) begin (dir-open) mkdir "xyzzy" (dir-open) open "xyzzy" (dir-open) write "xyzzy" (must return -1, actually -1) (dir-open) end +EOF diff --git a/grading/filesys/dir-over-file.c b/src/tests/filesys/extended/dir-over-file.c similarity index 71% rename from grading/filesys/dir-over-file.c rename to src/tests/filesys/extended/dir-over-file.c index 8bcb36e..a7866f7 100644 --- a/grading/filesys/dir-over-file.c +++ b/src/tests/filesys/extended/dir-over-file.c @@ -1,7 +1,6 @@ #include -#include "fslib.h" - -const char test_name[] = "dir-over-file"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/grading/filesys/dir-over-file.exp b/src/tests/filesys/extended/dir-over-file.ck similarity index 50% rename from grading/filesys/dir-over-file.exp rename to src/tests/filesys/extended/dir-over-file.ck index 2bc552b..208fd9f 100644 --- a/grading/filesys/dir-over-file.exp +++ b/src/tests/filesys/extended/dir-over-file.ck @@ -1,4 +1,10 @@ +# -*- 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 diff --git a/grading/filesys/dir-rm-cwd-cd.c b/src/tests/filesys/extended/dir-rm-cwd-cd.c similarity index 85% rename from grading/filesys/dir-rm-cwd-cd.c rename to src/tests/filesys/extended/dir-rm-cwd-cd.c index 94bc0a3..7c362ae 100644 --- a/grading/filesys/dir-rm-cwd-cd.c +++ b/src/tests/filesys/extended/dir-rm-cwd-cd.c @@ -1,7 +1,6 @@ #include -#include "fslib.h" - -const char test_name[] = "dir-rm-cwd-cd"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/grading/filesys/dir-rm-cwd-cd.exp b/src/tests/filesys/extended/dir-rm-cwd-cd.ck similarity index 76% rename from grading/filesys/dir-rm-cwd-cd.exp rename to src/tests/filesys/extended/dir-rm-cwd-cd.ck index fb8a9e7..fc49668 100644 --- a/grading/filesys/dir-rm-cwd-cd.exp +++ b/src/tests/filesys/extended/dir-rm-cwd-cd.ck @@ -1,13 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF', <<'EOF']); (dir-rm-cwd-cd) begin (dir-rm-cwd-cd) mkdir "a" (dir-rm-cwd-cd) chdir "a" (dir-rm-cwd-cd) remove "/a" (must not crash) (dir-rm-cwd-cd) chdir "/a" (remove succeeded so this must return false) (dir-rm-cwd-cd) end ---OR-- +EOF (dir-rm-cwd-cd) begin (dir-rm-cwd-cd) mkdir "a" (dir-rm-cwd-cd) chdir "a" (dir-rm-cwd-cd) remove "/a" (must not crash) (dir-rm-cwd-cd) chdir "/a" (remove failed so this must succeed) (dir-rm-cwd-cd) end +EOF diff --git a/grading/filesys/dir-rm-cwd.c b/src/tests/filesys/extended/dir-rm-cwd.c similarity index 80% rename from grading/filesys/dir-rm-cwd.c rename to src/tests/filesys/extended/dir-rm-cwd.c index f99983b..3e6fc44 100644 --- a/grading/filesys/dir-rm-cwd.c +++ b/src/tests/filesys/extended/dir-rm-cwd.c @@ -1,7 +1,6 @@ #include -#include "fslib.h" - -const char test_name[] = "dir-rm-cwd"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/grading/filesys/dir-rm-cwd.exp b/src/tests/filesys/extended/dir-rm-cwd.ck similarity index 58% rename from grading/filesys/dir-rm-cwd.exp rename to src/tests/filesys/extended/dir-rm-cwd.ck index ae91c54..91e7c0f 100644 --- a/grading/filesys/dir-rm-cwd.exp +++ b/src/tests/filesys/extended/dir-rm-cwd.ck @@ -1,6 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); (dir-rm-cwd) begin (dir-rm-cwd) mkdir "a" (dir-rm-cwd) chdir "a" (dir-rm-cwd) remove "/a" (must not crash) (dir-rm-cwd) create "b" (must not crash) (dir-rm-cwd) end +EOF diff --git a/grading/filesys/dir-rm-parent.c b/src/tests/filesys/extended/dir-rm-parent.c similarity index 83% rename from grading/filesys/dir-rm-parent.c rename to src/tests/filesys/extended/dir-rm-parent.c index f71895a..e18d6fd 100644 --- a/grading/filesys/dir-rm-parent.c +++ b/src/tests/filesys/extended/dir-rm-parent.c @@ -1,7 +1,6 @@ #include -#include "fslib.h" - -const char test_name[] = "dir-rm-parent"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/grading/filesys/dir-rm-parent.exp b/src/tests/filesys/extended/dir-rm-parent.ck similarity index 67% rename from grading/filesys/dir-rm-parent.exp rename to src/tests/filesys/extended/dir-rm-parent.ck index 863681b..92301a5 100644 --- a/grading/filesys/dir-rm-parent.exp +++ b/src/tests/filesys/extended/dir-rm-parent.ck @@ -1,3 +1,8 @@ +# -*- 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" @@ -6,3 +11,4 @@ (dir-rm-parent) remove "/b" (must not crash) (dir-rm-parent) remove "/a" (must not crash) (dir-rm-parent) end +EOF diff --git a/grading/filesys/dir-rm-root.c b/src/tests/filesys/extended/dir-rm-root.c similarity index 72% rename from grading/filesys/dir-rm-root.c rename to src/tests/filesys/extended/dir-rm-root.c index 7489507..d44b880 100644 --- a/grading/filesys/dir-rm-root.c +++ b/src/tests/filesys/extended/dir-rm-root.c @@ -1,7 +1,6 @@ #include -#include "fslib.h" - -const char test_name[] = "dir-rm-root"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/src/tests/filesys/extended/dir-rm-root.ck b/src/tests/filesys/extended/dir-rm-root.ck new file mode 100644 index 0000000..38894dd --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-root.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-rm-root) begin +(dir-rm-root) remove "/" (must return false) +(dir-rm-root) create "/a" +(dir-rm-root) end +EOF diff --git a/grading/filesys/dir-rm-tree.c b/src/tests/filesys/extended/dir-rm-tree.c similarity index 93% rename from grading/filesys/dir-rm-tree.c rename to src/tests/filesys/extended/dir-rm-tree.c index 9bfd8ba..c4186f9 100644 --- a/grading/filesys/dir-rm-tree.c +++ b/src/tests/filesys/extended/dir-rm-tree.c @@ -1,10 +1,9 @@ #include #include #include -#include "fslib.h" -#include "mk-tree.h" - -const char test_name[] = "dir-rm-tree"; +#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); diff --git a/grading/filesys/dir-rm-tree.exp b/src/tests/filesys/extended/dir-rm-tree.ck similarity index 68% rename from grading/filesys/dir-rm-tree.exp rename to src/tests/filesys/extended/dir-rm-tree.ck index 97bcd21..519eff0 100644 --- a/grading/filesys/dir-rm-tree.exp +++ b/src/tests/filesys/extended/dir-rm-tree.ck @@ -1,3 +1,8 @@ +# -*- 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" @@ -5,3 +10,4 @@ (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 diff --git a/grading/filesys/dir-rm-vine.c b/src/tests/filesys/extended/dir-rm-vine.c similarity index 92% rename from grading/filesys/dir-rm-vine.c rename to src/tests/filesys/extended/dir-rm-vine.c index 6259d3e..990bcb4 100644 --- a/grading/filesys/dir-rm-vine.c +++ b/src/tests/filesys/extended/dir-rm-vine.c @@ -1,8 +1,7 @@ #include #include -#include "fslib.h" - -const char test_name[] = "dir-rm-vine"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/grading/filesys/dir-rm-vine.exp b/src/tests/filesys/extended/dir-rm-vine.ck similarity index 90% rename from grading/filesys/dir-rm-vine.exp rename to src/tests/filesys/extended/dir-rm-vine.ck index ecc1329..d8e3638 100644 --- a/grading/filesys/dir-rm-vine.exp +++ b/src/tests/filesys/extended/dir-rm-vine.ck @@ -1,3 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); (dir-rm-vine) begin (dir-rm-vine) mkdir "0" (dir-rm-vine) chdir "0" @@ -36,3 +41,4 @@ (dir-rm-vine) remove "/0" (dir-rm-vine) open "/0/1/2/3/4/5/6/7/8/9/test" (must return -1) (dir-rm-vine) end +EOF diff --git a/grading/filesys/dir-rmdir.c b/src/tests/filesys/extended/dir-rmdir.c similarity index 76% rename from grading/filesys/dir-rmdir.c rename to src/tests/filesys/extended/dir-rmdir.c index ca5893d..38317dc 100644 --- a/grading/filesys/dir-rmdir.c +++ b/src/tests/filesys/extended/dir-rmdir.c @@ -1,7 +1,6 @@ #include -#include "fslib.h" - -const char test_name[] = "dir-rmdir"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/grading/filesys/dir-rmdir.exp b/src/tests/filesys/extended/dir-rmdir.ck similarity index 51% rename from grading/filesys/dir-rmdir.exp rename to src/tests/filesys/extended/dir-rmdir.ck index 010c3e0..f8f00e6 100644 --- a/grading/filesys/dir-rmdir.exp +++ b/src/tests/filesys/extended/dir-rmdir.ck @@ -1,5 +1,11 @@ +# -*- 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 diff --git a/grading/filesys/dir-under-file.c b/src/tests/filesys/extended/dir-under-file.c similarity index 71% rename from grading/filesys/dir-under-file.c rename to src/tests/filesys/extended/dir-under-file.c index 08cef94..88a633b 100644 --- a/grading/filesys/dir-under-file.c +++ b/src/tests/filesys/extended/dir-under-file.c @@ -1,7 +1,6 @@ #include -#include "fslib.h" - -const char test_name[] = "dir-under-file"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/grading/filesys/dir-under-file.exp b/src/tests/filesys/extended/dir-under-file.ck similarity index 51% rename from grading/filesys/dir-under-file.exp rename to src/tests/filesys/extended/dir-under-file.ck index 8bbdca7..8b5cffd 100644 --- a/grading/filesys/dir-under-file.exp +++ b/src/tests/filesys/extended/dir-under-file.ck @@ -1,4 +1,10 @@ +# -*- 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 diff --git a/src/tests/filesys/extended/grow-create.c b/src/tests/filesys/extended/grow-create.c new file mode 100644 index 0000000..45d8d54 --- /dev/null +++ b/src/tests/filesys/extended/grow-create.c @@ -0,0 +1,2 @@ +#define TEST_SIZE 0 +#include "tests/filesys/create.inc" diff --git a/src/tests/filesys/extended/grow-create.ck b/src/tests/filesys/extended/grow-create.ck new file mode 100644 index 0000000..54d7f6c --- /dev/null +++ b/src/tests/filesys/extended/grow-create.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/filesys/extended/grow-dir-lg.c b/src/tests/filesys/extended/grow-dir-lg.c new file mode 100644 index 0000000..bd05dad --- /dev/null +++ b/src/tests/filesys/extended/grow-dir-lg.c @@ -0,0 +1,3 @@ +#define FILE_CNT 50 +#define DIRECTORY "/x" +#include "tests/filesys/extended/grow-dir.inc" diff --git a/grading/filesys/grow-dir-lg.exp b/src/tests/filesys/extended/grow-dir-lg.ck similarity index 95% rename from grading/filesys/grow-dir-lg.exp rename to src/tests/filesys/extended/grow-dir-lg.ck index f83f326..4214eec 100644 --- a/grading/filesys/grow-dir-lg.exp +++ b/src/tests/filesys/extended/grow-dir-lg.ck @@ -1,3 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); (grow-dir-lg) begin (grow-dir-lg) mkdir /x (grow-dir-lg) creating and checking "/x/file0" @@ -51,3 +56,4 @@ (grow-dir-lg) creating and checking "/x/file48" (grow-dir-lg) creating and checking "/x/file49" (grow-dir-lg) end +EOF diff --git a/grading/filesys/grow-dir.inc b/src/tests/filesys/extended/grow-dir.inc similarity index 89% rename from grading/filesys/grow-dir.inc rename to src/tests/filesys/extended/grow-dir.inc index 4a764f1..ffc32d3 100644 --- a/grading/filesys/grow-dir.inc +++ b/src/tests/filesys/extended/grow-dir.inc @@ -2,7 +2,9 @@ #include #include -#include "fslib.h" +#include "tests/filesys/seq-test.h" +#include "tests/lib.h" +#include "tests/main.h" static char buf[512]; diff --git a/grading/filesys/grow-file-size.c b/src/tests/filesys/extended/grow-file-size.c similarity index 83% rename from grading/filesys/grow-file-size.c rename to src/tests/filesys/extended/grow-file-size.c index cf36c17..e7c269f 100644 --- a/grading/filesys/grow-file-size.c +++ b/src/tests/filesys/extended/grow-file-size.c @@ -1,9 +1,8 @@ -/* -*- c -*- */ - #include -#include "fslib.h" +#include "tests/filesys/seq-test.h" +#include "tests/lib.h" +#include "tests/main.h" -const char test_name[] = "grow-file-size"; static char buf[2134]; static size_t diff --git a/grading/filesys/grow-file-size.exp b/src/tests/filesys/extended/grow-file-size.ck similarity index 61% rename from grading/filesys/grow-file-size.exp rename to src/tests/filesys/extended/grow-file-size.ck index 6c8f2ea..0a70837 100644 --- a/grading/filesys/grow-file-size.exp +++ b/src/tests/filesys/extended/grow-file-size.ck @@ -1,8 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +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 diff --git a/src/tests/filesys/extended/grow-root-lg.c b/src/tests/filesys/extended/grow-root-lg.c new file mode 100644 index 0000000..b4731b2 --- /dev/null +++ b/src/tests/filesys/extended/grow-root-lg.c @@ -0,0 +1,2 @@ +#define FILE_CNT 50 +#include "tests/filesys/extended/grow-dir.inc" diff --git a/grading/filesys/grow-root-lg.exp b/src/tests/filesys/extended/grow-root-lg.ck similarity index 95% rename from grading/filesys/grow-root-lg.exp rename to src/tests/filesys/extended/grow-root-lg.ck index 9736808..d44b244 100644 --- a/grading/filesys/grow-root-lg.exp +++ b/src/tests/filesys/extended/grow-root-lg.ck @@ -1,3 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +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" @@ -50,3 +55,4 @@ (grow-root-lg) creating and checking "file48" (grow-root-lg) creating and checking "file49" (grow-root-lg) end +EOF diff --git a/src/tests/filesys/extended/grow-root-sm.c b/src/tests/filesys/extended/grow-root-sm.c new file mode 100644 index 0000000..7e81256 --- /dev/null +++ b/src/tests/filesys/extended/grow-root-sm.c @@ -0,0 +1,2 @@ +#define FILE_CNT 10 +#include "tests/filesys/extended/grow-dir.inc" diff --git a/grading/filesys/grow-root-sm.exp b/src/tests/filesys/extended/grow-root-sm.ck similarity index 80% rename from grading/filesys/grow-root-sm.exp rename to src/tests/filesys/extended/grow-root-sm.ck index a4df5cd..993fbe3 100644 --- a/grading/filesys/grow-root-sm.exp +++ b/src/tests/filesys/extended/grow-root-sm.ck @@ -1,3 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +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" @@ -10,3 +15,4 @@ (grow-root-sm) creating and checking "file8" (grow-root-sm) creating and checking "file9" (grow-root-sm) end +EOF diff --git a/src/tests/filesys/extended/grow-seq-lg.c b/src/tests/filesys/extended/grow-seq-lg.c new file mode 100644 index 0000000..4474d50 --- /dev/null +++ b/src/tests/filesys/extended/grow-seq-lg.c @@ -0,0 +1,2 @@ +#define TEST_SIZE 72943 +#include "tests/filesys/extended/grow-seq.inc" diff --git a/grading/filesys/grow-seq-lg.exp b/src/tests/filesys/extended/grow-seq-lg.ck similarity index 59% rename from grading/filesys/grow-seq-lg.exp rename to src/tests/filesys/extended/grow-seq-lg.ck index 36953cb..659aeff 100644 --- a/grading/filesys/grow-seq-lg.exp +++ b/src/tests/filesys/extended/grow-seq-lg.ck @@ -1,8 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +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 diff --git a/src/tests/filesys/extended/grow-seq-sm.c b/src/tests/filesys/extended/grow-seq-sm.c new file mode 100644 index 0000000..bf54c64 --- /dev/null +++ b/src/tests/filesys/extended/grow-seq-sm.c @@ -0,0 +1,2 @@ +#define TEST_SIZE 5678 +#include "tests/filesys/extended/grow-seq.inc" diff --git a/grading/filesys/grow-seq-sm.exp b/src/tests/filesys/extended/grow-seq-sm.ck similarity index 59% rename from grading/filesys/grow-seq-sm.exp rename to src/tests/filesys/extended/grow-seq-sm.ck index 8fb12e5..242aa0e 100644 --- a/grading/filesys/grow-seq-sm.exp +++ b/src/tests/filesys/extended/grow-seq-sm.ck @@ -1,8 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +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 diff --git a/grading/filesys/grow-seq.inc b/src/tests/filesys/extended/grow-seq.inc similarity index 79% rename from grading/filesys/grow-seq.inc rename to src/tests/filesys/extended/grow-seq.inc index 8a32cd0..1b7710c 100644 --- a/grading/filesys/grow-seq.inc +++ b/src/tests/filesys/extended/grow-seq.inc @@ -1,6 +1,7 @@ /* -*- c -*- */ -#include "fslib.h" +#include "tests/filesys/seq-test.h" +#include "tests/main.h" static char buf[TEST_SIZE]; diff --git a/grading/filesys/grow-sparse.c b/src/tests/filesys/extended/grow-sparse.c similarity index 86% rename from grading/filesys/grow-sparse.c rename to src/tests/filesys/extended/grow-sparse.c index c8b6a8f..11e3b6e 100644 --- a/grading/filesys/grow-sparse.c +++ b/src/tests/filesys/extended/grow-sparse.c @@ -1,9 +1,7 @@ -/* -*- c -*- */ - #include -#include "fslib.h" +#include "tests/lib.h" +#include "tests/main.h" -const char test_name[] = "grow-sparse"; static char buf[76543]; void diff --git a/grading/filesys/grow-sparse.exp b/src/tests/filesys/extended/grow-sparse.ck similarity index 62% rename from grading/filesys/grow-sparse.exp rename to src/tests/filesys/extended/grow-sparse.ck index c7cd00c..2b2d950 100644 --- a/grading/filesys/grow-sparse.exp +++ b/src/tests/filesys/extended/grow-sparse.ck @@ -1,3 +1,8 @@ +# -*- 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" @@ -5,5 +10,7 @@ (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 diff --git a/grading/filesys/grow-tell.c b/src/tests/filesys/extended/grow-tell.c similarity index 83% rename from grading/filesys/grow-tell.c rename to src/tests/filesys/extended/grow-tell.c index 672786a..3f1e211 100644 --- a/grading/filesys/grow-tell.c +++ b/src/tests/filesys/extended/grow-tell.c @@ -1,9 +1,8 @@ -/* -*- c -*- */ - #include -#include "fslib.h" +#include "tests/filesys/seq-test.h" +#include "tests/lib.h" +#include "tests/main.h" -const char test_name[] = "grow-tell"; static char buf[2134]; static size_t diff --git a/grading/filesys/grow-tell.exp b/src/tests/filesys/extended/grow-tell.ck similarity index 57% rename from grading/filesys/grow-tell.exp rename to src/tests/filesys/extended/grow-tell.ck index e7788f9..3e8abcf 100644 --- a/grading/filesys/grow-tell.exp +++ b/src/tests/filesys/extended/grow-tell.ck @@ -1,8 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +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 diff --git a/grading/filesys/grow-too-big.c b/src/tests/filesys/extended/grow-too-big.c similarity index 84% rename from grading/filesys/grow-too-big.c rename to src/tests/filesys/extended/grow-too-big.c index bf8e300..5edcf95 100644 --- a/grading/filesys/grow-too-big.c +++ b/src/tests/filesys/extended/grow-too-big.c @@ -1,10 +1,7 @@ -/* -*- c -*- */ - #include #include -#include "fslib.h" - -const char test_name[] = "grow-sparse"; +#include "tests/lib.h" +#include "tests/main.h" void test_main (void) diff --git a/src/tests/filesys/extended/grow-too-big.ck b/src/tests/filesys/extended/grow-too-big.ck new file mode 100644 index 0000000..9a4027c --- /dev/null +++ b/src/tests/filesys/extended/grow-too-big.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-too-big) begin +(grow-too-big) create "fumble" +(grow-too-big) open "fumble" +(grow-too-big) seek "fumble" +(grow-too-big) write "fumble" +(grow-too-big) close "fumble" +(grow-too-big) end +EOF diff --git a/grading/filesys/grow-two-files.c b/src/tests/filesys/extended/grow-two-files.c similarity index 78% rename from grading/filesys/grow-two-files.c rename to src/tests/filesys/extended/grow-two-files.c index e4b8904..69f17ae 100644 --- a/grading/filesys/grow-two-files.c +++ b/src/tests/filesys/extended/grow-two-files.c @@ -1,8 +1,7 @@ #include #include -#include "fslib.h" - -const char test_name[] = "grow-two-files"; +#include "tests/lib.h" +#include "tests/main.h" #define FILE_SIZE 8143 static char buf_a[FILE_SIZE]; @@ -14,12 +13,14 @@ write_some_bytes (const char *filename, 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; - - if (write (fd, buf + *ofs, block_size) <= 0) - fail ("write %zu bytes at offset %zu in \"%s\"", - block_size, *ofs, filename); + + 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, filename, ret_val); *ofs += block_size; } } @@ -28,7 +29,7 @@ void test_main (void) { int fd_a, fd_b; - size_t ofs_a, ofs_b; + size_t ofs_a = 0, ofs_b = 0; random_init (0); random_bytes (buf_a, sizeof buf_a); diff --git a/grading/filesys/grow-two-files.exp b/src/tests/filesys/extended/grow-two-files.ck similarity index 66% rename from grading/filesys/grow-two-files.exp rename to src/tests/filesys/extended/grow-two-files.ck index 5d3403a..d62d9fa 100644 --- a/grading/filesys/grow-two-files.exp +++ b/src/tests/filesys/extended/grow-two-files.ck @@ -1,3 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); (grow-two-files) begin (grow-two-files) create "a" (grow-two-files) create "b" @@ -7,7 +12,10 @@ (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 diff --git a/grading/filesys/mk-tree.c b/src/tests/filesys/extended/mk-tree.c similarity index 95% rename from grading/filesys/mk-tree.c rename to src/tests/filesys/extended/mk-tree.c index aac5251..66a3ae3 100644 --- a/grading/filesys/mk-tree.c +++ b/src/tests/filesys/extended/mk-tree.c @@ -1,7 +1,7 @@ #include #include -#include "fslib.h" -#include "mk-tree.h" +#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); diff --git a/src/tests/filesys/extended/mk-tree.h b/src/tests/filesys/extended/mk-tree.h new file mode 100644 index 0000000..df0d5a6 --- /dev/null +++ b/src/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/grading/filesys/syn-rw.c b/src/tests/filesys/extended/syn-rw.c similarity index 87% rename from grading/filesys/syn-rw.c rename to src/tests/filesys/extended/syn-rw.c index cacdd59..a4c2f5d 100644 --- a/grading/filesys/syn-rw.c +++ b/src/tests/filesys/extended/syn-rw.c @@ -1,10 +1,8 @@ #include -#include #include -#include "fslib.h" -#include "syn-rw.h" - -const char test_name[] = "syn-rw"; +#include "tests/filesys/extended/syn-rw.h" +#include "tests/lib.h" +#include "tests/main.h" char buf[BUF_SIZE]; diff --git a/src/tests/filesys/extended/syn-rw.ck b/src/tests/filesys/extended/syn-rw.ck new file mode 100644 index 0000000..41b6e8a --- /dev/null +++ b/src/tests/filesys/extended/syn-rw.ck @@ -0,0 +1,18 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +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 diff --git a/grading/filesys/syn-rw.h b/src/tests/filesys/extended/syn-rw.h similarity index 50% rename from grading/filesys/syn-rw.h rename to src/tests/filesys/extended/syn-rw.h index 679a69e..50ad2bb 100644 --- a/grading/filesys/syn-rw.h +++ b/src/tests/filesys/extended/syn-rw.h @@ -1,4 +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 filename[] = "logfile"; + +#endif /* tests/filesys/extended/syn-rw.h */ diff --git a/src/tests/filesys/seq-test.c b/src/tests/filesys/seq-test.c new file mode 100644 index 0000000..5494921 --- /dev/null +++ b/src/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 *filename, 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 (filename, initial_size), "create \"%s\"", filename); + CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename); + + ofs = 0; + msg ("writing \"%s\"", filename); + 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, filename); + + ofs += block_size; + if (check_func != NULL) + check_func (fd, ofs); + } + msg ("close \"%s\"", filename); + close (fd); + check_file (filename, buf, size); +} diff --git a/src/tests/filesys/seq-test.h b/src/tests/filesys/seq-test.h new file mode 100644 index 0000000..e85ff74 --- /dev/null +++ b/src/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 *filename, + 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/src/tests/threads/list.c b/src/tests/internal/list.c similarity index 100% rename from src/tests/threads/list.c rename to src/tests/internal/list.c diff --git a/src/tests/threads/stdio.c b/src/tests/internal/stdio.c similarity index 100% rename from src/tests/threads/stdio.c rename to src/tests/internal/stdio.c diff --git a/src/tests/threads/stdlib.c b/src/tests/internal/stdlib.c similarity index 100% rename from src/tests/threads/stdlib.c rename to src/tests/internal/stdlib.c diff --git a/grading/filesys/fslib.c b/src/tests/lib.c similarity index 75% rename from grading/filesys/fslib.c rename to src/tests/lib.c index 2087d07..b1a1cbb 100644 --- a/grading/filesys/fslib.c +++ b/src/tests/lib.c @@ -1,10 +1,11 @@ -#include "fslib.h" +#include "tests/lib.h" #include #include #include #include #include +const char *test_name; bool quiet = false; static void @@ -47,39 +48,6 @@ fail (const char *format, ...) exit (1); } -void -seq_test (const char *filename, 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 (filename, initial_size), "create \"%s\"", filename); - CHECK ((fd = open (filename)) > 1, "open \"%s\"", filename); - - ofs = 0; - msg ("writing \"%s\"", filename); - 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) <= 0) - fail ("write %zu bytes at offset %zu in \"%s\" failed", - block_size, ofs, filename); - - ofs += block_size; - if (check_func != NULL) - check_func (fd, ofs); - } - msg ("close \"%s\"", filename); - close (fd); - check_file (filename, buf, size); -} - static void swap (void *a_, void *b_, size_t size) { @@ -109,30 +77,83 @@ shuffle (void *buf_, size_t cnt, size_t size) } void -check_file (const char *filename, const void *buf_, size_t size) +exec_children (const char *child_name, pid_t pids[], size_t child_cnt) { - const char *buf = buf_; - size_t ofs; - char block[512]; - int fd; + size_t i; - CHECK ((fd = open (filename)) > 1, "open \"%s\" for verification", filename); + 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); + } +} - ofs = 0; +void +check_file_handle (int fd, + const char *filename, 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)", + filename, file_size, size); + + /* Read the file block-by-block, comparing data as we go. */ while (ofs < size) { - size_t block_size = size - ofs; + char block[512]; + size_t block_size, ret_val; + + block_size = size - ofs; if (block_size > sizeof block) block_size = sizeof block; - if (read (fd, block, block_size) <= 0) - fail ("read %zu bytes at offset %zu in \"%s\" failed", - block_size, ofs, filename); + 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, filename, ret_val); compare_bytes (block, buf + ofs, block_size, ofs, filename); ofs += block_size; } + /* Now fail due to wrong file size. */ + if (file_size != size) + fail ("size of %s (%zu) differs from expected (%zu)", + filename, file_size, size); + + msg ("verified contents of \"%s\"", filename); +} + +void +check_file (const char *filename, const void *buf, size_t size) +{ + int fd; + + CHECK ((fd = open (filename)) > 1, "open \"%s\" for verification", filename); + check_file_handle (fd, filename, buf, size); msg ("close \"%s\"", filename); close (fd); } @@ -172,31 +193,3 @@ compare_bytes (const void *read_data_, const void *expected_data_, size_t size, fail ("%zu bytes read starting at offset %zu in \"%s\" differ " "from expected", j - i, ofs, filename); } - -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); - } -} diff --git a/grading/filesys/fslib.h b/src/tests/lib.h similarity index 83% rename from grading/filesys/fslib.h rename to src/tests/lib.h index 0d4f12d..c13e08b 100644 --- a/grading/filesys/fslib.h +++ b/src/tests/lib.h @@ -1,12 +1,12 @@ -#ifndef FSLIB_H -#define FSLIB_H +#ifndef TESTS_LIB_H +#define TESTS_LIB_H #include #include #include #include -extern const char test_name[]; +extern const char *test_name; extern bool quiet; void msg (const char *, ...) PRINTF_FORMAT (1, 2); @@ -37,19 +37,14 @@ void fail (const char *, ...) PRINTF_FORMAT (1, 2) NO_RETURN; void shuffle (void *, size_t cnt, size_t size); -void seq_test (const char *filename, - void *buf, size_t size, size_t initial_size, - size_t (*block_size_func) (void), - void (*check_func) (int fd, long ofs)); +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 *filename, + const void *buf_, size_t filesize); void check_file (const char *filename, 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 *filename); -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 test_main (void); - -#endif /* fslib.h */ +#endif /* test/lib.h */ diff --git a/src/tests/lib.pm b/src/tests/lib.pm new file mode 100644 index 0000000..bc37ae5 --- /dev/null +++ b/src/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/src/tests/main.c b/src/tests/main.c new file mode 100644 index 0000000..ad1b0f1 --- /dev/null +++ b/src/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/src/tests/main.h b/src/tests/main.h new file mode 100644 index 0000000..f0e8818 --- /dev/null +++ b/src/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/src/tests/make-grade b/src/tests/make-grade new file mode 100755 index 0000000..adb0fdd --- /dev/null +++ b/src/tests/make-grade @@ -0,0 +1,53 @@ +#! /usr/bin/perl + +use strict; +use warnings; + +my (@rubric) = read_text_file (shift); +my (@pass) = @ARGV; + +my (@grade); + +our ($possible_overall, $score_overall) = (0, 0); +our ($possible, $score) = (0, 0); +for my $i (0...$#rubric) { + local ($_) = $rubric[$i]; + if (/^\S/ || /^\s*$/) { + end_section (); + push (@grade, $_); + } elsif (my ($value, $name, $desc) = /^\s+(\d+)\s+(\S+):\s+(.*)$/) { + $possible += $value; + my ($marker); + if (grep ($_ eq $name, @pass)) { + $score += $value; + $marker = ' '; + } else { + $marker = '-'; + } + push (@grade, " $marker$value $name: $desc"); + } else { + die; + } +} +end_section (); + +push (@grade, "", "TESTING TOTAL: $score_overall of $possible_overall points"); + +print map ("$_\n", @grade); + +sub end_section { + return if !$possible; + push (@grade, "Subtotal: $score of $possible points"); + $possible_overall += $possible; + $score_overall += $score; + $possible = $score = 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; +} diff --git a/src/tests/random.pm b/src/tests/random.pm new file mode 100644 index 0000000..be008ff --- /dev/null +++ b/src/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/src/tests/tests.pm b/src/tests/tests.pm new file mode 100644 index 0000000..d085df1 --- /dev/null +++ b/src/tests/tests.pm @@ -0,0 +1,211 @@ +use strict; +use warnings; +use Algorithm::Diff; + +sub fail; +sub pass; + +die if @ARGV != 2; +our ($test, $src_dir) = @ARGV; +our ($src_stem) = "$src_dir/$test"; + +our ($messages) = ""; +open (MESSAGES, '>', \$messages); +select (MESSAGES); + +sub check_expected { + my ($expected) = pop @_; + my (@options) = @_; + my (@output) = read_text_file ("$test.output"); + common_checks (@output); + compare_output (@options, \@output, $expected); +} + +sub common_checks { + my (@output) = @_; + + fail "No output at all\n" if @output == 0; + + check_for_panic (@output); + check_for_keyword ("FAIL", @output); + check_for_triple_fault (@output); + check_for_keyword ("TIMEOUT", @output); + + fail "Didn't start up properly: no \"Pintos booting\" startup message\n" + if !grep (/Pintos booting with.*kB RAM\.\.\./, @output); + fail "Didn't start up properly: no \"Boot complete\" startup message\n" + if !grep (/Boot complete/, @output); + fail "Didn't shut down properly: no \"Timer: # ticks\" shutdown message\n" + if !grep (/Timer: \d+ ticks/, @output); + fail "Didn't shut down properly: no \"Powering off\" shutdown message\n" + if !grep (/Powering off/, @output); +} + +sub check_for_panic { + my (@output) = @_; + + my ($panic) = grep (/PANIC/, @output); + return unless defined $panic; + + print "Kernel panic: ", 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]+)+)/; + print "Call stack: @addrs\n"; + print "Translation of call stack:\n"; + print `backtrace kernel.o @addrs`; + } + + if ($panic =~ /sec_no \< d-\>capacity/) { + print < 1; + + print < 1; + } + + fail; +} + +# Get @output without header or trailer. +sub get_core_output { + my ($p); + do { $p = shift; } while (defined ($p) && $p !~ /^Executing '.*':$/); + do { $p = pop; } while (defined ($p) && $p !~ /^Execution of '.*' complete.$/); + return @_; +} + +sub compare_output { + my ($expected) = pop @_; + my ($output) = pop @_; + my (%options) = @_; + + my (@output) = get_core_output (@$output); + fail "'$test' didn't run or didn't produce any output\n" if !@output; + + if (exists $options{IGNORE_EXIT_CODES}) { + delete $options{IGNORE_EXIT_CODES}; + @output = grep (!/^[a-zA-Z0-9-_]+: exit\(\d+\)$/, @output); + } + die "unknown option " . (keys (%options))[0] . "\n" if %options; + + my ($msg) = "Actual output:\n" . join ('', map (" $_\n", @output)); + + # Compare actual output against each allowed output. + foreach my $exp_string (@$expected) { + my (@expected) = split ("\n", $exp_string); + + $msg .= "\nAcceptable 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]; + } + pass 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. + fail "Test output failed to match any acceptable form.\n\n$msg"; +} + +sub fail { + finish ("FAIL", @_); +} + +sub pass { + finish ("PASS", @_); +} + +sub finish { + my ($verdict, @rest) = @_; + + my ($result_fn) = "$test.result"; + open (RESULT, '>', $result_fn) or die "$result_fn: create: $!\n"; + print RESULT "$verdict\n", $messages, @rest; + close (RESULT); + + if ($verdict eq 'PASS') { + print STDOUT "pass $test\n"; + } else { + print STDOUT "FAIL $test\n"; + } + print STDOUT $messages, @rest, "\n"; + + 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/src/tests/threads/Make.tests b/src/tests/threads/Make.tests new file mode 100644 index 0000000..64b9ef3 --- /dev/null +++ b/src/tests/threads/Make.tests @@ -0,0 +1,38 @@ +# -*- makefile -*- + +# Test names. +tests/threads_TESTS = $(addprefix tests/threads/,alarm-single \ +alarm-multiple alarm-priority alarm-zero alarm-negative \ +priority-change priority-donate-one priority-donate-multiple \ +priority-donate-nest priority-fifo priority-preempt priority-sema \ +priority-condvar mlfqs-load-1 mlfqs-load-60 mlfqs-load-avg \ +mlfqs-recent-1 mlfqs-fair-2 mlfqs-fair-20 mlfqs-nice-2 mlfqs-nice-10) + +# Sources for tests. +tests/threads_SRC = tests/threads/tests.c +tests/threads_SRC += tests/threads/alarm-wait.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-nest.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/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/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: KERNELFLAGS += -mlfqs diff --git a/src/tests/threads/alarm-multiple.ck b/src/tests/threads/alarm-multiple.ck new file mode 100644 index 0000000..fd83bcd --- /dev/null +++ b/src/tests/threads/alarm-multiple.ck @@ -0,0 +1,4 @@ +# -*- perl -*- +use tests::tests; +use tests::threads::alarm; +check_alarm (7); diff --git a/grading/threads/alarm-negative.c b/src/tests/threads/alarm-negative.c similarity index 68% rename from grading/threads/alarm-negative.c rename to src/tests/threads/alarm-negative.c index a627b9b..83b3ac3 100644 --- a/grading/threads/alarm-negative.c +++ b/src/tests/threads/alarm-negative.c @@ -2,18 +2,16 @@ Tests timer_sleep(-100). Only requirement is that it not crash. */ -#include "threads/test.h" #include +#include "tests/threads/tests.h" #include "threads/malloc.h" #include "threads/synch.h" #include "threads/thread.h" #include "devices/timer.h" void -test (void) +test_alarm_negative (void) { - printf ("\n" - "Testing timer_sleep(-100).\n"); timer_sleep (-100); - printf ("Success.\n"); + pass (); } diff --git a/src/tests/threads/alarm-negative.ck b/src/tests/threads/alarm-negative.ck new file mode 100644 index 0000000..e40ff30 --- /dev/null +++ b/src/tests/threads/alarm-negative.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-negative) begin +(alarm-negative) PASS +(alarm-negative) end +EOF diff --git a/src/tests/threads/alarm-priority.c b/src/tests/threads/alarm-priority.c new file mode 100644 index 0000000..379f492 --- /dev/null +++ b/src/tests/threads/alarm-priority.c @@ -0,0 +1,55 @@ +#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 (!enable_mlfqs); + + wake_time = timer_ticks () + 5 * TIMER_FREQ; + sema_init (&wait_sema, 0); + + for (i = 0; i < 10; i++) + { + int priority = (i + 5) % 10 + PRI_DEFAULT + 1; + char name[16]; + snprintf (name, sizeof name, "priority %d", priority); + thread_create (name, priority, alarm_priority_thread, NULL); + } + + thread_set_priority (PRI_MAX); + + 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; + 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/src/tests/threads/alarm-priority.ck b/src/tests/threads/alarm-priority.ck new file mode 100644 index 0000000..6fa6128 --- /dev/null +++ b/src/tests/threads/alarm-priority.ck @@ -0,0 +1,18 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-priority) begin +(alarm-priority) Thread priority 32 woke up. +(alarm-priority) Thread priority 33 woke up. +(alarm-priority) Thread priority 34 woke up. +(alarm-priority) Thread priority 35 woke up. +(alarm-priority) Thread priority 36 woke up. +(alarm-priority) Thread priority 37 woke up. +(alarm-priority) Thread priority 38 woke up. +(alarm-priority) Thread priority 39 woke up. +(alarm-priority) Thread priority 40 woke up. +(alarm-priority) Thread priority 41 woke up. +(alarm-priority) end +EOF diff --git a/src/tests/threads/alarm-single.ck b/src/tests/threads/alarm-single.ck new file mode 100644 index 0000000..31215df --- /dev/null +++ b/src/tests/threads/alarm-single.ck @@ -0,0 +1,4 @@ +# -*- perl -*- +use tests::tests; +use tests::threads::alarm; +check_alarm (1); diff --git a/grading/threads/alarm-single.c b/src/tests/threads/alarm-wait.c similarity index 79% rename from grading/threads/alarm-single.c rename to src/tests/threads/alarm-wait.c index ebed46f..1e58438 100644 --- a/grading/threads/alarm-single.c +++ b/src/tests/threads/alarm-wait.c @@ -4,8 +4,9 @@ duration, M times. Records the wake-up order and verifies that it is valid. */ -#include "threads/test.h" #include +#include "tests/threads/tests.h" +#include "threads/init.h" #include "threads/malloc.h" #include "threads/synch.h" #include "threads/thread.h" @@ -14,13 +15,16 @@ static void test_sleep (int thread_cnt, int iterations); void -test (void) +test_alarm_single (void) { - /* This test does not work with the MLFQS. */ - ASSERT (!enable_mlfqs); - test_sleep (5, 1); } + +void +test_alarm_multiple (void) +{ + test_sleep (5, 7); +} /* Information about the test. */ struct sleep_test @@ -54,14 +58,14 @@ test_sleep (int thread_cnt, int iterations) int product; int i; - printf ("\n" - "Creating %d threads to sleep %d times each.\n" - "Thread 0 sleeps 10 ticks each time,\n" - "thread 1 sleeps 20 ticks each time, and so on.\n" - "If successful, product of iteration count and\n" - "sleep duration will appear in nondescending order.\n\n" - "Running test... ", - thread_cnt, iterations); + /* This test does not work with the MLFQS. */ + ASSERT (!enable_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); @@ -72,7 +76,7 @@ test_sleep (int thread_cnt, int iterations) /* Initialize test. */ test.start = timer_ticks () + 100; test.iterations = iterations; - lock_init (&test.output_lock, "output"); + lock_init (&test.output_lock); test.output_pos = output; /* Start threads. */ @@ -93,7 +97,6 @@ test_sleep (int thread_cnt, int iterations) /* Wait long enough for all the threads to finish. */ timer_sleep (100 + thread_cnt * iterations * 10 + 100); - printf ("done\n\n"); /* Acquire the output lock in case some rogue thread is still running. */ @@ -111,24 +114,22 @@ test_sleep (int thread_cnt, int iterations) new_prod = ++t->iterations * t->duration; - printf ("thread %d: duration=%d, iteration=%d, product=%d\n", - t->id, t->duration, t->iterations, new_prod); + 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 - printf ("FAIL: thread %d woke up out of order (%d > %d)!\n", - t->id, product, new_prod); + 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) - printf ("FAIL: thread %d woke up %d times instead of %d\n", - i, threads[i].iterations, iterations); + fail ("thread %d woke up %d times instead of %d", + i, threads[i].iterations, iterations); - printf ("Test complete.\n"); - lock_release (&test.output_lock); free (output); free (threads); diff --git a/grading/threads/alarm-zero.c b/src/tests/threads/alarm-zero.c similarity index 68% rename from grading/threads/alarm-zero.c rename to src/tests/threads/alarm-zero.c index f1ac775..472b9d0 100644 --- a/grading/threads/alarm-zero.c +++ b/src/tests/threads/alarm-zero.c @@ -2,18 +2,16 @@ Tests timer_sleep(0). Only requirement is that it not crash. */ -#include "threads/test.h" #include +#include "tests/threads/tests.h" #include "threads/malloc.h" #include "threads/synch.h" #include "threads/thread.h" #include "devices/timer.h" void -test (void) +test_alarm_zero (void) { - printf ("\n" - "Testing timer_sleep(0).\n"); timer_sleep (0); - printf ("Success.\n"); + pass (); } diff --git a/src/tests/threads/alarm-zero.ck b/src/tests/threads/alarm-zero.ck new file mode 100644 index 0000000..6a967b4 --- /dev/null +++ b/src/tests/threads/alarm-zero.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-zero) begin +(alarm-zero) PASS +(alarm-zero) end +EOF diff --git a/src/tests/threads/alarm.pm b/src/tests/threads/alarm.pm new file mode 100644 index 0000000..84d6d12 --- /dev/null +++ b/src/tests/threads/alarm.pm @@ -0,0 +1,32 @@ +sub check_alarm { + my ($iterations) = @_; + our ($test); + + @output = read_text_file ("$test.output"); + common_checks (@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/src/tests/threads/mlfqs-fair-2.ck b/src/tests/threads/mlfqs-fair-2.ck new file mode 100644 index 0000000..5b19ff1 --- /dev/null +++ b/src/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/src/tests/threads/mlfqs-fair-20.ck b/src/tests/threads/mlfqs-fair-20.ck new file mode 100644 index 0000000..bb4d051 --- /dev/null +++ b/src/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/src/tests/threads/mlfqs-fair.c b/src/tests/threads/mlfqs-fair.c new file mode 100644 index 0000000..fd8ab86 --- /dev/null +++ b/src/tests/threads/mlfqs-fair.c @@ -0,0 +1,107 @@ +#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 (enable_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/src/tests/threads/mlfqs-load-1.c b/src/tests/threads/mlfqs-load-1.c new file mode 100644 index 0000000..652407c --- /dev/null +++ b/src/tests/threads/mlfqs-load-1.c @@ -0,0 +1,52 @@ +#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 (enable_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 > 50) + break; + else 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 (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/src/tests/threads/mlfqs-load-1.ck b/src/tests/threads/mlfqs-load-1.ck new file mode 100644 index 0000000..db78881 --- /dev/null +++ b/src/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 (@output); + +@output = get_core_output (@output); +fail "missing PASS in output" + unless grep ($_ eq '(mlfqs-load-1) PASS', @output); + +pass; diff --git a/src/tests/threads/mlfqs-load-60.c b/src/tests/threads/mlfqs-load-60.c new file mode 100644 index 0000000..0d01cba --- /dev/null +++ b/src/tests/threads/mlfqs-load-60.c @@ -0,0 +1,56 @@ +#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 (enable_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/src/tests/threads/mlfqs-load-60.ck b/src/tests/threads/mlfqs-load-60.ck new file mode 100644 index 0000000..18806e4 --- /dev/null +++ b/src/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 (@output); +@output = get_core_output (@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, 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/src/tests/threads/mlfqs-load-avg.c b/src/tests/threads/mlfqs-load-avg.c new file mode 100644 index 0000000..32238c9 --- /dev/null +++ b/src/tests/threads/mlfqs-load-avg.c @@ -0,0 +1,57 @@ +#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 (enable_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/src/tests/threads/mlfqs-load-avg.ck b/src/tests/threads/mlfqs-load-avg.ck new file mode 100644 index 0000000..3852072 --- /dev/null +++ b/src/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 (@output); +@output = get_core_output (@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/src/tests/threads/mlfqs-nice-10.ck b/src/tests/threads/mlfqs-nice-10.ck new file mode 100644 index 0000000..53e0abe --- /dev/null +++ b/src/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/src/tests/threads/mlfqs-nice-2.ck b/src/tests/threads/mlfqs-nice-2.ck new file mode 100644 index 0000000..ada366b --- /dev/null +++ b/src/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/src/tests/threads/mlfqs-recent-1.c b/src/tests/threads/mlfqs-recent-1.c new file mode 100644 index 0000000..727bcb1 --- /dev/null +++ b/src/tests/threads/mlfqs-recent-1.c @@ -0,0 +1,43 @@ +#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 (enable_mlfqs); + + 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); + + 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/src/tests/threads/mlfqs-recent-1.ck b/src/tests/threads/mlfqs-recent-1.ck new file mode 100644 index 0000000..76b7223 --- /dev/null +++ b/src/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 (@output); +@output = get_core_output (@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/src/tests/threads/mlfqs.pm b/src/tests/threads/mlfqs.pm new file mode 100644 index 0000000..4de61b2 --- /dev/null +++ b/src/tests/threads/mlfqs.pm @@ -0,0 +1,145 @@ +# -*- perl -*- +use strict; +use warnings; + +sub mlfqs_expected_load { + my ($ready, $recent_delta) = @_; + my (@load_avg, @recent_cpu); + 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 (@output); + @output = get_core_output (@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/src/tests/threads/p1-1.c b/src/tests/threads/p1-1.c deleted file mode 100644 index 694ea0b..0000000 --- a/src/tests/threads/p1-1.c +++ /dev/null @@ -1,158 +0,0 @@ -/* Problem 1-1: Alarm Clock tests. - - 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 "threads/test.h" -#include -#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 (void) -{ - /* This test does not work with the MLFQS. */ - ASSERT (!enable_mlfqs); - - /* Easy test: 5 threads sleep once each. */ - test_sleep (5, 1); - - /* Somewhat harder test: 5 threads sleep 7 times each. */ - 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; - - printf ("\n" - "Creating %d threads to sleep %d times each.\n" - "Thread 0 sleeps 10 ticks each time,\n" - "thread 1 sleeps 20 ticks each time, and so on.\n" - "If successful, product of iteration count and\n" - "sleep duration will appear in nondescending order.\n\n" - "Running test... ", - thread_cnt, iterations); - - /* 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, "output"); - 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); - printf ("done\n\n"); - - /* 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; - - printf ("thread %d: duration=%d, iteration=%d, product=%d\n", - t->id, t->duration, t->iterations, new_prod); - - if (new_prod >= product) - product = new_prod; - else - printf ("FAIL: thread %d woke up out of order (%d > %d)!\n", - 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) - printf ("FAIL: thread %d woke up %d times instead of %d\n", - i, threads[i].iterations, iterations); - - printf ("Test complete.\n"); - - 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/src/tests/threads/p1-2.c b/src/tests/threads/p1-2.c deleted file mode 100644 index e96f03c..0000000 --- a/src/tests/threads/p1-2.c +++ /dev/null @@ -1,112 +0,0 @@ -/* Problem 1-2: Priority Scheduling tests. - - 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 "threads/test.h" -#include -#include "threads/synch.h" -#include "threads/thread.h" - -static void test_preempt (void); -static void test_fifo (void); -static void test_donate_return (void); - -void -test (void) -{ - /* This test does not work with the MLFQS. */ - ASSERT (!enable_mlfqs); - - /* Make sure our priority is the default. */ - ASSERT (thread_get_priority () == PRI_DEFAULT); - - test_preempt (); - test_fifo (); - test_donate_return (); -} - -static thread_func simple_thread_func; -static thread_func acquire_thread_func; - -static void -test_preempt (void) -{ - printf ("\n" - "Testing priority preemption.\n"); - thread_create ("high-priority", PRI_DEFAULT + 1, simple_thread_func, NULL); - printf ("The high-priority thread should have already completed.\n" - "Priority preemption test done.\n"); -} - -static void -test_fifo (void) -{ - int i; - - printf ("\n" - "Testing FIFO preemption.\n" - "5 threads will iterate 10 times in the same order each time.\n" - "If the order varies then there is a bug.\n"); - - thread_set_priority (PRI_DEFAULT + 2); - for (i = 0; i < 10; i++) - { - char name[16]; - snprintf (name, sizeof name, "%d", i); - thread_create (name, PRI_DEFAULT + 1, simple_thread_func, NULL); - } - thread_set_priority (PRI_DEFAULT); - - printf ("FIFO preemption test done.\n"); -} - -static void -test_donate_return (void) -{ - struct lock lock; - - printf ("\n" - "Testing priority donation.\n" - "If the statements printed below are all true, you pass.\n"); - - lock_init (&lock, "donor"); - lock_acquire (&lock); - thread_create ("acquire1", PRI_DEFAULT + 1, acquire_thread_func, &lock); - printf ("This thread should have priority %d. Actual priority: %d.\n", - PRI_DEFAULT + 1, thread_get_priority ()); - thread_create ("acquire2", PRI_DEFAULT + 2, acquire_thread_func, &lock); - printf ("This thread should have priority %d. Actual priority: %d.\n", - PRI_DEFAULT + 2, thread_get_priority ()); - lock_release (&lock); - printf ("acquire2 and acquire1 must already have finished, in that order.\n" - "This should be the last line before finishing this test.\n" - "Priority donation test done.\n"); -} - -static void -simple_thread_func (void *aux UNUSED) -{ - int i; - - for (i = 0; i < 5; i++) - { - printf ("Thread %s iteration %d\n", thread_name (), i); - thread_yield (); - } - printf ("Thread %s done!\n", thread_name ()); -} - -static void -acquire_thread_func (void *lock_) -{ - struct lock *lock = lock_; - - lock_acquire (lock); - printf ("%s: got the lock\n", thread_name ()); - lock_release (lock); - printf ("%s: done\n", thread_name ()); -} diff --git a/src/tests/threads/p1-3.c b/src/tests/threads/p1-3.c deleted file mode 100644 index 1b6dbdc..0000000 --- a/src/tests/threads/p1-3.c +++ /dev/null @@ -1,130 +0,0 @@ -/* Problem 1-3: Advanced Scheduler tests. - - This depends on a correctly working Alarm Clock (Problem 1-1). - - Run this test with and without the MLFQS enabled. The - threads' reported test should be better with MLFQS on than - with it off. You may have to tune the loop counts to get - reasonable numbers. - - 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 and yph. */ - -/* Uncomment to print progress messages. */ -/*#define SHOW_PROGRESS*/ - -#include "threads/test.h" -#include -#include -#include "threads/synch.h" -#include "threads/thread.h" -#include "devices/timer.h" - -static thread_func io_thread; -static thread_func cpu_thread; -static thread_func io_cpu_thread; - -void -test (void) -{ - static thread_func *funcs[] = {io_thread, cpu_thread, io_cpu_thread}; - static const char *names[] = {"IO", "CPU", "IO & CPU"}; - struct semaphore done[3]; - int i; - - printf ("\n" - "Testing multilevel feedback queue scheduler.\n"); - - /* Start threads. */ - for (i = 0; i < 3; i++) - { - sema_init (&done[i], 0, names[i]); - thread_create (names[i], PRI_DEFAULT, funcs[i], &done[i]); - } - - /* Wait for threads to finish. */ - for (i = 0; i < 3; i++) - sema_down (&done[i]); - printf ("Multilevel feedback queue scheduler test done.\n"); -} - -static void -cpu_thread (void *sema_) -{ - struct semaphore *sema = sema_; - int64_t start = timer_ticks (); - struct lock lock; - int i; - - lock_init (&lock, "cpu"); - - for (i = 0; i < 5000; i++) - { - lock_acquire (&lock); -#ifdef SHOW_PROGRESS - printf ("CPU intensive: %d\n", thread_get_priority ()); -#endif - lock_release (&lock); - } - - printf ("CPU bound thread finished in %"PRId64" ticks.\n", - timer_elapsed (start)); - - sema_up (sema); -} - -static void -io_thread (void *sema_) -{ - struct semaphore *sema = sema_; - int64_t start = timer_ticks (); - int i; - - for (i = 0; i < 1000; i++) - { - timer_sleep (10); -#ifdef SHOW_PROGRESS - printf ("IO intensive: %d\n", thread_get_priority ()); -#endif - } - - printf ("IO bound thread finished in %"PRId64" ticks.\n", - timer_elapsed (start)); - - sema_up (sema); -} - -static void -io_cpu_thread (void *sema_) -{ - struct semaphore *sema = sema_; - struct lock lock; - int64_t start = timer_ticks (); - int i; - - lock_init (&lock, "io & cpu"); - - for (i = 0; i < 800; i++) - { - int j; - - timer_sleep (10); - - for (j = 0; j < 15; j++) - { - lock_acquire (&lock); -#ifdef SHOW_PROGRESS - printf ("Alternating IO/CPU: %d\n", thread_get_priority ()); -#endif - lock_release (&lock); - } - } - - printf ("Alternating IO/CPU thread finished in %"PRId64" ticks.\n", - timer_elapsed (start)); - - sema_up (sema); -} diff --git a/src/tests/threads/priority-change.c b/src/tests/threads/priority-change.c new file mode 100644 index 0000000..456de33 --- /dev/null +++ b/src/tests/threads/priority-change.c @@ -0,0 +1,27 @@ +#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 (!enable_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/src/tests/threads/priority-change.ck b/src/tests/threads/priority-change.ck new file mode 100644 index 0000000..a0c759a --- /dev/null +++ b/src/tests/threads/priority-change.ck @@ -0,0 +1,13 @@ +# -*- 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 diff --git a/src/tests/threads/priority-condvar.c b/src/tests/threads/priority-condvar.c new file mode 100644 index 0000000..0d34380 --- /dev/null +++ b/src/tests/threads/priority-condvar.c @@ -0,0 +1,50 @@ +#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 (!enable_mlfqs); + + lock_init (&lock); + cond_init (&condition); + + thread_set_priority (PRI_MAX); + for (i = 0; i < 10; i++) + { + int priority = (i + 7) % 10 + PRI_DEFAULT + 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/src/tests/threads/priority-condvar.ck b/src/tests/threads/priority-condvar.ck new file mode 100644 index 0000000..d1d9038 --- /dev/null +++ b/src/tests/threads/priority-condvar.ck @@ -0,0 +1,38 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-condvar) begin +(priority-condvar) Thread priority 39 starting. +(priority-condvar) Thread priority 40 starting. +(priority-condvar) Thread priority 41 starting. +(priority-condvar) Thread priority 32 starting. +(priority-condvar) Thread priority 33 starting. +(priority-condvar) Thread priority 34 starting. +(priority-condvar) Thread priority 35 starting. +(priority-condvar) Thread priority 36 starting. +(priority-condvar) Thread priority 37 starting. +(priority-condvar) Thread priority 38 starting. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 32 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 33 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 34 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 35 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 36 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 37 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 38 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 39 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 40 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 41 woke up. +(priority-condvar) end +EOF diff --git a/src/tests/threads/priority-donate-multiple.c b/src/tests/threads/priority-donate-multiple.c new file mode 100644 index 0000000..012022b --- /dev/null +++ b/src/tests/threads/priority-donate-multiple.c @@ -0,0 +1,74 @@ +/* Problem 1-3: Priority Scheduling tests. + + 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 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 (!enable_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/src/tests/threads/priority-donate-multiple.ck b/src/tests/threads/priority-donate-multiple.ck new file mode 100644 index 0000000..9118722 --- /dev/null +++ b/src/tests/threads/priority-donate-multiple.ck @@ -0,0 +1,18 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-multiple) begin +(priority-donate-multiple) Main thread should have priority 30. Actual priority: 30. +(priority-donate-multiple) Main thread should have priority 29. Actual priority: 29. +(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 30. Actual priority: 30. +(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 diff --git a/src/tests/threads/priority-donate-nest.c b/src/tests/threads/priority-donate-nest.c new file mode 100644 index 0000000..b1ee690 --- /dev/null +++ b/src/tests/threads/priority-donate-nest.c @@ -0,0 +1,91 @@ +/* Problem 1-3: Priority Scheduling tests. + + 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" + +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 (!enable_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/src/tests/threads/priority-donate-nest.ck b/src/tests/threads/priority-donate-nest.ck new file mode 100644 index 0000000..597d99d --- /dev/null +++ b/src/tests/threads/priority-donate-nest.ck @@ -0,0 +1,18 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-nest) begin +(priority-donate-nest) Low thread should have priority 30. Actual priority: 30. +(priority-donate-nest) Low thread should have priority 29. Actual priority: 29. +(priority-donate-nest) Medium thread should have priority 29. Actual priority: 29. +(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 diff --git a/src/tests/threads/priority-donate-one.c b/src/tests/threads/priority-donate-one.c new file mode 100644 index 0000000..087371f --- /dev/null +++ b/src/tests/threads/priority-donate-one.c @@ -0,0 +1,62 @@ +/* Problem 1-3: Priority Scheduling tests. + + 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 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 (!enable_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/src/tests/threads/priority-donate-one.ck b/src/tests/threads/priority-donate-one.ck new file mode 100644 index 0000000..ff019a2 --- /dev/null +++ b/src/tests/threads/priority-donate-one.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-one) begin +(priority-donate-one) This thread should have priority 30. Actual priority: 30. +(priority-donate-one) This thread should have priority 29. Actual priority: 29. +(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 diff --git a/grading/threads/priority-fifo.c b/src/tests/threads/priority-fifo.c similarity index 65% rename from grading/threads/priority-fifo.c rename to src/tests/threads/priority-fifo.c index 01cc7a7..541cf5a 100644 --- a/grading/threads/priority-fifo.c +++ b/src/tests/threads/priority-fifo.c @@ -6,29 +6,14 @@ , Yu Ping Hu . Modified by arens. */ -#include "threads/test.h" #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" -static void test_fifo (void); - -void -test (void) -{ - /* This test does not work with the MLFQS. */ - ASSERT (!enable_mlfqs); - - /* Make sure our priority is the default. */ - ASSERT (thread_get_priority () == PRI_DEFAULT); - - test_fifo (); -} - -static thread_func simple_thread_func; - struct simple_thread_data { int id; /* Sleeper ID. */ @@ -37,28 +22,34 @@ struct simple_thread_data int **op; /* Output buffer position. */ }; -#define THREAD_CNT 10 -#define ITER_CNT 5 +#define THREAD_CNT 16 +#define ITER_CNT 16 -static void -test_fifo (void) +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; - - printf ("\n" - "Testing FIFO preemption.\n" - "%d threads will iterate %d times in the same order each time.\n" - "If the order varies then there is a bug.\n", - THREAD_CNT, ITER_CNT); + int i, cnt; + + /* This test does not work with the MLFQS. */ + ASSERT (!enable_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, "output"); + lock_init (&lock); - thread_set_priority (PRI_DEFAULT + 2); + thread_set_priority (PRI_DEFAULT - 2); for (i = 0; i < THREAD_CNT; i++) { char name[16]; @@ -68,29 +59,27 @@ test_fifo (void) d->iterations = 0; d->lock = &lock; d->op = &op; - thread_create (name, PRI_DEFAULT + 1, simple_thread_func, d); + thread_create (name, PRI_DEFAULT - 1, simple_thread_func, d); } - /* This should ensure that the iterations start at the - beginning of a timer tick. */ - timer_sleep (10); thread_set_priority (PRI_DEFAULT); + /* All the other threads now run to termination here. */ + ASSERT (lock.holder == NULL); - lock_acquire (&lock); + cnt = 0; for (; output < op; output++) { struct simple_thread_data *d; ASSERT (*output >= 0 && *output < THREAD_CNT); d = data + *output; - if (d->iterations != ITER_CNT) - printf ("Thread %d iteration %d\n", d->id, d->iterations); - else - printf ("Thread %d done!\n", d->id); + if (cnt % THREAD_CNT == 0) + printf ("(priority-fifo) iteration:"); + printf (" %d", d->id); + if (++cnt % THREAD_CNT == 0) + printf ("\n"); d->iterations++; } - printf ("FIFO preemption test done.\n"); - lock_release (&lock); } static void @@ -99,7 +88,7 @@ simple_thread_func (void *data_) struct simple_thread_data *data = data_; int i; - for (i = 0; i <= ITER_CNT; i++) + for (i = 0; i < ITER_CNT; i++) { lock_acquire (data->lock); *(*data->op)++ = data->id; diff --git a/src/tests/threads/priority-fifo.ck b/src/tests/threads/priority-fifo.ck new file mode 100644 index 0000000..2407e54 --- /dev/null +++ b/src/tests/threads/priority-fifo.ck @@ -0,0 +1,40 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks (@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/src/tests/threads/priority-lower.c b/src/tests/threads/priority-lower.c new file mode 100644 index 0000000..8843324 --- /dev/null +++ b/src/tests/threads/priority-lower.c @@ -0,0 +1,54 @@ +#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 (!enable_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/grading/threads/priority-preempt.c b/src/tests/threads/priority-preempt.c similarity index 60% rename from grading/threads/priority-preempt.c rename to src/tests/threads/priority-preempt.c index 8953748..f6f799c 100644 --- a/grading/threads/priority-preempt.c +++ b/src/tests/threads/priority-preempt.c @@ -6,15 +6,16 @@ , Yu Ping Hu . Modified by arens. */ -#include "threads/test.h" #include +#include "tests/threads/tests.h" +#include "threads/init.h" #include "threads/synch.h" #include "threads/thread.h" -static void test_preempt (void); +static thread_func simple_thread_func; void -test (void) +test_priority_preempt (void) { /* This test does not work with the MLFQS. */ ASSERT (!enable_mlfqs); @@ -22,19 +23,8 @@ test (void) /* Make sure our priority is the default. */ ASSERT (thread_get_priority () == PRI_DEFAULT); - test_preempt (); -} - -static thread_func simple_thread_func; - -static void -test_preempt (void) -{ - printf ("\n" - "Testing priority preemption.\n"); - thread_create ("high-priority", PRI_DEFAULT + 1, simple_thread_func, NULL); - printf ("The high-priority thread should have already completed.\n" - "Priority preemption test done.\n"); + thread_create ("high-priority", PRI_DEFAULT - 1, simple_thread_func, NULL); + msg ("The high-priority thread should have already completed."); } static void @@ -44,8 +34,8 @@ simple_thread_func (void *aux UNUSED) for (i = 0; i < 5; i++) { - printf ("Thread %s iteration %d\n", thread_name (), i); + msg ("Thread %s iteration %d", thread_name (), i); thread_yield (); } - printf ("Thread %s done!\n", thread_name ()); + msg ("Thread %s done!", thread_name ()); } diff --git a/src/tests/threads/priority-preempt.ck b/src/tests/threads/priority-preempt.ck new file mode 100644 index 0000000..e41f332 --- /dev/null +++ b/src/tests/threads/priority-preempt.ck @@ -0,0 +1,15 @@ +# -*- 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 diff --git a/src/tests/threads/priority-sema.c b/src/tests/threads/priority-sema.c new file mode 100644 index 0000000..743b05a --- /dev/null +++ b/src/tests/threads/priority-sema.c @@ -0,0 +1,42 @@ +#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 (!enable_mlfqs); + + sema_init (&sema, 0); + thread_set_priority (PRI_MAX); + for (i = 0; i < 10; i++) + { + int priority = (i + 3) % 10 + PRI_DEFAULT + 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/src/tests/threads/priority-sema.ck b/src/tests/threads/priority-sema.ck new file mode 100644 index 0000000..eee1de6 --- /dev/null +++ b/src/tests/threads/priority-sema.ck @@ -0,0 +1,28 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-sema) begin +(priority-sema) Thread priority 32 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 33 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 34 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 35 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 36 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 37 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 38 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 39 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 40 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 41 woke up. +(priority-sema) Back in main thread. +(priority-sema) end +EOF diff --git a/src/tests/threads/tests.c b/src/tests/threads/tests.c new file mode 100644 index 0000000..07a32e4 --- /dev/null +++ b/src/tests/threads/tests.c @@ -0,0 +1,96 @@ +#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-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-nest", test_priority_donate_nest}, + {"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}, + }; + +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/src/tests/threads/tests.h b/src/tests/threads/tests.h new file mode 100644 index 0000000..bbe9adb --- /dev/null +++ b/src/tests/threads/tests.h @@ -0,0 +1,35 @@ +#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_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_nest; +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; + +void msg (const char *, ...); +void fail (const char *, ...); +void pass (void); + +#endif /* tests/threads/tests.h */ + diff --git a/src/tests/userprog/Make.tests b/src/tests/userprog/Make.tests new file mode 100644 index 0000000..f8ad9c9 --- /dev/null +++ b/src/tests/userprog/Make.tests @@ -0,0 +1,128 @@ +# -*- makefile -*- + +PINTOSFLAGS += --fs-disk=2 +KERNELFLAGS += -f + +tests/%.output: PUTFILES = $(filter-out os.dsk, $^) + +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) + +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/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/src/tests/userprog/args-dbl-space.ck b/src/tests/userprog/args-dbl-space.ck new file mode 100644 index 0000000..b0a32e0 --- /dev/null +++ b/src/tests/userprog/args-dbl-space.ck @@ -0,0 +1,14 @@ +# -*- 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 diff --git a/src/tests/userprog/args-many.ck b/src/tests/userprog/args-many.ck new file mode 100644 index 0000000..3ed939f --- /dev/null +++ b/src/tests/userprog/args-many.ck @@ -0,0 +1,34 @@ +# -*- 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 diff --git a/src/tests/userprog/args-multiple.ck b/src/tests/userprog/args-multiple.ck new file mode 100644 index 0000000..11596ff --- /dev/null +++ b/src/tests/userprog/args-multiple.ck @@ -0,0 +1,16 @@ +# -*- 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 diff --git a/src/tests/userprog/args-none.ck b/src/tests/userprog/args-none.ck new file mode 100644 index 0000000..9679fa0 --- /dev/null +++ b/src/tests/userprog/args-none.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/userprog/args-single.ck b/src/tests/userprog/args-single.ck new file mode 100644 index 0000000..aeca358 --- /dev/null +++ b/src/tests/userprog/args-single.ck @@ -0,0 +1,13 @@ +# -*- 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 diff --git a/src/tests/userprog/args.c b/src/tests/userprog/args.c new file mode 100644 index 0000000..504b1c2 --- /dev/null +++ b/src/tests/userprog/args.c @@ -0,0 +1,20 @@ +#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/src/tests/userprog/boundary.c b/src/tests/userprog/boundary.c new file mode 100644 index 0000000..50f2618 --- /dev/null +++ b/src/tests/userprog/boundary.c @@ -0,0 +1,29 @@ +#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/src/tests/userprog/boundary.h b/src/tests/userprog/boundary.h new file mode 100644 index 0000000..c8e4b3b --- /dev/null +++ b/src/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/src/tests/userprog/child-bad.c b/src/tests/userprog/child-bad.c new file mode 100644 index 0000000..70cfc6a --- /dev/null +++ b/src/tests/userprog/child-bad.c @@ -0,0 +1,9 @@ +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile ("mov %esp, 0x20101234; int 0x30"); + fail ("should have exited with -1"); +} diff --git a/src/tests/userprog/child-close.c b/src/tests/userprog/child-close.c new file mode 100644 index 0000000..83e3d06 --- /dev/null +++ b/src/tests/userprog/child-close.c @@ -0,0 +1,19 @@ +#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/src/tests/userprog/child-rox.c b/src/tests/userprog/child-rox.c new file mode 100644 index 0000000..fdff97b --- /dev/null +++ b/src/tests/userprog/child-rox.c @@ -0,0 +1,49 @@ +#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/src/tests/userprog/child-simple.c b/src/tests/userprog/child-simple.c new file mode 100644 index 0000000..eb4cb63 --- /dev/null +++ b/src/tests/userprog/child-simple.c @@ -0,0 +1,11 @@ +#include +#include "tests/lib.h" + +const char *test_name = "child-simple"; + +int +main (void) +{ + msg ("run"); + return 81; +} diff --git a/src/tests/userprog/close-bad-fd.c b/src/tests/userprog/close-bad-fd.c new file mode 100644 index 0000000..a69cdfd --- /dev/null +++ b/src/tests/userprog/close-bad-fd.c @@ -0,0 +1,8 @@ +#include +#include "tests/main.h" + +void +test_main (void) +{ + close (0x20101234); +} diff --git a/grading/userprog/close-bad-fd.exp b/src/tests/userprog/close-bad-fd.ck similarity index 50% rename from grading/userprog/close-bad-fd.exp rename to src/tests/userprog/close-bad-fd.ck index 5ac59da..4a0be96 100644 --- a/grading/userprog/close-bad-fd.exp +++ b/src/tests/userprog/close-bad-fd.ck @@ -1,6 +1,12 @@ +# -*- 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) ---OR-- +EOF (close-bad-fd) begin close-bad-fd: exit(-1) +EOF diff --git a/src/tests/userprog/close-normal.c b/src/tests/userprog/close-normal.c new file mode 100644 index 0000000..0fb2006 --- /dev/null +++ b/src/tests/userprog/close-normal.c @@ -0,0 +1,12 @@ +#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/src/tests/userprog/close-normal.ck b/src/tests/userprog/close-normal.ck new file mode 100644 index 0000000..890cea0 --- /dev/null +++ b/src/tests/userprog/close-normal.ck @@ -0,0 +1,11 @@ +# -*- 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 diff --git a/src/tests/userprog/close-stdin.c b/src/tests/userprog/close-stdin.c new file mode 100644 index 0000000..0b64ef9 --- /dev/null +++ b/src/tests/userprog/close-stdin.c @@ -0,0 +1,8 @@ +#include +#include "tests/main.h" + +void +test_main (void) +{ + close (0); +} diff --git a/src/tests/userprog/close-stdin.ck b/src/tests/userprog/close-stdin.ck new file mode 100644 index 0000000..c62b53f --- /dev/null +++ b/src/tests/userprog/close-stdin.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/userprog/close-stdout.c b/src/tests/userprog/close-stdout.c new file mode 100644 index 0000000..72f30d7 --- /dev/null +++ b/src/tests/userprog/close-stdout.c @@ -0,0 +1,8 @@ +#include +#include "tests/main.h" + +void +test_main (void) +{ + close (1); +} diff --git a/grading/userprog/close-stdout.exp b/src/tests/userprog/close-stdout.ck similarity index 50% rename from grading/userprog/close-stdout.exp rename to src/tests/userprog/close-stdout.ck index 7abe3b2..83cf984 100644 --- a/grading/userprog/close-stdout.exp +++ b/src/tests/userprog/close-stdout.ck @@ -1,6 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); (close-stdout) begin (close-stdout) end close-stdout: exit(0) ---OR-- +EOF (close-stdout) begin close-stdout: exit(-1) +EOF diff --git a/src/tests/userprog/close-twice.c b/src/tests/userprog/close-twice.c new file mode 100644 index 0000000..d3f1e57 --- /dev/null +++ b/src/tests/userprog/close-twice.c @@ -0,0 +1,14 @@ +#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/src/tests/userprog/close-twice.ck b/src/tests/userprog/close-twice.ck new file mode 100644 index 0000000..e21b169 --- /dev/null +++ b/src/tests/userprog/close-twice.ck @@ -0,0 +1,18 @@ +# -*- 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 diff --git a/src/tests/userprog/create-bad-ptr.c b/src/tests/userprog/create-bad-ptr.c new file mode 100644 index 0000000..f5df1f0 --- /dev/null +++ b/src/tests/userprog/create-bad-ptr.c @@ -0,0 +1,8 @@ +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("create(0x20101234): %d", create ((char *) 0x20101234, 0)); +} diff --git a/src/tests/userprog/create-bad-ptr.ck b/src/tests/userprog/create-bad-ptr.ck new file mode 100644 index 0000000..46453bb --- /dev/null +++ b/src/tests/userprog/create-bad-ptr.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-bad-ptr) begin +create-bad-ptr: exit(-1) +EOF diff --git a/src/tests/userprog/create-bound.c b/src/tests/userprog/create-bound.c new file mode 100644 index 0000000..7ce0ae3 --- /dev/null +++ b/src/tests/userprog/create-bound.c @@ -0,0 +1,11 @@ +#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/src/tests/userprog/create-bound.ck b/src/tests/userprog/create-bound.ck new file mode 100644 index 0000000..87b7bd5 --- /dev/null +++ b/src/tests/userprog/create-bound.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/userprog/create-empty.c b/src/tests/userprog/create-empty.c new file mode 100644 index 0000000..4cb74d8 --- /dev/null +++ b/src/tests/userprog/create-empty.c @@ -0,0 +1,8 @@ +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("create(\"\"): %d", create ("", 0)); +} diff --git a/src/tests/userprog/create-empty.ck b/src/tests/userprog/create-empty.ck new file mode 100644 index 0000000..32fcb00 --- /dev/null +++ b/src/tests/userprog/create-empty.ck @@ -0,0 +1,13 @@ +# -*- 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 diff --git a/src/tests/userprog/create-exists.c b/src/tests/userprog/create-exists.c new file mode 100644 index 0000000..a947ab7 --- /dev/null +++ b/src/tests/userprog/create-exists.c @@ -0,0 +1,13 @@ +#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/src/tests/userprog/create-exists.ck b/src/tests/userprog/create-exists.ck new file mode 100644 index 0000000..d227033 --- /dev/null +++ b/src/tests/userprog/create-exists.ck @@ -0,0 +1,14 @@ +# -*- 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 diff --git a/src/tests/userprog/create-long.c b/src/tests/userprog/create-long.c new file mode 100644 index 0000000..005a8b8 --- /dev/null +++ b/src/tests/userprog/create-long.c @@ -0,0 +1,14 @@ +#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/src/tests/userprog/create-long.ck b/src/tests/userprog/create-long.ck new file mode 100644 index 0000000..3124584 --- /dev/null +++ b/src/tests/userprog/create-long.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/userprog/create-normal.c b/src/tests/userprog/create-normal.c new file mode 100644 index 0000000..10eba33 --- /dev/null +++ b/src/tests/userprog/create-normal.c @@ -0,0 +1,8 @@ +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (create ("quux.dat", 0), "create quux.dat"); +} diff --git a/src/tests/userprog/create-normal.ck b/src/tests/userprog/create-normal.ck new file mode 100644 index 0000000..c956a1c --- /dev/null +++ b/src/tests/userprog/create-normal.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/userprog/create-null.c b/src/tests/userprog/create-null.c new file mode 100644 index 0000000..98b2bed --- /dev/null +++ b/src/tests/userprog/create-null.c @@ -0,0 +1,8 @@ +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("create(NULL): %d", create (NULL, 0)); +} diff --git a/src/tests/userprog/create-null.ck b/src/tests/userprog/create-null.ck new file mode 100644 index 0000000..641ff9b --- /dev/null +++ b/src/tests/userprog/create-null.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-null) begin +create-null: exit(-1) +EOF diff --git a/src/tests/userprog/exec-arg.c b/src/tests/userprog/exec-arg.c new file mode 100644 index 0000000..b49304e --- /dev/null +++ b/src/tests/userprog/exec-arg.c @@ -0,0 +1,8 @@ +#include +#include "tests/main.h" + +void +test_main (void) +{ + wait (exec ("child-args childarg")); +} diff --git a/src/tests/userprog/exec-arg.ck b/src/tests/userprog/exec-arg.ck new file mode 100644 index 0000000..bdedca6 --- /dev/null +++ b/src/tests/userprog/exec-arg.ck @@ -0,0 +1,16 @@ +# -*- 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 diff --git a/src/tests/userprog/exec-bad-ptr.c b/src/tests/userprog/exec-bad-ptr.c new file mode 100644 index 0000000..a70c504 --- /dev/null +++ b/src/tests/userprog/exec-bad-ptr.c @@ -0,0 +1,8 @@ +#include +#include "tests/main.h" + +void +test_main (void) +{ + exec ((char *) 0x20101234); +} diff --git a/grading/userprog/exec-bad-ptr.exp b/src/tests/userprog/exec-bad-ptr.ck similarity index 50% rename from grading/userprog/exec-bad-ptr.exp rename to src/tests/userprog/exec-bad-ptr.ck index a90700c..f0fdd69 100644 --- a/grading/userprog/exec-bad-ptr.exp +++ b/src/tests/userprog/exec-bad-ptr.ck @@ -1,6 +1,12 @@ +# -*- 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) ---OR-- +EOF (exec-bad-ptr) begin exec-bad-ptr: exit(-1) +EOF diff --git a/src/tests/userprog/exec-missing.c b/src/tests/userprog/exec-missing.c new file mode 100644 index 0000000..53617e0 --- /dev/null +++ b/src/tests/userprog/exec-missing.c @@ -0,0 +1,9 @@ +#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/grading/userprog/exec-missing.exp b/src/tests/userprog/exec-missing.ck similarity index 77% rename from grading/userprog/exec-missing.exp rename to src/tests/userprog/exec-missing.ck index 9241cc7..d26c52a 100644 --- a/grading/userprog/exec-missing.exp +++ b/src/tests/userprog/exec-missing.ck @@ -1,17 +1,23 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'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) ---OR-- +EOF (exec-missing) begin (exec-missing) exec("no-such-file"): -1 (exec-missing) end exec-missing: exit(0) ---OR-- +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 diff --git a/grading/userprog/exec-multiple.c b/src/tests/userprog/exec-multiple.c similarity index 55% rename from grading/userprog/exec-multiple.c rename to src/tests/userprog/exec-multiple.c index a690dad..02345fa 100644 --- a/grading/userprog/exec-multiple.c +++ b/src/tests/userprog/exec-multiple.c @@ -1,14 +1,12 @@ -#include #include +#include "tests/lib.h" +#include "tests/main.h" -int -main (void) +void +test_main (void) { - printf ("(exec-multiple) begin\n"); wait (exec ("child-simple")); wait (exec ("child-simple")); wait (exec ("child-simple")); wait (exec ("child-simple")); - printf ("(exec-multiple) end\n"); - return 0; } diff --git a/grading/userprog/exec-multiple.exp b/src/tests/userprog/exec-multiple.ck similarity index 71% rename from grading/userprog/exec-multiple.exp rename to src/tests/userprog/exec-multiple.ck index 0f7752c..6076958 100644 --- a/grading/userprog/exec-multiple.exp +++ b/src/tests/userprog/exec-multiple.ck @@ -1,3 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); (exec-multiple) begin (child-simple) run child-simple: exit(81) @@ -9,3 +14,4 @@ child-simple: exit(81) child-simple: exit(81) (exec-multiple) end exec-multiple: exit(0) +EOF diff --git a/src/tests/userprog/exec-once.c b/src/tests/userprog/exec-once.c new file mode 100644 index 0000000..aa043fa --- /dev/null +++ b/src/tests/userprog/exec-once.c @@ -0,0 +1,9 @@ +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + wait (exec ("child-simple")); +} diff --git a/grading/userprog/exec-once.exp b/src/tests/userprog/exec-once.ck similarity index 51% rename from grading/userprog/exec-once.exp rename to src/tests/userprog/exec-once.ck index 82132e1..7aa688a 100644 --- a/grading/userprog/exec-once.exp +++ b/src/tests/userprog/exec-once.ck @@ -1,5 +1,11 @@ +# -*- 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 diff --git a/src/tests/userprog/exit.c b/src/tests/userprog/exit.c new file mode 100644 index 0000000..ca59969 --- /dev/null +++ b/src/tests/userprog/exit.c @@ -0,0 +1,9 @@ +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + exit (57); + fail ("should have called exit(57)"); +} diff --git a/src/tests/userprog/exit.ck b/src/tests/userprog/exit.ck new file mode 100644 index 0000000..206c48b --- /dev/null +++ b/src/tests/userprog/exit.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(exit) begin +exit: exit(57) +EOF diff --git a/src/tests/userprog/halt.c b/src/tests/userprog/halt.c index bad7250..b4fd9e1 100644 --- a/src/tests/userprog/halt.c +++ b/src/tests/userprog/halt.c @@ -1,14 +1,9 @@ -/* halt.c +#include "tests/lib.h" +#include "tests/main.h" - Simple program to test whether running a user program works. - - Just invokes a system call that shuts down the OS. */ - -#include - -int -main (void) +void +test_main (void) { halt (); - /* not reached */ + fail ("should have halted"); } diff --git a/src/tests/userprog/halt.ck b/src/tests/userprog/halt.ck new file mode 100644 index 0000000..4b1207d --- /dev/null +++ b/src/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 (@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/src/tests/userprog/multi-child-fd.c b/src/tests/userprog/multi-child-fd.c new file mode 100644 index 0000000..019708e --- /dev/null +++ b/src/tests/userprog/multi-child-fd.c @@ -0,0 +1,20 @@ +#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/src/tests/userprog/multi-child-fd.ck b/src/tests/userprog/multi-child-fd.ck new file mode 100644 index 0000000..bad0378 --- /dev/null +++ b/src/tests/userprog/multi-child-fd.ck @@ -0,0 +1,24 @@ +# -*- 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 diff --git a/src/tests/userprog/multi-parent-fd.c b/src/tests/userprog/multi-parent-fd.c new file mode 100644 index 0000000..7058a0a --- /dev/null +++ b/src/tests/userprog/multi-parent-fd.c @@ -0,0 +1,19 @@ +#include +#include +#include "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/src/tests/userprog/multi-recurse.c b/src/tests/userprog/multi-recurse.c new file mode 100644 index 0000000..7786831 --- /dev/null +++ b/src/tests/userprog/multi-recurse.c @@ -0,0 +1,33 @@ +#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]); + if (n == 0) + n = atoi (argv[0]); + + 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/grading/userprog/multi-recurse.exp b/src/tests/userprog/multi-recurse.ck similarity index 61% rename from grading/userprog/multi-recurse.exp rename to src/tests/userprog/multi-recurse.ck index c5c0ad6..d2a4e1b 100644 --- a/grading/userprog/multi-recurse.exp +++ b/src/tests/userprog/multi-recurse.ck @@ -1,18 +1,38 @@ +# -*- 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) @@ -46,3 +66,4 @@ multi-recurse: exit(13) multi-recurse: exit(14) (multi-recurse) end 15 multi-recurse: exit(15) +EOF diff --git a/src/tests/userprog/no-vm/Make.tests b/src/tests/userprog/no-vm/Make.tests new file mode 100644 index 0000000..68ea197 --- /dev/null +++ b/src/tests/userprog/no-vm/Make.tests @@ -0,0 +1,9 @@ +# -*- 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_ARGS = 0 + + diff --git a/grading/userprog/multi-oom.c b/src/tests/userprog/no-vm/multi-oom.c similarity index 60% rename from grading/userprog/multi-oom.c rename to src/tests/userprog/no-vm/multi-oom.c index cfdda66..042cb2c 100644 --- a/grading/userprog/multi-oom.c +++ b/src/tests/userprog/no-vm/multi-oom.c @@ -2,6 +2,9 @@ #include #include #include +#include "tests/lib.h" + +const char *test_name = "multi-oom"; int main (int argc UNUSED, char *argv[]) @@ -14,7 +17,7 @@ main (int argc UNUSED, char *argv[]) if (n == 0) n = atoi (argv[0]); - printf ("(multi-oom) begin %d\n", n); + msg ("begin %d", n); snprintf (child_cmd, sizeof child_cmd, "multi-oom %d", n + 1); child_pid = exec (child_cmd); @@ -22,14 +25,11 @@ main (int argc UNUSED, char *argv[]) { int code = wait (child_pid); if (code != n + 1) - printf ("(multi-oom) fail: wait(exec(\"%s\")) returned %d\n", - child_cmd, code); + fail ("wait(exec(\"%s\")) returned %d", child_cmd, code); } else if (n < 15) - printf ("(multi-oom) fail: exec(\"%s\") returned -1 " - "after only %d recursions\n", - child_cmd, n); + fail ("exec(\"%s\") returned -1 after only %d recursions", child_cmd, n); - printf ("(multi-oom) end %d\n", n); + msg ("end %d", n); return n; } diff --git a/src/tests/userprog/no-vm/multi-oom.ck b/src/tests/userprog/no-vm/multi-oom.ck new file mode 100644 index 0000000..52434a2 --- /dev/null +++ b/src/tests/userprog/no-vm/multi-oom.ck @@ -0,0 +1,43 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; + +our ($test); +my (@output) = read_text_file ("$test.output"); +common_checks (@output); + +@output = get_core_output (@output); +my ($n) = 0; +while (my ($m) = $output[0] =~ /^\(multi-oom\) begin (\d+)$/) { + fail "Child process $m started out of order.\n" if $m != $n; + $n = $m + 1; + shift @output; +} +fail "Only $n child process(es) started.\n" if $n < 15; + +# There could be a death notice for a process that didn't get +# fully loaded, and/or notices from the loader. +while (@output > 0 + && ($output[0] =~ /^multi-oom: exit\(-1\)$/ + || $output[0] =~ /^load: /)) { + shift @output; +} + +while (--$n >= 0) { + fail "Output ended unexpectedly before process $n finished.\n" + if @output < 2; + + local ($_); + chomp ($_ = shift @output); + fail "Found '$_' expecting 'end' message.\n" if !/^\(multi-oom\) end/; + fail "Child process $n ended out of order.\n" + if !/^\(multi-oom\) end $n$/; + + chomp ($_ = shift @output); + fail "Kernel didn't print proper exit message for process $n.\n" + if !/^multi-oom: exit\($n\)$/; +} +fail "Spurious output at end: '$output[0]'.\n" if @output; + +pass; diff --git a/src/tests/userprog/null.ck b/src/tests/userprog/null.ck new file mode 100644 index 0000000..cacc658 --- /dev/null +++ b/src/tests/userprog/null.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +system call! +EOF diff --git a/src/tests/userprog/open-bad-ptr.c b/src/tests/userprog/open-bad-ptr.c new file mode 100644 index 0000000..8176aa2 --- /dev/null +++ b/src/tests/userprog/open-bad-ptr.c @@ -0,0 +1,10 @@ +#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/grading/userprog/open-bad-ptr.exp b/src/tests/userprog/open-bad-ptr.ck similarity index 50% rename from grading/userprog/open-bad-ptr.exp rename to src/tests/userprog/open-bad-ptr.ck index b0376a3..c0a1153 100644 --- a/grading/userprog/open-bad-ptr.exp +++ b/src/tests/userprog/open-bad-ptr.ck @@ -1,6 +1,12 @@ +# -*- 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) ---OR-- +EOF (open-bad-ptr) begin open-bad-ptr: exit(-1) +EOF diff --git a/src/tests/userprog/open-boundary.c b/src/tests/userprog/open-boundary.c new file mode 100644 index 0000000..adf04d8 --- /dev/null +++ b/src/tests/userprog/open-boundary.c @@ -0,0 +1,11 @@ +#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/src/tests/userprog/open-boundary.ck b/src/tests/userprog/open-boundary.ck new file mode 100644 index 0000000..40c9bf6 --- /dev/null +++ b/src/tests/userprog/open-boundary.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/userprog/open-empty.c b/src/tests/userprog/open-empty.c new file mode 100644 index 0000000..28e7b80 --- /dev/null +++ b/src/tests/userprog/open-empty.c @@ -0,0 +1,11 @@ +#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/src/tests/userprog/open-empty.ck b/src/tests/userprog/open-empty.ck new file mode 100644 index 0000000..5537d25 --- /dev/null +++ b/src/tests/userprog/open-empty.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-empty) begin +(open-empty) end +open-empty: exit(0) +EOF diff --git a/src/tests/userprog/open-missing.c b/src/tests/userprog/open-missing.c new file mode 100644 index 0000000..58661af --- /dev/null +++ b/src/tests/userprog/open-missing.c @@ -0,0 +1,11 @@ +#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/src/tests/userprog/open-missing.ck b/src/tests/userprog/open-missing.ck new file mode 100644 index 0000000..a1aaf79 --- /dev/null +++ b/src/tests/userprog/open-missing.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-missing) begin +(open-missing) end +open-missing: exit(0) +EOF diff --git a/src/tests/userprog/open-normal.c b/src/tests/userprog/open-normal.c new file mode 100644 index 0000000..4040fc2 --- /dev/null +++ b/src/tests/userprog/open-normal.c @@ -0,0 +1,11 @@ +#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/src/tests/userprog/open-normal.ck b/src/tests/userprog/open-normal.ck new file mode 100644 index 0000000..492433f --- /dev/null +++ b/src/tests/userprog/open-normal.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-normal) begin +(open-normal) end +open-normal: exit(0) +EOF diff --git a/src/tests/userprog/open-null.c b/src/tests/userprog/open-null.c new file mode 100644 index 0000000..ba30807 --- /dev/null +++ b/src/tests/userprog/open-null.c @@ -0,0 +1,9 @@ +#include +#include +#include "tests/main.h" + +void +test_main (void) +{ + open (NULL); +} diff --git a/src/tests/userprog/open-null.ck b/src/tests/userprog/open-null.ck new file mode 100644 index 0000000..92188b3 --- /dev/null +++ b/src/tests/userprog/open-null.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/userprog/open-twice.c b/src/tests/userprog/open-twice.c new file mode 100644 index 0000000..c884191 --- /dev/null +++ b/src/tests/userprog/open-twice.c @@ -0,0 +1,15 @@ +#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/src/tests/userprog/open-twice.ck b/src/tests/userprog/open-twice.ck new file mode 100644 index 0000000..d23ee84 --- /dev/null +++ b/src/tests/userprog/open-twice.ck @@ -0,0 +1,11 @@ +# -*- 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 diff --git a/src/tests/userprog/read-bad-fd.c b/src/tests/userprog/read-bad-fd.c new file mode 100644 index 0000000..029f647 --- /dev/null +++ b/src/tests/userprog/read-bad-fd.c @@ -0,0 +1,17 @@ +#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/src/tests/userprog/read-bad-fd.ck b/src/tests/userprog/read-bad-fd.ck new file mode 100644 index 0000000..3dd5a93 --- /dev/null +++ b/src/tests/userprog/read-bad-fd.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/userprog/read-bad-ptr.c b/src/tests/userprog/read-bad-ptr.c new file mode 100644 index 0000000..9506427 --- /dev/null +++ b/src/tests/userprog/read-bad-ptr.c @@ -0,0 +1,13 @@ +#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/src/tests/userprog/read-bad-ptr.ck b/src/tests/userprog/read-bad-ptr.ck new file mode 100644 index 0000000..2c95bc6 --- /dev/null +++ b/src/tests/userprog/read-bad-ptr.ck @@ -0,0 +1,14 @@ +# -*- 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 diff --git a/src/tests/userprog/read-boundary.c b/src/tests/userprog/read-boundary.c new file mode 100644 index 0000000..b5843a8 --- /dev/null +++ b/src/tests/userprog/read-boundary.c @@ -0,0 +1,27 @@ +#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/src/tests/userprog/read-boundary.ck b/src/tests/userprog/read-boundary.ck new file mode 100644 index 0000000..505b24b --- /dev/null +++ b/src/tests/userprog/read-boundary.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/userprog/read-normal.c b/src/tests/userprog/read-normal.c new file mode 100644 index 0000000..a688818 --- /dev/null +++ b/src/tests/userprog/read-normal.c @@ -0,0 +1,9 @@ +#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/src/tests/userprog/read-normal.ck b/src/tests/userprog/read-normal.ck new file mode 100644 index 0000000..b1139cf --- /dev/null +++ b/src/tests/userprog/read-normal.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/userprog/read-stdout.c b/src/tests/userprog/read-stdout.c new file mode 100644 index 0000000..0018e4f --- /dev/null +++ b/src/tests/userprog/read-stdout.c @@ -0,0 +1,10 @@ +#include +#include +#include "tests/main.h" + +void +test_main (void) +{ + char buf; + read (STDOUT_FILENO, &buf, 1); +} diff --git a/src/tests/userprog/read-stdout.ck b/src/tests/userprog/read-stdout.ck new file mode 100644 index 0000000..758a1c2 --- /dev/null +++ b/src/tests/userprog/read-stdout.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/userprog/read-zero.c b/src/tests/userprog/read-zero.c new file mode 100644 index 0000000..db9d206 --- /dev/null +++ b/src/tests/userprog/read-zero.c @@ -0,0 +1,19 @@ +#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/src/tests/userprog/read-zero.ck b/src/tests/userprog/read-zero.ck new file mode 100644 index 0000000..3d9b091 --- /dev/null +++ b/src/tests/userprog/read-zero.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/userprog/rox-child.c b/src/tests/userprog/rox-child.c new file mode 100644 index 0000000..257c4d9 --- /dev/null +++ b/src/tests/userprog/rox-child.c @@ -0,0 +1,2 @@ +#define CHILD_CNT "1" +#include "tests/userprog/rox-child.inc" diff --git a/src/tests/userprog/rox-child.ck b/src/tests/userprog/rox-child.ck new file mode 100644 index 0000000..2994575 --- /dev/null +++ b/src/tests/userprog/rox-child.ck @@ -0,0 +1,19 @@ +# -*- 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 diff --git a/src/tests/userprog/rox-child.inc b/src/tests/userprog/rox-child.inc new file mode 100644 index 0000000..1e2ade9 --- /dev/null +++ b/src/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/src/tests/userprog/rox-multichild.c b/src/tests/userprog/rox-multichild.c new file mode 100644 index 0000000..602d767 --- /dev/null +++ b/src/tests/userprog/rox-multichild.c @@ -0,0 +1,2 @@ +#define CHILD_CNT "5" +#include "tests/userprog/rox-child.inc" diff --git a/src/tests/userprog/rox-multichild.ck b/src/tests/userprog/rox-multichild.ck new file mode 100644 index 0000000..704ccce --- /dev/null +++ b/src/tests/userprog/rox-multichild.ck @@ -0,0 +1,43 @@ +# -*- 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 diff --git a/src/tests/userprog/rox-simple.c b/src/tests/userprog/rox-simple.c new file mode 100644 index 0000000..d024394 --- /dev/null +++ b/src/tests/userprog/rox-simple.c @@ -0,0 +1,16 @@ +#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/src/tests/userprog/rox-simple.ck b/src/tests/userprog/rox-simple.ck new file mode 100644 index 0000000..67c3116 --- /dev/null +++ b/src/tests/userprog/rox-simple.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/userprog/sample.inc b/src/tests/userprog/sample.inc new file mode 100644 index 0000000..59f2bcb --- /dev/null +++ b/src/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/src/tests/userprog/sample.txt b/src/tests/userprog/sample.txt new file mode 100644 index 0000000..5050fec --- /dev/null +++ b/src/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/grading/userprog/sc-bad-arg.c b/src/tests/userprog/sc-bad-arg.c similarity index 54% rename from grading/userprog/sc-bad-arg.c rename to src/tests/userprog/sc-bad-arg.c index bfab37b..2df966f 100644 --- a/grading/userprog/sc-bad-arg.c +++ b/src/tests/userprog/sc-bad-arg.c @@ -1,12 +1,11 @@ -#include #include +#include "tests/lib.h" +#include "tests/main.h" -int -main (void) +void +test_main (void) { - printf ("(sc-bad-arg) begin\n"); asm volatile ("mov %%esp, 0xbffffffc; mov [dword ptr %%esp], %0; int 0x30" :: "i" (SYS_exit)); - printf ("(sc-bad-arg) end\n"); - return 0; + fail ("should have called exit(-1)"); } diff --git a/src/tests/userprog/sc-bad-arg.ck b/src/tests/userprog/sc-bad-arg.ck new file mode 100644 index 0000000..a255768 --- /dev/null +++ b/src/tests/userprog/sc-bad-arg.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(sc-bad-arg) begin +sc-bad-arg: exit(-1) +EOF diff --git a/src/tests/userprog/sc-bad-sp.c b/src/tests/userprog/sc-bad-sp.c new file mode 100644 index 0000000..8c42298 --- /dev/null +++ b/src/tests/userprog/sc-bad-sp.c @@ -0,0 +1,9 @@ +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile ("mov %esp, 0x20101234; int 0x30"); + fail ("should have called exit(-1)"); +} diff --git a/src/tests/userprog/sc-bad-sp.ck b/src/tests/userprog/sc-bad-sp.ck new file mode 100644 index 0000000..d0622d4 --- /dev/null +++ b/src/tests/userprog/sc-bad-sp.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(sc-bad-sp) begin +sc-bad-sp: exit(-1) +EOF diff --git a/src/tests/userprog/sc-boundary-2.c b/src/tests/userprog/sc-boundary-2.c new file mode 100644 index 0000000..c287d24 --- /dev/null +++ b/src/tests/userprog/sc-boundary-2.c @@ -0,0 +1,18 @@ +#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 ("mov %%esp, %0; int 0x30" :: "g" (p)); + fail ("should have called exit(67)"); +} diff --git a/src/tests/userprog/sc-boundary-2.ck b/src/tests/userprog/sc-boundary-2.ck new file mode 100644 index 0000000..61c9f23 --- /dev/null +++ b/src/tests/userprog/sc-boundary-2.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(sc-boundary-2) begin +sc-boundary-2: exit(67) +EOF diff --git a/src/tests/userprog/sc-boundary.c b/src/tests/userprog/sc-boundary.c new file mode 100644 index 0000000..10340b4 --- /dev/null +++ b/src/tests/userprog/sc-boundary.c @@ -0,0 +1,19 @@ +#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 ("mov %%esp, %0; int 0x30" :: "g" (p)); + fail ("should have called exit(42)"); +} diff --git a/src/tests/userprog/sc-boundary.ck b/src/tests/userprog/sc-boundary.ck new file mode 100644 index 0000000..f00c3eb --- /dev/null +++ b/src/tests/userprog/sc-boundary.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(sc-boundary) begin +sc-boundary: exit(42) +EOF diff --git a/src/tests/userprog/wait-bad-pid.c b/src/tests/userprog/wait-bad-pid.c new file mode 100644 index 0000000..12a2ae4 --- /dev/null +++ b/src/tests/userprog/wait-bad-pid.c @@ -0,0 +1,8 @@ +#include +#include "tests/main.h" + +void +test_main (void) +{ + wait ((pid_t) 0x0c020301); +} diff --git a/grading/userprog/wait-bad-pid.exp b/src/tests/userprog/wait-bad-pid.ck similarity index 50% rename from grading/userprog/wait-bad-pid.exp rename to src/tests/userprog/wait-bad-pid.ck index 8795167..3c36a53 100644 --- a/grading/userprog/wait-bad-pid.exp +++ b/src/tests/userprog/wait-bad-pid.ck @@ -1,6 +1,12 @@ +# -*- 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) ---OR-- +EOF (wait-bad-pid) begin wait-bad-pid: exit(-1) +EOF diff --git a/src/tests/userprog/wait-killed.c b/src/tests/userprog/wait-killed.c new file mode 100644 index 0000000..bc1e9c7 --- /dev/null +++ b/src/tests/userprog/wait-killed.c @@ -0,0 +1,9 @@ +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("wait(exec()) = %d", wait (exec ("child-bad"))); +} diff --git a/grading/userprog/wait-killed.exp b/src/tests/userprog/wait-killed.ck similarity index 58% rename from grading/userprog/wait-killed.exp rename to src/tests/userprog/wait-killed.ck index 3032421..6bc5c20 100644 --- a/grading/userprog/wait-killed.exp +++ b/src/tests/userprog/wait-killed.ck @@ -1,6 +1,12 @@ +# -*- 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 diff --git a/src/tests/userprog/wait-simple.c b/src/tests/userprog/wait-simple.c new file mode 100644 index 0000000..f4e209b --- /dev/null +++ b/src/tests/userprog/wait-simple.c @@ -0,0 +1,9 @@ +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("wait(exec()) = %d", wait (exec ("child-simple"))); +} diff --git a/grading/userprog/wait-simple.exp b/src/tests/userprog/wait-simple.ck similarity index 59% rename from grading/userprog/wait-simple.exp rename to src/tests/userprog/wait-simple.ck index 082f1a3..8e53e8c 100644 --- a/grading/userprog/wait-simple.exp +++ b/src/tests/userprog/wait-simple.ck @@ -1,6 +1,12 @@ +# -*- 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 diff --git a/src/tests/userprog/wait-twice.c b/src/tests/userprog/wait-twice.c new file mode 100644 index 0000000..9386ffe --- /dev/null +++ b/src/tests/userprog/wait-twice.c @@ -0,0 +1,11 @@ +#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/grading/userprog/wait-twice.exp b/src/tests/userprog/wait-twice.ck similarity index 51% rename from grading/userprog/wait-twice.exp rename to src/tests/userprog/wait-twice.ck index fd6bf29..e013123 100644 --- a/grading/userprog/wait-twice.exp +++ b/src/tests/userprog/wait-twice.ck @@ -1,6 +1,13 @@ +# -*- 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 diff --git a/src/tests/userprog/write-bad-fd.c b/src/tests/userprog/write-bad-fd.c new file mode 100644 index 0000000..af9a7f2 --- /dev/null +++ b/src/tests/userprog/write-bad-fd.c @@ -0,0 +1,16 @@ +#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/grading/userprog/write-bad-fd.exp b/src/tests/userprog/write-bad-fd.ck similarity index 50% rename from grading/userprog/write-bad-fd.exp rename to src/tests/userprog/write-bad-fd.ck index dd05467..23cee06 100644 --- a/grading/userprog/write-bad-fd.exp +++ b/src/tests/userprog/write-bad-fd.ck @@ -1,6 +1,12 @@ +# -*- 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) ---OR-- +EOF (write-bad-fd) begin write-bad-fd: exit(-1) +EOF diff --git a/src/tests/userprog/write-bad-ptr.c b/src/tests/userprog/write-bad-ptr.c new file mode 100644 index 0000000..bf0dead --- /dev/null +++ b/src/tests/userprog/write-bad-ptr.c @@ -0,0 +1,13 @@ +#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/src/tests/userprog/write-bad-ptr.ck b/src/tests/userprog/write-bad-ptr.ck new file mode 100644 index 0000000..84d3372 --- /dev/null +++ b/src/tests/userprog/write-bad-ptr.ck @@ -0,0 +1,14 @@ +# -*- 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 diff --git a/src/tests/userprog/write-boundary.c b/src/tests/userprog/write-boundary.c new file mode 100644 index 0000000..639e981 --- /dev/null +++ b/src/tests/userprog/write-boundary.c @@ -0,0 +1,22 @@ +#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/src/tests/userprog/write-boundary.ck b/src/tests/userprog/write-boundary.ck new file mode 100644 index 0000000..720e3cd --- /dev/null +++ b/src/tests/userprog/write-boundary.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/userprog/write-normal.c b/src/tests/userprog/write-normal.c new file mode 100644 index 0000000..b13da9a --- /dev/null +++ b/src/tests/userprog/write-normal.c @@ -0,0 +1,18 @@ +#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/src/tests/userprog/write-normal.ck b/src/tests/userprog/write-normal.ck new file mode 100644 index 0000000..c78cfb5 --- /dev/null +++ b/src/tests/userprog/write-normal.ck @@ -0,0 +1,11 @@ +# -*- 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 diff --git a/src/tests/userprog/write-stdin.c b/src/tests/userprog/write-stdin.c new file mode 100644 index 0000000..9c42bd2 --- /dev/null +++ b/src/tests/userprog/write-stdin.c @@ -0,0 +1,10 @@ +#include +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char buf = 123; + write (0, &buf, 1); +} diff --git a/src/tests/userprog/write-stdin.ck b/src/tests/userprog/write-stdin.ck new file mode 100644 index 0000000..3aca818 --- /dev/null +++ b/src/tests/userprog/write-stdin.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/userprog/write-zero.c b/src/tests/userprog/write-zero.c new file mode 100644 index 0000000..4634e5b --- /dev/null +++ b/src/tests/userprog/write-zero.c @@ -0,0 +1,17 @@ +#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/src/tests/userprog/write-zero.ck b/src/tests/userprog/write-zero.ck new file mode 100644 index 0000000..5fe4385 --- /dev/null +++ b/src/tests/userprog/write-zero.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/vm/Make.tests b/src/tests/vm/Make.tests new file mode 100644 index 0000000..1c2257b --- /dev/null +++ b/src/tests/vm/Make.tests @@ -0,0 +1,89 @@ +# -*- makefile -*- + +PINTOSFLAGS += --swap-disk=4 + +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 page-linear page-parallel page-merge-seq page-merge-par \ +page-shuffle 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) + +tests/vm_PROGS = $(tests/vm_TESTS) $(addprefix tests/vm/,child-linear \ +child-sort 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/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/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-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-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/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/zeros: + dd if=/dev/zero of=$@ bs=1024 count=6 + + + + diff --git a/src/tests/vm/child-inherit.c b/src/tests/vm/child-inherit.c new file mode 100644 index 0000000..4310520 --- /dev/null +++ b/src/tests/vm/child-inherit.c @@ -0,0 +1,12 @@ +#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/grading/vm/child-linear.c b/src/tests/vm/child-linear.c similarity index 72% rename from grading/vm/child-linear.c rename to src/tests/vm/child-linear.c index 4fa58b4..4fde22c 100644 --- a/grading/vm/child-linear.c +++ b/src/tests/vm/child-linear.c @@ -1,13 +1,15 @@ -#include #include -#include "../lib/arc4.h" +#include "tests/arc4.h" +#include "tests/lib.h" +#include "tests/main.h" -#define SIZE (128 * 1024) +const char *test_name = "child-linear"; +#define SIZE (128 * 1024) static char buf[SIZE]; int -main (int argc, char *argv[]) +main (int argc, char *argv[]) { const char *key = argv[argc - 1]; struct arc4 arc4; @@ -24,10 +26,7 @@ main (int argc, char *argv[]) /* Check that it's all zeros. */ for (i = 0; i < SIZE; i++) if (buf[i] != '\0') - { - printf ("(child-linear) byte %zu != 0\n", i); - return 1; - } + fail ("byte %zu != 0", i); return 0x42; } diff --git a/src/tests/vm/child-mm-wrt.c b/src/tests/vm/child-mm-wrt.c new file mode 100644 index 0000000..9372729 --- /dev/null +++ b/src/tests/vm/child-mm-wrt.c @@ -0,0 +1,19 @@ +#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/src/tests/vm/child-sort.c b/src/tests/vm/child-sort.c new file mode 100644 index 0000000..638a66d --- /dev/null +++ b/src/tests/vm/child-sort.c @@ -0,0 +1,38 @@ +#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/src/tests/vm/mmap-bad-fd.c b/src/tests/vm/mmap-bad-fd.c new file mode 100644 index 0000000..1461996 --- /dev/null +++ b/src/tests/vm/mmap-bad-fd.c @@ -0,0 +1,11 @@ +#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/src/tests/vm/mmap-bad-fd.ck b/src/tests/vm/mmap-bad-fd.ck new file mode 100644 index 0000000..2873af1 --- /dev/null +++ b/src/tests/vm/mmap-bad-fd.ck @@ -0,0 +1,14 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-clean.c b/src/tests/vm/mmap-clean.c new file mode 100644 index 0000000..c01f69c --- /dev/null +++ b/src/tests/vm/mmap-clean.c @@ -0,0 +1,50 @@ +#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/src/tests/vm/mmap-clean.ck b/src/tests/vm/mmap-clean.ck new file mode 100644 index 0000000..29e44aa --- /dev/null +++ b/src/tests/vm/mmap-clean.ck @@ -0,0 +1,15 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-close.c b/src/tests/vm/mmap-close.c new file mode 100644 index 0000000..f353304 --- /dev/null +++ b/src/tests/vm/mmap-close.c @@ -0,0 +1,25 @@ +#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/src/tests/vm/mmap-close.ck b/src/tests/vm/mmap-close.ck new file mode 100644 index 0000000..6c7a199 --- /dev/null +++ b/src/tests/vm/mmap-close.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-exit.c b/src/tests/vm/mmap-exit.c new file mode 100644 index 0000000..202e3c4 --- /dev/null +++ b/src/tests/vm/mmap-exit.c @@ -0,0 +1,19 @@ +#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/src/tests/vm/mmap-exit.ck b/src/tests/vm/mmap-exit.ck new file mode 100644 index 0000000..7f11742 --- /dev/null +++ b/src/tests/vm/mmap-exit.ck @@ -0,0 +1,16 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-inherit.c b/src/tests/vm/mmap-inherit.c new file mode 100644 index 0000000..1c02c24 --- /dev/null +++ b/src/tests/vm/mmap-inherit.c @@ -0,0 +1,29 @@ +#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/src/tests/vm/mmap-inherit.ck b/src/tests/vm/mmap-inherit.ck new file mode 100644 index 0000000..59b5389 --- /dev/null +++ b/src/tests/vm/mmap-inherit.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ( [<<'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 diff --git a/src/tests/vm/mmap-misalign.c b/src/tests/vm/mmap-misalign.c new file mode 100644 index 0000000..d273468 --- /dev/null +++ b/src/tests/vm/mmap-misalign.c @@ -0,0 +1,14 @@ +#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/src/tests/vm/mmap-misalign.ck b/src/tests/vm/mmap-misalign.ck new file mode 100644 index 0000000..fc1234d --- /dev/null +++ b/src/tests/vm/mmap-misalign.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-null.c b/src/tests/vm/mmap-null.c new file mode 100644 index 0000000..98d7269 --- /dev/null +++ b/src/tests/vm/mmap-null.c @@ -0,0 +1,13 @@ +#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/src/tests/vm/mmap-null.ck b/src/tests/vm/mmap-null.ck new file mode 100644 index 0000000..7cfc1f1 --- /dev/null +++ b/src/tests/vm/mmap-null.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-over-code.c b/src/tests/vm/mmap-over-code.c new file mode 100644 index 0000000..e64e449 --- /dev/null +++ b/src/tests/vm/mmap-over-code.c @@ -0,0 +1,17 @@ +#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/src/tests/vm/mmap-over-code.ck b/src/tests/vm/mmap-over-code.ck new file mode 100644 index 0000000..bb8cb2d --- /dev/null +++ b/src/tests/vm/mmap-over-code.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-over-data.c b/src/tests/vm/mmap-over-data.c new file mode 100644 index 0000000..0f18a2f --- /dev/null +++ b/src/tests/vm/mmap-over-data.c @@ -0,0 +1,19 @@ +#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/src/tests/vm/mmap-over-data.ck b/src/tests/vm/mmap-over-data.ck new file mode 100644 index 0000000..15a4f6d --- /dev/null +++ b/src/tests/vm/mmap-over-data.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-over-stk.c b/src/tests/vm/mmap-over-stk.c new file mode 100644 index 0000000..4dbf8f3 --- /dev/null +++ b/src/tests/vm/mmap-over-stk.c @@ -0,0 +1,17 @@ +#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/src/tests/vm/mmap-over-stk.ck b/src/tests/vm/mmap-over-stk.ck new file mode 100644 index 0000000..3b7ac98 --- /dev/null +++ b/src/tests/vm/mmap-over-stk.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-overlap.c b/src/tests/vm/mmap-overlap.c new file mode 100644 index 0000000..8a7a022 --- /dev/null +++ b/src/tests/vm/mmap-overlap.c @@ -0,0 +1,18 @@ +#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/src/tests/vm/mmap-overlap.ck b/src/tests/vm/mmap-overlap.ck new file mode 100644 index 0000000..f8b58ab --- /dev/null +++ b/src/tests/vm/mmap-overlap.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-read.c b/src/tests/vm/mmap-read.c new file mode 100644 index 0000000..e4ca961 --- /dev/null +++ b/src/tests/vm/mmap-read.c @@ -0,0 +1,30 @@ +#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/src/tests/vm/mmap-read.ck b/src/tests/vm/mmap-read.ck new file mode 100644 index 0000000..9978310 --- /dev/null +++ b/src/tests/vm/mmap-read.ck @@ -0,0 +1,10 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-remove.c b/src/tests/vm/mmap-remove.c new file mode 100644 index 0000000..0964e47 --- /dev/null +++ b/src/tests/vm/mmap-remove.c @@ -0,0 +1,40 @@ +#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/src/tests/vm/mmap-remove.ck b/src/tests/vm/mmap-remove.ck new file mode 100644 index 0000000..1b8a311 --- /dev/null +++ b/src/tests/vm/mmap-remove.ck @@ -0,0 +1,13 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-shuffle.c b/src/tests/vm/mmap-shuffle.c new file mode 100644 index 0000000..8ba625f --- /dev/null +++ b/src/tests/vm/mmap-shuffle.c @@ -0,0 +1,35 @@ +#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/src/tests/vm/mmap-shuffle.ck b/src/tests/vm/mmap-shuffle.ck new file mode 100644 index 0000000..fcfa0b2 --- /dev/null +++ b/src/tests/vm/mmap-shuffle.ck @@ -0,0 +1,46 @@ +# -*- 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/src/tests/vm/mmap-twice.ck b/src/tests/vm/mmap-twice.ck new file mode 100644 index 0000000..8fde8df --- /dev/null +++ b/src/tests/vm/mmap-twice.ck @@ -0,0 +1,14 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-unmap.c b/src/tests/vm/mmap-unmap.c new file mode 100644 index 0000000..9a6a173 --- /dev/null +++ b/src/tests/vm/mmap-unmap.c @@ -0,0 +1,20 @@ +#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/src/tests/vm/mmap-unmap.ck b/src/tests/vm/mmap-unmap.ck new file mode 100644 index 0000000..119658c --- /dev/null +++ b/src/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/src/tests/vm/mmap-write.c b/src/tests/vm/mmap-write.c new file mode 100644 index 0000000..b231282 --- /dev/null +++ b/src/tests/vm/mmap-write.c @@ -0,0 +1,28 @@ +#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/src/tests/vm/mmap-write.ck b/src/tests/vm/mmap-write.ck new file mode 100644 index 0000000..d48ce3a --- /dev/null +++ b/src/tests/vm/mmap-write.ck @@ -0,0 +1,12 @@ +# -*- 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 diff --git a/src/tests/vm/mmap-zero.c b/src/tests/vm/mmap-zero.c new file mode 100644 index 0000000..8b399c4 --- /dev/null +++ b/src/tests/vm/mmap-zero.c @@ -0,0 +1,22 @@ +#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/src/tests/vm/mmap-zero.ck b/src/tests/vm/mmap-zero.ck new file mode 100644 index 0000000..1625798 --- /dev/null +++ b/src/tests/vm/mmap-zero.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(mmap-zero) begin +(mmap-zero) create empty file "empty" +(mmap-zero) open "empty" +(mmap-zero) mmap "empty" +mmap-zero: exit(-1) +EOF diff --git a/src/tests/vm/page-linear.c b/src/tests/vm/page-linear.c new file mode 100644 index 0000000..85aeab6 --- /dev/null +++ b/src/tests/vm/page-linear.c @@ -0,0 +1,41 @@ +#include +#include "tests/arc4.h" +#include "tests/lib.h" +#include "tests/main.h" + +#define SIZE (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/src/tests/vm/page-linear.ck b/src/tests/vm/page-linear.ck new file mode 100644 index 0000000..c4a26c8 --- /dev/null +++ b/src/tests/vm/page-linear.ck @@ -0,0 +1,13 @@ +# -*- 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 diff --git a/grading/vm/page-merge-par.c b/src/tests/vm/page-merge-par.c similarity index 59% rename from grading/vm/page-merge-par.c rename to src/tests/vm/page-merge-par.c index 36f7840..0c2cc7f 100644 --- a/grading/vm/page-merge-par.c +++ b/src/tests/vm/page-merge-par.c @@ -1,16 +1,10 @@ #include -#include -#ifdef PINTOS #include -#else -#include "posix-compat.h" -#endif -#include "../lib/arc4.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) +#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. */ @@ -25,7 +19,7 @@ init (void) struct arc4 arc4; size_t i; - printf ("(page-merge-par) init\n"); + msg ("init"); arc4_init (&arc4, "foobar", 6); arc4_crypt (&arc4, buf1, sizeof buf1); @@ -44,53 +38,38 @@ sort_chunks (void) { char fn[128]; char cmd[128]; - int fd; + int handle; - printf ("(page-merge-par) sort chunk %zu\n", i); + msg ("sort chunk %zu", i); /* Write this chunk to a file. */ snprintf (fn, sizeof fn, "buf%zu", i); create (fn, CHUNK_SIZE); - fd = open (fn); - if (fd < 0) - { - printf ("(page-merge-par) open() failed\n"); - exit (1); - } - write (fd, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); - close (fd); + 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, "child-sort %s", fn); - children[i] = exec (cmd); - if (children[i] == -1) - { - printf ("(page-merge-par) exec() failed\n"); - exit (1); - } + CHECK ((children[i] = exec (cmd)) != -1, "exec \"%s\"", cmd); + quiet = false; } for (i = 0; i < CHUNK_CNT; i++) { char fn[128]; - int fd; + int handle; - if (wait (children[i]) != 123) - { - printf ("(page-merge-par) wait(exec()) returned bad value\n"); - exit (1); - } + CHECK (wait (children[i]) == 123, "wait for child %zu", i); /* Read chunk back from file. */ + quiet = true; snprintf (fn, sizeof fn, "buf%zu", i); - fd = open (fn); - if (fd < 0) - { - printf ("(page-merge-par) open() failed\n"); - exit (1); - } - read (fd, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); - close (fd); + CHECK ((handle = open (fn)) > 1, "open \"%s\"", fn); + read (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); + close (handle); + quiet = false; } } @@ -103,7 +82,7 @@ merge (void) unsigned char *op; size_t i; - printf ("(page-merge-par) merge\n"); + msg ("merge"); /* Initialize merge pointers. */ mp_left = CHUNK_CNT; @@ -136,7 +115,7 @@ verify (void) size_t buf_idx; size_t hist_idx; - printf ("(page-merge-par) verify\n"); + msg ("verify"); buf_idx = 0; for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram; @@ -145,26 +124,19 @@ verify (void) while (histogram[hist_idx]-- > 0) { if (buf2[buf_idx] != hist_idx) - { - printf ("(page-merge-par) bad value %d in byte %zu\n", - buf2[buf_idx], buf_idx); - exit (1); - } + fail ("bad value %d in byte %zu", buf2[buf_idx], buf_idx); buf_idx++; } } - printf ("(page-merge-par) success, buf_idx=%zu\n", buf_idx); + msg ("success, buf_idx=%'zu", buf_idx); } -int -main (void) +void +test_main (void) { - printf ("(page-merge-par) begin\n"); init (); sort_chunks (); merge (); verify (); - printf ("(page-merge-par) end\n"); - return 0; } diff --git a/src/tests/vm/page-merge-par.ck b/src/tests/vm/page-merge-par.ck new file mode 100644 index 0000000..9f6205a --- /dev/null +++ b/src/tests/vm/page-merge-par.ck @@ -0,0 +1,28 @@ +# -*- 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 diff --git a/grading/vm/page-merge-seq.c b/src/tests/vm/page-merge-seq.c similarity index 57% rename from grading/vm/page-merge-seq.c rename to src/tests/vm/page-merge-seq.c index 34d14f6..f41de2b 100644 --- a/grading/vm/page-merge-seq.c +++ b/src/tests/vm/page-merge-seq.c @@ -1,11 +1,7 @@ -#include -#include -#ifdef PINTOS #include -#else -#include "posix-compat.h" -#endif -#include "../lib/arc4.h" +#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 @@ -25,7 +21,7 @@ init (void) struct arc4 arc4; size_t i; - printf ("page-merge-seq) init\n"); + msg ("init"); arc4_init (&arc4, "foobar", 6); arc4_crypt (&arc4, buf1, sizeof buf1); @@ -42,43 +38,28 @@ sort_chunks (void) create ("buffer", CHUNK_SIZE); for (i = 0; i < CHUNK_CNT; i++) { - int fd; + pid_t child; + int handle; - printf ("(page-merge-seq) sort chunk %zu\n", i); + msg ("sort chunk %zu", i); /* Write this chunk to a file. */ - fd = open ("buffer"); - - if (fd < 0) - { - printf ("(page-merge-seq) open() failed\n"); - exit (1); - } - write (fd, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); - close (fd); + quiet = true; + CHECK ((handle = open ("buffer")) > 1, "open \"buffer\""); + write (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); + close (handle); /* Sort with subprocess. */ - pid_t child = exec ("child-sort buffer"); - if (child == -1) - { - printf ("(page-merge-seq) exec() failed\n"); - exit (1); - } - if (wait (child) != 123) - { - printf ("(page-merge-seq) wait(exec()) returned bad value\n"); - exit (1); - } + CHECK ((child = exec ("child-sort buffer")) != -1, + "exec \"child-sort buffer\""); + CHECK (wait (child) == 123, "wait for child-sort"); /* Read chunk back from file. */ - fd = open ("buffer"); - if (fd < 0) - { - printf ("(page-merge-seq) open() failed\n"); - exit (1); - } - read (fd, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); - close (fd); + CHECK ((handle = open ("buffer")) > 1, "open \"buffer\""); + read (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); + close (handle); + + quiet = false; } } @@ -91,7 +72,7 @@ merge (void) unsigned char *op; size_t i; - printf ("(page-merge-seq) merge\n"); + msg ("merge"); /* Initialize merge pointers. */ mp_left = CHUNK_CNT; @@ -112,9 +93,9 @@ merge (void) *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]; + Delete this chunk from the set if it's emptied. */ + if ((++mp[min] - buf1) % CHUNK_SIZE == 0) + mp[min] = mp[--mp_left]; } } @@ -124,7 +105,7 @@ verify (void) size_t buf_idx; size_t hist_idx; - printf ("(page-merge-seq) verify\n"); + msg ("verify"); buf_idx = 0; for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram; @@ -133,26 +114,19 @@ verify (void) while (histogram[hist_idx]-- > 0) { if (buf2[buf_idx] != hist_idx) - { - printf ("(page-merge-seq) bad value %d in byte %zu\n", - buf2[buf_idx], buf_idx); - exit (1); - } + fail ("bad value %d in byte %zu", buf2[buf_idx], buf_idx); buf_idx++; } } - printf ("(page-merge-seq) success, buf_idx=%zu\n", buf_idx); + msg ("success, buf_idx=%'zu", buf_idx); } -int -main (void) +void +test_main (void) { - printf ("(page-merge-seq) begin\n"); init (); sort_chunks (); merge (); verify (); - printf ("(page-merge-seq) end\n"); - return 0; } diff --git a/grading/vm/page-merge-seq.exp b/src/tests/vm/page-merge-seq.ck similarity index 76% rename from grading/vm/page-merge-seq.exp rename to src/tests/vm/page-merge-seq.ck index db78189..7bfe275 100644 --- a/grading/vm/page-merge-seq.exp +++ b/src/tests/vm/page-merge-seq.ck @@ -1,5 +1,10 @@ +# -*- 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) init (page-merge-seq) sort chunk 0 (page-merge-seq) sort chunk 1 (page-merge-seq) sort chunk 2 @@ -18,5 +23,6 @@ page-merge-seq) init (page-merge-seq) sort chunk 15 (page-merge-seq) merge (page-merge-seq) verify -(page-merge-seq) success, buf_idx=1032192 +(page-merge-seq) success, buf_idx=1,032,192 (page-merge-seq) end +EOF diff --git a/src/tests/vm/page-parallel.c b/src/tests/vm/page-parallel.c new file mode 100644 index 0000000..c7dd829 --- /dev/null +++ b/src/tests/vm/page-parallel.c @@ -0,0 +1,19 @@ +#include +#include "tests/lib.h" +#include "tests/main.h" + +#define CHILD_CNT 3 + +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/src/tests/vm/page-parallel.ck b/src/tests/vm/page-parallel.ck new file mode 100644 index 0000000..14b37df --- /dev/null +++ b/src/tests/vm/page-parallel.ck @@ -0,0 +1,14 @@ +# -*- 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) wait for child 0 +(page-parallel) wait for child 1 +(page-parallel) wait for child 2 +(page-parallel) end +EOF diff --git a/src/tests/vm/page-shuffle.c b/src/tests/vm/page-shuffle.c new file mode 100644 index 0000000..c0caa57 --- /dev/null +++ b/src/tests/vm/page-shuffle.c @@ -0,0 +1,27 @@ +#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/src/tests/vm/page-shuffle.ck b/src/tests/vm/page-shuffle.ck new file mode 100644 index 0000000..88ec934 --- /dev/null +++ b/src/tests/vm/page-shuffle.ck @@ -0,0 +1,43 @@ +# -*- 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 "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/src/tests/vm/pt-bad-read.ck b/src/tests/vm/pt-bad-read.ck new file mode 100644 index 0000000..35543fe --- /dev/null +++ b/src/tests/vm/pt-bad-read.ck @@ -0,0 +1,9 @@ +# -*- 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 diff --git a/src/tests/vm/pt-big-stk-obj.c b/src/tests/vm/pt-big-stk-obj.c new file mode 100644 index 0000000..a417a7e --- /dev/null +++ b/src/tests/vm/pt-big-stk-obj.c @@ -0,0 +1,17 @@ +#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/src/tests/vm/pt-big-stk-obj.ck b/src/tests/vm/pt-big-stk-obj.ck new file mode 100644 index 0000000..bfaa59a --- /dev/null +++ b/src/tests/vm/pt-big-stk-obj.ck @@ -0,0 +1,9 @@ +# -*- 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 diff --git a/src/tests/vm/pt-grow-bad.c b/src/tests/vm/pt-grow-bad.c new file mode 100644 index 0000000..2629574 --- /dev/null +++ b/src/tests/vm/pt-grow-bad.c @@ -0,0 +1,13 @@ +#include +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + /* Read from an address 4,096 bytes below the stack pointer. + Must kill the program. */ + asm volatile ("mov %eax, [%esp - 4096]"); +} diff --git a/src/tests/vm/pt-grow-bad.ck b/src/tests/vm/pt-grow-bad.ck new file mode 100644 index 0000000..cc3d31e --- /dev/null +++ b/src/tests/vm/pt-grow-bad.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(pt-grow-bad) begin +pt-grow-bad: exit(-1) +EOF diff --git a/src/tests/vm/pt-grow-pusha.c b/src/tests/vm/pt-grow-pusha.c new file mode 100644 index 0000000..61efb01 --- /dev/null +++ b/src/tests/vm/pt-grow-pusha.c @@ -0,0 +1,16 @@ +#include +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile + ("mov %%eax, %%esp;" /* Save a copy of the stack pointer. */ + "and %%esp, 0xfffff000;" /* Move stack pointer to bottom of page. */ + "pusha;" /* Push 32 bytes on stack at once. */ + "mov %%esp, %%eax" /* Restore copied stack pointer. */ + ::: "eax"); /* Tell GCC we modified eax. */ +} diff --git a/src/tests/vm/pt-grow-pusha.ck b/src/tests/vm/pt-grow-pusha.ck new file mode 100644 index 0000000..8653888 --- /dev/null +++ b/src/tests/vm/pt-grow-pusha.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(pt-grow-pusha) begin +(pt-grow-pusha) end +EOF diff --git a/src/tests/vm/pt-grow-stack.c b/src/tests/vm/pt-grow-stack.c new file mode 100644 index 0000000..d9b708a --- /dev/null +++ b/src/tests/vm/pt-grow-stack.c @@ -0,0 +1,17 @@ +#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/src/tests/vm/pt-grow-stack.ck b/src/tests/vm/pt-grow-stack.ck new file mode 100644 index 0000000..92d8f93 --- /dev/null +++ b/src/tests/vm/pt-grow-stack.ck @@ -0,0 +1,9 @@ +# -*- 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 diff --git a/src/tests/vm/pt-write-code-2.c b/src/tests/vm/pt-write-code-2.c new file mode 100644 index 0000000..282667a --- /dev/null +++ b/src/tests/vm/pt-write-code-2.c @@ -0,0 +1,12 @@ +#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/src/tests/vm/pt-write-code.c b/src/tests/vm/pt-write-code.c new file mode 100644 index 0000000..4c580b5 --- /dev/null +++ b/src/tests/vm/pt-write-code.c @@ -0,0 +1,9 @@ +#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/src/tests/vm/pt-write-code.ck b/src/tests/vm/pt-write-code.ck new file mode 100644 index 0000000..65610fb --- /dev/null +++ b/src/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/src/tests/vm/pt-write-code2.ck b/src/tests/vm/pt-write-code2.ck new file mode 100644 index 0000000..ac8ff2a --- /dev/null +++ b/src/tests/vm/pt-write-code2.ck @@ -0,0 +1,9 @@ +# -*- 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 diff --git a/grading/vm/sample.inc b/src/tests/vm/sample.inc similarity index 100% rename from grading/vm/sample.inc rename to src/tests/vm/sample.inc diff --git a/grading/vm/sample.txt b/src/tests/vm/sample.txt similarity index 100% rename from grading/vm/sample.txt rename to src/tests/vm/sample.txt diff --git a/src/threads/Make.vars b/src/threads/Make.vars index 55ef2b2..24e7762 100644 --- a/src/threads/Make.vars +++ b/src/threads/Make.vars @@ -1,2 +1,5 @@ -DEFINES = -SUBDIRS = threads devices lib lib/kernel +# -*- makefile -*- + +os.dsk: DEFINES = +KERNEL_SUBDIRS = threads devices lib lib/kernel $(TEST_SUBDIRS) +TEST_SUBDIRS = tests/threads diff --git a/src/threads/flags.h b/src/threads/flags.h index dd83ed2..5654ac7 100644 --- a/src/threads/flags.h +++ b/src/threads/flags.h @@ -1,5 +1,5 @@ #ifndef THREADS_FLAGS_H -#define THREADS_FLAGS_H 1 +#define THREADS_FLAGS_H /* EFLAGS Register. */ #define FLAG_MBS 0x00000002 /* Must be set. */ diff --git a/src/threads/init.c b/src/threads/init.c index 51d6bdd..8ee50a2 100644 --- a/src/threads/init.c +++ b/src/threads/init.c @@ -18,7 +18,6 @@ #include "threads/malloc.h" #include "threads/mmu.h" #include "threads/palloc.h" -#include "threads/test.h" #include "threads/thread.h" #ifdef USERPROG #include "userprog/process.h" @@ -26,6 +25,8 @@ #include "userprog/gdt.h" #include "userprog/syscall.h" #include "userprog/tss.h" +#else +#include "tests/threads/tests.h" #endif #ifdef FILESYS #include "devices/disk.h" @@ -39,18 +40,13 @@ size_t ram_pages; /* Page directory with kernel mappings only. */ uint32_t *base_page_dir; -/* -o mlfqs: +/* -mlfqs: If false (default), use round-robin scheduler. If true, use multi-level feedback queue scheduler. */ bool enable_mlfqs; -#ifdef USERPROG -/* -ex: Initial program to run. */ -static char *initial_program; -#endif - #ifdef VM -/* -o random-paging: +/* -rndpg: If false (default), use LRU page replacement policy. If true, use random page replacement policy. */ bool enable_random_paging; @@ -66,14 +62,23 @@ bool power_off_when_done; static void ram_init (void); static void paging_init (void); -static void argv_init (void); + +static char **read_command_line (void); +static char **parse_options (char **argv); +static void run_actions (char **argv); +static void usage (void); + static void print_stats (void); + int main (void) NO_RETURN; +/* Pintos main program. */ int main (void) { + char **argv; + /* Clear BSS and get machine's RAM size. */ ram_init (); @@ -88,8 +93,9 @@ main (void) /* Greet user. */ printf ("Pintos booting with %'zu kB RAM...\n", ram_pages * PGSIZE / 1024); - /* Parse command line. */ - argv_init (); + /* Break command line into arguments and parse options. */ + argv = read_command_line (); + argv = parse_options (argv); /* Initialize memory system. */ palloc_init (); @@ -102,7 +108,7 @@ main (void) gdt_init (); #endif - /* Set random seed if argv_init() didn't. */ + /* Set random seed if parse_options() didn't. */ random_init (0); /* Initialize interrupt handlers. */ @@ -123,28 +129,17 @@ main (void) /* Initialize filesystem. */ disk_init (); filesys_init (format_filesys); - fsutil_run (); #endif printf ("Boot complete.\n"); -#ifdef USERPROG - /* Run a user program. */ - if (initial_program != NULL) - { - printf ("\nExecuting '%s':\n", initial_program); - process_wait (process_execute (initial_program)); - } -#else - /* Run the compiled-in test function. */ - test (); -#endif + /* Run actions specified on kernel command line. */ + run_actions (argv); /* Finish up. */ - if (power_off_when_done) + if (power_off_when_done) power_off (); - else - thread_exit (); + thread_exit (); } /* Clear BSS and obtain RAM size from loader. */ @@ -161,7 +156,7 @@ ram_init (void) memset (&_start_bss, 0, &_end_bss - &_start_bss); /* Get RAM size from loader. See loader.S. */ - ram_pages = *(uint32_t *) ptov (LOADER_RAM_PAGES); + ram_pages = *(uint32_t *) ptov (LOADER_RAM_PGS); } /* Populates the base page directory and page table with the @@ -204,105 +199,182 @@ paging_init (void) asm volatile ("mov %%cr3, %0" :: "r" (vtop (base_page_dir))); } -/* Parses the command line. */ -static void -argv_init (void) +/* Breaks the kernel command line into words and returns them as + an argv-like array. */ +static char ** +read_command_line (void) { - char *cmd_line, *pos; - char *argv[LOADER_CMD_LINE_LEN / 2 + 2]; - int argc = 0; + static char *argv[LOADER_ARGS_LEN / 2 + 1]; + char *p, *end; + int argc; int i; - /* The command line is made up of null terminated strings - followed by an empty string. Break it up into words. */ - cmd_line = pos = ptov (LOADER_CMD_LINE); - printf ("Kernel command line:"); - while (pos < cmd_line + LOADER_CMD_LINE_LEN) + argc = *(uint32_t *) ptov (LOADER_ARG_CNT); + p = ptov (LOADER_ARGS); + end = p + LOADER_ARGS_LEN; + for (i = 0; i < argc; i++) { - ASSERT (argc < LOADER_CMD_LINE_LEN / 2); - if (*pos == '\0') - break; - argv[argc++] = pos; - printf (" %s", pos); - pos = strchr (pos, '\0') + 1; + if (p >= end) + PANIC ("command line arguments overflow"); + + argv[i] = p; + p += strnlen (p, end - p) + 1; } - printf ("\n"); - argv[argc] = ""; - argv[argc + 1] = ""; + argv[argc] = NULL; - /* Parse the words. */ + /* Print kernel command line. */ + printf ("Kernel command line:"); for (i = 0; i < argc; i++) - if (!strcmp (argv[i], "-o")) - { - i++; - if (!strcmp (argv[i], "mlfqs")) - enable_mlfqs = true; + 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); + + if (!strcmp (name, "-h")) + usage (); + else if (!strcmp (name, "-q")) + power_off_when_done = true; +#ifdef FILESYS + else if (!strcmp (name, "-f")) + format_filesys = true; +#endif + else if (!strcmp (name, "-rs")) + random_init (atoi (value)); + else if (!strcmp (name, "-mlfqs")) + enable_mlfqs = true; +#ifdef USERPROG + else if (!strcmp (name, "-ul")) + user_page_limit = atoi (value); +#endif #ifdef VM - else if (!strcmp (argv[i], "random-paging")) - enable_random_paging = true; + else if (!strcmp (name, "-rndpg")) + enable_random_paging = true; +#endif + else + PANIC ("unknown option `%s' (use -h for help)", name); + + } + + 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 + 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}, + {"put", 2, fsutil_put}, + {"get", 2, fsutil_get}, #endif - else - PANIC ("unknown option `-o %s' (use -u for help)", argv[i]); - } - else if (!strcmp (argv[i], "-rs")) - random_init (atoi (argv[++i])); - else if (!strcmp (argv[i], "-q")) - power_off_when_done = true; + {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 - else if (!strcmp (argv[i], "-ex")) - initial_program = argv[++i]; - else if (!strcmp (argv[i], "-ul")) - user_page_limit = atoi (argv[++i]); + " run 'PROG [ARG...]' Run PROG and wait for it to complete.\n" +#else + " run TEST Run TEST.\n" #endif #ifdef FILESYS - else if (!strcmp (argv[i], "-f")) - format_filesys = true; - else if (!strcmp (argv[i], "-ci")) - { - fsutil_copyin_file = argv[++i]; - fsutil_copyin_size = atoi (argv[++i]); - } - else if (!strcmp (argv[i], "-co")) - fsutil_copyout_file = argv[++i]; - else if (!strcmp (argv[i], "-p")) - fsutil_print_file = argv[++i]; - else if (!strcmp (argv[i], "-r")) - fsutil_remove_file = argv[++i]; - else if (!strcmp (argv[i], "-ls")) - fsutil_list_files = true; + " 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" + " put FILE Put FILE into file system from scratch disk.\n" + " get FILE Get FILE from file system into scratch disk.\n" #endif - else if (!strcmp (argv[i], "-u")) - { - printf ( - "Kernel options:\n" - " -o mlfqs Use multi-level feedback queue scheduler.\n" + "\nOptions:\n" + " -h Print this help message and power off.\n" + " -q Power off VM after actions or on panic.\n" + " -f Format file system disk during startup.\n" + " -rs=SEED Set random number seed to SEED.\n" + " -mlfqs Use multi-level feedback queue scheduler.\n" #ifdef USERPROG - " -ex 'PROG [ARG...]' Run PROG, passing the optional arguments.\n" - " -ul USER_MAX Limit user memory to USER_MAX pages.\n" + " -ul=COUNT Limit user memory to COUNT pages.\n" #endif #ifdef VM - " -o random-paging Use random page replacement policy.\n" + " -rndpg Use random page replacement policy.\n" #endif -#ifdef FILESYS - " -f Format the filesystem disk (hdb or hd0:1).\n" - " -ci FILE SIZE Copy SIZE bytes from the scratch disk (hdc\n" - " or hd1:0) into the filesystem as FILE.\n" - " -co FILE Copy FILE to the scratch disk, with\n" - " size at start of sector 0 and data after.\n" - " -p FILE Print the contents of FILE.\n" - " -r FILE Delete FILE.\n" - " -ls List files in the root directory.\n" -#endif - " -rs SEED Set random seed to SEED.\n" - " -q Power off after doing requested actions.\n" - " -u Print this help message and power off.\n" ); - power_off (); - } - else - PANIC ("unknown option `%s' (use -u for help)", argv[i]); + power_off (); } + /* Powers down the machine we're running on, as long as we're running on Bochs or qemu. */ void diff --git a/src/threads/interrupt.c b/src/threads/interrupt.c index 3e52228..82b7db7 100644 --- a/src/threads/interrupt.c +++ b/src/threads/interrupt.c @@ -137,9 +137,37 @@ intr_init (void) intr_names[19] = "#XF SIMD Floating-Point Exception"; } -/* Registers interrupt VEC_NO to invoke HANDLER, which is named - NAME for debugging purposes. The interrupt handler will be - invoked with interrupt status set to LEVEL. +/* 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 @@ -149,27 +177,11 @@ intr_init (void) still cause interrupts with DPL==0 to be invoked. See [IA32-v3] sections 4.5 and 4.8.1.1 for further discussion. */ void -intr_register (uint8_t vec_no, int dpl, enum intr_level level, - intr_handler_func *handler, - const char *name) +intr_register_int (uint8_t vec_no, int dpl, enum intr_level level, + intr_handler_func *handler, const char *name) { - /* Make sure this handler isn't already registered to someone - else. */ - ASSERT (intr_handlers[vec_no] == NULL); - - /* Interrupts generated by external hardware (0x20 <= VEC_NO <= - 0x2f) should specify INTR_OFF for LEVEL. Otherwise a timer - interrupt could cause a task switch during interrupt - handling. Most other interrupts can and should be handled - with interrupts enabled. */ - ASSERT (vec_no < 0x20 || vec_no > 0x2f || level == INTR_OFF); - - 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; + ASSERT (vec_no < 0x20 || vec_no > 0x2f); + register_handler (vec_no, dpl, level, handler, name); } /* Returns true during processing of an external interrupt diff --git a/src/threads/interrupt.h b/src/threads/interrupt.h index b805671..d43e06d 100644 --- a/src/threads/interrupt.h +++ b/src/threads/interrupt.h @@ -58,8 +58,9 @@ struct intr_frame typedef void intr_handler_func (struct intr_frame *); void intr_init (void); -void intr_register (uint8_t vec, int dpl, enum intr_level, intr_handler_func *, - const char *name); +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); diff --git a/src/threads/loader.S b/src/threads/loader.S index 11ab7c9..796dca8 100644 --- a/src/threads/loader.S +++ b/src/threads/loader.S @@ -125,7 +125,7 @@ start: jbe 1f mov eax, 0x10000 1: shr eax, 2 # Total 4 kB pages - mov ram_pages, eax + mov ram_pgs, eax #### Create temporary page directory and page table and set page #### directory base register. @@ -304,7 +304,7 @@ gdtdesc: #### Print panicmsg (with help from the BIOS) and spin. panic: .code16 # We only panic in real mode. - mov si, offset panicmsg + mov si, offset panic_message mov ah, 0xe sub bh, bh 1: lodsb @@ -313,22 +313,27 @@ panic: .code16 # We only panic in real mode. int 0x10 jmp 1b -panicmsg: +panic_message: .ascii "Panic!" .byte 0 -#### Memory size in 4 kB pages. - .org LOADER_RAM_PAGES - LOADER_BASE -ram_pages: +#### Physical memory size in 4 kB pages. +#### This is initialized by the loader and read by the kernel. + .org LOADER_RAM_PGS - LOADER_BASE +ram_pgs: .long 0 -#### Command-line arguments inserted by another utility. -#### The loader doesn't use these, but we note their -#### location here for easy reference. - .org LOADER_CMD_LINE - LOADER_BASE -cmd_line: +#### 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 +arg_cnt: + .long 0 + .org LOADER_ARGS - LOADER_BASE +args: .fill 0x80, 1, 0 -#### Boot-sector signature for BIOS inspection. - .org LOADER_BIOS_SIG - LOADER_BASE +#### Boot-sector signature. +#### The BIOS checks that this is set properly. + .org LOADER_SIG - LOADER_BASE .word 0xaa55 diff --git a/src/threads/loader.h b/src/threads/loader.h index b412af3..5bd9813 100644 --- a/src/threads/loader.h +++ b/src/threads/loader.h @@ -17,12 +17,17 @@ This must be aligned on a 4 MB boundary. */ #define LOADER_PHYS_BASE 0xc0000000 /* 3 GB. */ -/* Offsets within the loader. */ -#define LOADER_BIOS_SIG (LOADER_END - 2) /* 0xaa55 BIOS signature. */ -#define LOADER_CMD_LINE_LEN 0x80 /* Command line length. */ -#define LOADER_CMD_LINE (LOADER_BIOS_SIG - LOADER_CMD_LINE_LEN) - /* Kernel command line. */ -#define LOADER_RAM_PAGES (LOADER_CMD_LINE - 4) /* # of pages of RAM. */ +/* Important loader physical addresses. */ +#define LOADER_SIG (LOADER_END - LOADER_SIG_LEN) /* 0xaa55 BIOS signature. */ +#define LOADER_ARGS (LOADER_SIG - LOADER_ARGS_LEN) /* Command-line args. */ +#define LOADER_ARG_CNT (LOADER_ARGS - LOADER_ARG_CNT_LEN) /* Number of args. */ +#define LOADER_RAM_PGS (LOADER_ARG_CNT - LOADER_RAM_PGS_LEN) /* # RAM pages. */ + +/* Sizes of loader data structures. */ +#define LOADER_SIG_LEN 2 +#define LOADER_ARGS_LEN 128 +#define LOADER_ARG_CNT_LEN 4 +#define LOADER_RAM_PGS_LEN 4 /* GDT selectors defined by loader. More selectors are defined by userprog/gdt.h. */ diff --git a/src/threads/malloc.c b/src/threads/malloc.c index f74d439..2bb5571 100644 --- a/src/threads/malloc.c +++ b/src/threads/malloc.c @@ -80,7 +80,7 @@ malloc_init (void) d->block_size = block_size; d->blocks_per_arena = (PGSIZE - sizeof (struct arena)) / block_size; list_init (&d->free_list); - lock_init (&d->lock, "malloc"); + lock_init (&d->lock); } } @@ -174,53 +174,93 @@ calloc (size_t a, size_t b) 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() or calloc(). */ void free (void *p) { - struct block *b; - struct arena *a; - struct desc *d; - - if (p == NULL) - return; - - b = p; - a = block_to_arena (b); - d = a->desc; - - if (d == NULL) + if (p != NULL) { - /* It's a big block. Free its pages. */ - palloc_free_multiple (a, a->free_cnt); - return; - } + 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 - memset (b, 0xcc, d->block_size); + /* Clear the block to help detect use-after-free bugs. */ + memset (b, 0xcc, d->block_size); #endif - lock_acquire (&d->lock); + lock_acquire (&d->lock); - /* Add block to free list. */ - list_push_front (&d->free_list, &b->free_elem); + /* 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; + /* 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++) + 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 { - struct block *b = arena_to_block (a, i); - list_remove (&b->free_elem); + /* It's a big block. Free its pages. */ + palloc_free_multiple (a, a->free_cnt); + return; } - palloc_free_page (a); } - - lock_release (&d->lock); } /* Returns the arena that block B is inside. */ diff --git a/src/threads/malloc.h b/src/threads/malloc.h index 2f19155..bc55d36 100644 --- a/src/threads/malloc.h +++ b/src/threads/malloc.h @@ -7,6 +7,7 @@ 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/src/threads/mmu.h b/src/threads/mmu.h index 64d82da..6f8741c 100644 --- a/src/threads/mmu.h +++ b/src/threads/mmu.h @@ -106,6 +106,20 @@ static inline void *pg_round_down (const void *va) { 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 * @@ -121,7 +135,7 @@ ptov (uintptr_t paddr) static inline uintptr_t vtop (const void *vaddr) { - ASSERT (vaddr >= PHYS_BASE); + ASSERT (is_kernel_vaddr (vaddr)); return (uintptr_t) vaddr - (uintptr_t) PHYS_BASE; } @@ -169,7 +183,7 @@ static inline uint32_t *pde_get_pt (uint32_t pde) { } /* Obtains page table index from a virtual address. */ -static inline unsigned pt_no (void *va) { +static inline unsigned pt_no (const void *va) { return ((uintptr_t) va & PTMASK) >> PTSHIFT; } @@ -190,10 +204,9 @@ static inline uint32_t pte_create_user (uint32_t *page, bool writable) { return pte_create_kernel (page, writable) | PG_U; } -/* Returns a pointer to the page that page table entry PTE, which - must "present", points to. */ +/* Returns a pointer to the page that page table entry PTE points + to. */ static inline void *pte_get_page (uint32_t pte) { - ASSERT (pte & PG_P); return ptov (pte & ~PGMASK); } diff --git a/src/threads/palloc.c b/src/threads/palloc.c index 545a7d4..cef065a 100644 --- a/src/threads/palloc.c +++ b/src/threads/palloc.c @@ -162,7 +162,7 @@ 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_needed_bytes (page_cnt), PGSIZE); + 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; @@ -170,9 +170,8 @@ init_pool (struct pool *p, void *base, size_t page_cnt, const char *name) printf ("%zu pages available in %s.\n", page_cnt, name); /* Initialize the pool. */ - lock_init (&p->lock, name); - p->used_map = bitmap_create_preallocated (page_cnt, base, - bm_pages * PGSIZE); + lock_init (&p->lock); + p->used_map = bitmap_create_in_buf (page_cnt, base, bm_pages * PGSIZE); p->base = base + bm_pages * PGSIZE; } diff --git a/src/threads/synch.c b/src/threads/synch.c index 1504d98..79dcfb7 100644 --- a/src/threads/synch.c +++ b/src/threads/synch.c @@ -32,9 +32,9 @@ #include "threads/interrupt.h" #include "threads/thread.h" -/* Initializes semaphore SEMA to VALUE and names it NAME (for - debugging purposes only). A semaphore is a nonnegative - integer along with two atomic operators for manipulating it: +/* 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. @@ -42,12 +42,10 @@ - up or "V": increment the value (and wake up one waiting thread, if any). */ void -sema_init (struct semaphore *sema, unsigned value, const char *name) +sema_init (struct semaphore *sema, unsigned value) { ASSERT (sema != NULL); - ASSERT (name != NULL); - strlcpy (sema->name, name, sizeof sema->name); sema->value = value; list_init (&sema->waiters); } @@ -77,6 +75,32 @@ sema_down (struct semaphore *sema) 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. @@ -96,13 +120,6 @@ sema_up (struct semaphore *sema) intr_set_level (old_level); } -/* Return SEMA's name (for debugging purposes). */ -const char * -sema_name (const struct semaphore *sema) -{ - return sema->name; -} - static void sema_test_helper (void *sema_); /* Self-test for semaphores that makes control "ping-pong" @@ -115,8 +132,8 @@ sema_self_test (void) int i; printf ("Testing semaphores..."); - sema_init (&sema[0], 0, "ping"); - sema_init (&sema[1], 0, "pong"); + 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++) { @@ -140,11 +157,10 @@ sema_test_helper (void *sema_) } } -/* Initializes LOCK and names it NAME (for debugging purposes). - 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. +/* 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 @@ -157,13 +173,12 @@ sema_test_helper (void *sema_) onerous, it's a good sign that a semaphore should be used, instead of a lock. */ void -lock_init (struct lock *lock, const char *name) +lock_init (struct lock *lock) { ASSERT (lock != NULL); - ASSERT (name != NULL); lock->holder = NULL; - sema_init (&lock->semaphore, 1, name); + sema_init (&lock->semaphore, 1); } /* Acquires LOCK, sleeping until it becomes available if @@ -189,6 +204,30 @@ lock_acquire (struct lock *lock) intr_set_level (old_level); } +/* 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 + interupt handler. */ +bool +lock_try_acquire (struct lock *lock) +{ + enum intr_level old_level; + bool success; + + ASSERT (lock != NULL); + ASSERT (!lock_held_by_current_thread (lock)); + + old_level = intr_disable (); + success = sema_try_down (&lock->semaphore); + if (success) + lock->holder = thread_current (); + intr_set_level (old_level); + + return success; +} + /* Releases LOCK, which must be owned by the current thread. An interrupt handler cannot acquire a lock, so it does not @@ -218,15 +257,6 @@ lock_held_by_current_thread (const struct lock *lock) return lock->holder == thread_current (); } - -/* Returns the name of LOCK (for debugging purposes). */ -const char * -lock_name (const struct lock *lock) -{ - ASSERT (lock != NULL); - - return sema_name (&lock->semaphore); -} /* One semaphore in a list. */ struct semaphore_elem @@ -235,17 +265,14 @@ struct semaphore_elem struct semaphore semaphore; /* This semaphore. */ }; -/* Initializes condition variable COND and names it NAME. A - condition variable allows one piece of code to signal a - condition and cooperating code to receive the signal and act - upon it. */ +/* 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, const char *name) +cond_init (struct condition *cond) { ASSERT (cond != NULL); - ASSERT (name != NULL); - strlcpy (cond->name, name, sizeof cond->name); list_init (&cond->waiters); } @@ -279,7 +306,7 @@ cond_wait (struct condition *cond, struct lock *lock) ASSERT (!intr_context ()); ASSERT (lock_held_by_current_thread (lock)); - sema_init (&waiter.semaphore, 0, "condition"); + sema_init (&waiter.semaphore, 0); list_push_back (&cond->waiters, &waiter.elem); lock_release (lock); sema_down (&waiter.semaphore); @@ -294,7 +321,7 @@ cond_wait (struct condition *cond, struct lock *lock) make sense to try to signal a condition variable within an interrupt handler. */ void -cond_signal (struct condition *cond, struct lock *lock) +cond_signal (struct condition *cond, struct lock *lock UNUSED) { ASSERT (cond != NULL); ASSERT (lock != NULL); @@ -321,12 +348,3 @@ cond_broadcast (struct condition *cond, struct lock *lock) while (!list_empty (&cond->waiters)) cond_signal (cond, lock); } - -/* Returns COND's name (for debugging purposes). */ -const char * -cond_name (const struct condition *cond) -{ - ASSERT (cond != NULL); - - return cond->name; -} diff --git a/src/threads/synch.h b/src/threads/synch.h index c13f95d..53a5ee4 100644 --- a/src/threads/synch.h +++ b/src/threads/synch.h @@ -7,15 +7,14 @@ /* A counting semaphore. */ struct semaphore { - char name[16]; /* Name (for debugging purposes only). */ unsigned value; /* Current value. */ struct list waiters; /* List of waiting threads. */ }; -void sema_init (struct semaphore *, unsigned value, const char *name); +void sema_init (struct semaphore *, unsigned value); void sema_down (struct semaphore *); +bool sema_try_down (struct semaphore *); void sema_up (struct semaphore *); -const char *sema_name (const struct semaphore *); void sema_self_test (void); /* Lock. */ @@ -25,23 +24,27 @@ struct lock struct semaphore semaphore; /* Binary semaphore controlling access. */ }; -void lock_init (struct lock *, const char *name); +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 *); -const char *lock_name (const struct lock *); /* Condition variable. */ struct condition { - char name[16]; /* Name (for debugging purposes only). */ struct list waiters; /* List of waiting threads. */ }; -void cond_init (struct condition *, const char *name); +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 *); -const char *cond_name (const struct condition *); + +/* Memory barrier. + + The compiler will not reorder operations that access memory + across a memory barrier. */ +#define barrier() asm volatile ("") #endif /* threads/synch.h */ diff --git a/src/threads/test.c b/src/threads/test.c deleted file mode 100644 index 694ea0b..0000000 --- a/src/threads/test.c +++ /dev/null @@ -1,158 +0,0 @@ -/* Problem 1-1: Alarm Clock tests. - - 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 "threads/test.h" -#include -#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 (void) -{ - /* This test does not work with the MLFQS. */ - ASSERT (!enable_mlfqs); - - /* Easy test: 5 threads sleep once each. */ - test_sleep (5, 1); - - /* Somewhat harder test: 5 threads sleep 7 times each. */ - 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; - - printf ("\n" - "Creating %d threads to sleep %d times each.\n" - "Thread 0 sleeps 10 ticks each time,\n" - "thread 1 sleeps 20 ticks each time, and so on.\n" - "If successful, product of iteration count and\n" - "sleep duration will appear in nondescending order.\n\n" - "Running test... ", - thread_cnt, iterations); - - /* 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, "output"); - 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); - printf ("done\n\n"); - - /* 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; - - printf ("thread %d: duration=%d, iteration=%d, product=%d\n", - t->id, t->duration, t->iterations, new_prod); - - if (new_prod >= product) - product = new_prod; - else - printf ("FAIL: thread %d woke up out of order (%d > %d)!\n", - 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) - printf ("FAIL: thread %d woke up %d times instead of %d\n", - i, threads[i].iterations, iterations); - - printf ("Test complete.\n"); - - 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/src/threads/test.h b/src/threads/test.h deleted file mode 100644 index d6b3ca8..0000000 --- a/src/threads/test.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef THREADS_TEST_H -#define THREADS_TEST_H - -#include "threads/init.h" - -void test (void); - -#endif /* threads/test.h */ diff --git a/src/threads/thread.c b/src/threads/thread.c index 02f3c7b..7a5c726 100644 --- a/src/threads/thread.c +++ b/src/threads/thread.c @@ -46,13 +46,17 @@ 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. */ + 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 *); +static bool is_thread (struct thread *) UNUSED; static void *alloc_frame (struct thread *, size_t size); static void schedule (void); void schedule_tail (struct thread *prev); @@ -73,7 +77,7 @@ thread_init (void) { ASSERT (intr_get_level () == INTR_OFF); - lock_init (&tid_lock, "tid"); + lock_init (&tid_lock); list_init (&ready_list); /* Set up a thread structure for the running thread. */ @@ -88,16 +92,17 @@ thread_init (void) void thread_start (void) { - thread_create ("idle", PRI_DEFAULT, idle, NULL); + thread_create ("idle", PRI_MAX, idle, NULL); intr_enable (); } -/* Called by the timer interrupt handler at each timer tick to - update statistics. */ +/* Called by the timer interrupt handler at each timer tick. */ void thread_tick (void) { struct thread *t = thread_current (); + + /* Update statistics. */ if (t == idle_thread) idle_ticks++; #ifdef USERPROG @@ -106,6 +111,10 @@ thread_tick (void) #endif else kernel_ticks++; + + /* Enforce preemption. */ + if (++thread_ticks >= TIME_SLICE) + intr_yield_on_return (); } /* Prints thread statistics. */ @@ -287,6 +296,37 @@ 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. */ static void @@ -342,7 +382,7 @@ running_thread (void) /* Returns true if T appears to point to a valid thread. */ static bool -is_thread (struct thread *t) +is_thread (struct thread *t) { return t != NULL && t->magic == THREAD_MAGIC; } @@ -417,6 +457,9 @@ schedule_tail (struct thread *prev) /* Mark us as running. */ cur->status = THREAD_RUNNING; + /* Start new time slice. */ + thread_ticks = 0; + #ifdef USERPROG /* Activate the new address space. */ process_activate (); diff --git a/src/threads/thread.h b/src/threads/thread.h index 1f17a9c..2043d8d 100644 --- a/src/threads/thread.h +++ b/src/threads/thread.h @@ -20,9 +20,9 @@ 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 29 /* Default priority. */ -#define PRI_MAX 59 /* Highest priority. */ +#define PRI_MIN 0 /* Highest priority. */ +#define PRI_DEFAULT 31 /* Default priority. */ +#define PRI_MAX 63 /* Lowest priority. */ /* A kernel thread or user process. @@ -103,6 +103,7 @@ struct thread void thread_init (void); void thread_start (void); + void thread_tick (void); void thread_print_stats (void); @@ -119,7 +120,12 @@ const char *thread_name (void); void thread_exit (void) NO_RETURN; void thread_yield (void); -void thread_set_priority (int); 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/src/userprog/Make.vars b/src/userprog/Make.vars index 03e8f08..5d09807 100644 --- a/src/userprog/Make.vars +++ b/src/userprog/Make.vars @@ -1,2 +1,5 @@ -DEFINES = -DUSERPROG -DFILESYS -SUBDIRS = threads devices lib lib/kernel userprog filesys +# -*- makefile -*- + +os.dsk: DEFINES = -DUSERPROG -DFILESYS +KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys +TEST_SUBDIRS = tests/userprog tests/userprog/no-vm tests/filesys/base diff --git a/src/userprog/exception.c b/src/userprog/exception.c index dcfccb8..9a4eb90 100644 --- a/src/userprog/exception.c +++ b/src/userprog/exception.c @@ -33,28 +33,31 @@ exception_init (void) 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 (3, 3, INTR_ON, kill, "#BP Breakpoint Exception"); - intr_register (4, 3, INTR_ON, kill, "#OF Overflow Exception"); - intr_register (5, 3, INTR_ON, kill, "#BR BOUND Range Exceeded Exception"); + 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 (0, 0, INTR_ON, kill, "#DE Divide Error"); - intr_register (1, 0, INTR_ON, kill, "#DB Debug Exception"); - intr_register (6, 0, INTR_ON, kill, "#UD Invalid Opcode Exception"); - intr_register (7, 0, INTR_ON, kill, "#NM Device Not Available Exception"); - intr_register (11, 0, INTR_ON, kill, "#NP Segment Not Present"); - intr_register (12, 0, INTR_ON, kill, "#SS Stack Fault Exception"); - intr_register (13, 0, INTR_ON, kill, "#GP General Protection Exception"); - intr_register (16, 0, INTR_ON, kill, "#MF x87 FPU Floating-Point Error"); - intr_register (19, 0, INTR_ON, kill, "#XF SIMD Floating-Point Exception"); + 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 (14, 0, INTR_OFF, page_fault, "#PF Page-Fault Exception"); + intr_register_int (14, 0, INTR_OFF, page_fault, "#PF Page-Fault Exception"); } /* Prints exception statistics. */ diff --git a/src/userprog/pagedir.c b/src/userprog/pagedir.c index d9cfd30..4604ca2 100644 --- a/src/userprog/pagedir.c +++ b/src/userprog/pagedir.c @@ -7,6 +7,7 @@ #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. @@ -16,7 +17,8 @@ uint32_t * pagedir_create (void) { uint32_t *pd = palloc_get_page (0); - memcpy (pd, base_page_dir, PGSIZE); + if (pd != NULL) + memcpy (pd, base_page_dir, PGSIZE); return pd; } @@ -45,28 +47,25 @@ pagedir_destroy (uint32_t *pd) palloc_free_page (pd); } -/* Returns the address of the page table entry for user virtual - address UADDR in page directory PD. - If PD does not have a page table for UADDR, behavior varies - based 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. - Also returns a null pointer if UADDR is a kernel address. */ +/* 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, void *uaddr, bool create) +lookup_page (uint32_t *pd, const void *vaddr, bool create) { uint32_t *pt, *pde; ASSERT (pd != NULL); - /* Make sure it's a user address. */ - if (uaddr >= PHYS_BASE) - return NULL; + /* Shouldn't create new kernel virtual mappings. */ + ASSERT (!create || is_user_vaddr (vaddr)); - /* Check for a page table for UADDR. + /* Check for a page table for VADDR. If one is missing, create one if requested. */ - pde = pd + pd_no (uaddr); + pde = pd + pd_no (vaddr); if (*pde == 0) { if (create) @@ -83,11 +82,11 @@ lookup_page (uint32_t *pd, void *uaddr, bool create) /* Return the page table entry. */ pt = pde_get_pt (*pde); - return &pt[pt_no (uaddr)]; + return &pt[pt_no (vaddr)]; } -/* Adds a mapping from user virtual address UPAGE to kernel - virtual address KPAGE in page directory PD. +/* Adds a mapping from user virtual page UPAGE to kernel virtual + address KPAGE in page directory PD. UPAGE must not already be mapped. If WRITABLE is true, the new page is read/write; otherwise it is read-only. @@ -101,12 +100,15 @@ pagedir_set_page (uint32_t *pd, void *upage, void *kpage, ASSERT (pg_ofs (upage) == 0); ASSERT (pg_ofs (kpage) == 0); - ASSERT (upage < PHYS_BASE); - ASSERT (pagedir_get_page (pd, upage) == NULL); + ASSERT (is_user_vaddr (upage)); + ASSERT (vtop (kpage) >> PTSHIFT < ram_pages); + ASSERT (pd != base_page_dir); pte = lookup_page (pd, upage, true); + if (pte != NULL) { + ASSERT ((*pte & PG_P) == 0); *pte = pte_create_user (kpage, writable); return true; } @@ -115,69 +117,98 @@ pagedir_set_page (uint32_t *pd, void *upage, void *kpage, } /* Returns the kernel virtual address that user virtual address - UADDR is mapped to in PD, or a null pointer if there is no - mapping. */ + UADDR is mapped to in PD, or a null pointer if UADDR is not + present. */ void * pagedir_get_page (uint32_t *pd, const void *uaddr) { - uint32_t *pte = lookup_page (pd, (void *) uaddr, false); - return (pte != NULL && *pte != 0 - ? (uint8_t *) pte_get_page (*pte) + pg_ofs (uaddr) - : NULL); + uint32_t *pte; + + ASSERT (is_user_vaddr (uaddr)); + + pte = lookup_page (pd, uaddr, false); + if (pte != NULL && (*pte & PG_P) != 0) + return pte_get_page (*pte) + pg_ofs (uaddr); + else + return NULL; } -/* Clears any mapping for user virtual address UPAGE in page - directory PD. - UPAGE need not already be mapped. */ +/* 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 = lookup_page (pd, upage, false); - if (pte != NULL && *pte != 0) - { - *pte = 0; + uint32_t *pte; - if (active_pd () == pd) - { - /* We cleared a page-table entry in the active page - table, so we have to invalidate the TLB. See - [IA32-v3], section 3.11. */ - pagedir_activate (pd); - } - } + ASSERT (pg_ofs (upage) == 0); + ASSERT (is_user_vaddr (upage)); + pte = lookup_page (pd, upage, false); + if (pte != NULL && (*pte & PG_P) != 0) + { + *pte &= ~PG_P; + invalidate_pagedir (pd); + } } -/* Returns true if the PTE for user virtual page UPAGE in PD is - dirty, that is, if the page has been modified since the PTE - was installed. - Returns false if PD contains no PDE for UPAGE. */ +/* 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_test_dirty (uint32_t *pd, const void *upage) +pagedir_is_dirty (uint32_t *pd, const void *vpage) { - uint32_t *pte = lookup_page (pd, (void *) upage, false); + uint32_t *pte = lookup_page (pd, vpage, false); return pte != NULL && (*pte & PG_D) != 0; } -/* Returns true if the PTE for user virtual page UPAGE 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 PDE for UPAGE. */ +/* 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 |= PG_D; + else + { + *pte &= ~(uint32_t) PG_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_test_accessed (uint32_t *pd, const void *upage) +pagedir_is_accessed (uint32_t *pd, const void *vpage) { - uint32_t *pte = lookup_page (pd, (void *) upage, false); + uint32_t *pte = lookup_page (pd, vpage, false); return pte != NULL && (*pte & PG_A) != 0; } -/* Resets the accessed bit in the PTE for user virtual page UPAGE - in PD. */ +/* Sets the accessed bit to ACCESSED in the PTE for virtual page + VPAGE in PD. */ void -pagedir_clear_accessed (uint32_t *pd, const void *upage) +pagedir_set_accessed (uint32_t *pd, const void *vpage, bool accessed) { - uint32_t *pte = lookup_page (pd, (void *) upage, false); + uint32_t *pte = lookup_page (pd, vpage, false); if (pte != NULL) - *pte &= ~(uint32_t) PG_A; + { + if (accessed) + *pte |= PG_A; + else + { + *pte &= ~(uint32_t) PG_A; + invalidate_pagedir (pd); + } + } } /* Loads page directory PD into the CPU's page directory base @@ -204,6 +235,26 @@ active_pd (void) See [IA32-v2a] "MOV--Move to/from Control Registers" and [IA32-v3] 3.7.5. */ uintptr_t pd; - asm ("mov %0, %%cr3" : "=r" (pd)); + asm volatile ("mov %0, %%cr3" : "=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) + { + /* We cleared a page-table entry in the active page + table, so we have to invalidate the TLB. See + [IA32-v3], section 3.11. */ + pagedir_activate (pd); + } +} diff --git a/src/userprog/pagedir.h b/src/userprog/pagedir.h index f1ddace..cd92447 100644 --- a/src/userprog/pagedir.h +++ b/src/userprog/pagedir.h @@ -1,5 +1,5 @@ #ifndef USERPROG_PAGEDIR_H -#define USERPROG_PAGEDIR_H 1 +#define USERPROG_PAGEDIR_H #include #include @@ -9,9 +9,10 @@ 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_test_dirty (uint32_t *pd, const void *upage); -bool pagedir_test_accessed (uint32_t *pd, const void *upage); -void pagedir_clear_accessed (uint32_t *pd, const 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/src/userprog/process.c b/src/userprog/process.c index 31629c7..58e81fd 100644 --- a/src/userprog/process.c +++ b/src/userprog/process.c @@ -102,10 +102,11 @@ process_exit (void) pd = cur->pagedir; if (pd != NULL) { - /* 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 + /* 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). */ cur->pagedir = NULL; @@ -195,15 +196,6 @@ struct Elf32_Phdr static bool load_segment (struct file *, const struct Elf32_Phdr *); static bool setup_stack (void **esp); -/* Aborts loading an executable, with an error message. */ -#define LOAD_ERROR(MSG) \ - do { \ - printf ("load: %s: ", filename); \ - printf MSG; \ - printf ("\n"); \ - goto done; \ - } while (0) - /* Loads an ELF executable from FILENAME into the current thread. Stores the executable's entry point into *EIP and its initial stack pointer into *ESP. @@ -220,31 +212,30 @@ load (const char *filename, void (**eip) (void), void **esp) /* Allocate and activate page directory. */ t->pagedir = pagedir_create (); - if (t->pagedir == NULL) - LOAD_ERROR (("page directory allocation failed")); + if (t->pagedir == NULL) + goto done; process_activate (); /* Open executable file. */ file = filesys_open (filename); - if (file == NULL) - LOAD_ERROR (("open failed")); + if (file == NULL) + { + printf ("load: %s: open failed\n", filename); + goto done; + } /* Read and verify executable header. */ - if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr) - LOAD_ERROR (("error reading executable header")); - if (memcmp (ehdr.e_ident, "\177ELF\1\1\1", 7) != 0) - LOAD_ERROR (("file is not ELF")); - if (ehdr.e_type != 2) - LOAD_ERROR (("ELF file is not an executable")); - if (ehdr.e_machine != 3) - LOAD_ERROR (("ELF executable is not x86")); - if (ehdr.e_version != 1) - LOAD_ERROR (("ELF executable has unknown version %d", - (int) ehdr.e_version)); - if (ehdr.e_phentsize != sizeof (struct Elf32_Phdr)) - LOAD_ERROR (("bad ELF program header size")); - if (ehdr.e_phnum > 1024) - LOAD_ERROR (("too many ELF program headers")); + 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", filename); + goto done; + } /* Read program headers. */ file_ofs = ehdr.e_phoff; @@ -253,11 +244,11 @@ load (const char *filename, void (**eip) (void), void **esp) struct Elf32_Phdr phdr; if (file_ofs < 0 || file_ofs > file_length (file)) - LOAD_ERROR (("bad file offset %ld", (long) file_ofs)); + goto done; file_seek (file, file_ofs); if (file_read (file, &phdr, sizeof phdr) != sizeof phdr) - LOAD_ERROR (("error reading program header")); + goto done; file_ofs += sizeof phdr; switch (phdr.p_type) { @@ -265,17 +256,13 @@ load (const char *filename, void (**eip) (void), void **esp) case PT_NOTE: case PT_PHDR: case PT_STACK: + default: /* Ignore this segment. */ break; case PT_DYNAMIC: case PT_INTERP: case PT_SHLIB: - /* Reject the executable. */ - LOAD_ERROR (("unsupported ELF segment type %d\n", phdr.p_type)); - break; - default: - printf ("unknown ELF segment type %08x\n", phdr.p_type); - break; + goto done; case PT_LOAD: if (!load_segment (file, &phdr)) goto done; @@ -325,40 +312,25 @@ load_segment (struct file *file, const struct Elf32_Phdr *phdr) /* [ELF1] 2-2 says that p_offset and p_vaddr must be congruent modulo PGSIZE. */ if (phdr->p_offset % PGSIZE != phdr->p_vaddr % PGSIZE) - { - printf ("%#08"PE32Ox" and %#08"PE32Ax" not congruent modulo %#x\n", - phdr->p_offset, phdr->p_vaddr, (unsigned) PGSIZE); - return false; - } + return false; /* p_offset must point within file. */ if (phdr->p_offset > (Elf32_Off) file_length (file)) - { - printf ("bad p_offset %"PE32Ox, phdr->p_offset); - return false; - } + return false; /* [ELF1] 2-3 says that p_memsz must be at least as big as p_filesz. */ if (phdr->p_memsz < phdr->p_filesz) - { - printf ("p_memsz (%08"PE32Wx") < p_filesz (%08"PE32Wx")\n", - phdr->p_memsz, phdr->p_filesz); - return false; - } + return false; /* Validate virtual memory region to be mapped. The region must both start and end within the user address - space range starting at 0 and ending at PHYS_BASE (typically - 3 GB == 0xc0000000). We don't allow mapping page 0 .*/ + space range. We don't allow mapping page 0.*/ start = pg_round_down ((void *) phdr->p_vaddr); end = pg_round_up ((void *) (phdr->p_vaddr + phdr->p_memsz)); - if (start == 0 || start >= PHYS_BASE || end >= PHYS_BASE || end < start) - { - printf ("bad virtual region %08lx...%08lx\n", - (unsigned long) start, (unsigned long) end); - return false; - } + if (!is_user_vaddr (start) || !is_user_vaddr (end) || end < start + || start == 0) + return false; /* Load the segment page-by-page into memory. */ filesz_left = phdr->p_filesz + (phdr->p_vaddr & PGMASK); @@ -410,9 +382,6 @@ setup_stack (void **esp) else palloc_free_page (kpage); } - else - printf ("failed to allocate process stack\n"); - return success; } diff --git a/src/userprog/syscall.c b/src/userprog/syscall.c index db67593..370c89b 100644 --- a/src/userprog/syscall.c +++ b/src/userprog/syscall.c @@ -9,7 +9,7 @@ static void syscall_handler (struct intr_frame *); void syscall_init (void) { - intr_register (0x30, 3, INTR_ON, syscall_handler, "syscall"); + intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall"); } static void diff --git a/src/utils/backtrace b/src/utils/backtrace index 487e224..baa5eff 100755 --- a/src/utils/backtrace +++ b/src/utils/backtrace @@ -4,27 +4,41 @@ use strict; # Check command line. if (grep ($_ eq '-h' || $_ eq '--help', @ARGV)) { - print "backtrace, for converting raw addresses into symbolic backtraces\n"; - print "\n"; - print "usage: backtrace BINARY ADDRESS...\n"; - print "where BINARY is the binary file from which to obtain symbols\n"; - print " and each ADDRESS is a raw address to convert to a symbol name.\n"; - print "\n"; - print "In use with Pintos, BINARY is usually kernel.o and the ADDRESS\n"; - print "list is taken from the \"Call stack:\" printed by the kernel.\n"; - print "Read \"Backtraces\" in the \"Debugging Tools\" chapter\n"; - print "of the Pintos documentation for more information.\n"; + print <<'EOF'; +backtrace, for converting raw addresses into symbolic backtraces +usage: backtrace [BINARY] ADDRESS... +where BINARY is the binary file from which to obtain symbols + and ADDRESS is a raw address to convert to a symbol name. + +If BINARY is unspecified, the default is the first of kernel.o or +build/kernel.o that exists. + +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: binary file argument required (use --help for help)\n" +die "backtrace: at least one argument required (use --help for help)\n" if @ARGV == 0; -die "backtrace: at least one address argument required (use --help for help)\n" - if @ARGV == 1; + +# Drop leading and trailing garbage inserted by kernel. +shift while grep (/^(call|stack:?)$/i, $ARGV[0]); +s/\.$// foreach @ARGV; # Find binary file. -my ($bin) = shift @ARGV; -die "backtrace: $bin: not found (use --help for help)\n" - if ! -e $bin; +my ($bin) = $ARGV[0]; +if (-e $bin) { + shift @ARGV; +} elsif ($bin !~ /^0/) { + die "backtrace: $bin: not found (use --help for help)\n"; +} elsif (-e 'kernel.o') { + $bin = 'kernel.o'; +} elsif (-e 'build/kernel.o') { + $bin = 'build/kernel.o'; +} else { + die "backtrace: can't find binary for backtrace (use --help for help)\n"; +} # Find addr2line. my ($a2l) = search_path ("i386-elf-addr2line") || search_path ("addr2line"); @@ -40,10 +54,6 @@ sub search_path { return undef; } -# Drop leading and trailing garbage inserted by kernel. -shift while grep (/call|stack/i, $ARGV[0]); -s/\.$// foreach @ARGV; - # Do backtrace. open (A2L, "$a2l -fe $bin " . join (' ', @ARGV) . "|"); while () { diff --git a/src/utils/pintos b/src/utils/pintos index 40a240c..8721db3 100755 --- a/src/utils/pintos +++ b/src/utils/pintos @@ -2,50 +2,134 @@ use strict; use POSIX; +use Fcntl; +use File::Temp 'tempfile'; +use Getopt::Long qw(:config bundling); + +# Command-line options. +our ($sim); # Simulator: bochs, qemu, or gsx. +our ($debug) = "none"; # Debugger: none, monitor, or gdb. +our ($mem) = 4; # Physical RAM in MB. +our ($serial_out) = 1; # Send output to serial port? +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 (@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 (%disks) = (OS => {FILENAME => 'os.dsk'}, # Disks to give VM. + FS => {DEF_FN => 'fs.dsk'}, + SCRATCH => {DEF_FN => 'scratch.dsk'}, + SWAP => {DEF_FN => 'swap.dsk'}); +our (@disks_by_iface) = @disks{qw (OS FS SCRATCH SWAP)}; + +parse_command_line (); +find_disks (); +prepare_scratch_disk (); +prepare_arguments (); +run_vm (); +finish_scratch_disk (); -our ($mem) = 4; -our ($serial_out) = 1; -our (@disks) = ("os.dsk", "fs.dsk", "scratch.dsk", "swap.dsk"); -our ($sim); -our ($debug); -our ($vga); -our ($jitter, $realtime); - -use Getopt::Long qw(:config require_order bundling); -unshift (@ARGV, split (' ', $ENV{PINTOSOPTS})) - if defined $ENV{PINTOSOPTS}; -GetOptions ("sim=s" => sub { set_sim (@_) }, - "bochs" => sub { set_sim ("bochs") }, - "qemu" => sub { set_sim ("qemu") }, - "gsx" => sub { set_sim ("gsx") }, - - "debug=s" => sub { set_debug (@_) }, - "no-debug" => sub { set_debug ("no-debug") }, - "monitor" => sub { set_debug ("monitor") }, - "gdb" => sub { set_debug ("gdb") }, - - "run|get|put|make-disk" => \&cmd_option, - - "m|memory=i" => \$mem, - "j|jitter=i" => sub { set_jitter (@_) }, - "r|realtime" => sub { set_realtime () }, - - "v|no-vga" => sub { set_vga ('none'); }, - "s|no-serial" => sub { $serial_out = 0; }, - "t|terminal" => sub { set_vga ('terminal'); }, - - "h|help" => sub { usage (0); }, - - "0|os-disk|disk-0|hda=s" => \$disks[0], - "1|fs-disk|disk-1|hdb=s" => \$disks[1], - "2|scratch-disk|disk-2|hdc=s" => \$disks[2], - "3|swap-disk|disk-3|hdd=s" => \$disks[3]) - or exit 1; - -$sim = "bochs" if !defined $sim; -$debug = "no-debug" if !defined $debug; -$vga = "window" if !defined $vga; +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 (@_) }, + "bochs" => sub { set_sim ("bochs") }, + "qemu" => sub { set_sim ("qemu") }, + "gsx" => sub { set_sim ("gsx") }, + + "debug=s" => sub { set_debug (@_) }, + "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 (@_) }, + "r|realtime" => sub { set_realtime () }, + "T|timeout=i" => \$timeout, + + "v|no-vga" => sub { set_vga ('none'); }, + "s|no-serial" => sub { $serial_out = 0; }, + "t|terminal" => sub { set_vga ('terminal'); }, + + "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); }, + + "os-disk=s" => \$disks{OS}{FILENAME}, + "fs-disk=s" => \$disks{FS}{FILENAME}, + "scratch-disk=s" => \$disks{SCRATCH}{FILENAME}, + "swap-disk=s" => \$disks{SWAP}{FILENAME}, + + "0|disk-0|hda=s" => \$disks_by_iface[0]{FILENAME}, + "1|disk-1|hdb=s" => \$disks_by_iface[1]{FILENAME}, + "2|disk-2|hdc=s" => \$disks_by_iface[2]{FILENAME}, + "3|disk-3|hdd=s" => \$disks_by_iface[3]{FILENAME}) + or exit 1; + } + + $sim = "bochs" if !defined $sim; + $debug = "none" if !defined $debug; + $vga = "window" if !defined $vga; +} +# 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 + --gsx Use VMware GSX Server 3.x 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 + -s, --no-serial No serial 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 + -T, --timeout=N Time out and kill Pintos after N seconds +Configuration options: + -m, --mem=N Give Pintos N MB physical RAM (default: 4) +File system commands (for `run' command): + -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 +Disk options: (name an existing FILE or specify SIZE in MB for a temp disk) + --os-disk=FILE Set OS disk file (default: os.dsk) + --fs-disk=FILE|SIZE Set FS disk file (default: fs.dsk) + --scratch-disk=FILE|SIZE Set scratch disk (default: scratch.dsk) + --swap-disk=FILE|SIZE Set swap disk file (default: swap.dsk) +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" @@ -53,13 +137,15 @@ sub set_sim { $sim = $new_sim; } +# Sets the debugger. sub set_debug { my ($new_debug) = @_; die "--$new_debug conflicts with --$debug\n" - if defined ($debug) && $debug ne $new_debug; + 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) { @@ -68,6 +154,7 @@ sub set_vga { $vga = $new_vga; } +# Sets randomized timer interrupts. sub set_jitter { my ($new_jitter) = @_; die "--realtime conflicts with --jitter\n" if defined $realtime; @@ -76,358 +163,417 @@ sub set_jitter { $jitter = $new_jitter; } +# Sets real-time timer interrupts. sub set_realtime { die "--realtime conflicts with --jitter\n" if defined $jitter; $realtime = 1; } -sub cmd_option { - # Force an end to option processing, as with --. - die ("!FINISH"); -} - -die "no command specified; use --help for help\n" - if @ARGV < 1; -my ($cmd) = shift @ARGV; -if ($cmd eq 'run') { - run_vm (@ARGV); -} elsif ($cmd eq 'make-disk') { - usage () if @ARGV != 2; - my ($file, $mb) = @ARGV; - usage () if $mb !~ /^\d+(\.\d+)?|\.\d+$/; - die "$file: already exists\n" if -e $file; - - create_disk ($file, int ($mb * 1008)); -} elsif ($cmd eq 'put') { - # Take a -f option to combine formatting with putting. - my ($format) = 0; - if (@ARGV > 0 && $ARGV[0] eq '-f') { - shift @ARGV; - $format = 1; - } - - usage () if @ARGV != 1 && @ARGV != 2; - my ($hostfn, $guestfn) = @ARGV; - $guestfn = $hostfn if !defined $guestfn; - - # Create scratch disk from file. - die "$hostfn: $!\n" if ! -e $hostfn; - my ($size) = -s _; - if ($size) { - copy_pad ($hostfn, "scratch.dsk", 512); - } else { - open (SCRATCH, ">scratch.dsk") or die "scratch.dsk: create: $!\n"; - syswrite (SCRATCH, "\0" x 512); - close (SCRATCH); - } - - # Do copy. - my (@cmd) = ("-ci", $guestfn, $size, "-q"); - unshift (@cmd, "-f") if $format; - run_vm (@cmd); -} elsif ($cmd eq 'get') { - usage () if @ARGV != 1 && @ARGV != 2; - my ($guestfn, $hostfn) = @ARGV; - $hostfn = $guestfn if !defined $hostfn; - die "$hostfn: already exists\n" if -e $hostfn; - - # Create scratch disk big enough for any file in the filesystem - # (modulo sparse files). - die "$disks[1]: $!\n" if ! -e $disks[1]; - my ($fs_size) = -s _; - my ($scratch_size) = -s $disks[2]; - $scratch_size = 0 if !defined $scratch_size; - create_disk ($disks[2], $fs_size / 1024 + 16) - if $scratch_size < $fs_size + 16384; - - # Do copy. - run_vm ("-co", $guestfn, "-q"); - - # Read out scratch disk. - print "copying $guestfn from $disks[2] to $hostfn...\n"; - open (SRC, "<$disks[2]") or die "$disks[2]: open: $!\n"; - open (DST, ">$hostfn") or die "$hostfn: create: $!\n"; - my ($input); - read (SRC, $input, 512) == 512 or die "$disks[2]: read error\n"; - my ($size) = unpack ("V", $input); - $size != 0xffffffff or die "$guestfn: too big for $disks[2]?"; - my ($src); - read (SRC, $src, $size) == $size or die "$disks[2]: read error\n"; - print DST $src or die "$hostfn: write error\n"; - close (DST); - close (SRC); -} elsif ($cmd eq 'help') { - usage (0); -} else { - die "unknown command `$cmd'; use --help for help\n"; +# 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); } -exit 0; -sub usage { - my ($exitcode) = @_; - $exitcode = 1 unless defined $exitcode; - print "pintos, a utility for invoking Pintos in a simulator\n"; - print "Usage: pintos [OPTION...] COMMAND [ARG...]\n"; - print "where COMMAND is one of the following:\n"; - print " run [CMDLINE...] run a VM in the simulator\n"; - print " make-disk FILE.DSK SIZE create FILE.DSK as empty SIZE MB disk\n"; - print " put HOSTFN [GUESTFN] copy HOSTFN into VM (as GUESTFN)\n"; - print " get GUESTFN [HOSTFN] copy GUESTFN out of VM (to HOSTFN)\n"; - print " help print this help message and exit\n"; - print "Simulator options:\n"; - print " --bochs (default) Use Bochs as simulator\n"; - print " --qemu Use qemu as simulator\n"; - print " --gsx Use VMware GSX Server 3.x as simulator\n"; - print "Debugger options:\n"; - print " --no-debug (default) No debugger\n"; - print " --monitor Debug with simulator's monitor\n"; - print " --gdb Debug with gdb\n"; - print "Display options: (default is VGA + serial)\n"; - print " -v, --no-vga No VGA display\n"; - print " -s, --no-serial No serial output\n"; - print " -t, --terminal Display VGA in terminal (Bochs only)\n"; - print "VM options:\n"; - print " -j SEED Randomize timer interrupts (Bochs only)\n"; - print " -r, --realtime Use realistic, but not reproducible, timings\n"; - print " -m, --mem=MB Run VM with MB megabytes of physical memory\n"; - print "Disk options:\n"; - print " --os-disk=DISK Set OS disk file (default: os.dsk)\n"; - print " --fs-disk=DISK Set FS disk file (default: fs.dsk)\n"; - print " --scratch-disk=DISK Set scratch disk (default: scratch.dsk)\n"; - print " --swap-disk=DISK Set swap disk file (default: swap.dsk)\n"; - exit $exitcode; +# 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; } - -sub copy_pad { - my ($src, $dst, $blocksize) = @_; - run_command ("dd", "if=$src", "of=$dst", "bs=$blocksize", "conv=sync"); -} - -sub create_disk { - my ($disk, $kb) = @_; - run_command ("dd", "if=/dev/zero", "of=$disk", "bs=1024", "count=$kb"); -} - -sub run_vm { - my (@args) = @_; - - our (@disks); - - die "$disks[0]: can't find OS disk\n" if ! -e $disks[0]; - die "$disks[0]: OS disk cannot have zero size\n" if ! -s $disks[0]; - for my $i (1...3) { - undef $disks[$i] if ! -s $disks[$i]; + +# Locates the files used to back each of the virtual disks, +# and creates temporary disks. +sub find_disks { + for my $disk (values %disks) { + # If there's no assigned file name but the default file exists, + # assign the default file. + $disk->{FILENAME} = $disk->{DEF_FN} + if !defined ($disk->{FILENAME}) && -e $disk->{DEF_FN}; + + # If there's no file name, we're done. + next if !defined ($disk->{FILENAME}); + + if ($disk->{FILENAME} =~ /^\d+(\.\d+)?|\.\d+$/) { + # Create a temporary disk of approximately the specified + # size in megabytes. + die "OS disk can't be temporary\n" if $disk == $disks{OS}; + + my ($mb) = $disk->{FILENAME}; + undef $disk->{FILENAME}; + + my ($cylinder) = 1024 * 504; + my ($bytes) = $mb * ($cylinder * 2); + $bytes = int (($bytes + $cylinder - 1) / $cylinder) * $cylinder; + extend_disk ($disk, $bytes); + } else { + # The file must exist and have nonzero size. + -e $disk->{FILENAME} or die "$disk->{FILENAME}: stat: $!\n"; + -s _ or die "$disk->{FILENAME}: disk has zero size\n"; + } } + # Warn about (potentially) missing disks. if (my ($project) = `pwd` =~ /\b(threads|userprog|vm|filesys)\b/) { if ((grep ($project eq $_, qw (userprog vm filesys))) - && !defined ($disks[1])) { + && !defined ($disks{FS}{FILENAME})) { print STDERR "warning: it looks like you're running the $project "; print STDERR "project, but no file system disk is present\n"; } - if ($project eq 'vm' && !defined $disks[3]) { + if ($project eq 'vm' && !defined $disks{SWAP}{FILENAME}) { print STDERR "warning: it looks like you're running the $project "; print STDERR "project, but no swap disk is present\n"; } } +} + +# Prepare the scratch disk for gets and puts. +sub prepare_scratch_disk { + # Copy the files to put onto the scratch disk. + put_scratch_file ($_->[0]) foreach @puts; + + # Make sure the scratch disk is big enough to get big files. + extend_disk ($disks{SCRATCH}, @gets * 1024 * 1024) if @gets; +} - write_cmd_line ($disks[0], @args); +# Read "get" files from the scratch disk. +sub finish_scratch_disk { + # We need to start reading the scratch disk from the beginning again. + if (@gets) { + close ($disks{SCRATCH}{HANDLE}); + undef ($disks{SCRATCH}{HANDLE}); + } - if ($sim eq 'bochs') { - my ($bin); - if ($debug eq 'no-debug') { - $bin = 'bochs'; - } elsif ($debug eq 'monitor') { - $bin = 'bochs-dbg'; - } elsif ($debug eq 'gdb') { - $bin = 'bochs-gdb'; - } + # Read each file. + get_scratch_file (defined ($_->[1]) ? $_->[1] : $_->[0]) foreach @gets; +} - my ($bochsbin) = search_path ($bin); - my ($bochsshare) = "$bochsbin/../share/bochs"; - - open (BOCHSRC, ">bochsrc.txt") or die "bochsrc.txt: create: $!\n"; - print BOCHSRC "romimage: file=$bochsshare/BIOS-bochs-latest, " - . "address=0xf0000\n"; - print BOCHSRC "vgaromimage: $bochsshare/VGABIOS-lgpl-latest\n"; - print BOCHSRC bochs_disk_line ("ata0-master", $disks[0]); - print BOCHSRC bochs_disk_line ("ata0-slave", $disks[1]); - print BOCHSRC "ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15\n" - if defined ($disks[2]) || defined ($disks[3]); - print BOCHSRC bochs_disk_line ("ata1-master", $disks[2]); - print BOCHSRC bochs_disk_line ("ata1-slave", $disks[3]); - print BOCHSRC "boot: c\n"; - print BOCHSRC "ips: 1000000\n"; - if (!$realtime) { - print BOCHSRC "clock: sync=none, time0=0\n"; - } else { - print BOCHSRC "clock: sync=realtime, time0=0\n"; - } - print BOCHSRC "megs: $mem\n"; - print BOCHSRC "log: bochsout.txt\n"; - if ($vga ne 'terminal') { - print BOCHSRC "com1: enabled=1, dev=/dev/stdout\n" - if $serial_out; - print BOCHSRC "display_library: nogui\n" - if $vga eq 'none'; - } else { - print BOCHSRC "display_library: term\n"; - } - close (BOCHSRC); - - my (@cmd) = ($bin, '-q'); - push (@cmd, '-j', $jitter) if defined $jitter; - 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"; - } - } elsif ($sim eq '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, '-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, '-nographic') if $vga eq 'none'; - push (@cmd, '-serial', 'stdio') if $serial_out && $vga ne 'none'; - push (@cmd, '-S') if $debug eq 'monitor'; - push (@cmd, '-s') if $debug eq 'gdb'; - run_command (@cmd); - } elsif ($sim eq 'gsx') { - print "warning: VMware GSX Server doesn't support --$debug\n" - if $debug ne 'no-debug'; - print "warning: VMware GSX Server doesn't support --no-vga\n" - if $vga eq 'none'; - print "warning: VMware GSX Server doesn't support --terminal\n" - if $vga eq 'terminal'; - print "warning: VMware GSX Server doesn't support jitter\n" - if defined $jitter; - - open (VMX, ">pintos.vmx") or die "pintos.vmx: create: $!\n"; - chmod 0777 & ~umask, "pintos.vmx"; - print VMX "#! /usr/bin/vmware -G\n"; - print VMX "config.version = 6\n"; - print VMX "guestOS = \"linux\"\n"; - print VMX "floppy0.present = FALSE\n"; - - unlink ("pintos.out"); - print VMX "serial0.present = TRUE\n"; - print VMX "serial0.fileType = \"file\"\n"; - print VMX "serial0.fileName = \"pintos.out\"\n"; - - if (! -e 'null.bin') { - open (NULL, ">null.bin") or die "null.bin: create: $!\n"; - close (NULL); - } +# put_scratch_file($file). +# +# Copies $file into the scratch disk. +sub put_scratch_file { + my ($put_filename) = @_; + my ($disk_handle, $disk_filename) = open_disk ($disks{SCRATCH}); - for (my ($i) = 0; $i < 4; $i++) { - my ($dsk) = $disks[$i]; - next if !defined $dsk; - - my ($pln) = $dsk; - $pln =~ s/\.dsk//; - $pln .= ".pln"; - - my ($device) = "ide" . int ($i / 2) . ":" . ($i % 2); - print VMX "\n$device.present = TRUE\n"; - print VMX "$device.deviceType = \"plainDisk\"\n"; - print VMX "$device.fileName = \"$pln\"\n"; - - my (%geom) = disk_geometry ($dsk); - open (PLN, ">$pln") or die "$pln: create: $!\n"; - print PLN "DRIVETYPE ide\n"; - print PLN "#vm|VERSION 2\n"; - print PLN "#vm|TOOLSVERSION 2\n"; - print PLN "CYLINDERS $geom{C}\n"; - print PLN "HEADS $geom{H}\n"; - print PLN "SECTORS $geom{S}\n"; - print PLN "#vm|CAPACITY $geom{CAPACITY}\n"; - print PLN "ACCESS \"$dsk\" 0 $geom{CAPACITY}\n"; - close (PLN); - } - close (VMX); + print "Copying $put_filename into $disk_filename...\n"; - my ($vmx) = getcwd () . "/pintos.vmx"; - system ("vmware-cmd -s register $vmx >&/dev/null"); - system ("vmware-cmd $vmx stop hard >&/dev/null"); - system ("vmware -l -G -x -q $vmx"); - system ("vmware-cmd $vmx stop hard >&/dev/null"); - } + # Write metadata sector, which consists of a 4-byte signature + # followed by the file size. + stat $put_filename or die "$put_filename: stat: $!\n"; + my ($size) = -s _; + my ($metadata) = pack ("a4 V x504", "PUT\0", $size); + write_fully ($disk_handle, $disk_filename, $metadata); + + # Copy file data. + my ($put_handle); + sysopen ($put_handle, $put_filename, O_RDONLY) + or die "$put_filename: open: $!\n"; + copy_file ($put_handle, $put_filename, $disk_handle, $disk_filename, + $size); + close ($put_handle); + + # Round up disk data to beginning of next sector. + write_fully ($disk_handle, $disk_filename, "\0" x (512 - $size % 512)) + if $size % 512; } -sub relay_signal { - my ($pid, $signal) = @_; - kill $signal, $pid; - $SIG{$signal} = 'DEFAULT'; - kill $signal, getpid (); +# get_scratch_file($file). +# +# Copies from the scratch disk to $file. +sub get_scratch_file { + my ($get_filename) = @_; + my ($disk_handle, $disk_filename) = open_disk ($disks{SCRATCH}); + + print "Copying $get_filename out of $disk_filename...\n"; + + # Read metadata sector, which has a 4-byte signature followed by + # the file size. + my ($metadata) = read_fully ($disk_handle, $disk_filename, 512); + my ($signature, $size) = unpack ("a4 V", $metadata); + die "bad signature reading scratch disk--did Pintos run correctly?\n" + if $signature ne "GET\0"; + + # Copy file data. + my ($get_handle); + sysopen ($get_handle, $get_filename, O_WRONLY | O_CREAT | O_EXCL, 0666) + or die "$get_filename: create: $!\n"; + copy_file ($disk_handle, $disk_filename, $get_handle, $get_filename, + $size); + close ($get_handle); + + # Skip forward in disk up to beginning of next sector. + read_fully ($disk_handle, $disk_filename, 512 - $size % 512) + if $size % 512; } - -sub xsystem { - my ($pid) = fork; - if (!defined ($pid)) { - # Fork failed. - die "fork: $!\n"; - } elsif (!$pid) { - # Running in child process. - exec (@_); - exit (1); - } else { - # Running in parent process. - local $SIG{INT} = sub { relay_signal ($pid, "INT"); }; - local $SIG{TERM} = sub { relay_signal ($pid, "TERM"); }; - waitpid ($pid, 0); - return $?; - } + +# Prepares the arguments to pass to the Pintos kernel, +# and then write them into Pintos bootloader. +sub prepare_arguments { + my (@args); + push (@args, shift (@kernel_args)) + while @kernel_args && $kernel_args[0] =~ /^-/; + push (@args, 'put', defined $_->[1] ? $_->[1] : $_->[0]) foreach @puts; + push (@args, @kernel_args); + push (@args, 'get', $_->[0]) foreach @gets; + write_cmd_line ($disks{OS}, @args); } +# Writes @args into the Pintos bootloader at the beginning of $disk. sub write_cmd_line { my ($disk, @args) = @_; - die "command line includes empty string" if grep (/^$/, @args); - my ($args) = join ("\0", @args) . "\0\0"; + # Figure out command line to write. + my ($arg_cnt) = pack ("V", scalar (@args)); + my ($args) = join ('', map ("$_\0", @args)); die "command line exceeds 128 bytes" if length ($args) > 128; $args .= "\0" x (128 - length ($args)); - print "writing command line to $disk...\n"; - open (DISK, "+<$disk") or die "$disk: open: $!\n"; - seek (DISK, 0x17e, 0) or die "$disk: seek: $!\n"; - syswrite (DISK, $args) or die "$disk: write: $!\n"; - close (DISK) or die "$disk: close: $!\n"; + # Write command line. + my ($handle, $filename) = open_disk_copy ($disk); + print "Writing command line to $filename...\n"; + sysseek ($handle, 0x17a, 0) == 0x17a or die "$filename: seek: $!\n"; + syswrite ($handle, "$arg_cnt$args") or die "$filename: write: $!\n"; } + +# Running simulators. -sub run_command { - print join (' ', @_), "\n"; - die "command failed\n" if xsystem (@_); +# Runs the selected simulator. +sub run_vm { + if ($sim eq 'bochs') { + run_bochs (); + } elsif ($sim eq 'qemu') { + run_qemu (); + } elsif ($sim eq 'gsx') { + run_gsx (); + } else { + die "unknown simulator `$sim'\n"; + } } -sub search_path { - my ($target) = @_; - for my $dir (split (':', $ENV{PATH})) { - return $dir if -e "$dir/$target"; +# Runs Bochs. +sub run_bochs { + # Select Bochs binary based on the chosen debugger. + my ($bin); + if ($debug eq 'none') { + $bin = 'bochs'; + } elsif ($debug eq 'monitor') { + $bin = 'bochs-dbg'; + } elsif ($debug eq 'gdb') { + $bin = 'bochs-gdb'; + } + + # Write bochsrc.txt configuration file. + open (BOCHSRC, ">", "bochsrc.txt") or die "bochsrc.txt: create: $!\n"; + print BOCHSRC <", "pintos.vmx") or die "pintos.vmx: create: $!\n"; + chmod 0777 & ~umask, "pintos.vmx"; + print VMX <", $pln) or die "$pln: create: $!\n"; + print PLN <&/dev/null"); + system ("vmware-cmd $vmx stop hard >&/dev/null"); + system ("vmware -l -G -x -q $vmx"); + system ("vmware-cmd $vmx stop hard >&/dev/null"); + system ("vmware-cmd -s unregister $vmx >&/dev/null"); +} + +# Disk utilities. + +# open_disk($disk) +# +# Opens $disk, if it is not already open, and returns its file handle +# and file name. +sub open_disk { + my ($disk) = @_; + if (!defined ($disk->{HANDLE})) { + if ($disk->{FILENAME}) { + sysopen ($disk->{HANDLE}, $disk->{FILENAME}, O_RDWR) + or die "$disk->{FILENAME}: open: $!\n"; + } else { + ($disk->{HANDLE}, $disk->{FILENAME}) = tempfile (UNLINK => 1, + SUFFIX => '.dsk'); + } + } + return ($disk->{HANDLE}, $disk->{FILENAME}); +} + +# open_disk_copy($disk) +# +# Makes a temporary copy of $disk and returns its file handle and file name. +sub open_disk_copy { + my ($disk) = @_; + die if !$disk->{FILENAME}; + + my ($orig_handle, $orig_filename) = open_disk ($disk); + my ($cp_handle, $cp_filename) = tempfile (UNLINK => 1, SUFFIX => '.dsk'); + copy_file ($orig_handle, $orig_filename, $cp_handle, $cp_filename, + -s $orig_handle); + return ($disk->{HANDLE}, $disk->{FILENAME}) = ($cp_handle, $cp_filename); +} + +# extend_disk($disk, $size) +# +# Extends $disk, if necessary, so that it is at least $size bytes +# long. +sub extend_disk { + my ($disk, $size) = @_; + my ($handle, $filename) = open_disk ($disk); + if (-s ($handle) < $size) { + sysseek ($handle, $size - 1, 0) == $size - 1 + or die "$filename: seek: $!\n"; + syswrite ($handle, "\0") == 1 + or die "$filename: write: $!\n"; + } +} + +# disk_geometry($file) +# +# Examines $file and returns a valid IDE disk geometry for it, as a +# hash. sub disk_geometry { my ($file) = @_; my ($size) = -s $file; @@ -441,3 +587,105 @@ sub disk_geometry { H => 16, S => 63); } + +# copy_file($from_handle, $from_filename, $to_handle, $to_filename, $size) +# +# Copies $size bytes from $from_handle to $to_handle. +# $from_filename and $to_filename are used in error messages. +sub copy_file { + my ($from_handle, $from_filename, $to_handle, $to_filename, $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_filename, $chunk_size); + write_fully ($to_handle, $to_filename, $data); + } +} + +# read_fully($handle, $filename, $bytes) +# +# Reads exactly $bytes bytes from $handle and returns the data read. +# $filename is used in error messages. +sub read_fully { + my ($handle, $filename, $bytes) = @_; + my ($data); + my ($read_bytes) = sysread ($handle, $data, $bytes); + die "$filename: read: $!\n" if !defined $read_bytes; + die "$filename: unexpected end of file\n" if $read_bytes != $bytes; + return $data; +} + +# write_fully($handle, $filename, $data) +# +# Write $data to $handle. +# $filename is used in error messages. +sub write_fully { + my ($handle, $filename, $data) = @_; + my ($written_bytes) = syswrite ($handle, $data); + die "$filename: write: $!\n" if !defined $written_bytes; + die "$filename: short write\n" if $written_bytes != length $data; +} + +# 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 { + my ($pid) = fork; + if (!defined ($pid)) { + # Fork failed. + die "fork: $!\n"; + } elsif (!$pid) { + # Running in child process. + exec (@_); + exit (1); + } else { + # Running in parent process. + local $SIG{ALRM} = sub { timeout ($pid); }; + local $SIG{INT} = sub { relay_signal ($pid, "INT"); }; + local $SIG{TERM} = sub { relay_signal ($pid, "TERM"); }; + alarm ($timeout) if defined ($timeout); + waitpid ($pid, 0); + alarm (0); + return $?; + } +} + +# relay_signal($pid, $signal) +# +# Relays $signal to $pid and then reinvokes it for us with the default +# handler. +sub relay_signal { + my ($pid, $signal) = @_; + kill $signal, $pid; + $SIG{$signal} = 'DEFAULT'; + kill $signal, getpid (); +} + +# timeout($pid) +# +# Interrupts $pid and dies with a timeout error message. +sub timeout { + my ($pid) = @_; + relay_signal ($pid, "INT"); + my ($load_avg) = `uptime` =~ /(load average:.*)$/i; + print "TIMEOUT after $timeout seconds"; + print " - $load_avg" if defined $load_avg; + print "\n"; + exit (2); +} diff --git a/src/utils/pintos-mkdisk b/src/utils/pintos-mkdisk new file mode 100755 index 0000000..662b2e5 --- /dev/null +++ b/src/utils/pintos-mkdisk @@ -0,0 +1,37 @@ +#! /usr/bin/perl + +use strict; +use warnings; +use POSIX; +use Getopt::Long; +use Fcntl 'SEEK_SET'; + +GetOptions ("h|help" => sub { usage (0); }) + or exit 1; +usage (1) if @ARGV != 2; + +my ($disk, $mb) = @ARGV; +die "$disk: already exists\n" if -e $disk; +die "\"$mb\" is not a valid size in megabytes\n" + if $mb <= 0 || $mb > 1024 || $mb !~ /^\d+(\.\d+)?|\.\d+/; + +my ($cyl_cnt) = ceil ($mb * 2); +my ($cyl_bytes) = 512 * 16 * 63; +my ($bytes) = $cyl_bytes * $cyl_cnt; + +open (DISK, '>', $disk) or die "$disk: create: $!\n"; +sysseek (DISK, $bytes - 1, SEEK_SET) or die "$disk: seek: $!\n"; +syswrite (DISK, "\0", 1) == 1 or die "$disk: write: $!\n"; +close (DISK) or die "$disk: close: $!\n"; + +sub usage { + print <<'EOF'; +pintos-mkdisk, a utility for creating Pintos virtual disks +Usage: pintos DISKFILE MB +where DISKFILE is the file to use for the disk + and MB is the disk size in (approximate) megabytes. +Options: + -h, --help Display this help message. +EOF + exit (@_); +} diff --git a/src/vm/Make.vars b/src/vm/Make.vars index 8becbf7..525f2b5 100644 --- a/src/vm/Make.vars +++ b/src/vm/Make.vars @@ -1,2 +1,5 @@ -DEFINES = -DUSERPROG -DFILESYS -DVM -SUBDIRS = threads devices lib lib/kernel userprog filesys vm +# -*- makefile -*- + +os.dsk: DEFINES = -DUSERPROG -DFILESYS -DVM +KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys vm +TEST_SUBDIRS = tests/userprog tests/vm tests/filesys/base diff --git a/tests/Makefile b/tests/Makefile index 58ca417..be8b0d5 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,4 +1,4 @@ -TESTS = threads p1-1 p1-2 list stdlib stdio userprog p2 vm filesys +TESTS = threads p1 userprog p2 vm p3 filesys p4 PATH := $(shell pwd)/../src/utils:$(PATH) @@ -15,34 +15,17 @@ clean: SUBMAKEFLAGS = -s -define prep-threads-grading -endef - -define prep-userprog-grading -$(MAKE) -C ../src/userprog $(SUBMAKEFLAGS) -$(MAKE) -C ../grading/userprog $(SUBMAKEFLAGS) -endef - -define prep-vm-grading -$(MAKE) -C ../src/userprog $(SUBMAKEFLAGS) -$(MAKE) -C ../grading/vm $(SUBMAKEFLAGS) -endef - -define prep-filesys-grading -$(MAKE) -C ../grading/filesys $(SUBMAKEFLAGS) -endef - -define prep-grading -$(prep-$(PROJECT)-grading) -endef - define mk-sandbox -rm -rf $@ && mkdir -p $@/pintos && cp -R ../src $@/pintos -cd $@/pintos/src && $(MAKE) clean $(SUBMAKEFLAGS) +rm -rf $@ && mkdir $@ && cp -R ../src $@/src +cd $@/src && $(MAKE) clean $(SUBMAKEFLAGS) endef define run-tests -cd $@ && ../../grading/$(PROJECT)/run-tests +cd $@/src/$(PROJECT) && make check +endef + +define compile +cd $@/src/$(PROJECT) && make endef define clean @@ -50,64 +33,46 @@ rm -rf $@ endef define apply-patch -(cd $@/pintos && patch -p0) +(cd $@ && patch -p0) endef -threads: PROJECT = threads -threads:: - $(mk-sandbox) - $(run-tests) -d alarm-single - $(clean) +PROJECT = $@ -p1-1: PROJECT = threads -p1-1:: - $(mk-sandbox) - $(apply-patch) < ../solutions/p1-1.patch - $(run-tests) -d alarm.* - $(clean) - -p1-2: PROJECT = threads -p1-2:: +threads:: $(mk-sandbox) - $(apply-patch) < ../solutions/p1-2.patch - $(run-tests) -d priority.* + $(compile) + $(run-tests) TESTS=tests/threads/alarm-single $(clean) -list: PROJECT = threads -list stdlib stdio:: +userprog vm filesys:: $(mk-sandbox) - cp ../src/tests/threads/$@.c $@/pintos/src/threads/test.c - $(MAKE) -C $@/pintos/src/threads $(SUBMAKEFLAGS) - -(cd $@/pintos/src/threads/build && pintos -v run -q) | tee $@/output - grep -q '$@: PASS' $@/output + $(compile) $(clean) -userprog: PROJECT = userprog -userprog:: - $(prep-grading) null.dsk +p1: PROJECT = threads +p1:: $(mk-sandbox) - $(apply-patch) < ../solutions/p2-null.patch - $(run-tests) null + $(apply-patch) < ../solutions/p1.patch + $(run-tests) $(clean) p2: PROJECT = userprog p2:: - $(prep-grading) $(mk-sandbox) $(apply-patch) < ../solutions/p2.patch $(run-tests) $(clean) -vm: PROJECT = vm -vm:: - $(prep-grading) +p3: PROJECT = vm +p3:: $(mk-sandbox) - $(MAKE) -C $@/pintos/src/vm $(SUBMAKEFLAGS) + $(apply-patch) < ../solutions/p3.patch + $(run-tests) $(clean) -filesys: PROJECT = filesys -filesys:: - $(prep-grading) +p4: PROJECT = filesys +p4:: $(mk-sandbox) - $(MAKE) -C $@/pintos/src/filesys $(SUBMAKEFLAGS) + $(apply-patch) < ../solutions/p4.patch + $(run-tests) $(clean) -- 2.30.2